Skip to content

app_model.expressions #

Abstraction on expressions, and contexts in which to evaluate them.

Classes:

  • BinOp

    A binary operation (like addition or division).

  • BoolOp

    A boolean operation, 'or' or 'and'.

  • Compare

    A comparison of two or more values.

  • Constant

    A constant value.

  • Context

    Evented Mapping of keys to values.

  • ContextKey

    Context key name, default, description, and getter.

  • ContextKeyInfo

    Just a recordkeeping tuple.

  • ContextNamespace

    A collection of related keys in a context.

  • Expr

    Base Expression class providing dunder and convenience methods.

  • IfExp

    An expression such as 'a if b else c'.

  • Name

    A variable name.

  • UnaryOp

    A unary operation.

Functions:

BinOp #

BinOp(
    left: T | Expr[T],
    op: operator,
    right: T | Expr[T],
    **kwargs: Unpack[_Attributes],
)

Bases: Expr[T], BinOp

A binary operation (like addition or division).

op is the operator, and left and right are any expression nodes.

Methods:

  • bitand

    Return bitwise self & other.

  • bitor

    Return bitwise self | other.

  • eval

    Evaluate this expression with names in context.

  • in_

    Return a comparison for self in other.

  • not_in

    Return a comparison for self no in other.

  • parse

    Parse string into Expr (classmethod).

Source code in src/app_model/expressions/_expressions.py
449
450
451
452
453
454
455
456
def __init__(
    self,
    left: T | Expr[T],
    op: ast.operator,
    right: T | Expr[T],
    **kwargs: Unpack[_Attributes],
) -> None:
    super().__init__(Expr._cast(left), op, Expr._cast(right), **kwargs)

bitand #

bitand(other: T | Expr[T]) -> BinOp[T]

Return bitwise self & other.

