Skip to content

codegen

codegen #

Emit importable Python source that reproduces a schema as cg.Struct classes.

schema_to_source is the readable inverse of the JSON wire form. from_schema rebuilds a runtime type from a schema, but collapses every struct to an anonymous Struct and every composite to its bare shape; this instead writes source — a self-contained module whose cg.Struct subclasses have real names, so captured data loads with named types a person can read and analysis code can annotate.

The generated module is meant to be pretty enough to check in: two blank lines between definitions, double-quoted literals, a docstring on every class, and an explanatory comment above any field whose type could not be recognized.

Three naming decisions are isolated as extension points — change or grow the generator by editing one named unit, not the traversal:

  • singularize turns a collection field's name into its element class (framesFrame).
  • MAP_CONVENTIONS names a map's element from the map's key type: a Map[Time, …] (a session collection) names its entry a …Session, and a Map[UserID, …] (per-user data) a …Preference. Append a MapNaming to add another; each guards against doubling a suffix a name already carries.
  • _canonical_key is the identity by which two structs count as the same class, so an identical struct reached twice collapses to one definition.

A sub-schema that matches a built-in composite (cg.FlashPair, cg.Metadata, the sequence aliases, ...) is referenced by that name rather than regenerated, and an unrecognized scalar falls back to an opaque cg.from_schema(...) rebuild.

The produced module's types satisfy schema(module.<root>) == schema — the same round-trip guarantee from_schema makes, verified in the tests.

MapNaming dataclass #

Names a map's element class from the map's key type.

A map whose key scalar is key names its element rename(base) — where base is the singularized field name — instead of the plain singular. Append an instance to MAP_CONVENTIONS to add a convention; the first match wins.

Source code in capturegraph-lib/capturegraph/types/schema/codegen.py
@dataclass(frozen=True)
class MapNaming:
    """Names a map's element class from the map's key type.

    A map whose key scalar ``is`` ``key`` names its element ``rename(base)`` — where
    ``base`` is the singularized field name — instead of the plain singular. Append
    an instance to ``MAP_CONVENTIONS`` to add a convention; the first match wins.
    """

    key: type[CGType]
    rename: Callable[[str], str]

    def matches(self, key_schema: dict[str, JSONValue]) -> bool:
        """Whether a map keyed by ``key_schema`` follows this convention."""
        return scalar_for(str(key_schema.get("type"))) is self.key

matches(key_schema) #

Whether a map keyed by key_schema follows this convention.

Source code in capturegraph-lib/capturegraph/types/schema/codegen.py
def matches(self, key_schema: dict[str, JSONValue]) -> bool:
    """Whether a map keyed by ``key_schema`` follows this convention."""
    return scalar_for(str(key_schema.get("type"))) is self.key

schema_to_source(schema, *, root_name) #

Python source for a module of cg.Struct classes that reproduce schema.

The module imports capturegraph as cg, defines a class per distinct struct (built-in composites referenced by name), binds the root type to root_name, and exposes a load helper that reads captured data from disk into it.

Parameters:

Name Type Description Default
schema dict[str, JSONValue]

A type wire form — the schema of a {schema, root, nodes} procedure definition, or any cg.schema(T) output.

required
root_name str

The root type's name, typically the target's; sanitized to a valid class identifier.

required

Returns:

Type Description
str

Importable module source. Its root type satisfies

str

schema(module.<root_name>) == schema.

Source code in capturegraph-lib/capturegraph/types/schema/codegen.py
def schema_to_source(schema: dict[str, JSONValue], *, root_name: str) -> str:
    """Python source for a module of ``cg.Struct`` classes that reproduce ``schema``.

    The module imports ``capturegraph as cg``, defines a class per distinct struct
    (built-in composites referenced by name), binds the root type to ``root_name``,
    and exposes a ``load`` helper that reads captured data from disk into it.

    Args:
        schema: A type wire form — the ``schema`` of a ``{schema, root, nodes}``
            procedure definition, or any ``cg.schema(T)`` output.
        root_name: The root type's name, typically the target's; sanitized to a
            valid class identifier.

    Returns:
        Importable module source. Its root type satisfies
        ``schema(module.<root_name>) == schema``.
    """
    root = _class_name(root_name)
    builder = _SourceBuilder()

    if schema.get("type") == "struct":
        builder.root_class(schema, root)
        root_binding = ""
    else:
        expression = builder.reference(schema, root)
        root_binding = f"{root} = {expression}\n\n\n"

    return _MODULE_TEMPLATE.format(
        root=root,
        classes=f"{builder.classes}\n\n\n" if builder.classes else "",
        root_binding=root_binding,
    )

singularize(name) #

A naive English singular of a PascalCase name (FramesFrame).

A collection field names its element class with this, so replacing it changes that mapping everywhere at once.

Source code in capturegraph-lib/capturegraph/types/schema/codegen.py
def singularize(name: str) -> str:
    """A naive English singular of a PascalCase name (``Frames`` → ``Frame``).

    A collection field names its element class with this, so replacing it changes
    that mapping everywhere at once.
    """
    if len(name) > 3 and name.endswith("ies"):
        return f"{name[:-3]}y"
    if any(name.endswith(suffix) for suffix in ("ses", "xes", "zes", "ches", "shes")):
        return name[:-2]
    if name.endswith("s") and not name.endswith("ss"):
        return name[:-1]
    return name