Skip to content

Nodes and visitors

Node

Node is a class that stores the expression parameters. All nodes must be hashable. You can inherit from a base class dynamic_expressions.nodes.Node

Visitor

Visitor is a class that stores behavior for the concrete node.

Built-in nodes and visitors

Literal

Literal - constant value in the expression.

Source code in dynamic_expressions/nodes.py
@dataclass(slots=True, frozen=True)
class LiteralNode(Node):
    value: Any

    def __hash__(self) -> int:
        return hash((self.value, type(self.value)))
Source code in dynamic_expressions/visitors.py
class LiteralVisitor(Visitor[LiteralNode, EmptyContext]):
    async def visit(
        self,
        *,
        node: LiteralNode,
        dispatch: Dispatch[EmptyContext],  # noqa: ARG002
        context: EmptyContext,  # noqa: ARG002
    ) -> Any:  # noqa: ANN401
        return node.value

Any Of

Sequence of the nodes. Returns True if at least one element equals True, else is False

Source code in dynamic_expressions/nodes.py
@dataclass(slots=True, frozen=True, kw_only=True, unsafe_hash=True)
class AnyOfNode(Node):
    expressions: tuple[Node, ...]
Source code in dynamic_expressions/visitors.py
class AnyOfVisitor(Visitor[AnyOfNode, EmptyContext]):
    async def visit(
        self,
        *,
        node: AnyOfNode,
        dispatch: Dispatch[EmptyContext],
        context: object,
    ) -> bool:
        for expr in node.expressions:
            value = await dispatch(expr, context)
            if value:
                return True
        return False

All Of

Sequence of the nodes. Returns True if all elements equal True, else is False

Source code in dynamic_expressions/nodes.py
@dataclass(slots=True, frozen=True, kw_only=True, unsafe_hash=True)
class AllOfNode(Node):
    expressions: tuple[Node, ...]
Source code in dynamic_expressions/visitors.py
class AllOfVisitor(Visitor[AllOfNode, EmptyContext]):
    async def visit(
        self,
        *,
        node: AllOfNode,
        dispatch: Dispatch[EmptyContext],
        context: EmptyContext,
    ) -> bool:
        for expr in node.expressions:
            value = await dispatch(expr, context)
            if not value:
                return False
        return True

Binary Expression

Binary operator. See the available operation in dynamic_expressions.types.BinaryExpressionOperator

Source code in dynamic_expressions/nodes.py
@dataclass(slots=True, frozen=True, kw_only=True, unsafe_hash=True)
class BinaryExpressionNode(Node):
    operator: BinaryExpressionOperator
    left: Node
    right: Node
Source code in dynamic_expressions/visitors.py
class BinaryExpressionVisitor(Visitor[BinaryExpressionNode, EmptyContext]):
    operator_mapping: ClassVar[
        Mapping[BinaryExpressionOperator, Callable[[Any, Any], object]]
    ] = {
        "=": operator.eq,
        ">": operator.gt,
        ">=": operator.ge,
        "<": operator.lt,
        "<=": operator.le,
        "!=": operator.ne,
        "in": operator.contains,
        "+": operator.add,
        "-": operator.sub,
        "*": operator.mul,
        "/": operator.truediv,
        "//": operator.floordiv,
        "^": operator.pow,
        "&": operator.and_,
        "|": operator.or_,
        "getitem": operator.getitem,
        "getattr": _visit_getattr,
    }

    async def visit(
        self,
        *,
        node: BinaryExpressionNode,
        dispatch: Dispatch[EmptyContext],
        context: EmptyContext,
    ) -> object:
        left = await dispatch(node.left, context)
        right = await dispatch(node.right, context)

        operator_callable = self.operator_mapping.get(node.operator)
        if operator_callable is None:
            msg = f"Unknown operator '{node.operator}'"
            raise ValueError(msg)
        return operator_callable(left, right)

Coalesce

Analogue SQL Coalesce. Returns the first non null item

Source code in dynamic_expressions/nodes.py
@dataclass(slots=True, frozen=True, kw_only=True)
class CoalesceNode(Node):
    items: tuple[Node, ...]
Source code in dynamic_expressions/visitors.py
class CoalesceVisitor(Visitor[CoalesceNode, EmptyContext]):
    async def visit(
        self,
        *,
        node: CoalesceNode,
        dispatch: Dispatch[EmptyContext],
        context: EmptyContext,
    ) -> Any:  # noqa: ANN401
        for item in node.items:
            node_result = await dispatch(item, context)
            if node_result:
                return node_result
        return None

Match

Match case operator. Returns the first value for which the expression returns true

Source code in dynamic_expressions/nodes.py
@dataclass(slots=True, frozen=True, kw_only=True)
class MatchNode(Node):
    cases: tuple[CaseNode, ...]
    default: Node | None = None
Source code in dynamic_expressions/visitors.py
class MatchVisitor(Visitor[MatchNode, EmptyContext]):
    async def visit(
        self,
        *,
        node: MatchNode,
        dispatch: Dispatch[EmptyContext],
        context: EmptyContext,
    ) -> Any:  # noqa: ANN401
        for case_ in node.cases:
            if await dispatch(case_.expression, context):
                return await dispatch(case_.value, context)
        if node.default is not None:
            return await dispatch(node.default, context)

        msg = "MatchCase doesn't find CaseNode with the appropriate expression"
        raise ValueError(msg)

Case

Use only in Match operator

Source code in dynamic_expressions/nodes.py
@dataclass(slots=True, frozen=True, kw_only=True)
class CaseNode(Node):
    expression: Node
    value: Node
Source code in dynamic_expressions/visitors.py
class CaseVisitor(Visitor[CaseNode, EmptyContext]):
    async def visit(
        self,
        *,
        node: CaseNode,  # noqa: ARG002
        dispatch: Dispatch[EmptyContext],  # noqa: ARG002
        context: EmptyContext,  # noqa: ARG002
    ) -> Any:  # noqa: ANN401
        msg = "Use CaseNode only in MatchNode"
        raise ValueError(msg)