Skip to content

mode.utils.objects

Object utilities.

InvalidAnnotation

Bases: Exception

Raised by annotations when encountering an invalid type.

Source code in mode/utils/objects.py
162
163
class InvalidAnnotation(Exception):
    """Raised by `annotations` when encountering an invalid type."""

KeywordReduce

Mixin class for objects that can be "pickled".

"Pickled" means the object can be serialized using the Python binary serializer -- the pickle module.

Python objects are made pickleable through defining the __reduce__ method, that returns a tuple of: (restore_function, function_starargs):

class X:

    def __init__(self, arg1, kw1=None):
        self.arg1 = arg1
        self.kw1 = kw1

    def __reduce__(self) -> Tuple[Callable, Tuple[Any, ...]]:
        return type(self), (self.arg1, self.kw1)

This is tedious since this means you cannot accept **kwargs in the constructor, so what we do is define a __reduce_keywords__ argument that returns a dict instead:

class X:

    def __init__(self, arg1, kw1=None):
        self.arg1 = arg1
        self.kw1 = kw1

    def __reduce_keywords__(self) -> Mapping[str, Any]:
        return {
            'arg1': self.arg1,
            'kw1': self.kw1,
        }
Source code in mode/utils/objects.py
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
class KeywordReduce:
    """Mixin class for objects that can be "pickled".

    "Pickled" means the object can be serialized using the Python binary
    serializer -- the `pickle` module.

    Python objects are made pickleable through defining the ``__reduce__``
    method, that returns a tuple of:
    `(restore_function, function_starargs)`:

    ```python
    class X:

        def __init__(self, arg1, kw1=None):
            self.arg1 = arg1
            self.kw1 = kw1

        def __reduce__(self) -> Tuple[Callable, Tuple[Any, ...]]:
            return type(self), (self.arg1, self.kw1)
    ```

    This is *tedious* since this means you cannot accept ``**kwargs`` in the
    constructor, so what we do is define a ``__reduce_keywords__``
    argument that returns a dict instead:

    ```python
    class X:

        def __init__(self, arg1, kw1=None):
            self.arg1 = arg1
            self.kw1 = kw1

        def __reduce_keywords__(self) -> Mapping[str, Any]:
            return {
                'arg1': self.arg1,
                'kw1': self.kw1,
            }
    ```
    """

    def __reduce_keywords__(self) -> Mapping:
        raise NotImplementedError()

    def __reduce__(self) -> tuple:
        return _restore_from_keywords, (type(self), self.__reduce_keywords__())

Unordered

Bases: Generic[_T]

Shield object from being ordered in heapq/__le__/etc.

Source code in mode/utils/objects.py
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
@total_ordering
class Unordered(Generic[_T]):
    """Shield object from being ordered in heapq/``__le__``/etc."""

    # Used to put anything inside a heapq, even things that cannot be ordered
    # like dicts and lists.

    def __init__(self, value: _T) -> None:
        self.value = value

    def __le__(self, other: Any) -> bool:
        return True

    def __repr__(self) -> str:
        return f"<{type(self).__name__}: {self.value!r}>"

cached_property

Bases: Generic[RT]

Cached property.

A property descriptor that caches the return value of the get function.

Examples:

@cached_property
def connection(self):
    return Connection()

@connection.setter  # Prepares stored value
def connection(self, value):
    if value is None:
        raise TypeError('Connection must be a connection')
    return value

@connection.deleter
def connection(self, value):
    # Additional action to do at del(self.attr)
    if value is not None:
        print(f'Connection {value!r} deleted')
