Skip to content

type_bridge.generator.render

render

Code generation renderers for TypeBridge models.

This package contains modules for rendering Python code from parsed schemas: - attributes: Attribute class definitions - entities: Entity class definitions - relations: Relation class definitions - registry: Pre-computed schema metadata registry - package: Package init.py with exports

render_api_dto

render_api_dto(schema, config=None)

Render the api_dto.py file from the parsed schema.

Parameters:

Name Type Description Default
schema ParsedSchema

The parsed TypeDB schema

required
config DTOConfig | None

Optional DTO configuration for customization

None

Returns:

Type Description
str

Generated Python code as a string

Source code in type_bridge/generator/render/api_dto.py
def render_api_dto(
    schema: ParsedSchema,
    config: DTOConfig | None = None,
) -> str:
    """Render the api_dto.py file from the parsed schema.

    Args:
        schema: The parsed TypeDB schema
        config: Optional DTO configuration for customization

    Returns:
        Generated Python code as a string
    """
    effective_config = config or DTOConfig()

    # Build class name mappings
    entity_class_names = {name: to_class_name(name) for name in schema.entities}
    relation_class_names = {name: to_class_name(name) for name in schema.relations}

    # Check for name clashes between entity/relation names and union type names
    entity_union = effective_config.entity_union_name
    relation_union = effective_config.relation_union_name

    for name, class_name in entity_class_names.items():
        if class_name == entity_union:
            raise ValueError(
                f"Entity '{name}' generates class name '{class_name}' which clashes "
                f"with entity_union_name='{entity_union}'. Use a different "
                f"entity_union_name in DTOConfig to avoid this conflict."
            )
        if class_name == relation_union:
            raise ValueError(
                f"Entity '{name}' generates class name '{class_name}' which clashes "
                f"with relation_union_name='{relation_union}'. Use a different "
                f"relation_union_name in DTOConfig to avoid this conflict."
            )

    for name, class_name in relation_class_names.items():
        if class_name == entity_union:
            raise ValueError(
                f"Relation '{name}' generates class name '{class_name}' which clashes "
                f"with entity_union_name='{entity_union}'. Use a different "
                f"entity_union_name in DTOConfig to avoid this conflict."
            )
        if class_name == relation_union:
            raise ValueError(
                f"Relation '{name}' generates class name '{class_name}' which clashes "
                f"with relation_union_name='{relation_union}'. Use a different "
                f"relation_union_name in DTOConfig to avoid this conflict."
            )

    # Build custom base class contexts
    base_class_contexts = _build_base_class_contexts(effective_config, schema)

    # Build validator contexts
    validator_contexts = _build_validator_contexts(effective_config)

    # Build composite entity contexts
    composite_contexts = _build_composite_contexts(effective_config, schema)

    # Build set of inherited attrs to skip (from all base classes)
    inherited_attrs_to_skip: dict[str, set[str]] = {}
    for bc in effective_config.base_classes:
        inherited_attrs_to_skip[bc.source_entity] = set(bc.inherited_attrs)

    # Build set of excluded entities
    excluded_entities = set(effective_config.exclude_entities)

    # Build entity contexts
    entities = []
    for name, spec in schema.entities.items():
        # Skip excluded entities
        if name in excluded_entities:
            continue

        parent_class = _get_parent_class(
            name,
            spec.parent,
            entity_class_names,
            "BaseDTO",
            effective_config,
            schema.entities,
        )

        # Find which attrs to skip (inherited from custom base)
        skip_attrs: set[str] = set()
        base_config = effective_config.get_base_class_for_entity(name, schema.entities)
        if base_config and name != base_config.source_entity:
            skip_attrs = inherited_attrs_to_skip.get(base_config.source_entity, set())

        # Get attributes owned by this entity (excluding inherited ones)
        own_attrs = []
        for attr_name in sorted(spec.owns):
            if attr_name in skip_attrs:
                continue
            attr_ctx = _get_attribute_context(attr_name, schema, spec)
            # Apply entity_field_overrides
            for efo in effective_config.entity_field_overrides:
                if efo.entity == name and efo.field == attr_name:
                    if efo.variant == "create":
                        if efo.required is not None:
                            attr_ctx.create_required = efo.required
                        if efo.default is not None:
                            attr_ctx.create_default = efo.default
                    elif efo.variant == "out":
                        if efo.required is not None:
                            attr_ctx.out_required = efo.required
                        if efo.default is not None:
                            attr_ctx.out_default = efo.default
            own_attrs.append(attr_ctx)

        entities.append(
            EntityContext(
                name=name,
                class_name=entity_class_names[name],
                parent_class=parent_class,
                abstract=spec.abstract,
                own_attributes=own_attrs,
            )
        )

    # Sort entities by inheritance (parents before children)
    entities = _sort_by_inheritance(entities, schema.entities)

    # Build relation contexts
    relations = []
    for name, spec in schema.relations.items():
        parent_class = _get_parent_class(
            name,
            spec.parent,
            relation_class_names,
            "BaseRelation",
            effective_config,
            {},  # Relations don't have custom base classes yet
        )

        # Get roles for this relation
        roles = []
        for role_spec in spec.roles:
            # Check cardinality to determine if required
            required = False
            if role_spec.cardinality:
                required = role_spec.cardinality.min >= 1

            roles.append(
                RoleContext(
                    name=role_spec.name,
                    python_name=to_python_name(role_spec.name),
                    required=required,
                )
            )

        # Get attributes owned by this relation
        own_attrs = []
        for attr_name in sorted(spec.owns):
            attr_ctx = _get_attribute_context(attr_name, schema, spec)
            own_attrs.append(attr_ctx)

        relations.append(
            RelationContext(
                name=name,
                class_name=relation_class_names[name],
                parent_class=parent_class,
                abstract=spec.abstract,
                roles=roles,
                own_attributes=own_attrs,
            )
        )

    # Sort relations by inheritance
    relations = _sort_by_inheritance(relations, schema.relations)

    # Filter concrete (non-abstract) types for union types
    concrete_entities = [e for e in entities if not e.abstract]
    concrete_relations = [r for r in relations if not r.abstract]

    template = get_template("api_dto.py.jinja")
    return template.render(
        entities=entities,
        relations=relations,
        concrete_entities=concrete_entities,
        concrete_relations=concrete_relations,
        base_classes=base_class_contexts,
        validators=validator_contexts,
        composites=composite_contexts,
        preamble=effective_config.preamble,
        entity_union_name=effective_config.entity_union_name,
        relation_union_name=effective_config.relation_union_name,
        iid_field_name=effective_config.iid_field_name,
        skip_relation_output=effective_config.skip_relation_output,
        relation_create_base_class=effective_config.relation_create_base_class,
        relation_preamble=effective_config.relation_preamble,
        strict_out_models=effective_config.strict_out_models,
    )

