Skip to content

:>> [[ABIO]] → ABIO DocsABIO spec_lang

Spec Language Module

What remains of the M1 spec language after M47.7 — the distribution builtins, the inline-expression allowlist (safe_eval), lexical Scope and the @biotype registry — the pieces Expr stands on.

alienbio.spec_lang

What remains of the M1 spec language after M47.7: the pieces the Expr language stands on — the distribution builtins, the inline-expression allowlist (validate_expression), lexical Scope, and the @biotype class registry. The old YAML loader, evaluator, typed keys, rate compiler and Bio facade are gone; alienbio.expr is the one language.

UnsafeExpressionError

Bases: Exception

Raised when a spec expression uses a construct outside the allowlist.

Source code in src/alienbio/spec_lang/safe_eval.py
class UnsafeExpressionError(Exception):
    """Raised when a spec expression uses a construct outside the allowlist."""

Scope

Bases: dict

A dict with lexical scoping (parent chain lookup).

Variables are inherited through the scope chain. Lookups check the current scope first, then climb to parent scopes until found.

The scope hierarchy is built by the loader (a file is a scope, a template call is a child scope); lookups are dynamic - they climb the hierarchy at access time.

Attributes:

Name Type Description
parent

Optional parent Scope for inheritance chain

name

Optional name for this scope (for debugging)

Source code in src/alienbio/spec_lang/scope.py
class Scope(dict):
    """A dict with lexical scoping (parent chain lookup).

    Variables are inherited through the scope chain. Lookups check the
    current scope first, then climb to parent scopes until found.

    The scope hierarchy is built by the loader (a file is a scope, a
    template call is a child scope); lookups are dynamic - they climb
    the hierarchy at access time.

    Attributes:
        parent: Optional parent Scope for inheritance chain
        name: Optional name for this scope (for debugging)
    """

    def __init__(
        self,
        data: dict[str, Any] | None = None,
        parent: Scope | None = None,
        name: str | None = None,
    ):
        """Create a new Scope.

        Args:
            data: Initial dict content
            parent: Parent scope for inheritance
            name: Optional name for debugging
        """
        super().__init__(data or {})
        self.parent = parent
        self.name = name

    def __getitem__(self, key: str) -> Any:
        """Get item, climbing parent chain if not found locally."""
        if key in self.keys():
            return super().__getitem__(key)
        if self.parent is not None:
            return self.parent[key]
        raise KeyError(key)

    def get(self, key: str, default: Any = None) -> Any:
        """Get item with default, climbing parent chain."""
        try:
            return self[key]
        except KeyError:
            return default

    def __contains__(self, key: object) -> bool:
        """Check if key exists in this scope or any parent."""
        if super().__contains__(key):
            return True
        if self.parent is not None:
            return key in self.parent
        return False

    def local_keys(self) -> Iterator[str]:
        """Return keys defined directly in this scope (not inherited)."""
        return iter(super().keys())

    def all_keys(self) -> set[str]:
        """Return all keys including inherited ones."""
        keys = set(super().keys())
        if self.parent is not None:
            keys |= self.parent.all_keys()
        return keys

    def child(self, data: dict[str, Any] | None = None, name: str | None = None) -> Scope:
        """Create a child scope that inherits from this one.

        Args:
            data: Initial content for child scope
            name: Optional name for the child scope

        Returns:
            New Scope with this scope as parent
        """
        return Scope(data, parent=self, name=name)

    def resolve(self, key: str) -> tuple[Any, Scope | None]:
        """Resolve a key and return (value, defining_scope).

        Useful for debugging to see where a value comes from.

        Args:
            key: The key to look up

        Returns:
            Tuple of (value, scope_that_defined_it)

        Raises:
            KeyError: If key not found in any scope
        """
        if key in self.keys():
            return super().__getitem__(key), self
        if self.parent is not None:
            return self.parent.resolve(key)
        raise KeyError(key)

    def __repr__(self) -> str:
        name_part = f" {self.name!r}" if self.name else ""
        parent_part = f" parent={self.parent.name!r}" if self.parent and self.parent.name else ""
        if not parent_part and self.parent:
            parent_part = " parent=<Scope>"
        return f"<Scope{name_part}{parent_part} {dict(self)}>"

__init__(data=None, parent=None, name=None)

Create a new Scope.

Parameters:

Name Type Description Default
data dict[str, Any] | None

Initial dict content

None
parent Scope | None

Parent scope for inheritance

None
name str | None

Optional name for debugging

None
Source code in src/alienbio/spec_lang/scope.py
def __init__(
    self,
    data: dict[str, Any] | None = None,
    parent: Scope | None = None,
    name: str | None = None,
):
    """Create a new Scope.

    Args:
        data: Initial dict content
        parent: Parent scope for inheritance
        name: Optional name for debugging
    """
    super().__init__(data or {})
    self.parent = parent
    self.name = name

__getitem__(key)

Get item, climbing parent chain if not found locally.

