Source code for muvid.subgenres._contract
"""What a subgenre renderer is handed, and what it must give back.
A :class:`typing.Protocol` rather than a base class: a plugin author writes a
plain function and never imports a muvid class to inherit from, and muvid can
type-check the shape without either side depending on the other at runtime.
The request carries **paths and primitives only**. That is the load-bearing
decision in this module. Passing a rich host object (Sphinx's ``app``, MkDocs'
``config``, Datasette's ``datasette``) is the well-documented way to end up
unable to change your own schema for years, because every plugin has reached
into it. A plugin that needs more than this asks muvid through a narrow
accessor function, and that request becomes a visible, versionable API change.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Mapping, Protocol, runtime_checkable
[docs]
@dataclass(frozen=True, slots=True, kw_only=True)
class RenderRequest:
"""Everything a subgenre renderer gets.
:param subgenre: the slug being rendered, so one function can serve several.
:param inputs: the caller's files and primitives, validated against the
manifest's ``inputs`` schema before it gets here.
:param params: the styling/treatment knobs, validated against
``params_schema``.
:param workdir: a directory the renderer may write intermediates into. It
exists, and it belongs to this render — nothing else writes there.
:param output: where the finished artifact must be written.
"""
subgenre: str
inputs: Mapping[str, Any]
params: Mapping[str, Any] = field(default_factory=dict)
workdir: Path
output: Path
[docs]
@dataclass(frozen=True, slots=True, kw_only=True)
class RenderResult:
"""What a renderer gives back.
:param output: the finished artifact. Must be the request's ``output``.
:param duration_s: length of the produced media, when it has one.
:param artifacts: named side products worth keeping — a ``.ass`` subtitle
file, a thumbnail, the resolved treatment spec. These are how a
subgenre stays inspectable instead of producing an opaque file.
:param meta: anything else worth recording; must be JSON-able.
"""
output: Path
duration_s: float | None = None
artifacts: Mapping[str, Path] = field(default_factory=dict)
meta: Mapping[str, Any] = field(default_factory=dict)
def to_dict(self) -> dict[str, Any]:
return {
"output": str(self.output),
"duration_s": self.duration_s,
"artifacts": {k: str(v) for k, v in self.artifacts.items()},
"meta": dict(self.meta),
}
[docs]
@runtime_checkable
class Renderer(Protocol):
"""``(RenderRequest) -> RenderResult``, and nothing else."""
def __call__(self, request: RenderRequest) -> RenderResult: ...