render_attributes

render_attributes(schema, class_names)

Render the complete attributes module source.

Parameters:

Name Type Description Default
schema ParsedSchema

Parsed schema containing attribute definitions

required
class_names dict[str, str]

Mapping from TypeDB names to Python class names

required

Returns:

Type Description
str

Complete Python source code for attributes.py

Source code in type_bridge/generator/render/attributes.py
def render_attributes(schema: ParsedSchema, class_names: dict[str, str]) -> str:
    """Render the complete attributes module source.

    Args:
        schema: Parsed schema containing attribute definitions
        class_names: Mapping from TypeDB names to Python class names

    Returns:
        Complete Python source code for attributes.py
    """
    logger.debug(f"Rendering {len(schema.attributes)} attribute classes")

    imports = sorted(_get_required_imports(schema, class_names))
    uses_classvar = any(
        attr.allowed_values or attr.regex or attr.range_min or attr.range_max
        for attr in schema.attributes.values()
    )

    attributes = []
    all_names = []
    for attr_name in _topological_sort_attributes(schema):
        all_names.append(class_names[attr_name])
        attributes.append(_build_attribute_context(attr_name, schema, class_names))

    template = get_template("attributes.py.jinja")
    result = template.render(
        imports=imports,
        uses_classvar=uses_classvar,
        attributes=attributes,
        all_names=sorted(all_names),
    )

    logger.info(f"Rendered {len(all_names)} attribute classes")
    return result

render_entities

render_entities(schema, attr_class_names, entity_class_names, implicit_key_attributes=None)

Render the complete entities module source.

Parameters:

Name Type Description Default
schema ParsedSchema

Parsed schema containing entity definitions

required
attr_class_names dict[str, str]

Mapping from TypeDB attr names to Python class names

required
entity_class_names dict[str, str]

Mapping from TypeDB entity names to Python class names

required
implicit_key_attributes set[str] | None

Attributes to treat as keys even without @key

None

Returns:

Type Description
str

Complete Python source code for entities.py

Source code in type_bridge/generator/render/entities.py
def render_entities(
    schema: ParsedSchema,
    attr_class_names: dict[str, str],
    entity_class_names: dict[str, str],
    implicit_key_attributes: set[str] | None = None,
) -> str:
    """Render the complete entities module source.

    Args:
        schema: Parsed schema containing entity definitions
        attr_class_names: Mapping from TypeDB attr names to Python class names
        entity_class_names: Mapping from TypeDB entity names to Python class names
        implicit_key_attributes: Attributes to treat as keys even without @key

    Returns:
        Complete Python source code for entities.py
    """
    logger.debug(f"Rendering {len(schema.entities)} entity classes")
    implicit_keys = implicit_key_attributes or set()
    needs_card = _needs_card_import(schema)

    imports = ["Entity", "Flag", "Key", "TypeFlags", "Unique"]
    if needs_card:
        imports.insert(1, "Card")

    entities = []
    all_names = []
    for entity_name in _topological_sort_entities(schema):
        all_names.append(entity_class_names[entity_name])
        entities.append(
            _build_entity_context(
                entity_name,
                schema,
                attr_class_names,
                entity_class_names,
                implicit_keys,
            )
        )

    template = get_template("entities.py.jinja")
    result = template.render(
        imports=imports,
        entities=entities,
        all_names=sorted(all_names),
    )

    logger.info(f"Rendered {len(all_names)} entity classes")
    return result

render_functions

render_functions(schema)

Render the complete functions module.

Generates Python function wrappers that return FunctionQuery objects for each TypeDB function defined in the schema.

Parameters:

Name Type Description Default
schema ParsedSchema

Parsed schema containing function definitions

required

Returns:

Type Description
str

Complete Python source code for functions.py, or empty string if no functions

Source code in type_bridge/generator/render/functions.py
def render_functions(schema: ParsedSchema) -> str:
    """Render the complete functions module.

    Generates Python function wrappers that return FunctionQuery objects
    for each TypeDB function defined in the schema.

    Args:
        schema: Parsed schema containing function definitions

    Returns:
        Complete Python source code for functions.py, or empty string if no functions
    """
    if not schema.functions:
        return ""

    logger.debug(f"Rendering {len(schema.functions)} function wrappers")

    contexts = []
    all_names = []
    for name, spec in sorted(schema.functions.items()):
        py_name = to_python_name(name)
        all_names.append(py_name)
        contexts.append(_build_function_context(name, spec))

    imports = _get_required_imports(contexts)
    datetime_imports = sorted(imports & {"datetime", "date"})
    has_decimal = "Decimal" in imports
    has_duration = "Duration" in imports

    # Generate the source code directly (template is simple enough)
    lines = [
        '"""Function wrappers generated from a TypeDB schema.',
        "",
        "These functions return FunctionQuery objects that can generate TypeQL queries",
        "for calling the corresponding TypeDB schema functions.",
        '"""',
        "",
        "from __future__ import annotations",
        "",
    ]

    # Add imports
    if datetime_imports:
        lines.append(f"from datetime import {', '.join(datetime_imports)}")
    if has_decimal:
        lines.append("from decimal import Decimal")
    if has_duration:
        lines.append("from isodate import Duration")
    lines.append("from typing import Iterator")
    lines.append("")
    lines.append("from type_bridge.expressions import FunctionQuery, ReturnType")
    lines.append("")
    lines.append("")

    # Generate each function
    for ctx in contexts:
        # Function signature
        if ctx.param_signature:
            lines.append(f"def {ctx.py_name}({ctx.param_signature}) -> {ctx.return_hint}:")
        else:
            lines.append(f"def {ctx.py_name}() -> {ctx.return_hint}:")

        # Docstring
        if ctx.docstring:
            lines.append(f'    """{ctx.docstring}')
        else:
            lines.append(f'    """Call TypeDB function `{ctx.name}`.')

        # Add return type info to docstring
        stream_info = "stream of " if ctx.is_stream else ""
        type_info = ", ".join(ctx.return_types)
        lines.append("")
        lines.append(f"    Returns: {stream_info}{type_info}")
        lines.append('    """')

        # Build args list
        if ctx.params:
            args_items = [f'("{p.name}", {p.py_name})' for p in ctx.params]
            args_str = f"[{', '.join(args_items)}]"
        else:
            args_str = "[]"

        # Build return type
        return_types_str = ", ".join(f'"{t}"' for t in ctx.return_types)
        is_stream_str = "True" if ctx.is_stream else "False"

        lines.append("    return FunctionQuery(")
        lines.append(f'        name="{ctx.name}",')
        lines.append(f"        args={args_str},")
        lines.append(
            f"        return_type=ReturnType([{return_types_str}], is_stream={is_stream_str}),"
        )
        lines.append("    )")
        lines.append("")
        lines.append("")

    # Generate __all__
    lines.append("__all__ = [")
    for name in sorted(all_names):
        lines.append(f'    "{name}",')
    lines.append("]")
    lines.append("")

    result = "\n".join(lines)
    logger.info(f"Rendered {len(all_names)} function wrappers")
    return result