Source code in mode/utils/objects.py
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
class cached_property(Generic[RT]):
    """Cached property.

    A property descriptor that caches the return value
    of the get function.

    Examples:

    ```python
    @cached_property
    def connection(self):
        return Connection()

    @connection.setter  # Prepares stored value
    def connection(self, value):
        if value is None:
            raise TypeError('Connection must be a connection')
        return value

    @connection.deleter
    def connection(self, value):
        # Additional action to do at del(self.attr)
        if value is not None:
            print(f'Connection {value!r} deleted')
    ```
    """

    def __init__(
        self,
        fget: Callable[[Any], RT],
        fset: Optional[Callable[[Any, RT], RT]] = None,
        fdel: Optional[Callable[[Any, RT], None]] = None,
        doc: Optional[str] = None,
        class_attribute: Optional[str] = None,
    ) -> None:
        self.__get: Callable[[Any], RT] = fget
        self.__set: Optional[Callable[[Any, RT], RT]] = fset
        self.__del: Optional[Callable[[Any, RT], None]] = fdel
        self.__doc__ = doc or fget.__doc__
        self.__name__ = fget.__name__
        self.__module__ = fget.__module__
        self.class_attribute: Optional[str] = class_attribute

    def is_set(self, obj: Any) -> bool:
        return self.__name__ in obj.__dict__

    def __get__(self, obj: Any, type: Optional[type] = None) -> RT:
        if obj is None:
            if type is not None and self.class_attribute:
                return cast(RT, getattr(type, self.class_attribute))
            return cast(RT, self)  # just have to cast this :-(
        try:
            return cast(RT, obj.__dict__[self.__name__])
        except KeyError:
            value = obj.__dict__[self.__name__] = self.__get(obj)
            return value

    def __set__(self, obj: Any, value: RT) -> None:
        if self.__set is not None:
            value = self.__set(obj, value)
        obj.__dict__[self.__name__] = value

    def __delete__(self, obj: Any, _sentinel: Any = object()) -> None:  # noqa: B008
        value = obj.__dict__.pop(self.__name__, _sentinel)
        if self.__del is not None and value is not _sentinel:
            self.__del(obj, value)

    def setter(self, fset: Callable[[Any, RT], RT]) -> "cached_property":
        return self.__class__(self.__get, fset, self.__del)

    def deleter(self, fdel: Callable[[Any, RT], None]) -> "cached_property":
        return self.__class__(self.__get, self.__set, fdel)

annotations(cls, *, stop=object, invalid_types=None, alias_types=None, skip_classvar=False, globalns=None, localns=None)

Get class field definition in MRO order.

Parameters:

Name Type Description Default
cls type

Class to get field information from.

required
stop type

Base class to stop at (default is object).

object
invalid_types Optional[set]

Set of types that if encountered should raise :exc:InvalidAnnotation (does not test for subclasses).

None
alias_types Optional[Mapping]

Mapping of original type to replacement type.

None
skip_classvar bool

Skip attributes annotated with typing.ClassVar.

False
globalns Optional[dict[str, Any]]

Global namespace to use when evaluating forward references (see typing.ForwardRef).

None
localns Optional[dict[str, Any]]

Local namespace to use when evaluating forward references (see typing.ForwardRef).

None

Returns:

Type Description
tuple[FieldMapping, DefaultsMapping]

Tuple[FieldMapping, DefaultsMapping]: Tuple with two dictionaries, the first containing a map of field names to their types, the second containing a map of field names to their default value. If a field is not in the second map, it means the field is required.

Raises:

Type Description
InvalidAnnotation

if a list of invalid types are provided and an invalid type is encountered.

Examples:

>>> class Point:
...    x: float
...    y: float

>>> class 3DPoint(Point):
...     z: float = 0.0

>>> fields, defaults = annotations(3DPoint)
>>> fields
{'x': float, 'y': float, 'z': 'float'}
>>> defaults
{'z': 0.0}
Source code in mode/utils/objects.py
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
def annotations(
    cls: type,
    *,
    stop: type = object,
    invalid_types: Optional[set] = None,
    alias_types: Optional[Mapping] = None,
    skip_classvar: bool = False,
    globalns: Optional[dict[str, Any]] = None,
    localns: Optional[dict[str, Any]] = None,
) -> tuple[FieldMapping, DefaultsMapping]:
    """Get class field definition in MRO order.

    Arguments:
        cls: Class to get field information from.
        stop: Base class to stop at (default is ``object``).
        invalid_types: Set of types that if encountered should raise
            :exc:`InvalidAnnotation` (does not test for subclasses).
        alias_types: Mapping of original type to replacement type.
        skip_classvar: Skip attributes annotated with
            `typing.ClassVar`.
        globalns: Global namespace to use when evaluating forward
            references (see `typing.ForwardRef`).
        localns: Local namespace to use when evaluating forward
            references (see `typing.ForwardRef`).

    Returns:
        Tuple[FieldMapping, DefaultsMapping]: Tuple with two dictionaries,
            the first containing a map of field names to their types,
            the second containing a map of field names to their default
            value.  If a field is not in the second map, it means the field
            is required.

    Raises:
        InvalidAnnotation: if a list of invalid types are provided and an
            invalid type is encountered.

    Examples:

    ```sh
    >>> class Point:
    ...    x: float
    ...    y: float

    >>> class 3DPoint(Point):
    ...     z: float = 0.0

    >>> fields, defaults = annotations(3DPoint)
    >>> fields
    {'x': float, 'y': float, 'z': 'float'}
    >>> defaults
    {'z': 0.0}
    ```
    """
    fields: dict[str, type] = {}
    defaults: dict[str, Any] = {}
    for subcls in iter_mro_reversed(cls, stop=stop):
        defaults.update(subcls.__dict__)
        with suppress(AttributeError):
            fields.update(
                local_annotations(
                    subcls,
                    invalid_types=invalid_types,
                    alias_types=alias_types,
                    skip_classvar=skip_classvar,
                    globalns=globalns,
                    localns=localns,
                )
            )

    # NOTE: Return the resolved field types as-is. `_resolve_refs`/`eval_type`
    # above already turned string/ForwardRef annotations into real types, so
    # nothing further is needed here. Do NOT run `_normalize_forwardref` over
    # the result: it rewrites any type whose ``__qualname__`` contains
    # "<locals>" (i.e. every class defined inside a function) down to a bare
    # ``__name__`` *string*, destroying the actual class object. Callers such
    # as faust.Record rely on identity of the returned types (e.g. matching a
    # field type against a custom coercion mapping), which silently breaks
    # when the type is replaced by its name string. `_normalize_forwardref`
    # remains available as a comparison helper for callers/tests that want a
    # canonical, hashable-by-name form.
    return fields, defaults

