Skip to content

Layout descriptions

Building a layout from JSON that describes it, rather than from JSON that holds it. The format is documented in Describing a layout in JSON, and unlike everything else here it is read only: a resolved layout cannot be turned back into the description that built it.

The dialect carries a schema number, and it is the producer that stamps it: this is the one format here written by something other than py2tosc, so whatever writes a description is the thing that has to say which schema it wrote. SCHEMAS and supports are how a generator asks what the installed release reads, before it writes a file that release cannot build.

required_schema answers the other half: the lowest schema that builds a given description, which is the number to stamp. Asking rather than remembering is what keeps the key honest, since nothing downstream audits it.

required_schema

required_schema(data: Any) -> int

The lowest schema that builds a description.

What a producer stamps. Asking rather than remembering is what keeps the schema key honest: this dialect is read and never written, so the number is a claim the writer makes about its own output, and nothing downstream audits it. A generated file whose stamp is below what it uses still builds on a new enough reader and fails on an older one with a message about a node, which is the confusion supports exists to prevent.

It detects spellings, not meanings. A schema that changed what an existing spelling does -- different default sizing, say -- leaves a description textually identical, so this returns the older number and says nothing. For that class the guard is a golden file, not a version number.

Nothing here validates. A description this cannot build gets an answer anyway, because the question is what spellings it uses rather than whether they are used correctly; build is what says no.

Parameters:

Name Type Description Default
data Any

The decoded JSON. The whole envelope, or any part of one.

required

Returns:

Type Description
int

The lowest schema in SCHEMAS that reads it.

Source code in src/py2tosc/ui_json.py
def required_schema(data: Any) -> int:
    """The lowest schema that builds a description.

    What a producer stamps. Asking rather than remembering is what keeps the
    `schema` key honest: this dialect is read and never written, so the number
    is a claim the writer makes about its own output, and nothing downstream
    audits it. A generated file whose stamp is below what it uses still builds
    on a new enough reader and fails on an older one with a message about a
    node, which is the confusion `supports` exists to prevent.

    It detects spellings, not meanings. A schema that changed what an existing
    spelling *does* -- different default sizing, say -- leaves a description
    textually identical, so this returns the older number and says nothing. For
    that class the guard is a golden file, not a version number.

    Nothing here validates. A description this cannot build gets an answer
    anyway, because the question is what spellings it uses rather than whether
    they are used correctly; `build` is what says no.

    Args:
        data: The decoded JSON. The whole envelope, or any part of one.

    Returns:
        The lowest schema in `SCHEMAS` that reads it.
    """
    needed = SCHEMAS.start
    pending: list[Any] = [data]
    while pending:
        item = pending.pop()
        if isinstance(item, dict):
            needed = max(needed, _needs(item))
            pending.extend(item.values())
        elif isinstance(item, list):
            pending.extend(item)
    return needed

supports

supports(schema: int) -> bool

Whether this release builds a description declaring that schema.

The question a generator has before it writes one. Asking here is what lets it fail with a sentence naming its own remedy -- this py2tosc reads schemas 1 to 2, and the generator emits 3 -- rather than with a SchemaError raised from inside a file that is not the thing that is wrong.

Parameters:

Name Type Description Default
schema int

The schema number a description declares or would declare.

required

Returns:

Type Description
bool

True if this release reads it.

Source code in src/py2tosc/ui_json.py
def supports(schema: int) -> bool:
    """Whether this release builds a description declaring that schema.

    The question a generator has before it writes one. Asking here is what lets
    it fail with a sentence naming its own remedy -- this py2tosc reads schemas
    1 to 2, and the generator emits 3 -- rather than with a `SchemaError` raised
    from inside a file that is not the thing that is wrong.

    Args:
        schema: The schema number a description declares or would declare.

    Returns:
        True if this release reads it.
    """
    return schema in SCHEMAS

build

build(data: Any) -> Document

Build a layout from already-parsed JSON.

The tree is built, every connect is pointed at the control it named, and the whole thing is resolved, so what comes back is sized and ready to save.

Parameters:

Name Type Description Default
data Any

The decoded JSON.

required

Returns:

Type Description
Document

The document.

Raises:

Type Description
FormatError

If the envelope is not this dialect, or holds a node that cannot be built. The message names the node it gave up on.

SchemaError

If the description declares a schema this release does not read. supports answers that before a file is written.

Source code in src/py2tosc/ui_json.py
def build(data: Any) -> Document:
    """Build a layout from already-parsed JSON.

    The tree is built, every `connect` is pointed at the control it named, and
    the whole thing is resolved, so what comes back is sized and ready to save.

    Args:
        data: The decoded JSON.

    Returns:
        The document.

    Raises:
        FormatError: If the envelope is not this dialect, or holds a node that
            cannot be built. The message names the node it gave up on.
        SchemaError: If the description declares a schema this release does not
            read. `supports` answers that before a file is written.
    """
    document = _object(data, "the layout")
    check_keys(document, _ENVELOPE_KEYS, "the layout")

    declared = document.get("format")
    if declared != DIALECT:
        raise FormatError(f"{declared!r} is not a {DIALECT} layout")

    read_schema(document, SCHEMAS, DIALECT)

    if "root" not in document:
        raise FormatError("the layout holds no root node")

    version = document.get("lexml", "6")
    if not isinstance(version, str):
        raise FormatError(f"lexml should be a string, found {describe(version)}")

    deferred: list[_Deferred] = []
    described = _object(document["root"], "root")
    root = _node(described, "root", deferred)

    # Every control type defaults a frame, so the root having one says nothing
    # about whether the layout asked for a canvas. What the file wrote does.
    given = _object(described.get("props", {}), "root.props")
    if "frame" not in described and "frame" not in given:
        root.set("frame", CANVAS)

    doc = Document(root=root, version=version)
    _resolve_connections(doc, deferred)
    try:
        return doc.resolve()
    except ValueError as exc:
        raise FormatError(f"the layout will not resolve: {exc}") from exc

from_json

from_json(source: str | bytes) -> Document

Build a layout from JSON text.

Parameters:

Name Type Description Default
source str | bytes

The description, as text or UTF-8 bytes.

required

Returns:

Type Description
Document

The document, built and resolved.

Raises:

Type Description
FormatError

If the source is not JSON, or is not a layout this can build.

SchemaError

If it declares a schema this release does not read.

Source code in src/py2tosc/ui_json.py
def from_json(source: str | bytes) -> Document:
    """Build a layout from JSON text.

    Args:
        source: The description, as text or UTF-8 bytes.

    Returns:
        The document, built and resolved.

    Raises:
        FormatError: If the source is not JSON, or is not a layout this can
            build.
        SchemaError: If it declares a schema this release does not read.
    """
    try:
        return build(json.loads(source))
    except json.JSONDecodeError as exc:
        raise FormatError(f"not valid JSON: {exc}") from exc