Skip to content

type_bridge.models.base

base

Abstract base class for TypeDB entities and relations.

TypeDBType

Bases: BaseModel, ABC

Abstract base class for TypeDB entities and relations.

This class provides common functionality for both Entity and Relation types, including type name management, abstract/base flags, and attribute ownership.

Subclasses must implement: - get_supertype(): Get parent type in TypeDB hierarchy - to_schema_definition(): Generate TypeQL schema definition - to_insert_query(): Generate TypeQL insert query for instances

manager classmethod

manager(connection)

Create a CRUD manager for this type.

Parameters:

Name Type Description Default
connection Connection

Database, Transaction, or TransactionContext

required

Returns:

Type Description
TypeDBManager[Self]

Manager instance for this type

Source code in type_bridge/models/base.py
@classmethod
def manager(cls, connection: Connection) -> TypeDBManager[Self]:
    """Create a CRUD manager for this type.

    Args:
        connection: Database, Transaction, or TransactionContext

    Returns:
        Manager instance for this type
    """
    manager_class = cls._get_manager_class()
    return cast("TypeDBManager[Self]", manager_class(connection, cls))

insert

insert(connection)

Insert this instance into the database.

Parameters:

Name Type Description Default
connection Connection

Database, Transaction, or TransactionContext

required

Returns:

Type Description
Self

Self for chaining

Source code in type_bridge/models/base.py
def insert(self, connection: Connection) -> Self:
    """Insert this instance into the database.

    Args:
        connection: Database, Transaction, or TransactionContext

    Returns:
        Self for chaining
    """
    self.manager(connection).insert(self)
    return self

delete

delete(connection)

Delete this instance from the database.

Parameters:

Name Type Description Default
connection Connection

Database, Transaction, or TransactionContext

required

Returns:

Type Description
Self

Self for chaining

Source code in type_bridge/models/base.py
def delete(self, connection: Connection) -> Self:
    """Delete this instance from the database.

    Args:
        connection: Database, Transaction, or TransactionContext

    Returns:
        Self for chaining
    """
    self.manager(connection).delete(self)
    return self

has classmethod

has(connection, attr_class, value=None)

Find all instances of this class (and its subtypes) that own attr_class.

Behaviour depends on the receiver:

  • Entity.has(...) / Relation.has(...): cross-type lookup — returns instances across all concrete types of that kind.
  • <ConcreteType>.has(...) / <AbstractBase>.has(...): narrowed lookup — restricted to that type and its TypeDB subtypes via isa polymorphism.

Returned relation instances always have their role players hydrated (in addition to attributes). This is implemented by re-fetching each relation through concrete_class.manager(connection).get(_iid=...) after the initial wildcard query, so the relation path is N+1 in the number of returned relations. Entity lookups remain single-query.

Parameters:

Name Type Description Default
connection Connection

Database, Transaction, or TransactionContext.

required
attr_class type[Attribute]

Attribute type to search for (e.g. Name).

required
value Any | None

Optional filter — raw value, Attribute instance, or Expression (e.g. Name.gt(Name("B"))).

None

Returns:

Type Description
list[TypeDBType]

List of hydrated model instances (may contain mixed concrete types

list[TypeDBType]

when called on the base Entity / Relation class or an

list[TypeDBType]

abstract base subclass).

Raises:

Type Description
TypeError

If called directly on :class:TypeDBType (use :class:Entity or :class:Relation instead).

Source code in type_bridge/models/base.py
@classmethod
def has(
    cls,
    connection: Connection,
    attr_class: type[Attribute],
    value: Any | None = None,
) -> list[TypeDBType]:
    """Find all instances of this class (and its subtypes) that own *attr_class*.

    Behaviour depends on the receiver:

    * ``Entity.has(...)`` / ``Relation.has(...)``: cross-type lookup — returns
      instances across **all** concrete types of that kind.
    * ``<ConcreteType>.has(...)`` / ``<AbstractBase>.has(...)``: narrowed
      lookup — restricted to that type and its TypeDB subtypes via ``isa``
      polymorphism.

    Returned relation instances always have their role players hydrated
    (in addition to attributes). This is implemented by re-fetching each
    relation through ``concrete_class.manager(connection).get(_iid=...)``
    after the initial wildcard query, so the relation path is N+1 in the
    number of returned relations. Entity lookups remain single-query.

    Args:
        connection: Database, Transaction, or TransactionContext.
        attr_class: Attribute type to search for (e.g. ``Name``).
        value: Optional filter — raw value, Attribute instance,
               or Expression (e.g. ``Name.gt(Name("B"))``).

    Returns:
        List of hydrated model instances (may contain mixed concrete types
        when called on the base ``Entity`` / ``Relation`` class or an
        abstract base subclass).

    Raises:
        TypeError: If called directly on :class:`TypeDBType` (use
            :class:`Entity` or :class:`Relation` instead).
    """
    from type_bridge.crud.has_lookup import has_lookup
    from type_bridge.models.entity import Entity
    from type_bridge.models.relation import Relation

    if cls is TypeDBType:
        raise TypeError("has() must be called on Entity or Relation, not TypeDBType directly")

    if issubclass(cls, Entity):
        kind: Literal["entity", "relation"] = "entity"
        base_cls: type[TypeDBType] = Entity
    elif issubclass(cls, Relation):
        kind = "relation"
        base_cls = Relation
    else:
        raise TypeError(f"has() requires an Entity or Relation class, got {cls.__name__}")

    # Narrow to the concrete (or abstract base) type when the caller is
    # not the bare Entity / Relation class. TypeDB's `isa` is polymorphic,
    # so subtypes of an abstract base are matched automatically.
    narrow_type = None if cls is base_cls else cls.get_type_name()

    return has_lookup(connection, attr_class, value, kind=kind, type_name=narrow_type)