canoname(obj, *, main_name=None)

Get qualname of obj, trying to resolve the real name of __main__.

Source code in mode/utils/objects.py
250
251
252
253
254
255
256
def canoname(obj: Any, *, main_name: Optional[str] = None) -> str:
    """Get qualname of obj, trying to resolve the real name of ``__main__``."""
    name = qualname(obj)
    parts = name.split(".")
    if parts[0] == "__main__":
        return ".".join([main_name or _detect_main_name(), *parts[1:]])
    return name

canonshortname(obj, *, main_name=None)

Get non-qualified name of obj, resolve real name of __main__.

Source code in mode/utils/objects.py
259
260
261
262
263
264
265
def canonshortname(obj: Any, *, main_name: Optional[str] = None) -> str:
    """Get non-qualified name of obj, resolve real name of ``__main__``."""
    name = shortname(obj)
    parts = name.split(".")
    if parts[0] == "__main__":
        return ".".join([main_name or _detect_main_name(), *parts[1:]])
    return name

eval_type(typ, globalns=None, localns=None, invalid_types=None, alias_types=None)

Convert (possible) string annotation to actual type.

Examples:

>>> eval_type('List[int]') == typing.List[int]
>>> eval_type('list[int]') == list[int]
Source code in mode/utils/objects.py
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
def eval_type(
    typ: Any,
    globalns: Optional[dict[str, Any]] = None,
    localns: Optional[dict[str, Any]] = None,
    invalid_types: Optional[set] = None,
    alias_types: Optional[Mapping] = None,
) -> type:
    """Convert (possible) string annotation to actual type.

    Examples:

    ```sh
    >>> eval_type('List[int]') == typing.List[int]
    >>> eval_type('list[int]') == list[int]
    ```
    """
    invalid_types = invalid_types or set()
    alias_types = alias_types or {}
    original_str = typ if isinstance(typ, str) else None
    if isinstance(typ, str):
        typ = ForwardRef(typ)
    # `typing._eval_type` (imported above as `_eval_type`) already resolves
    # ForwardRef instances directly -- it is the same stdlib function
    # `typing.get_type_hints()` itself relies on, so it always matches
    # whatever internal ForwardRef implementation the running interpreter
    # has (this previously went through a hand-rolled evaluator here that
    # poked at ForwardRef's private __forward_evaluated__/__forward_code__/
    # __forward_value__ attributes as a workaround for Python 3.6/3.7; those
    # attributes are not part of any stable API and are no longer present at
    # all on Python 3.14's ForwardRef, which raised AttributeError. mode's
    # floor is Python 3.9, well past the versions that workaround targeted).
    if isinstance(typ, ForwardRef):
        try:
            typ = _eval_type(typ, globalns, localns)
        except TypeError:
            # `_eval_type` runs the forward ref through `typing._type_check`,
            # which rejects `ClassVar[...]` with "is not valid as type
            # argument". But a *string* ClassVar annotation is legitimate --
            # `from __future__ import annotations` stringizes every
            # annotation, ClassVars included -- and callers (see
            # `_resolve_refs` + `skip_classvar`) still want to see it so they
            # can drop it. Evaluate the forward arg directly and accept it
            # when it is a ClassVar; otherwise re-raise the real error.
            src = (
                original_str
                if original_str is not None
                else typ.__forward_arg__
            )
            evaluated = eval(src, globalns, localns)  # noqa: S307
            if _is_class_var(evaluated):
                typ = evaluated
            else:
                raise
    else:
        typ = _eval_type(typ, globalns, localns)
    if typ in invalid_types:
        raise InvalidAnnotation(typ)
    return alias_types.get(typ, typ)