render_package_init

render_package_init(attr_class_names, entity_class_names, relation_class_names, *, schema_version='1.0.0', include_schema_loader=True, schema_filename='schema.tql', functions_present=False)

Render the package init.py source.

Parameters:

Name Type Description Default
attr_class_names dict[str, str]

Mapping from TypeDB attr names to Python class names

required
entity_class_names dict[str, str]

Mapping from TypeDB entity names to Python class names

required
relation_class_names dict[str, str]

Mapping from TypeDB relation names to Python class names

required
schema_version str

Version string for SCHEMA_VERSION constant

'1.0.0'
include_schema_loader bool

Whether to include schema_text() helper

True
schema_filename str | None

Filename for the schema file (used in schema_text() loader)

'schema.tql'
functions_present bool

Whether to export functions module

False

Returns:

Type Description
str

Complete Python source code for init.py

Source code in type_bridge/generator/render/package.py
def render_package_init(
    attr_class_names: dict[str, str],
    entity_class_names: dict[str, str],
    relation_class_names: dict[str, str],
    *,
    schema_version: str = "1.0.0",
    include_schema_loader: bool = True,
    schema_filename: str | None = "schema.tql",
    functions_present: bool = False,
) -> str:
    """Render the package __init__.py source.

    Args:
        attr_class_names: Mapping from TypeDB attr names to Python class names
        entity_class_names: Mapping from TypeDB entity names to Python class names
        relation_class_names: Mapping from TypeDB relation names to Python class names
        schema_version: Version string for SCHEMA_VERSION constant
        include_schema_loader: Whether to include schema_text() helper
        schema_filename: Filename for the schema file (used in schema_text() loader)
        functions_present: Whether to export functions module

    Returns:
        Complete Python source code for __init__.py
    """
    # Don't include loader if no filename provided
    if schema_filename is None:
        include_schema_loader = False

    module_imports = ["attributes", "entities", "registry", "relations"]
    if functions_present:
        module_imports.append("functions")
    module_imports = sorted(module_imports)

    all_exports = [
        "ATTRIBUTES",
        "ENTITIES",
        "RELATIONS",
        "SCHEMA_VERSION",
        "attributes",
        "entities",
        "registry",
        "relations",
    ]
    if functions_present:
        all_exports.append("functions")
    if include_schema_loader:
        all_exports.append("schema_text")
    all_exports = sorted(all_exports)

    template = get_template("package_init.py.jinja")
    return template.render(
        module_imports=module_imports,
        schema_version=schema_version,
        include_schema_loader=include_schema_loader,
        schema_filename=schema_filename or "schema.tql",
        attributes=sorted(attr_class_names[name] for name in attr_class_names),
        entities=sorted(entity_class_names[name] for name in entity_class_names),
        relations=sorted(relation_class_names[name] for name in relation_class_names),
        all_exports=all_exports,
    )

render_registry