Source code in src/alienbio/spec_lang/scope.py
def __getitem__(self, key: str) -> Any:
    """Get item, climbing parent chain if not found locally."""
    if key in self.keys():
        return super().__getitem__(key)
    if self.parent is not None:
        return self.parent[key]
    raise KeyError(key)

get(key, default=None)

Get item with default, climbing parent chain.

Source code in src/alienbio/spec_lang/scope.py
def get(self, key: str, default: Any = None) -> Any:
    """Get item with default, climbing parent chain."""
    try:
        return self[key]
    except KeyError:
        return default

__contains__(key)

Check if key exists in this scope or any parent.

Source code in src/alienbio/spec_lang/scope.py
def __contains__(self, key: object) -> bool:
    """Check if key exists in this scope or any parent."""
    if super().__contains__(key):
        return True
    if self.parent is not None:
        return key in self.parent
    return False

local_keys()

Return keys defined directly in this scope (not inherited).

Source code in src/alienbio/spec_lang/scope.py
def local_keys(self) -> Iterator[str]:
    """Return keys defined directly in this scope (not inherited)."""
    return iter(super().keys())

all_keys()

Return all keys including inherited ones.

Source code in src/alienbio/spec_lang/scope.py
def all_keys(self) -> set[str]:
    """Return all keys including inherited ones."""
    keys = set(super().keys())
    if self.parent is not None:
        keys |= self.parent.all_keys()
    return keys

child(data=None, name=None)

Create a child scope that inherits from this one.

Parameters:

Name Type Description Default
data dict[str, Any] | None

Initial content for child scope

None
name str | None

Optional name for the child scope

None

Returns:

Type Description
Scope

New Scope with this scope as parent

Source code in src/alienbio/spec_lang/scope.py
def child(self, data: dict[str, Any] | None = None, name: str | None = None) -> Scope:
    """Create a child scope that inherits from this one.

    Args:
        data: Initial content for child scope
        name: Optional name for the child scope

    Returns:
        New Scope with this scope as parent
    """
    return Scope(data, parent=self, name=name)

resolve(key)

Resolve a key and return (value, defining_scope).

Useful for debugging to see where a value comes from.

Parameters:

Name Type Description Default
key str

The key to look up

required

Returns:

Type Description
tuple[Any, Scope | None]

Tuple of (value, scope_that_defined_it)

Raises:

Type Description
KeyError

If key not found in any scope

Source code in src/alienbio/spec_lang/scope.py
def resolve(self, key: str) -> tuple[Any, Scope | None]:
    """Resolve a key and return (value, defining_scope).

    Useful for debugging to see where a value comes from.

    Args:
        key: The key to look up

    Returns:
        Tuple of (value, scope_that_defined_it)

    Raises:
        KeyError: If key not found in any scope
    """
    if key in self.keys():
        return super().__getitem__(key), self
    if self.parent is not None:
        return self.parent.resolve(key)
    raise KeyError(key)

biotype(arg=None)

biotype(cls: type[T]) -> type[T]
biotype(name: str) -> Callable[[type[T]], type[T]]

Register a class for hydration from YAML.

Usage

@biotype class Chemistry: ...

@biotype("custom_name") class World: ...

Source code in src/alienbio/spec_lang/decorators.py
def biotype(arg: type[T] | str | None = None, /) -> type[T] | Callable[[type[T]], type[T]]:
    """Register a class for hydration from YAML.

    Usage:
        @biotype
        class Chemistry: ...

        @biotype("custom_name")
        class World: ...
    """

    def decorator(cls: type[T]) -> type[T]:
        type_name = arg if isinstance(arg, str) else cls.__name__.lower()
        biotype_registry[type_name] = cls
        # Add _biotype_name attribute for dehydration
        cls._biotype_name = type_name  # type: ignore
        return cls

    if isinstance(arg, type):
        # Called as @biotype without parens
        return decorator(arg)
    else:
        # Called as @biotype("name") or @biotype()
        return decorator

get_biotype(name)

Get a biotype class by name.

Raises:

Type Description
KeyError

If name not registered

Source code in src/alienbio/spec_lang/decorators.py
def get_biotype(name: str) -> type:
    """Get a biotype class by name.

    Raises:
        KeyError: If name not registered
    """
    if name not in biotype_registry:
        raise KeyError(f"Unknown biotype: {name}")
    return biotype_registry[name]

validate_expression(source)

Parse source and validate every node against the allowlist.

Returns the parsed ast.Expression (for reuse in compilation).

Raises:

Type Description
SyntaxError

If source is not a valid Python expression.

UnsafeExpressionError

If any node is outside the allowlist.

Source code in src/alienbio/spec_lang/safe_eval.py
def validate_expression(source: str) -> ast.Expression:
    """Parse ``source`` and validate every node against the allowlist.

    Returns the parsed ``ast.Expression`` (for reuse in compilation).

    Raises:
        SyntaxError: If ``source`` is not a valid Python expression.
        UnsafeExpressionError: If any node is outside the allowlist.
    """
    tree = ast.parse(source, mode="eval")
    for node in ast.walk(tree):
        _validate_node(node)
    return tree