Source code in src/app_model/expressions/_expressions.py
319
320
321
def bitand(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self & other."""
    return BinOp(self, ast.BitAnd(), other)

bitor #

bitor(other: T | Expr[T]) -> BinOp[T]

Return bitwise self | other.

Source code in src/app_model/expressions/_expressions.py
323
324
325
def bitor(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self | other."""
    return BinOp(self, ast.BitOr(), other)

eval #

eval(
    context: Mapping[str, object] | None = None,
    **ctx_kwargs: object,
) -> T

Evaluate this expression with names in context.

Source code in src/app_model/expressions/_expressions.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def eval(
    self, context: Mapping[str, object] | None = None, **ctx_kwargs: object
) -> T:
    """Evaluate this expression with names in `context`."""
    if context is None:
        context = ctx_kwargs
    elif ctx_kwargs:
        context = {**context, **ctx_kwargs}
    try:
        return eval(self._code, {}, context)  # type: ignore
    except NameError as e:
        miss = {k for k in self._names if k not in context}
        raise NameError(
            f"Names required to eval this expression are missing: {miss}"
        ) from e

in_ #

in_(other: Any) -> Compare

Return a comparison for self in other.

Source code in src/app_model/expressions/_expressions.py
280
281
282
283
def in_(self, other: Any) -> Compare:
    """Return a comparison for `self` in `other`."""
    # not a dunder, use with Expr.in_(a, other)
    return Compare(self, [ast.In()], [other])

not_in #

not_in(other: Any) -> Compare

Return a comparison for self no in other.

Source code in src/app_model/expressions/_expressions.py
285
286
287
def not_in(self, other: Any) -> Compare:
    """Return a comparison for `self` no in `other`."""
    return Compare(self, [ast.NotIn()], [other])

parse classmethod #

parse(expr: str) -> Expr

Parse string into Expr (classmethod).

see docstring of parse_expression for details.

Source code in src/app_model/expressions/_expressions.py
219
220
221
222
223
224
225
226
@classmethod
def parse(cls, expr: str) -> Expr:
    """Parse string into Expr (classmethod).

    see docstring of [`parse_expression`][app_model.expressions.parse_expression]
    for details.
    """
    return parse_expression(expr)

BoolOp #

BoolOp(
    op: boolop,
    values: Sequence[ConstType | Expr],
    **kwargs: Unpack[_Attributes],
)

Bases: Expr[T], BoolOp

A boolean operation, 'or' or 'and'.

op is Or or And. values are the values involved. Consecutive operations with the same operator, such as a or b or c, are collapsed into one node with several values.

This doesn't include not, which is a UnaryOp.

Methods:

  • bitand

    Return bitwise self & other.

  • bitor

    Return bitwise self | other.

  • eval

    Evaluate this expression with names in context.

  • in_

    Return a comparison for self in other.

  • not_in

    Return a comparison for self no in other.

  • parse

    Parse string into Expr (classmethod).

Source code in src/app_model/expressions/_expressions.py
469
470
471
472
473
474
475
def __init__(
    self,
    op: ast.boolop,
    values: Sequence[ConstType | Expr],
    **kwargs: Unpack[_Attributes],
) -> None:
    super().__init__(op, [Expr._cast(v) for v in values], **kwargs)

bitand #

bitand(other: T | Expr[T]) -> BinOp[T]

Return bitwise self & other.

Source code in src/app_model/expressions/_expressions.py
319
320
321
def bitand(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self & other."""
    return BinOp(self, ast.BitAnd(), other)

bitor #

bitor(other: T | Expr[T]) -> BinOp[T]

Return bitwise self | other.

Source code in src/app_model/expressions/_expressions.py
323
324
325
def bitor(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self | other."""
    return BinOp(self, ast.BitOr(), other)

eval #

eval(
    context: Mapping[str, object] | None = None,
    **ctx_kwargs: object,
) -> T

Evaluate this expression with names in context.

Source code in src/app_model/expressions/_expressions.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def eval(
    self, context: Mapping[str, object] | None = None, **ctx_kwargs: object
) -> T:
    """Evaluate this expression with names in `context`."""
    if context is None:
        context = ctx_kwargs
    elif ctx_kwargs:
        context = {**context, **ctx_kwargs}
    try:
        return eval(self._code, {}, context)  # type: ignore
    except NameError as e:
        miss = {k for k in self._names if k not in context}
        raise NameError(
            f"Names required to eval this expression are missing: {miss}"
        ) from e

in_ #

in_(other: Any) -> Compare

Return a comparison for self in other.

Source code in src/app_model/expressions/_expressions.py
280
281
282
283
def in_(self, other: Any) -> Compare:
    """Return a comparison for `self` in `other`."""
    # not a dunder, use with Expr.in_(a, other)
    return Compare(self, [ast.In()], [other])

not_in #

not_in(other: Any) -> Compare

Return a comparison for self no in other.

Source code in src/app_model/expressions/_expressions.py
285
286
287
def not_in(self, other: Any) -> Compare:
    """Return a comparison for `self` no in `other`."""
    return Compare(self, [ast.NotIn()], [other])

parse classmethod #

parse(expr: str) -> Expr

Parse string into Expr (classmethod).

see docstring of parse_expression for details.

Source code in src/app_model/expressions/_expressions.py
219
220
221
222
223
224
225
226
@classmethod
def parse(cls, expr: str) -> Expr:
    """Parse string into Expr (classmethod).

    see docstring of [`parse_expression`][app_model.expressions.parse_expression]
    for details.
    """
    return parse_expression(expr)

Compare #

Compare(
    left: Expr,
    ops: Sequence[cmpop],
    comparators: Sequence[Expr],
    **kwargs: Unpack[_Attributes],
)

Bases: Expr[bool], Compare

A comparison of two or more values.

left is the first value in the comparison, ops the list of operators, and comparators the list of values after the first element in the comparison.

Methods:

  • bitand

    Return bitwise self & other.

  • bitor

    Return bitwise self | other.

  • eval

    Evaluate this expression with names in context.

  • in_

    Return a comparison for self in other.

  • not_in

    Return a comparison for self no in other.

  • parse

    Parse string into Expr (classmethod).

Source code in src/app_model/expressions/_expressions.py
431
432
433
434
435
436
437
438
439
440
def __init__(
    self,
    left: Expr,
    ops: Sequence[ast.cmpop],
    comparators: Sequence[Expr],
    **kwargs: Unpack[_Attributes],
) -> None:
    super().__init__(
        Expr._cast(left), ops, [Expr._cast(c) for c in comparators], **kwargs
    )

bitand #

bitand(other: T | Expr[T]) -> BinOp[T]

Return bitwise self & other.

Source code in src/app_model/expressions/_expressions.py
319
320
321
def bitand(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self & other."""
    return BinOp(self, ast.BitAnd(), other)

bitor #

bitor(other: T | Expr[T]) -> BinOp[T]

Return bitwise self | other.

Source code in src/app_model/expressions/_expressions.py
323
324
325
def bitor(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self | other."""
    return BinOp(self, ast.BitOr(), other)

eval #

eval(
    context: Mapping[str, object] | None = None,
    **ctx_kwargs: object,
) -> T

Evaluate this expression with names in context.

Source code in src/app_model/expressions/_expressions.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def eval(
    self, context: Mapping[str, object] | None = None, **ctx_kwargs: object
) -> T:
    """Evaluate this expression with names in `context`."""
    if context is None:
        context = ctx_kwargs
    elif ctx_kwargs:
        context = {**context, **ctx_kwargs}
    try:
        return eval(self._code, {}, context)  # type: ignore
    except NameError as e:
        miss = {k for k in self._names if k not in context}
        raise NameError(
            f"Names required to eval this expression are missing: {miss}"
        ) from e

in_ #

in_(other: Any) -> Compare

Return a comparison for self in other.

Source code in src/app_model/expressions/_expressions.py
280
281
282
283
def in_(self, other: Any) -> Compare:
    """Return a comparison for `self` in `other`."""
    # not a dunder, use with Expr.in_(a, other)
    return Compare(self, [ast.In()], [other])

not_in #

not_in(other: Any) -> Compare

Return a comparison for self no in other.

Source code in src/app_model/expressions/_expressions.py
285
286
287
def not_in(self, other: Any) -> Compare:
    """Return a comparison for `self` no in `other`."""
    return Compare(self, [ast.NotIn()], [other])

parse classmethod #

parse(expr: str) -> Expr

Parse string into Expr (classmethod).

see docstring of parse_expression for details.

Source code in src/app_model/expressions/_expressions.py
219
220
221
222
223
224
225
226
@classmethod
def parse(cls, expr: str) -> Expr:
    """Parse string into Expr (classmethod).

    see docstring of [`parse_expression`][app_model.expressions.parse_expression]
    for details.
    """
    return parse_expression(expr)

Constant #

Constant(
    value: V,
    kind: str | None = None,
    **kwargs: Unpack[_Attributes],
)

Bases: Expr[V], Constant

A constant value.

Parameters:

  • value (V) –

    the Python object this constant represents. Types supported: NoneType, str, bytes, bool, int, float

  • kind (str | None, default: None ) –

    The kind of constant. This is used to provide type hints when

Methods:

  • bitand

    Return bitwise self & other.

  • bitor

    Return bitwise self | other.

  • eval

    Evaluate this expression with names in context.

  • in_

    Return a comparison for self in other.

  • not_in

    Return a comparison for self no in other.

  • parse

    Parse string into Expr (classmethod).

Source code in src/app_model/expressions/_expressions.py
414
415
416
417
418
419
420
def __init__(
    self, value: V, kind: str | None = None, **kwargs: Unpack[_Attributes]
) -> None:
    _valid_type = (type(None), str, bytes, bool, int, float)
    if not isinstance(value, _valid_type):
        raise TypeError(f"Constants must be type: {_valid_type!r}")
    super().__init__(value, kind, **kwargs)

bitand #

bitand(other: T | Expr[T]) -> BinOp[T]

Return bitwise self & other.

Source code in src/app_model/expressions/_expressions.py
319
320
321
def bitand(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self & other."""
    return BinOp(self, ast.BitAnd(), other)

bitor #

bitor(other: T | Expr[T]) -> BinOp[T]

Return bitwise self | other.

Source code in src/app_model/expressions/_expressions.py
323
324
325
def bitor(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self | other."""
    return BinOp(self, ast.BitOr(), other)

eval #

eval(
    context: Mapping[str, object] | None = None,
    **ctx_kwargs: object,
) -> T

Evaluate this expression with names in context.

Source code in src/app_model/expressions/_expressions.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def eval(
    self, context: Mapping[str, object] | None = None, **ctx_kwargs: object
) -> T:
    """Evaluate this expression with names in `context`."""
    if context is None:
        context = ctx_kwargs
    elif ctx_kwargs:
        context = {**context, **ctx_kwargs}
    try:
        return eval(self._code, {}, context)  # type: ignore
    except NameError as e:
        miss = {k for k in self._names if k not in context}
        raise NameError(
            f"Names required to eval this expression are missing: {miss}"
        ) from e

in_ #

in_(other: Any) -> Compare

Return a comparison for self in other.

Source code in src/app_model/expressions/_expressions.py
280
281
282
283
def in_(self, other: Any) -> Compare:
    """Return a comparison for `self` in `other`."""
    # not a dunder, use with Expr.in_(a, other)
    return Compare(self, [ast.In()], [other])

not_in #

not_in(other: Any) -> Compare

Return a comparison for self no in other.

Source code in src/app_model/expressions/_expressions.py
285
286
287
def not_in(self, other: Any) -> Compare:
    """Return a comparison for `self` no in `other`."""
    return Compare(self, [ast.NotIn()], [other])

parse classmethod #

parse(expr: str) -> Expr

Parse string into Expr (classmethod).

see docstring of parse_expression for details.

Source code in src/app_model/expressions/_expressions.py
219
220
221
222
223
224
225
226
@classmethod
def parse(cls, expr: str) -> Expr:
    """Parse string into Expr (classmethod).

    see docstring of [`parse_expression`][app_model.expressions.parse_expression]
    for details.
    """
    return parse_expression(expr)

Context #

Context(*maps: MutableMapping)

Bases: ChainMap

Evented Mapping of keys to values.

Methods:

  • buffered_changes

    Context in which to accumulated changes before emitting.

  • new_child

    Create a new child context from this one.

Attributes:

  • changed

    Event emitted (with a set of changed keys) whenever the context is changed.

Source code in src/app_model/expressions/_context.py
34
35
36
37
38
def __init__(self, *maps: MutableMapping) -> None:
    super().__init__(*maps)
    for m in maps:
        if isinstance(m, Context):
            m.changed.connect(self.changed)

changed class-attribute instance-attribute #

changed = Signal(set)

Event emitted (with a set of changed keys) whenever the context is changed.

buffered_changes #

buffered_changes() -> Iterator[None]

Context in which to accumulated changes before emitting.

Source code in src/app_model/expressions/_context.py
40
41
42
43
44
@contextmanager
def buffered_changes(self) -> Iterator[None]:
    """Context in which to accumulated changes before emitting."""
    with self.changed.paused(lambda a, b: (a[0].union(b[0]),)):
        yield

new_child #

new_child(m: MutableMapping | None = None) -> Context

Create a new child context from this one.

Source code in src/app_model/expressions/_context.py
58
59
60
61
62
def new_child(self, m: MutableMapping | None = None) -> Context:
    """Create a new child context from this one."""
    new = super().new_child(m=m)
    self.changed.connect(new.changed)
    return new

ContextKey #

ContextKey(
    default_value: T | __missing = MISSING,
    description: str | None = None,
    getter: Callable[[A], T] | None = None,
    *,
    id: str = "",
)

Bases: Name, Generic[A, T]

Context key name, default, description, and getter.

This is intended to be used as class attribute in a ContextNamespace. This is a subclass of Name, and is therefore usable in an Expression. (see examples.)

Parameters:

  • default_value (Any, default: MISSING ) –

    The default value for this key, by default MISSING

  • description (str, default: None ) –

    Description of this key. Useful for documentation, by default None

  • getter (callable, default: None ) –

    Callable that receives an object and retrieves the current value for this key, by default None. For example, if this ContextKey represented the length of some list, (like the layerlist) it might look like length = ContextKey(0, 'length of the list', lambda x: len(x))

  • id (str, default: '' ) –

    Explicitly provide the Name string used when evaluating a context, by default the key will be taken as the attribute name to which this object is assigned as a class attribute:

Examples:

>>> class MyNames(ContextNamespace):
...     some_key = ContextKey(0, "some description", lambda x: sum(x))
>>> expr = MyNames.some_key > 5  # create an expression using this key

these expressions can be later evaluated with some concrete context.

>>> expr.eval({"some_key": 3})  # False
>>> expr.eval({"some_key": 6})  # True

Methods:

  • bitand

    Return bitwise self & other.

  • bitor

    Return bitwise self | other.

  • eval

    Evaluate this expression with names in context.

  • in_

    Return a comparison for self in other.

  • info

    Return list of all stored context keys.

  • not_in

    Return a comparison for self no in other.

  • parse

    Parse string into Expr (classmethod).

Source code in src/app_model/expressions/_context_keys.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def __init__(
    self,
    default_value: T | __missing = MISSING,
    description: str | None = None,
    getter: Callable[[A], T] | None = None,
    *,
    id: str = "",  # optional because of __set_name__
) -> None:
    bound = type(default_value) if default_value is not MISSING else None
    super().__init__(id or "", bound=bound)
    self._default_value = default_value
    self._getter = getter
    self._description = description
    self._owner: type[ContextNamespace] | None = None
    self._type = (
        type(default_value) if default_value not in (None, MISSING) else None
    )
    if id:
        self._store()

bitand #

bitand(other: T | Expr[T]) -> BinOp[T]

Return bitwise self & other.

Source code in src/app_model/expressions/_expressions.py
319
320
321
def bitand(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self & other."""
    return BinOp(self, ast.BitAnd(), other)

bitor #

bitor(other: T | Expr[T]) -> BinOp[T]

Return bitwise self | other.

Source code in src/app_model/expressions/_expressions.py
323
324
325
def bitor(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self | other."""
    return BinOp(self, ast.BitOr(), other)

eval #

eval(
    context: Mapping[str, object] | None = None,
    **ctx_kwargs: object,
) -> T

Evaluate this expression with names in context.

Source code in src/app_model/expressions/_expressions.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def eval(
    self, context: Mapping[str, object] | None = None, **ctx_kwargs: object
) -> T:
    """Evaluate this expression with names in `context`."""
    if context is None:
        context = ctx_kwargs
    elif ctx_kwargs:
        context = {**context, **ctx_kwargs}
    try:
        return eval(self._code, {}, context)  # type: ignore
    except NameError as e:
        miss = {k for k in self._names if k not in context}
        raise NameError(
            f"Names required to eval this expression are missing: {miss}"
        ) from e

in_ #

in_(other: Any) -> Compare

Return a comparison for self in other.

Source code in src/app_model/expressions/_expressions.py
280
281
282
283
def in_(self, other: Any) -> Compare:
    """Return a comparison for `self` in `other`."""
    # not a dunder, use with Expr.in_(a, other)
    return Compare(self, [ast.In()], [other])

info classmethod #

info() -> list[ContextKeyInfo]

Return list of all stored context keys.

Source code in src/app_model/expressions/_context_keys.py
114
115
116
117
@classmethod
def info(cls) -> list[ContextKeyInfo]:
    """Return list of all stored context keys."""
    return list(cls._info)

not_in #

not_in(other: Any) -> Compare

Return a comparison for self no in other.

Source code in src/app_model/expressions/_expressions.py
285
286
287
def not_in(self, other: Any) -> Compare:
    """Return a comparison for `self` no in `other`."""
    return Compare(self, [ast.NotIn()], [other])

parse classmethod #

parse(expr: str) -> Expr

Parse string into Expr (classmethod).

see docstring of parse_expression for details.

Source code in src/app_model/expressions/_expressions.py
219
220
221
222
223
224
225
226
@classmethod
def parse(cls, expr: str) -> Expr:
    """Parse string into Expr (classmethod).

    see docstring of [`parse_expression`][app_model.expressions.parse_expression]
    for details.
    """
    return parse_expression(expr)

ContextKeyInfo #

Bases: NamedTuple

Just a recordkeeping tuple.

Retrieve all declared ContextKeys with ContextKeyInfo.info().

Parameters:

  • key (ForwardRef('str'), default: None ) –
  • type (ForwardRef('type | None'), default: None ) –
  • description (ForwardRef('str | None'), default: None ) –
  • namespace (ForwardRef('builtins.type[ContextNamespace] | None'), default: None ) –

ContextNamespace #

ContextNamespace(context: MutableMapping)

Bases: Generic[A]

A collection of related keys in a context.

meant to be subclassed, with ContextKeys as class attributes.

Methods:

  • dict

    Return all keys in this namespace.

  • reset

    Reset keys to its default.

  • reset_all

    Reset all keys to their defaults.

Source code in src/app_model/expressions/_context_keys.py
197
198
199
200
201
202
203
204
205
206
207
208
209
def __init__(self, context: MutableMapping) -> None:
    self._context = context

    # on instantiation we create an index of defaults and value-getters
    # to speed up retrieval later
    self._defaults: dict[str, Any] = {}  # default values per key
    self._getters: dict[str, Callable[[A], Any]] = {}  # value getters
    for name, ctxkey in type(self).__members__.items():
        self._defaults[name] = ctxkey._default_value
        if ctxkey._default_value is not MISSING:
            context[ctxkey.id] = ctxkey._default_value
        if callable(ctxkey._getter):
            self._getters[name] = ctxkey._getter

dict #

dict() -> dict

Return all keys in this namespace.

Source code in src/app_model/expressions/_context_keys.py
225
226
227
def dict(self) -> dict:
    """Return all keys in this namespace."""
    return {k: getattr(self, k) for k in type(self).__members__}

reset #

reset(key: str) -> None

Reset keys to its default.

Source code in src/app_model/expressions/_context_keys.py
211
212
213
214
215
216
217
218
def reset(self, key: str) -> None:
    """Reset keys to its default."""
    val = self._defaults[key]
    if val is MISSING:
        with contextlib.suppress(KeyError):
            delattr(self, key)
    else:
        setattr(self, key, self._defaults[key])

reset_all #

reset_all() -> None

Reset all keys to their defaults.

Source code in src/app_model/expressions/_context_keys.py
220
221
222
223
def reset_all(self) -> None:
    """Reset all keys to their defaults."""
    for key in self._defaults:
        self.reset(key)

Expr #

Expr(*args: Any, **kwargs: Any)

Bases: AST, Generic[T]

Base Expression class providing dunder and convenience methods.

This is a subclass of ast.AST that provides rich dunder methods that facilitate joining and comparing typed expressions. It only implements a subset of ast Expressions (for safety of evaluation), but provides more than ast.literal_eval.

Expressions that are supported:

  • Names: 'myvar' (these must be evaluated along with some context)
  • Constants: '1'
  • Comparisons: 'myvar > 1'
  • Boolean Operators: 'myvar & yourvar' (bitwise & and | are overloaded here to mean boolean and and or)
  • Binary Operators: 'myvar + 42' (includes //, @, ^)
  • Unary Operators: 'not myvar'

Things that are NOT supported:

  • attribute access: 'my.attr'
  • calls: 'f(x)'
  • containers (lists, tuples, sets, dicts)
  • indexing or slicing
  • joined strings (f-strings)
  • named expressions (walrus operator)
  • comprehensions (list, set, dict, generator)
  • statements & assignments (e.g. 'a = b')

This class is not meant to be instantiated directly. Instead, use parse_expression, or the Expr.parse classmethod to create an expression instance.

Once created, an expression can be joined with other expressions, or constants.

Examples:

>>> expr = parse_expression("myvar > 5")

combine expressions with operators

>>> new_expr = expr & parse_expression("v2")

nice repr

>>> new_expr
BoolOp(
    op=And(),
    values=[
        Compare(
        left=Name(id='myvar', ctx=Load()),
        ops=[
            Gt()],
        comparators=[
            Constant(value=5)]),
        Name(id='v2', ctx=Load())])

evaluate in some context

>>> new_expr.eval(dict(v2="hello!", myvar=8))
'hello!'

you can also use keyword arguments. This is slightly slower

>>> new_expr.eval(v2="hello!", myvar=4)

serialize

>>> str(new_expr)
'myvar > 5 and v2'

One reason you might want to use this object is to capture named expressions that can be evaluated repeatedly as some underlying context changes.

light_is_green = Name[bool]("light_is_green")
count = Name[int]("count")
is_ready = light_is_green & count > 5

assert is_ready.eval({"count": 4, "light_is_green": True}) == False
assert is_ready.eval({"count": 7, "light_is_green": False}) == False
assert is_ready.eval({"count": 7, "light_is_green": True}) == True

this will also preserve type information:

>>> reveal_type(is_ready())  # revealed type is `bool`

Methods:

  • bitand

    Return bitwise self & other.

  • bitor

    Return bitwise self | other.

  • eval

    Evaluate this expression with names in context.

  • in_

    Return a comparison for self in other.

  • not_in

    Return a comparison for self no in other.

  • parse

    Parse string into Expr (classmethod).

Source code in src/app_model/expressions/_expressions.py
192
193
194
195
196
def __init__(self, *args: Any, **kwargs: Any) -> None:
    if type(self).__name__ == "Expr":
        raise RuntimeError("Don't instantiate Expr. Use `Expr.parse`")
    super().__init__(*args, **kwargs)
    self._recompile()

bitand #

bitand(other: T | Expr[T]) -> BinOp[T]

Return bitwise self & other.

Source code in src/app_model/expressions/_expressions.py
319
320
321
def bitand(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self & other."""
    return BinOp(self, ast.BitAnd(), other)

bitor #

bitor(other: T | Expr[T]) -> BinOp[T]

Return bitwise self | other.

Source code in src/app_model/expressions/_expressions.py
323
324
325
def bitor(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self | other."""
    return BinOp(self, ast.BitOr(), other)

eval #

eval(
    context: Mapping[str, object] | None = None,
    **ctx_kwargs: object,
) -> T

Evaluate this expression with names in context.

Source code in src/app_model/expressions/_expressions.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def eval(
    self, context: Mapping[str, object] | None = None, **ctx_kwargs: object
) -> T:
    """Evaluate this expression with names in `context`."""
    if context is None:
        context = ctx_kwargs
    elif ctx_kwargs:
        context = {**context, **ctx_kwargs}
    try:
        return eval(self._code, {}, context)  # type: ignore
    except NameError as e:
        miss = {k for k in self._names if k not in context}
        raise NameError(
            f"Names required to eval this expression are missing: {miss}"
        ) from e

in_ #

in_(other: Any) -> Compare

Return a comparison for self in other.

Source code in src/app_model/expressions/_expressions.py
280
281
282
283
def in_(self, other: Any) -> Compare:
    """Return a comparison for `self` in `other`."""
    # not a dunder, use with Expr.in_(a, other)
    return Compare(self, [ast.In()], [other])

not_in #

not_in(other: Any) -> Compare

Return a comparison for self no in other.

Source code in src/app_model/expressions/_expressions.py
285
286
287
def not_in(self, other: Any) -> Compare:
    """Return a comparison for `self` no in `other`."""
    return Compare(self, [ast.NotIn()], [other])

parse classmethod #

parse(expr: str) -> Expr

Parse string into Expr (classmethod).

see docstring of parse_expression for details.

Source code in src/app_model/expressions/_expressions.py
219
220
221
222
223
224
225
226
@classmethod
def parse(cls, expr: str) -> Expr:
    """Parse string into Expr (classmethod).

    see docstring of [`parse_expression`][app_model.expressions.parse_expression]
    for details.
    """
    return parse_expression(expr)

IfExp #

IfExp(
    test: Expr,
    body: Expr,
    orelse: Expr,
    **kwargs: Unpack[_Attributes],
)

Bases: Expr, IfExp

An expression such as 'a if b else c'.

body if test else orelse

Methods:

  • bitand

    Return bitwise self & other.

  • bitor

    Return bitwise self | other.

  • eval

    Evaluate this expression with names in context.

  • in_

    Return a comparison for self in other.

  • not_in

    Return a comparison for self no in other.

  • parse

    Parse string into Expr (classmethod).

Source code in src/app_model/expressions/_expressions.py
496
497
498
499
500
501
def __init__(
    self, test: Expr, body: Expr, orelse: Expr, **kwargs: Unpack[_Attributes]
) -> None:
    super().__init__(
        Expr._cast(test), Expr._cast(body), Expr._cast(orelse), **kwargs
    )

bitand #

bitand(other: T | Expr[T]) -> BinOp[T]

Return bitwise self & other.

Source code in src/app_model/expressions/_expressions.py
319
320
321
def bitand(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self & other."""
    return BinOp(self, ast.BitAnd(), other)

bitor #

bitor(other: T | Expr[T]) -> BinOp[T]

Return bitwise self | other.

Source code in src/app_model/expressions/_expressions.py
323
324
325
def bitor(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self | other."""
    return BinOp(self, ast.BitOr(), other)

eval #

eval(
    context: Mapping[str, object] | None = None,
    **ctx_kwargs: object,
) -> T

Evaluate this expression with names in context.

Source code in src/app_model/expressions/_expressions.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def eval(
    self, context: Mapping[str, object] | None = None, **ctx_kwargs: object
) -> T:
    """Evaluate this expression with names in `context`."""
    if context is None:
        context = ctx_kwargs
    elif ctx_kwargs:
        context = {**context, **ctx_kwargs}
    try:
        return eval(self._code, {}, context)  # type: ignore
    except NameError as e:
        miss = {k for k in self._names if k not in context}
        raise NameError(
            f"Names required to eval this expression are missing: {miss}"
        ) from e

in_ #

in_(other: Any) -> Compare

Return a comparison for self in other.

Source code in src/app_model/expressions/_expressions.py
280
281
282
283
def in_(self, other: Any) -> Compare:
    """Return a comparison for `self` in `other`."""
    # not a dunder, use with Expr.in_(a, other)
    return Compare(self, [ast.In()], [other])

not_in #

not_in(other: Any) -> Compare

Return a comparison for self no in other.

Source code in src/app_model/expressions/_expressions.py
285
286
287
def not_in(self, other: Any) -> Compare:
    """Return a comparison for `self` no in `other`."""
    return Compare(self, [ast.NotIn()], [other])

parse classmethod #

parse(expr: str) -> Expr

Parse string into Expr (classmethod).

see docstring of parse_expression for details.

Source code in src/app_model/expressions/_expressions.py
219
220
221
222
223
224
225
226
@classmethod
def parse(cls, expr: str) -> Expr:
    """Parse string into Expr (classmethod).

    see docstring of [`parse_expression`][app_model.expressions.parse_expression]
    for details.
    """
    return parse_expression(expr)

Name #

Name(
    id: str,
    ctx: expr_context = LOAD,
    *,
    bound: type[T] | None = None,
    **kwargs: Unpack[_Attributes],
)

Bases: Expr[T], Name

A variable name.

Parameters:

  • id (str) –

    The name of the variable.

  • bound (Any | None, default: None ) –

    The type of the variable represented by this name (i.e. the type to which this name evaluates to when used in an expression). This is used to provide type hints when evaluating the expression. If None, the type is not known.

Methods:

  • bitand

    Return bitwise self & other.

  • bitor

    Return bitwise self | other.

  • eval

    Evaluate this expression with names in context.

  • in_

    Return a comparison for self in other.

  • not_in

    Return a comparison for self no in other.

  • parse

    Parse string into Expr (classmethod).

Source code in src/app_model/expressions/_expressions.py
388
389
390
391
392
393
394
395
396
397
def __init__(
    self,
    id: str,
    ctx: ast.expr_context = LOAD,
    *,
    bound: type[T] | None = None,
    **kwargs: Unpack[_Attributes],
) -> None:
    super().__init__(id, ctx=ctx, **kwargs)
    self.bound = bound

bitand #

bitand(other: T | Expr[T]) -> BinOp[T]

Return bitwise self & other.

Source code in src/app_model/expressions/_expressions.py
319
320
321
def bitand(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self & other."""
    return BinOp(self, ast.BitAnd(), other)

bitor #

bitor(other: T | Expr[T]) -> BinOp[T]

Return bitwise self | other.

Source code in src/app_model/expressions/_expressions.py
323
324
325
def bitor(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self | other."""
    return BinOp(self, ast.BitOr(), other)

eval #

eval(
    context: Mapping[str, object] | None = None,
    **ctx_kwargs: object,
) -> T

Evaluate this expression with names in context.

Source code in src/app_model/expressions/_expressions.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def eval(
    self, context: Mapping[str, object] | None = None, **ctx_kwargs: object
) -> T:
    """Evaluate this expression with names in `context`."""
    if context is None:
        context = ctx_kwargs
    elif ctx_kwargs:
        context = {**context, **ctx_kwargs}
    try:
        return eval(self._code, {}, context)  # type: ignore
    except NameError as e:
        miss = {k for k in self._names if k not in context}
        raise NameError(
            f"Names required to eval this expression are missing: {miss}"
        ) from e

in_ #

in_(other: Any) -> Compare

Return a comparison for self in other.

Source code in src/app_model/expressions/_expressions.py
280
281
282
283
def in_(self, other: Any) -> Compare:
    """Return a comparison for `self` in `other`."""
    # not a dunder, use with Expr.in_(a, other)
    return Compare(self, [ast.In()], [other])

not_in #

not_in(other: Any) -> Compare

Return a comparison for self no in other.

Source code in src/app_model/expressions/_expressions.py
285
286
287
def not_in(self, other: Any) -> Compare:
    """Return a comparison for `self` no in `other`."""
    return Compare(self, [ast.NotIn()], [other])

parse classmethod #

parse(expr: str) -> Expr

Parse string into Expr (classmethod).

see docstring of parse_expression for details.

Source code in src/app_model/expressions/_expressions.py
219
220
221
222
223
224
225
226
@classmethod
def parse(cls, expr: str) -> Expr:
    """Parse string into Expr (classmethod).

    see docstring of [`parse_expression`][app_model.expressions.parse_expression]
    for details.
    """
    return parse_expression(expr)

UnaryOp #

UnaryOp(
    op: unaryop,
    operand: Expr,
    **kwargs: Unpack[_Attributes],
)

Bases: Expr[T], UnaryOp

A unary operation.

op is the operator, and operand any expression node.

Methods:

  • bitand

    Return bitwise self & other.

  • bitor

    Return bitwise self | other.

  • eval

    Evaluate this expression with names in context.

  • in_

    Return a comparison for self in other.

  • not_in

    Return a comparison for self no in other.

  • parse

    Parse string into Expr (classmethod).

Source code in src/app_model/expressions/_expressions.py
484
485
486
487
def __init__(
    self, op: ast.unaryop, operand: Expr, **kwargs: Unpack[_Attributes]
) -> None:
    super().__init__(op, Expr._cast(operand), **kwargs)

bitand #

bitand(other: T | Expr[T]) -> BinOp[T]

Return bitwise self & other.

Source code in src/app_model/expressions/_expressions.py
319
320
321
def bitand(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self & other."""
    return BinOp(self, ast.BitAnd(), other)

bitor #

bitor(other: T | Expr[T]) -> BinOp[T]

Return bitwise self | other.

Source code in src/app_model/expressions/_expressions.py
323
324
325
def bitor(self, other: T | Expr[T]) -> BinOp[T]:
    """Return bitwise self | other."""
    return BinOp(self, ast.BitOr(), other)

eval #

eval(
    context: Mapping[str, object] | None = None,
    **ctx_kwargs: object,
) -> T

Evaluate this expression with names in context.

Source code in src/app_model/expressions/_expressions.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def eval(
    self, context: Mapping[str, object] | None = None, **ctx_kwargs: object
) -> T:
    """Evaluate this expression with names in `context`."""
    if context is None:
        context = ctx_kwargs
    elif ctx_kwargs:
        context = {**context, **ctx_kwargs}
    try:
        return eval(self._code, {}, context)  # type: ignore
    except NameError as e:
        miss = {k for k in self._names if k not in context}
        raise NameError(
            f"Names required to eval this expression are missing: {miss}"
        ) from e

in_ #

in_(other: Any) -> Compare

Return a comparison for self in other.

Source code in src/app_model/expressions/_expressions.py
280
281
282
283
def in_(self, other: Any) -> Compare:
    """Return a comparison for `self` in `other`."""
    # not a dunder, use with Expr.in_(a, other)
    return Compare(self, [ast.In()], [other])

not_in #

not_in(other: Any) -> Compare

Return a comparison for self no in other.

Source code in src/app_model/expressions/_expressions.py
285
286
287
def not_in(self, other: Any) -> Compare:
    """Return a comparison for `self` no in `other`."""
    return Compare(self, [ast.NotIn()], [other])

parse classmethod #

parse(expr: str) -> Expr

Parse string into Expr (classmethod).

see docstring of parse_expression for details.

Source code in src/app_model/expressions/_expressions.py
219
220
221
222
223
224
225
226
@classmethod
def parse(cls, expr: str) -> Expr:
    """Parse string into Expr (classmethod).

    see docstring of [`parse_expression`][app_model.expressions.parse_expression]
    for details.
    """
    return parse_expression(expr)

app_model_context #

app_model_context() -> AppModelContextDict

A set of useful global context keys to use.

Source code in src/app_model/expressions/_context.py
209
210
211
212
213
214
215
def app_model_context() -> AppModelContextDict:
    """A set of useful global context keys to use."""
    return {
        "is_linux": sys.platform.startswith("linux"),
        "is_mac": sys.platform == "darwin",
        "is_windows": os.name == "nt",
    }

create_context #

create_context(
    obj: object,
    max_depth: int = 20,
    start: int = 2,
    root: Context | None = None,
    root_class: type[Context] = Context,
    frame_predicate: Callable[
        [FrameType], bool
    ] = _pydantic_abort,
) -> Context

Create context for any object.

Parameters:

  • obj (object) –

    Any object

  • max_depth (int, default: 20 ) –

    Max frame depth to search for another object (that already has a context) off of which to scope this new context. by default 20

  • start (int, default: 2 ) –

    first frame to use in search, by default 2

  • root (Optional[Context], default: None ) –

    Root context to use, by default None

  • root_class (type[Context], default: Context ) –

    Root class to use when creating a global root context, by default Context The global context is used when root is None.

  • frame_predicate (Callable[[FrameType], bool], default: _pydantic_abort ) –

    Callback that can be used to abort context creation. Will be called on each frame in the stack, and if it returns True, the context will not be created. by default, uses pydantic-specific function to determine if a new pydantic BaseModel is being declared, (which means that the context will never be used) lambda frame: frame.f_code.co_name in ("__new__", "_set_default_and_type")

Returns:

  • Optional[Context]

    Context for the object, or None if no context was found

Source code in src/app_model/expressions/_context.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
def create_context(
    obj: object,
    max_depth: int = 20,
    start: int = 2,
    root: Context | None = None,
    root_class: type[Context] = Context,
    frame_predicate: Callable[[FrameType], bool] = _pydantic_abort,
) -> Context:
    """Create context for any object.

    Parameters
    ----------
    obj : object
        Any object
    max_depth : int, optional
        Max frame depth to search for another object (that already has a context) off
        of which to scope this new context.  by default 20
    start : int, optional
        first frame to use in search, by default 2
    root : Optional[Context], optional
        Root context to use, by default None
    root_class : type[Context], optional
        Root class to use when creating a global root context, by default Context
        The global context is used when root is None.
    frame_predicate : Callable[[FrameType], bool], optional
        Callback that can be used to abort context creation.  Will be called on each
        frame in the stack, and if it returns True, the context will not be created.
        by default, uses pydantic-specific function to determine if a new pydantic
        BaseModel is being *declared*, (which means that the context will never be used)
        `lambda frame: frame.f_code.co_name in ("__new__", "_set_default_and_type")`

    Returns
    -------
    Optional[Context]
        Context for the object, or None if no context was found
    """
    if root is None:
        global _ROOT_CONTEXT
        if _ROOT_CONTEXT is None:
            _ROOT_CONTEXT = root_class()
        root = _ROOT_CONTEXT
    else:
        assert isinstance(root, Context), "root must be an instance of Context"

    parent = root
    if hasattr(sys, "_getframe"):  # CPython implementation detail
        frame: FrameType | None = sys._getframe(start)
        i = -1
        # traverse call stack looking for another object that has a context
        # to scope this new context off of.
        while frame and (i := i + 1) < max_depth:
            if frame_predicate(frame):
                return root  # pragma: no cover  # FIXME: should this be allowed?

            # FIXME: this might be a bit napari "magic"
            # it also assumes someone uses "self" as the first argument
            if "self" in frame.f_locals:
                _ctx = _OBJ_TO_CONTEXT.get(id(frame.f_locals["self"]))
                if _ctx is not None:
                    parent = _ctx
                    break
            frame = frame.f_back

    new_context = parent.new_child()
    obj_id = id(obj)
    _OBJ_TO_CONTEXT[obj_id] = new_context
    # remove key from dict when object is deleted
    finalize(obj, lambda: _OBJ_TO_CONTEXT.pop(obj_id, None))
    return new_context

get_context #

get_context(obj: object) -> Context | None

Return context for any object, if found.

Source code in src/app_model/expressions/_context.py
204
205
206
def get_context(obj: object) -> Context | None:
    """Return context for any object, if found."""
    return _OBJ_TO_CONTEXT.get(id(obj))

parse_expression #

parse_expression(expr: Expr | str) -> Expr

Parse string expression into an Expr instance.

Parameters:

  • expr (Expr | str) –

    Expression to parse. (If already an Expr, it is returned)

Returns:

  • Expr

    Instance of Expr.

Raises:

  • SyntaxError

    If the provided string is not an expression (e.g. it's a statement), or if it uses any forbidden syntax components (e.g. Call, Attribute, Containers, Indexing, Slicing, f-strings, named expression, comprehensions.)

Source code in src/app_model/expressions/_expressions.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def parse_expression(expr: Expr | str) -> Expr:
    """Parse string expression into an [`Expr`][app_model.expressions.Expr] instance.

    Parameters
    ----------
    expr : Expr | str
        Expression to parse.  (If already an `Expr`, it is returned)

    Returns
    -------
    Expr
        Instance of `Expr`.

    Raises
    ------
    SyntaxError
        If the provided string is not an expression (e.g. it's a statement), or
        if it uses any forbidden syntax components (e.g. Call, Attribute,
        Containers, Indexing, Slicing, f-strings, named expression,
        comprehensions.)
    """
    if isinstance(expr, Expr):
        return expr
    try:
        # mode='eval' means the expr must consist of a single expression
        tree = ast.parse(str(expr), mode="eval")
        if not isinstance(tree, ast.Expression):
            raise SyntaxError  # pragma: no cover
        return ExprTransformer().visit(tree.body)
    except SyntaxError as e:
        raise SyntaxError(f"{expr!r} is not a valid expression: ({e}).") from None

safe_eval #

safe_eval(
    expr: str | bool | Expr, context: Mapping | None = None
) -> Any

Safely evaluate expr string given context dict.

This lets you evaluate a string expression with broader expression support than ast.literal_eval, but much less support than eval(). It also supports booleans (which are returned directly), and Expr instances, which are evaluated in the given context.

Parameters:

  • expr (str | bool | Expr) –

    Expression to evaluate. If expr is a string, it is parsed into an Expr instance. If a bool, it is returned directly.

  • context (Mapping | None, default: None ) –

    Context (mapping of names to objects) to evaluate the expression in.

Source code in src/app_model/expressions/_expressions.py
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def safe_eval(expr: str | bool | Expr, context: Mapping | None = None) -> Any:
    """Safely evaluate `expr` string given `context` dict.

    This lets you evaluate a string expression with broader expression
    support than `ast.literal_eval`, but much less support than `eval()`.
    It also supports booleans (which are returned directly), and `Expr` instances,
    which are evaluated in the given `context`.

    Parameters
    ----------
    expr : str | bool | Expr
        Expression to evaluate. If `expr` is a string, it is parsed into an
        `Expr` instance. If a `bool`, it is returned directly.
    context : Mapping | None
        Context (mapping of names to objects) to evaluate the expression in.
    """
    if isinstance(expr, bool):
        return expr
    return parse_expression(expr).eval(context)