Errors#
Every exception Scenet raises, under one root.
scenet.errors#
.. py:module:: scenet.errors
Every exception Scenet raises, in one place.
A library that scatters its exception types across the modules that happen to raise
them forces callers to import from six places to write one except clause. Everything
Scenet can raise is defined here instead, under a single root, so that
except ScenetError:
catches all of it and nothing else.
The hierarchy is three deep and the middle tier answers the question a caller actually has, which is whose fault is it:
ScenetError
|-- SourceError the document is wrong -- report it to whoever wrote the panel
|-- SolverError the document is fine, but no layout satisfies it
`-- AssetError a puppet is missing or malformed
Each also inherits the built-in exception a caller would have reached for before this
module existed – SourceError is a ValueError, UnknownPuppetError is a KeyError
– so pre-existing except ValueError handlers keep working unchanged.
.. py:exception:: AssetError
- module:
scenet.errors
Bases: :py:class:
~scenet.errors.ScenetErrorA puppet asset that is missing, unreadable or self-inconsistent.
.. py:exception:: BalloonPlacementError
- module:
scenet.errors
Bases: :py:class:
~scenet.errors.SolverErrorNo legal position exists for a balloon or a caption.
Every candidate position was rejected: it covered a face, left the panel, overlapped a box already placed, or would have broken reading order. Usually this means too many words for the panel size – widen the panel, shorten the line, or split it across two panels.
Captions raise this too. They obey the same hard rules and are placed in the same pass, so the failure is the same failure; the name is kept because the rule id
balloon-placementis stable across releases.
.. py:exception:: CompositionError
- module:
scenet.errors
Bases: :py:class:
~scenet.errors.SourceErrorA
panels:document whoseover:inheritance cannot be resolved.Raised for a panel inheriting from one that does not exist, and for a cycle –
aoverbovera– which has no fixed point to resolve to... admonition:: Example
from scenet import CompositionError, compile_scene try: … compile_scene(“panels: {a: {over: b}, b: {over: a}}”) … except CompositionError as exc: … print(exc) ‘over’ chain is cyclic: a -> b -> a
.. py:exception:: LayoutError
- module:
scenet.errors
Bases: :py:class:
~scenet.errors.SolverErrorA panel whose required constraints cannot all be satisfied.
Actor placement runs a Cassowary solver in which non-overlap and declared left-to-right ordering are required constraints. If those genuinely conflict – two actors each required to be left of the other – there is no solution and this is raised. Panel bounds are deliberately not required, so a merely crowded panel lets figures bleed off the edge instead of failing.
.. py:exception:: PanelSyntaxError
- module:
scenet.errors
Bases: :py:class:
~scenet.errors.SourceErrorA panel document that could not be parsed or validated.
Carries the source path when one is known, so the message reads
path/to/duel.panel.yaml: invalid panel: ...rather than losing the file it came from... attribute:: rule
Identifier from the catalogue in
- data:
RULES <scenet.diagnostics.RULES>, when the frontend knows which rule was broken. Most surface faults reachscenet checkasinvalid-field, which is honest for a value pydantic rejected; but a check the frontend performs itself – a place that does not exist, a setting that names one and lists masses too – knows exactly what it found, and saying so is the whole point of having a rule catalogue.
.. attribute:: loc
Path to the offending value, relative to the panel, in pydantic’s
locform. Empty means the panel as a whole... admonition:: Example
from scenet import PanelSyntaxError, compile_source try: … compile_source(“panel: {size: [0, 100]}”) … except PanelSyntaxError as exc: … print(exc) invalid panel: at panel: panel size must be positive
.. py:method:: PanelSyntaxError.init(message, *, source=None, rule=None, loc=())
- module:
scenet.errors
Build the error, keeping the rule and location as data as well as prose.
- type message:
- sphinx_autodoc_typehints_type:
\:py\:class\:\str``
- param message:
What went wrong, phrased for whoever wrote the document.
- type source:
- sphinx_autodoc_typehints_type:
\:py\:class\:\~pathlib.Path` | :py:obj:`None``
- param source:
Path the document came from, if it was read from disk.
- type rule:
- sphinx_autodoc_typehints_type:
\:py\:class\:\str` | :py:obj:`None``
- param rule:
Catalogue identifier, when the frontend knows which rule was broken.
- type loc:
- sphinx_autodoc_typehints_type:
\:py\:class\:\tuple`\ \[:py:class:`str` | :py:class:`int`, :py:data:`…<Ellipsis>`]`
- param loc:
Path to the offending value within the panel.
.. py:exception:: RuleViolationError
- module:
scenet.errors
Bases: :py:class:
ValueErrorA named rule, broken at a known place in the document.
Raised inside pydantic validators rather than out of them. pydantic wraps whatever a validator raises into its own
ValidationError, and for a model-level validator it records the location as()– the whole document – because a validator has no way to say which field it was unhappy about. That is accurate and useless: the two checks that matter most here,check_references_resolveandcheck_ordering_is_consistent, both knew the exact path and had nowhere to put it, so every such diagnostic readat <root>.pydantic keeps the exception object it caught, under
ctx["error"], so a subclass carrying extra attributes survives validation intact and can be recovered on the other side. That is the whole trick.A plain
ValueErrorso that a validator raising one behaves exactly as before for anybody not looking for the extra attributes. It deliberately does not inheritScenetError: it never escapes validation as itself – pydantic catches it and re-raises its ownValidationError– so putting it in that hierarchy would promise aexcept ScenetErrorclause could catch it, which it cannot... attribute:: rule
Identifier from the catalogue in
- mod:
scenet.diagnostics <scenet.diagnostics>. Stable across releases, because aruleIdthat moves breaks every alert that referenced it.
.. attribute:: loc
Path to the offending value, in pydantic’s
locform – string keys and integer indices, as in("script", 0, "by")... py:method:: RuleViolationError.init(message, *, rule, loc=())
- module:
scenet.errors
Build the violation.
- type message:
- sphinx_autodoc_typehints_type:
\:py\:class\:\str``
- param message:
What is wrong, phrased for whoever wrote the document.
- type rule:
- sphinx_autodoc_typehints_type:
\:py\:class\:\str``
- param rule:
Catalogue identifier for the rule that was broken.
- type loc:
- sphinx_autodoc_typehints_type:
\:py\:class\:\tuple`\ \[:py:class:`str` | :py:class:`int`, :py:data:`…<Ellipsis>`]`
- param loc:
Path to the offending value. Empty means the document as a whole.
.. py:exception:: ScenetError
- module:
scenet.errors
Bases: :py:class:
ExceptionRoot of every error Scenet raises.
Catch this to handle anything the compiler can go wrong with, without having to enumerate the specific cases or accidentally swallowing unrelated
ValueErrors from elsewhere in your program... admonition:: Example
from scenet import ScenetError, compile_source try: … compile_source(“{panel: {size: [1000, 1000]}, cast: {ghost: {reference: nobody}}}”) … except ScenetError as exc: … print(type(exc).name) UnknownPuppetError
.. seealso::
- exc:
SourceError <scenet.errors.SourceError>, for the “bad document” branch.- exc:
SolverError <scenet.errors.SolverError>, for the “impossible layout” branch.
.. py:exception:: ScriptSyntaxError
- module:
scenet.errors
Bases: :py:class:
~scenet.errors.PanelSyntaxErrorA comic script that could not be parsed.
A subclass of :exc:
PanelSyntaxError <scenet.errors.PanelSyntaxError>rather than a sibling, because both frontends produce the same IR and a caller handling “bad input” should not have to care which syntax it was written in... attribute:: line
One-based line the fault is on, when the parser knows it. The comic-script frontend is line-oriented, so it usually does – but it had only ever put the number into the message text, which is fine to read and useless to an editor drawing a squiggle. Structured diagnostics need it as a number. There is no column: a script line is prose, and pointing at a character within it would imply a precision the parser does not have.
.. py:method:: ScriptSyntaxError.init(message, *, source=None, line=None)
- module:
scenet.errors
Build the error, keeping the line number as data as well as prose.
- type message:
- sphinx_autodoc_typehints_type:
\:py\:class\:\str``
- param message:
What went wrong, phrased for whoever wrote the script.
- type source:
- sphinx_autodoc_typehints_type:
\:py\:class\:\~pathlib.Path` | :py:obj:`None``
- param source:
Path the script came from, if it was read from disk.
- type line:
- sphinx_autodoc_typehints_type:
\:py\:class\:\int` | :py:obj:`None``
- param line:
One-based line the fault is on, if known.
.. py:exception:: SolverError
- module:
scenet.errors
Bases: :py:class:
~scenet.errors.ScenetError, :py:class:ValueErrorA document that is valid but cannot be laid out.
The distinction from :exc:
SourceError <scenet.errors.SourceError>matters: nothing is misspelled and nothing is missing, but the panel as described has no solution – a cast with nowhere left to stand, or a balloon with no legal position. The fix is an editorial change to the panel, not a correction to its syntax.Also a
ValueError.
.. py:exception:: SourceError
- module:
scenet.errors
Bases: :py:class:
~scenet.errors.ScenetError, :py:class:ValueErrorA document that could not be understood.
Raised whenever the input is at fault: malformed YAML, an unknown predicate, a reference to a panel that does not exist, a negative panel size. The message names the location and the reason, and is written to be shown directly to whoever wrote the document – no traceback required.
Also a
ValueError, since that is what a malformed value has always been... attribute:: source
Path the document was read from, or
Nonefor a string compiled in memory. Prefixed to the message when present, so a diagnostic never loses the file it came from... py:method:: SourceError.init(message, *, source=None)
- module:
scenet.errors
Build the error, prefixing the source path when there is one.
- type message:
- sphinx_autodoc_typehints_type:
\:py\:class\:\str``
- param message:
What went wrong, phrased for the person who wrote the document.
- type source:
- sphinx_autodoc_typehints_type:
\:py\:class\:\~pathlib.Path` | :py:obj:`None``
- param source:
Path the document came from, if it was read from disk.
.. py:exception:: UnknownExpressionError
- module:
scenet.errors
Bases: :py:class:
~scenet.errors.AssetError, :py:class:KeyErrorA cast member’s
expressionnaming one its puppet does not declare.The counterpart of :exc:
UnknownPoseError <scenet.errors.UnknownPoseError>forPuppetSpec.expression_states, and deliberately identical in shape – an expression is selected by name exactly as a pose is... note::
KeyErrorstringifies asrepr(args[0]), sostr(exc)comes out quoted. Readexc.args[0]for the bare message.
.. py:exception:: UnknownPoseError
- module:
scenet.errors
Bases: :py:class:
~scenet.errors.AssetError, :py:class:KeyErrorA cast member’s
posenaming one its puppet does not declare.Also a
KeyError, for the same reason as- exc:
UnknownPuppetError <scenet.errors.UnknownPuppetError>: a pose lookup has always failed this way, andPuppetSpec.pose_angles’s documentedRaises: KeyErrorstays true.except KeyErrorkeeps working; a caller that wants the rule and location this now carries catchesUnknownPoseError(orAssetError) instead.
.. note::
KeyErrorstringifies asrepr(args[0]), sostr(exc)comes out quoted. Readexc.args[0]for the bare message.
.. py:exception:: UnknownPuppetError
- module:
scenet.errors
Bases: :py:class:
~scenet.errors.AssetError, :py:class:KeyErrorA cast member referencing a puppet the library does not contain.
Also a
KeyError, because that is what a lookup miss has always been, and because the library is a mapping in all but name... note::
KeyErrorstringifies asrepr(args[0]), sostr(exc)comes out quoted. Readexc.args[0]for the bare message – which is what the CLI does.