Skip to content

Assemble classes and methods

Start with the desired Python shape: class shell, slots declaration, methods, then method parameters and statements. Expose a named surface for each part that varies. Keep field policy in input facts and matcher rules.

The first generator uses a ClassShell resource and a nested InitProduction. Its class root binds the class-name identifier and the already-normalized slot tuple. Field rows supply direct stores through transparent attribute targets. There is no descriptor dispatch in those stores.

To add a property getter/setter, use an authored method/property resource, select it by field policy, bind its public field name and storage path, and insert it into the class body. Keep the public spelling deliberately; let unrelated locals be hygienically renamed. Consumer examples show specialized accessors selected from actual consumer facts.

Conditional omission

The tutorial's EnableInit selects InitContribution; the fallback selects an empty-source resource. Empty source suppresses that contribution. The always present ClassPass fills the mandatory class method hole so disabled-init output still materializes. --empty instead keeps an actual zero-parameter constructor. These outcomes differ: omitted init inherits Python's default construction; empty init is a generated function.

Compound Python constructs

YIDL embeds Astichi Python resources rather than defining separate syntax for if, try, with, decorators or comprehensions. An insertion must remain within a legal Python construct boundary. Put statement holes inside braced YIDL resources containing normal indented Python; use explicit pass contributions when a selected suite is empty. Interacting exception/loop/decorator constructs need their own SDK/version qualification. The first class does not establish all Python syntax as supported. See the bridge for known positions and limitations, and compatibility for unqualified compound cases.

Execute interacting compound constructs

example/documentation/control_flow.py qualifies a statement-hole insertion inside if nested in for/else, with, try/except/finally, with a decorated function and comprehension in the same resource. Zero/one/two ordered actions, empty input and a raised exception have explicit behavior assertions. This is a CPython 3.12.12 / Astichi 1.1.3 Python-backend recipe; other Python construct positions and version-specific syntax require separate checks.

Source authority: example/documentation/control_flow.py.

"""Compose ordered statements inside interacting Python compound constructs."""

from __future__ import annotations

import ast

from yidl.concept_parser import compile_yidl_files
from yidl.generation.data_def_sys import emit_concept_runtime_source

SOURCE = """
concept ControlFlow {
    property Label: str
    property Order: int
    record Action { Label Order }
    collection Actions: Action identity Label many
    resource Root = code $[
        from contextlib import nullcontext

        def identity(function):
            return function

        @identity
        def run(items, fail=False):
            events = []
            try:
                with nullcontext():
                    for item in items:
                        if item > 1:
                            astichi_hole(actions)
                        elif item == 1:
                            events.append("one")
                        else:
                            events.append("other")
                    else:
                        events.append("loop-else")
                    if fail:
                        raise ValueError("requested")
            except ValueError:
                events.append("caught")
            finally:
                events.append("finally")
            return events, [item * item for item in items]
    ]$ { keep run }
    resource ActionCode = template $[
        astichi_pass(events, outer_bind=True).append((astichi_pass(item, outer_bind=True), astichi_bind_external(label)))
    ]$
    resource Empty = template `pass`
    contribution ActionContribution = ActionCode {
        as Action
        index Order
        order Order
        target actions { build /Root }
        external label = Label
    }
    contribution EmptyContribution = Empty {
        as Empty
        target actions { build /Root }
    }
    matcher Selected(action: Actions) -> contribution {
        default -> ActionContribution
    }
    matcher EmptySelection() -> contribution {
        default -> EmptyContribution
    }
    production RootProduction -> composable {
        root Root = Root
        apply empty using EmptySelection
        apply actions from action: Actions using Selected
    }
    assembly Module = RootProduction
}
"""


def run() -> str:
    concept = compile_yidl_files({"control.yidl": SOURCE}, "control.yidl").concepts[
        "ControlFlow"
    ]
    runtime = emit_concept_runtime_source(
        concept.plan.build_data_definition(),
        resources=concept.resources,
        assembly_plan=concept,
    )
    schema: dict[str, object] = {}
    exec(runtime, schema)
    sources = []
    for labels in ((), ("first",), ("first", "second")):
        builder = schema["new_builder"]()
        for order, label in reversed(tuple(enumerate(labels))):
            builder.add(
                schema["ActionsCollection"], schema["Action"](label=label, order=order)
            )
        tree = schema["build_Module"](builder.freeze()).to_executable_ast()
        namespace: dict[str, object] = {}
        exec(compile(tree, "control_model.py", "exec"), namespace)
        events, squares = namespace["run"]([2, 1, 0])
        assert events == [(2, label) for label in labels] + [
            "one",
            "other",
            "loop-else",
            "finally",
        ]
        assert squares == [4, 1, 0]
        events, squares = namespace["run"]([3], fail=True)
        assert events == [(3, label) for label in labels] + [
            "loop-else",
            "caught",
            "finally",
        ]
        assert squares == [9]
        assert namespace["run"]([]) == (["loop-else", "finally"], [])
        sources.append(ast.unparse(tree) + "\n")
    return sources[-1]


if __name__ == "__main__":
    print(run(), end="")

Source authority: tests/data/goldens/materialized/documentation_classes/control_flow_model.py.

from contextlib import nullcontext

def identity(function):
    return function

@identity
def run(items, fail=False):
    events = []
    try:
        with nullcontext():
            for item in items:
                if item > 1:
                    pass
                    events.append((item, 'first'))
                    events.append((item, 'second'))
                elif item == 1:
                    events.append('one')
                else:
                    events.append('other')
            else:
                events.append('loop-else')
            if fail:
                raise ValueError('requested')
    except ValueError:
        events.append('caught')
    finally:
        events.append('finally')
    return (events, [item * item for item in items])