render_registry(schema, attr_class_names, entity_class_names, relation_class_names, *, schema_version='1.0.0', schema_text=None)

Render the registry.py module source.

Parameters:

Name Type Description Default
schema ParsedSchema

Parsed schema with all type information

required
attr_class_names dict[str, str]

Mapping from TypeDB attr names to Python class names

required
entity_class_names dict[str, str]

Mapping from TypeDB entity names to Python class names

required
relation_class_names dict[str, str]

Mapping from TypeDB relation names to Python class names

required
schema_version str

Version string for SCHEMA_VERSION constant

'1.0.0'
schema_text str | None

Original schema text for hash computation

None

Returns:

Type Description
str

Complete Python source code for registry.py

Source code in type_bridge/generator/render/registry.py
def render_registry(
    schema: ParsedSchema,
    attr_class_names: dict[str, str],
    entity_class_names: dict[str, str],
    relation_class_names: dict[str, str],
    *,
    schema_version: str = "1.0.0",
    schema_text: str | None = None,
) -> str:
    """Render the registry.py module source.

    Args:
        schema: Parsed schema with all type information
        attr_class_names: Mapping from TypeDB attr names to Python class names
        entity_class_names: Mapping from TypeDB entity names to Python class names
        relation_class_names: Mapping from TypeDB relation names to Python class names
        schema_version: Version string for SCHEMA_VERSION constant
        schema_text: Original schema text for hash computation

    Returns:
        Complete Python source code for registry.py
    """
    entity_names = sorted(schema.entities.keys())
    relation_names = sorted(schema.relations.keys())
    attribute_names = sorted(schema.attributes.keys())

    # Compute schema hash
    if schema_text:
        schema_hash = f"sha256:{hashlib.sha256(schema_text.encode('utf-8')).hexdigest()[:16]}"
    else:
        schema_hash = ""

    # Build type-to-class mappings
    entity_map = {name: entity_class_names[name] for name in entity_names}
    relation_map = {name: relation_class_names[name] for name in relation_names}
    attribute_map = {name: attr_class_names[name] for name in attribute_names}

    # Build relation roles
    relation_roles = {}
    for rel_name in relation_names:
        relation = schema.relations[rel_name]
        if not relation.roles:
            continue
        roles = []
        for role in relation.roles:
            players = list(minimal_role_players(schema, rel_name, role.name))
            roles.append(RoleContext(name=role.name, players=players))
        relation_roles[rel_name] = roles

    # Build relation attributes
    relation_attributes = {}
    for name in relation_names:
        relation = schema.relations[name]
        relation_attributes[name] = sorted(relation.owns)

    # Build entity attributes
    entity_attributes = {}
    for name in entity_names:
        entity = schema.entities[name]
        entity_attributes[name] = sorted(entity.owns)

    # Build entity keys
    entity_keys = {}
    for name in entity_names:
        entity = schema.entities[name]
        if entity.keys:
            entity_keys[name] = sorted(entity.keys)

    # Build attribute value types
    attribute_value_types = {}
    for name in attribute_names:
        attr = schema.attributes[name]
        if attr.value_type:
            attribute_value_types[name] = attr.value_type

    # Build inheritance metadata
    entity_parents = {name: schema.entities[name].parent for name in entity_names}
    relation_parents = {name: schema.relations[name].parent for name in relation_names}

    entity_abstract = [n for n in entity_names if schema.entities[n].abstract]
    relation_abstract = [n for n in relation_names if schema.relations[n].abstract]

    # Collect annotations
    entity_annotations = {
        name: dict(spec.annotations) for name, spec in schema.entities.items() if spec.annotations
    }
    attribute_annotations = {
        name: dict(spec.annotations) for name, spec in schema.attributes.items() if spec.annotations
    }
    relation_annotations = {
        name: dict(spec.annotations) for name, spec in schema.relations.items() if spec.annotations
    }

    template = get_template("registry.py.jinja")
    return template.render(
        schema_version=schema_version,
        schema_hash=schema_hash,
        entity_names=entity_names,
        relation_names=relation_names,
        attribute_names=attribute_names,
        entity_map=entity_map,
        relation_map=relation_map,
        attribute_map=attribute_map,
        relation_roles=relation_roles,
        relation_attributes=relation_attributes,
        entity_attributes=entity_attributes,
        entity_keys=entity_keys,
        attribute_value_types=attribute_value_types,
        entity_parents=entity_parents,
        relation_parents=relation_parents,
        entity_abstract=entity_abstract,
        relation_abstract=relation_abstract,
        entity_annotations=entity_annotations,
        attribute_annotations=attribute_annotations,
        relation_annotations=relation_annotations,
    )