__init_subclass__

__init_subclass__()

Called when a TypeDBType subclass is created.

Source code in type_bridge/models/base.py
def __init_subclass__(cls) -> None:
    """Called when a TypeDBType subclass is created."""
    super().__init_subclass__()

    # Get TypeFlags if defined, otherwise create new default flags
    # Check if flags is defined directly on this class (not inherited)
    if "flags" in cls.__dict__ and isinstance(cls.__dict__["flags"], TypeFlags):
        # Explicitly set flags on this class
        cls._flags = cls.__dict__["flags"]
    else:
        # No explicit flags on this class - create new default flags
        # This ensures each subclass gets its own flags instance
        cls._flags = TypeFlags()

    # Validate type name doesn't conflict with TypeDB built-ins
    # Skip validation for:
    # 1. Base classes that won't appear in schema (base=True)
    # 2. The abstract base Entity and Relation classes themselves
    is_base_entity_or_relation = cls.__name__ in ("Entity", "Relation") and cls.__module__ in (
        "type_bridge.models",
        "type_bridge.models.entity",
        "type_bridge.models.relation",
    )
    if not cls._flags.base and not is_base_entity_or_relation:
        type_name = cls._flags.name or format_type_name(cls.__name__, cls._flags.case)
        validate_type_name(type_name, cls.__name__, cls._type_context)

    # Register model in the central registry
    ModelRegistry.register(cls)

__pydantic_init_subclass__ classmethod

__pydantic_init_subclass__(**kwargs)

Called by Pydantic after model class initialization.

Injects FieldDescriptor instances for class-level query access. This runs after Pydantic's setup is complete, so descriptors won't be removed.

Example

Person.age # Returns FieldRef for query building (class-level access) person.age # Returns attribute value (instance-level access)

Source code in type_bridge/models/base.py
@classmethod
def __pydantic_init_subclass__(cls, **kwargs: Any) -> None:
    """Called by Pydantic after model class initialization.

    Injects FieldDescriptor instances for class-level query access.
    This runs after Pydantic's setup is complete, so descriptors won't be removed.

    Example:
        Person.age  # Returns FieldRef for query building (class-level access)
        person.age  # Returns attribute value (instance-level access)
    """
    super().__pydantic_init_subclass__(**kwargs)

    from type_bridge.fields import FieldDescriptor

    # Inject FieldDescriptors for class-level query access
    for field_name, attr_info in cls._owned_attrs.items():
        descriptor = FieldDescriptor(field_name=field_name, attr_type=attr_info.typ)
        type.__setattr__(cls, field_name, descriptor)

model_copy

model_copy(*, update=None, deep=False)

Override model_copy to ensure raw values are wrapped in Attribute instances.

Pydantic's model_copy bypasses validators even with revalidate_instances='always', so we pre-wrap values in the update dict before copying. Also preserves _iid from original using Pydantic's pydantic_private.

