Source code for todays_paper_web.widgets.view

from __future__ import annotations
import os
from pathlib import Path
from typing import BinaryIO, ClassVar, Iterator, Optional, Type

from PIL import Image
import xml.etree.ElementTree as ET

from pydantic import BaseModel, Extra, Field, validator

from todays_paper_web.storage.storage import Storage
from todays_paper_web.widgets import registerView, ImageType
from .datasource import DataSource


[docs] class ViewParams(BaseModel): """Base ``Params`` for every widget - unlike ``DataSource``'s equivalent, this carries real fields: ``x``/``y``/``align``/``valign``, read off *any* child by whichever container it's placed in (``box``/``row``/``column``, see `todays_paper_web.widgets.core.layout`) to decide where to place it - not owned by the child, its parent interprets it. A few widgets (e.g. ``line``) also happen to read their own ``x``/``y`` for unrelated, widget-specific reasons; that's the same attribute doing double duty, not a coincidence. Every widget's ``Params`` inherits this, even one with no attributes of its own to add (e.g. ``<column>``'s). The default for each field is ``None``, not e.g. ``"left"`` for `align`: a container decides *whether* and *how* to apply its own default when a value wasn't given, which needs to be able to tell "not given" apart from any real value, including a falsy one like ``x="0"`` - so this reports presence faithfully rather than pre-applying any one container's default. """ class Config: extra = Extra.ignore x: Optional[int] = Field(None, description="Horizontal position, if given.") y: Optional[int] = Field(None, description="Vertical position, if given.") align: Optional[str] = Field( None, description="Horizontal alignment (`left`/`center`/`right`), if given." ) valign: Optional[str] = Field( None, description="Vertical alignment (`top`/`middle`/`center`/`bottom`), if given." ) @validator("x", "y", "align", "valign", pre=True) def _blankToNone(cls, value): # pylint: disable=no-self-argument # An empty-string attribute (`x=""`) is absent in every practical # sense - without this, Pydantic would try (and fail) to parse "" as # an int rather than treating it the same as `x` being unset. return value or None
[docs] class View: #: Every widget's ``Params`` is a ``ViewParams`` subclass - there is no #: widget without one, even ``<column>``, which has nothing to add on top #: of ``ViewParams`` itself. Overridden per subclass; never unset. Params: ClassVar[Type[ViewParams]] = ViewParams def __init__(self, params): self._element = None self._source = None self._params = params self._parsedParams: Optional[ViewParams] = None self._storage: Storage = None self._config: str = "" @property def params(self) -> ViewParams: """The parsed ``Params`` model for this widget.""" if self._parsedParams is None: self._parsedParams = self.Params.parse_obj(self._params) return self._parsedParams @property def children(self) -> Iterator[View]: for child in self._element: view = View.get(child.tag, None, child.attrib, self._storage, self._config) view.element = child yield view @property def element(self) -> ET.Element: return self._element @element.setter def element(self, element: ET.Element): self._element = element @property def innerText(self): return "".join(self._element.itertext())
[docs] def getData(self): if not self.source: return {} return self.source.fetch()
[docs] def openFile(self, filename) -> BinaryIO: return self._storage.getFileObject(self._config, filename)
[docs] def render(self) -> ImageType: return Image.new("RGB", (0, 0), "white")
@property def source(self): return self._source @source.setter def source(self, source): self._source = source
[docs] @staticmethod def get(name, source, params, storage, config) -> View: ViewCls = registerView.get(name, View) # pylint: disable=invalid-name view = ViewCls(params) view.source = source view._storage = storage view._config = config return view
[docs] @staticmethod def paste(target: ImageType, source: ImageType, position: tuple[int, int]): if source.mode == "RGBA": target.paste(source, position, source) else: target.paste(source, position)