render_relations

render_relations(schema, attr_class_names, entity_class_names, relation_class_names=None)

Render the complete relations module source.

Parameters:

Name Type Description Default
schema ParsedSchema

Parsed schema containing relation definitions

required
attr_class_names dict[str, str]

Mapping from TypeDB attr names to Python class names

required
entity_class_names dict[str, str]

Mapping from TypeDB entity names to Python class names

required
relation_class_names dict[str, str] | None

Mapping from TypeDB relation names to Python class names

None

Returns:

Type Description
str

Complete Python source code for relations.py

Source code in type_bridge/generator/render/relations.py
def render_relations(
    schema: ParsedSchema,
    attr_class_names: dict[str, str],
    entity_class_names: dict[str, str],
    relation_class_names: dict[str, str] | None = None,
) -> str:
    """Render the complete relations module source.

    Args:
        schema: Parsed schema containing relation definitions
        attr_class_names: Mapping from TypeDB attr names to Python class names
        entity_class_names: Mapping from TypeDB entity names to Python class names
        relation_class_names: Mapping from TypeDB relation names to Python class names

    Returns:
        Complete Python source code for relations.py
    """
    logger.debug(f"Rendering {len(schema.relations)} relation classes")
    if relation_class_names is None:
        relation_class_names = {name: to_class_name(name) for name in schema.relations}

    # Build imports list
    imports = ["Relation", "Role", "TypeFlags"]
    # Flag is needed for @key, @unique, or multi-value attributes (Card used with Flag)
    needs_flag = (
        _needs_key_import(schema)
        or _needs_unique_import(schema)
        or _needs_card_for_attributes(schema)
    )
    if _needs_card_import(schema):
        imports.insert(0, "Card")
    if needs_flag:
        imports.insert(0, "Flag")
    if _needs_key_import(schema):
        imports.append("Key")
    if _needs_unique_import(schema):
        imports.append("Unique")

    relations = []
    all_names = []
    for relation_name in _topological_sort_relations(schema):
        all_names.append(relation_class_names[relation_name])
        relations.append(
            _build_relation_context(
                relation_name,
                schema,
                attr_class_names,
                entity_class_names,
                relation_class_names,
            )
        )

    template = get_template("relations.py.jinja")
    result = template.render(
        imports=imports,
        relations=relations,
        all_names=sorted(all_names),
    )

    logger.info(f"Rendered {len(all_names)} relation classes")
    return result

render_structs

render_structs(schema, class_names)

Render the complete structs module source.

Parameters:

Name Type Description Default
schema ParsedSchema

Parsed schema containing struct definitions

required
class_names dict[str, str]

Mapping from TypeDB names to Python class names

required

Returns:

Type Description
str

Complete Python source code for structs.py, or empty string if no structs

Source code in type_bridge/generator/render/structs.py
def render_structs(schema: ParsedSchema, class_names: dict[str, str]) -> str:
    """Render the complete structs module source.

    Args:
        schema: Parsed schema containing struct definitions
        class_names: Mapping from TypeDB names to Python class names

    Returns:
        Complete Python source code for structs.py, or empty string if no structs
    """
    if not schema.structs:
        return ""

    logger.debug(f"Rendering {len(schema.structs)} struct classes")

    imports = _get_required_imports(schema)
    datetime_imports = sorted(imports & {"datetime", "date"})
    decimal_import = "Decimal" in imports

    structs = []
    all_names = []
    for struct_name in sorted(schema.structs.keys()):
        all_names.append(class_names[struct_name])
        structs.append(_build_struct_context(struct_name, schema, class_names))

    template = get_template("structs.py.jinja")
    result = template.render(
        datetime_imports=datetime_imports,
        decimal_import=decimal_import,
        structs=structs,
        all_names=sorted(all_names),
    )

    logger.info(f"Rendered {len(all_names)} struct classes")
    return result