Source code in type_bridge/models/base.py
def model_copy(self, *, update: Mapping[str, Any] | None = None, deep: bool = False):
    """Override model_copy to ensure raw values are wrapped in Attribute instances.

    Pydantic's model_copy bypasses validators even with revalidate_instances='always',
    so we pre-wrap values in the update dict before copying.
    Also preserves _iid from original using Pydantic's __pydantic_private__.
    """
    # Preserve _iid before copy
    preserved_iid = getattr(self, "_iid", None)

    # Pre-wrap values in update dict before calling super()
    wrapped_update: dict[str, Any] | None = None
    if update:
        wrapped_update = {}
        owned_attrs = self.__class__.get_owned_attributes()
        for key, value in update.items():
            if key in owned_attrs and value is not None:
                attr_info = owned_attrs[key]
                attr_class = attr_info.typ
                if isinstance(value, list):
                    wrapped_update[key] = [
                        item if isinstance(item, attr_class) else attr_class(item)
                        for item in value
                    ]
                elif not isinstance(value, attr_class):
                    wrapped_update[key] = attr_class(value)
                else:
                    wrapped_update[key] = value
            else:
                wrapped_update[key] = value

    # Call parent model_copy with pre-wrapped update
    copied = super().model_copy(update=wrapped_update, deep=deep)

    # Restore _iid using Pydantic's official private attribute storage
    private = copied.__pydantic_private__
    if preserved_iid is not None and private is not None and private.get("_iid") is None:
        private["_iid"] = preserved_iid

    return copied

get_type_name classmethod

get_type_name()

Get the TypeDB type name for this type.

If name is explicitly set in TypeFlags, it is used as-is. Otherwise, the class name is formatted according to the case parameter.

Source code in type_bridge/models/base.py
@classmethod
def get_type_name(cls) -> str:
    """Get the TypeDB type name for this type.

    If name is explicitly set in TypeFlags, it is used as-is.
    Otherwise, the class name is formatted according to the case parameter.
    """
    if cls._flags.name:
        return cls._flags.name
    return format_type_name(cls.__name__, cls._flags.case)

get_supertype classmethod

get_supertype()

Get the supertype from Python inheritance, skipping base classes.

Base classes (with base=True) are Python-only and don't appear in TypeDB schema. This method skips them when determining the TypeDB supertype.

Returns:

Type Description
str | None

Type name of the parent class, or None if direct subclass

Source code in type_bridge/models/base.py
@classmethod
def get_supertype(cls) -> str | None:
    """Get the supertype from Python inheritance, skipping base classes.

    Base classes (with base=True) are Python-only and don't appear in TypeDB schema.
    This method skips them when determining the TypeDB supertype.

    Returns:
        Type name of the parent class, or None if direct subclass
    """
    base_class = cls._get_base_type_class()
    for base in cls.__bases__:
        if base is not base_class and issubclass(base, base_class):
            # Skip base classes - they don't appear in TypeDB schema
            if base.is_base():
                # Recursively find the first non-base parent
                return base.get_supertype()
            return base.get_type_name()
    return None

is_abstract classmethod

is_abstract()

Check if this is an abstract type.

Source code in type_bridge/models/base.py
@classmethod
def is_abstract(cls) -> bool:
    """Check if this is an abstract type."""
    return cls._flags.abstract

is_base classmethod

is_base()

Check if this is a Python base class (not in TypeDB schema).

Source code in type_bridge/models/base.py
@classmethod
def is_base(cls) -> bool:
    """Check if this is a Python base class (not in TypeDB schema)."""
    return cls._flags.base

get_owned_attributes classmethod

get_owned_attributes()

Get attributes owned directly by this type (not inherited).

Returns:

Type Description
dict[str, ModelAttrInfo]

Dictionary mapping field names to ModelAttrInfo (typ + flags)

Source code in type_bridge/models/base.py
@classmethod
def get_owned_attributes(cls) -> dict[str, ModelAttrInfo]:
    """Get attributes owned directly by this type (not inherited).

    Returns:
        Dictionary mapping field names to ModelAttrInfo (typ + flags)
    """
    return cls._owned_attrs.copy()

get_all_attributes classmethod

get_all_attributes()

Get all attributes including inherited ones.

Traverses the class hierarchy to collect all owned attributes, including those from parent Entity/Relation classes.

Returns:

Type Description
dict[str, ModelAttrInfo]

Dictionary mapping field names to ModelAttrInfo (typ + flags)

Source code in type_bridge/models/base.py
@classmethod
def get_all_attributes(cls) -> dict[str, ModelAttrInfo]:
    """Get all attributes including inherited ones.

    Traverses the class hierarchy to collect all owned attributes,
    including those from parent Entity/Relation classes.

    Returns:
        Dictionary mapping field names to ModelAttrInfo (typ + flags)
    """
    all_attrs: dict[str, ModelAttrInfo] = {}

    # Traverse MRO in reverse to get parent attributes first
    # Child attributes will override parent attributes with same name
    for base in reversed(cls.__mro__):
        if hasattr(base, "_owned_attrs") and isinstance(base._owned_attrs, dict):
            all_attrs.update(dict(base._owned_attrs))

    return all_attrs

get_polymorphic_attributes classmethod

