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
140
141
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
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
202
203
204
205
206
207
208
209
210
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
@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
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
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
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
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
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,
                )
            )
    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
228
229
230
231
232
233
234
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
237
238
239
240
241
242
243
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
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
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 {}
    if isinstance(typ, str):
        typ = ForwardRef(typ)
    if isinstance(typ, ForwardRef):
        typ = _ForwardRef_safe_eval(typ, globalns, localns)
    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
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
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
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
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
577
578
579
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
213
214
215
216
217
218
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
582
583
584
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
221
222
223
224
225
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__))