guess_polymorphic_type(typ, *, set_types=SET_TYPES, list_types=LIST_TYPES, tuple_types=TUPLE_TYPES, dict_types=DICT_TYPES)

Try to find the polymorphic and concrete type of an abstract type.

Returns tuple of (polymorphic_type, concrete_type).

Examples:

>>> guess_polymorphic_type(List[int])
(list, int)

>>> guess_polymorphic_type(Optional[list[int]])
(list, int)

>>> guess_polymorphic_type(MutableMapping[int, str])
(dict, str)
Source code in mode/utils/objects.py
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
def guess_polymorphic_type(
    typ: type,
    *,
    set_types: tuple[type, ...] = SET_TYPES,
    list_types: tuple[type, ...] = LIST_TYPES,
    tuple_types: tuple[type, ...] = TUPLE_TYPES,
    dict_types: tuple[type, ...] = DICT_TYPES,
) -> tuple[type, type]:
    """Try to find the polymorphic and concrete type of an abstract type.

    Returns tuple of `(polymorphic_type, concrete_type)`.

    Examples:

    ```sh
    >>> guess_polymorphic_type(List[int])
    (list, int)

    >>> guess_polymorphic_type(Optional[list[int]])
    (list, int)

    >>> guess_polymorphic_type(MutableMapping[int, str])
    (dict, str)
    ```
    """
    args, typ = _remove_optional(typ, find_origin=True)
    if typ is not str and typ is not bytes:
        if issubclass(typ, tuple_types):
            # Tuple[x]
            return tuple, _unary_type_arg(args)
        elif issubclass(typ, set_types):
            # Set[x]
            return set, _unary_type_arg(args)
        elif issubclass(typ, list_types):
            # list[x]
            return list, _unary_type_arg(args)
        elif issubclass(typ, dict_types):
            # Dict[_, x]
            return dict, args[1] if args and len(args) > 1 else Any
    raise TypeError(f"Not a generic type: {typ!r}")

iter_mro_reversed(cls, stop)

Iterate over superclasses, in reverse Method Resolution Order.

The stop argument specifies a base class that when seen will stop iterating (well actually start, since this is in reverse, see Example for demonstration).

Parameters:

Name Type Description Default
cls Type

Target class.

required
stop Type

A base class in which we stop iteration.

required
Notes

The last item produced will be the class itself (cls).

Examples:

>>> class A: ...
>>> class B(A): ...
>>> class C(B): ...

>>> list(iter_mro_reverse(C, object))
[A, B, C]

>>> list(iter_mro_reverse(C, A))
[B, C]

Yields:

Type Description
Iterable[type]

Iterable[Type]: every class.

Source code in mode/utils/objects.py
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
def iter_mro_reversed(cls: type, stop: type) -> Iterable[type]:
    """Iterate over superclasses, in reverse Method Resolution Order.

    The stop argument specifies a base class that when seen will
    stop iterating (well actually start, since this is in reverse, see Example
    for demonstration).

    Arguments:
        cls (Type): Target class.
        stop (Type): A base class in which we stop iteration.

    Notes:
        The last item produced will be the class itself (`cls`).

    Examples:

    ```sh
    >>> class A: ...
    >>> class B(A): ...
    >>> class C(B): ...

    >>> list(iter_mro_reverse(C, object))
    [A, B, C]

    >>> list(iter_mro_reverse(C, A))
    [B, C]
    ```

    Yields:
        Iterable[Type]: every class.
    """
    wanted = False
    for subcls in reversed(cls.__mro__):
        if wanted:
            yield cast(type, subcls)
        else:
            wanted = subcls == stop

label(s)

Return the name of an object as string.

Source code in mode/utils/objects.py
640
641
642
def label(s: Any) -> str:
    """Return the name of an object as string."""
    return _label("label", s)

qualname(obj)

Get object qualified name.

Source code in mode/utils/objects.py
235
236
237
238
239
240
def qualname(obj: Any) -> str:
    """Get object qualified name."""
    if not hasattr(obj, "__name__") and hasattr(obj, "__class__"):
        obj = obj.__class__
    name = getattr(obj, "__qualname__", obj.__name__)
    return ".".join((obj.__module__, name))

shortlabel(s)

Return the shortened name of an object as string.

Source code in mode/utils/objects.py
645
646
647
def shortlabel(s: Any) -> str:
    """Return the shortened name of an object as string."""
    return _label("shortlabel", s)

shortname(obj)

Get object name (non-qualified).

Source code in mode/utils/objects.py
243
244
245
246
247
def shortname(obj: Any) -> str:
    """Get object name (non-qualified)."""
    if not hasattr(obj, "__name__") and hasattr(obj, "__class__"):
        obj = obj.__class__
    return ".".join((obj.__module__, obj.__name__))