# Python API reference This section is for extending the server itself — adding a new widget or data source — rather than for designing a screen with the existing ones. It's generated from docstrings/signatures in the source, so it documents the *Python* interface of each class. A widget's or data source's per-attribute reference (`x`, `color`, `params.url`, …) is generated by declaring a typed `Params` model (see below); Sphinx pulls its properties table straight from the model rather than it being hand-maintained. Every widget and data source has one, with no exceptions — including ``, which has no attributes of its own but still gets a real (if minimal) `Params`. There is exactly one mechanism for typed attribute access — no separate plain-Python-property carve-out for anything, not even `x`/`y`/`align`/`valign`. Two *separate* base models sit under all of this: {doc}`view`'s `ViewParams` and {doc}`datasource`'s `DataSourceParams`. They aren't unified into one shared base, deliberately: a widget and a data source are different enough — a widget renders into an image and sits in a parent/child tree a container reads generic positioning attributes off of; a data source just fetches data on demand — that a shared base between them wouldn't represent anything real, only that both happen to want the same `extra=ignore` config. `ViewParams` carries actual shared fields (`x`/`y`/`align`/`valign`); `DataSourceParams` carries none. `Color`/`Number` (see {doc}`params`) are different: genuine shared *value types*, meaningful on a field of either hierarchy, which is why those two stay in their own small module rather than living next to either base. ```{toctree} :hidden: view params datasource generator ``` ## Adding a widget Subclass {py:class}`~todays_paper_web.widgets.view.View`, override `render()` to return a `PIL.Image`, and decorate the class with `@registerView("your-tag-name")`. Then import the module from `todays_paper_web/widgets/__init__.py` — only modules imported there register themselves, via the `@registerView`/`@registerDataSource` decorator running at import time. Give it typed, self-documenting attributes instead of reading off the raw dict: declare a {py:class}`~todays_paper_web.widgets.view.ViewParams` subclass (not `pydantic.BaseModel` directly — every widget needs `x`/`y`/`align`/`valign`, even one with no other attributes of its own, like `todays_paper_web.widgets.core.layout.ColumnParams`) with a `pydantic.Field(..., description=...)` per extra attribute, and set it as the class's `Params`. `self.params.` then gives a validated value, including `self.params.x`/`self.params.y` for the handful of widgets (`line`) that also happen to read their own position for widget-specific reasons. `View`'s own default `Params` is already `ViewParams` itself, so even an unrecognized tag name (which falls back to the inert base `View`) answers `.params.x` correctly when some other widget's container reads it. `View` has no dict fallback of any kind: an attribute that isn't a field on the widget's own `Params` raises `AttributeError`, the same as accessing any other undeclared Python attribute would. See `todays_paper_web/widgets/core/rectangle.py` for a worked example, and pass the model to Sphinx with `autopydantic_model` plus `:inherited-members: BaseModel` (see that file's raw source in {doc}`../widgets/shapes`) so the inherited position fields show up in the generated table too, instead of hand-writing a properties table. If a widget shares real rendering logic with others but isn't a tag itself — `ImageBaseView` (`todays_paper_web/widgets/core/image.py`), subclassed by `ImageView`/`GoComics`/`WeatherIconView`, all of which need the same resize/fit logic but load their source bitmap differently — give it a `Params` too (`ImageParams`, sizing/fitting only) and simply don't decorate it with `@registerView`; each concrete subclass then sets its own, more specific `Params` (e.g. `LocalImageParams(ImageParams)` adding `path`) and only needs to implement `fetch_img()`. ## Adding a data source Subclass {py:class}`~todays_paper_web.widgets.datasource.DataSource`, override `fetch()` to return whatever a template should receive, and decorate with `@registerDataSource("your-source-name")`. Same import-registration rule apply; declare a {py:class}`~todays_paper_web.widgets.datasource.DataSourceParams` subclass (not `ViewParams` — a data source has no parent/child relationship for something else to read a position off it generically, so there's nothing to inherit there) and set it as `Params`, same as a widget. `fetch()` always reads `self.params.` explicitly. `DataSource` has no `__getattr__` at all, so an undeclared attribute raises `AttributeError` for free, via Python's ordinary attribute lookup — no custom code required, matching `View` exactly. One difference from a widget: a data source's `params:` come from YAML, already native Python types rather than XML strings, which creates its own pitfall for a coordinate-like field that might be a whole number in one config and a decimal in another (e.g. `weather.yr`'s `altitude`): a plain `float` field silently turns `17` into `17.0`, and `Union[int, float]` is actively unsafe in this project's Pydantic v1 (it tries `int` first and *truncates* a real float's fractional part). Use {py:class}`~todays_paper_web.widgets.params.Number` for that case instead — it keeps whichever of `int`/`float` was actually given.