Source code for vibesketch.inputs.registry
"""The registry of input writers, and the one call that runs them."""
from __future__ import annotations
from dataclasses import dataclass, field
from vibesketch.document.model import Document, JobSpec
from vibesketch.inputs.base import InputBundle, InputWriter, WriterRefusal
__all__ = [
"Batch",
"UnknownReference",
"UnknownWriter",
"get_writer",
"register",
"write_input",
"write_inputs",
"writers",
]
_WRITERS: dict[str, InputWriter] = {}
[docs]
class UnknownWriter(LookupError):
"""No writer is registered under that name."""
def __init__(self, name: str) -> None:
self.name = name
known = ", ".join(sorted(_WRITERS)) or "none"
super().__init__(f"no input writer named {name!r}; available: {known}")
[docs]
class UnknownReference(KeyError):
"""The document has no job or species with that id."""
def __init__(self, kind: str, identifier: str) -> None:
self.kind = kind
self.identifier = identifier
super().__init__(f"the document has no {kind} {identifier!r}")
def __str__(self) -> str:
return str(self.args[0])
[docs]
def register(writer: InputWriter, *, replace: bool = False) -> None:
"""Make ``writer`` available under ``writer.name``."""
if writer.name in _WRITERS and not replace:
raise ValueError(f"an input writer named {writer.name!r} is already registered")
_WRITERS[writer.name] = writer
[docs]
def get_writer(name: str) -> InputWriter:
"""The writer registered as ``name``; raises :class:`UnknownWriter`."""
try:
return _WRITERS[name]
except KeyError:
raise UnknownWriter(name) from None
[docs]
def writers() -> list[InputWriter]:
"""Every registered writer, by name."""
return [_WRITERS[name] for name in sorted(_WRITERS)]
[docs]
def write_input(
document: Document,
*,
job: str | None = None,
species: str | None = None,
program: str | None = None,
spec: JobSpec | None = None,
) -> InputBundle:
"""Write the input for one job of ``document``.
Name a job by its id, or give a species id and a program (with an
optional specification; the default is the geometry only). Raises
:class:`~vibesketch.inputs.base.WriterRefusal` when the writer cannot
express the request, :class:`UnknownWriter` for an unknown program, and
:class:`UnknownReference` for an id the document does not have.
"""
if job is not None:
if species is not None or program is not None or spec is not None:
raise TypeError("name a job, or a species and a program, not both")
try:
chosen = document.job_by_id(job)
except KeyError:
raise UnknownReference("job", job) from None
# The model guarantees a job's species exists.
return get_writer(chosen.program).write(document.species_by_id(chosen.species), chosen.spec)
if species is None or program is None:
raise TypeError("name a job, or a species and a program")
try:
target = document.species_by_id(species)
except KeyError:
raise UnknownReference("species", species) from None
return get_writer(program).write(target, spec if spec is not None else JobSpec(task="geometry"))
[docs]
@dataclass
class Batch:
"""Inputs for several species with one writer and one specification."""
bundles: list[InputBundle] = field(default_factory=list)
#: Per species id, why the writer refused it.
refused: dict[str, list[str]] = field(default_factory=dict)
[docs]
def write_inputs(
document: Document,
*,
program: str,
spec: JobSpec,
species: list[str] | None = None,
) -> Batch:
"""The inputs for ``species`` (default: every species) with one writer.
A species the writer refuses is listed in :attr:`Batch.refused` with the
reasons, and the others are still written; a proton in a mechanism should
not stop the rest of it. Raises :class:`UnknownWriter` for an unknown
program and :class:`UnknownReference` for an unknown species id.
"""
writer = get_writer(program)
wanted = species if species is not None else [s.id for s in document.species]
batch = Batch()
for identifier in wanted:
try:
target = document.species_by_id(identifier)
except KeyError:
raise UnknownReference("species", identifier) from None
try:
batch.bundles.append(writer.write(target, spec))
except WriterRefusal as refusal:
batch.refused[identifier] = list(refusal.reasons)
return batch