get_polymorphic_attributes()

Get all attributes including those from registered subtypes.

For polymorphic queries where the base class is used but concrete subtypes may be returned, this method collects attributes from all known subtypes so the query can fetch all possible attributes.

Returns:

Type Description
dict[str, ModelAttrInfo]

Dictionary mapping field names to ModelAttrInfo, including

dict[str, ModelAttrInfo]

attributes from all registered subtypes.

Source code in type_bridge/models/base.py
@classmethod
def get_polymorphic_attributes(cls) -> dict[str, ModelAttrInfo]:
    """Get all attributes including those from registered subtypes.

    For polymorphic queries where the base class is used but concrete
    subtypes may be returned, this method collects attributes from all
    known subtypes so the query can fetch all possible attributes.

    Returns:
        Dictionary mapping field names to ModelAttrInfo, including
        attributes from all registered subtypes.
    """
    # Start with this class's attributes (including inherited)
    all_attrs = cls.get_all_attributes()

    # Recursively collect attributes from all subclasses
    def collect_subclass_attrs(klass: type[TypeDBType]) -> None:
        for subclass in klass.__subclasses__():
            # Skip if subclass is a base class (abstract, Python-only)
            if hasattr(subclass, "is_base") and subclass.is_base():
                continue

            # Get subclass attributes and merge (subclass attrs take precedence)
            if hasattr(subclass, "get_all_attributes"):
                subclass_attrs = subclass.get_all_attributes()
                for field_name, attr_info in subclass_attrs.items():
                    if field_name not in all_attrs:
                        all_attrs[field_name] = attr_info

            # Recurse into further subclasses
            collect_subclass_attrs(subclass)

    collect_subclass_attrs(cls)
    return all_attrs

to_schema_definition abstractmethod classmethod

to_schema_definition()

Generate TypeQL schema definition for this type.

Returns:

Type Description
str | None

TypeQL schema definition string, or None if this is a base class

Source code in type_bridge/models/base.py
@classmethod
@abstractmethod
def to_schema_definition(cls) -> str | None:
    """Generate TypeQL schema definition for this type.

    Returns:
        TypeQL schema definition string, or None if this is a base class
    """
    ...

get_match_clause_info abstractmethod

get_match_clause_info(var_name='$x')

Get information to build a TypeQL match clause for this instance.

Used by TypeDBManager for delete/update operations. Returns IID-based matching when available, otherwise falls back to type-specific identification (key attributes for entities, role players for relations).

Parameters:

Name Type Description Default
var_name str

Variable name to use in the match clause

'$x'

Returns:

Type Description
MatchClauseInfo

MatchClauseInfo with main clause, extra clauses, and variable name

Raises:

Type Description
ValueError

If instance cannot be identified (no IID and no keys/role players)

Source code in type_bridge/models/base.py
@abstractmethod
def get_match_clause_info(self, var_name: str = "$x") -> MatchClauseInfo:
    """Get information to build a TypeQL match clause for this instance.

    Used by TypeDBManager for delete/update operations. Returns IID-based
    matching when available, otherwise falls back to type-specific
    identification (key attributes for entities, role players for relations).

    Args:
        var_name: Variable name to use in the match clause

    Returns:
        MatchClauseInfo with main clause, extra clauses, and variable name

    Raises:
        ValueError: If instance cannot be identified (no IID and no keys/role players)
    """
    ...

to_ast abstractmethod

to_ast(var='$x')

Generate AST InsertClause for this instance.

Parameters:

Name Type Description Default
var str

Variable name to use

'$x'

Returns:

Type Description
Any

InsertClause containing statements

Source code in type_bridge/models/base.py
@abstractmethod
def to_ast(self, var: str = "$x") -> Any:
    """Generate AST InsertClause for this instance.

    Args:
        var: Variable name to use

    Returns:
        InsertClause containing statements
    """
    ...

to_insert_query

to_insert_query(var='$e')

Generate TypeQL insert query string for this instance.

This is a convenience method that uses the AST-based generation internally and compiles it to a string.

Parameters:

Name Type Description Default
var str

Variable name to use (default: "$e")

'$e'

Returns:

Type Description
str

TypeQL insert query string

Source code in type_bridge/models/base.py
def to_insert_query(self, var: str = "$e") -> str:
    """Generate TypeQL insert query string for this instance.

    This is a convenience method that uses the AST-based generation
    internally and compiles it to a string.

    Args:
        var: Variable name to use (default: "$e")

    Returns:
        TypeQL insert query string
    """
    from type_bridge.query.compiler import QueryCompiler

    insert_clause = self.to_ast(var=var)
    return QueryCompiler().compile(insert_clause)