File: typing_utils.py

package info (click to toggle)
python-atom 0.12.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,616 kB
  • sloc: cpp: 9,040; python: 6,249; makefile: 123
file content (159 lines) | stat: -rw-r--r-- 4,968 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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
# --------------------------------------------------------------------------------------
# Copyright (c) 2021-2025, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# --------------------------------------------------------------------------------------
import sys
from itertools import chain
from types import GenericAlias, UnionType
from typing import (
    TYPE_CHECKING,
    Any,
    List,
    Literal,
    NewType,
    Sequence,
    Tuple,
    TypedDict,
    TypeVar,
    Union,
    get_args,
    get_origin,
)

if sys.version_info >= (3, 14):
    import annotationlib

    _INVALID_TYPES = (str, annotationlib.ForwardRef)
else:
    _INVALID_TYPES = (str,)

GENERICS = (type(List), type(List[int]), GenericAlias)

UNION = (UnionType,)

TypeLike = Union[type, TypeVar, UnionType, GenericAlias, NewType]

if TYPE_CHECKING:
    from .atom import Atom


class _ChangeDict(TypedDict):
    type: Union[
        Literal["create"],
        Literal["update"],
        Literal["delete"],
        Literal["event"],
        Literal["property"],
        Literal["container"],
    ]
    name: str
    object: "Atom"
    value: Any


class ChangeDict(_ChangeDict, total=False):
    oldvalue: Any

    # ContainerList specific entries, present only when type == "container"
    operation: Union[
        Literal["reverse"],
        Literal["__delitem__"],
        Literal["__iadd__"],
        Literal["__imul__"],
        Literal["__setitem__"],
        Literal["append"],
        Literal["extend"],
        Literal["insert"],
        Literal["pop"],
        Literal["remove"],
        Literal["sort"],
    ]
    # The following are present based on the operation value
    olditem: Any  # operation in ("__setitem__",)
    newitem: Any  # operation in ("__setitem__",)
    item: Any  # operation in ("append", "insert", "pop", "remove", "__delitem__")
    index: int  # operation in ("insert", "pop")
    items: Sequence[Any]  # operation in ("extend", "__iadd__")
    count: int  # operation in ("__imul__")
    key: Any  # operation in ("sort")
    reverse: bool  # operation in ("sort")


def _extract_types(kind: TypeLike) -> Tuple[type, ...]:
    """Extract a tuple of types from a type-like object"""
    if isinstance(kind, _INVALID_TYPES):
        raise TypeError(
            f"Str-based annotations ({kind!r}) are not supported in atom Members."
        )

    ret: List[Any]
    if isinstance(kind, GENERICS):
        args = get_args(kind)
        origin = get_origin(kind)
        if origin is Union:
            ret = list(chain.from_iterable(extract_types(a) for a in args))
        else:
            ret = [origin]
    elif UNION and isinstance(kind, UNION):
        ret = list(chain.from_iterable(extract_types(a) for a in get_args(kind)))
    else:
        ret = [kind]

    extracted: List[type] = []
    for t in ret:
        if isinstance(t, TypeVar):
            b = t.__bound__
            if b is not None:
                if isinstance(b, str):
                    raise ValueError(
                        "Forward reference in type var bounds are not supported."
                    )
                extracted.extend(_extract_types(b))
            elif t.__constraints__:
                raise ValueError("Constraints in type var are not supported.")
            else:
                extracted.append(object)

            if t.__contravariant__:
                raise ValueError("TypeVar used in Atom object cannot be contravariant")
        # NewType only exists for the sake of type checkers so we fall back to
        # the supertype for runtime checks.
        elif isinstance(t, NewType):
            extracted.extend(_extract_types(t.__supertype__))
        elif t is Any:
            extracted.append(object)
        else:
            if not isinstance(t, type):
                raise TypeError(
                    f"Failed to extract types from {kind}. "
                    f"The extraction yielded {t} which is not a type. "
                    "One case in which this can occur is when using unions of "
                    "Literal, and the issues can be worked around by using a "
                    "single literal containing all the values."
                )
            extracted.append(t)

    return tuple(extracted)


def extract_types(kind: Union[TypeLike, Tuple[TypeLike, ...]]) -> Tuple[type, ...]:
    """Extract a tuple of types from a type-like object or tuple."""
    return tuple(
        chain.from_iterable(
            _extract_types(k) for k in (kind if isinstance(kind, tuple) else (kind,))
        )
    )


NONE_TYPE = type(None)


def is_optional(kinds: Tuple[type, ...]) -> Tuple[bool, Tuple[type, ...]]:
    """Determine if a tuple of types contains NoneType."""
    if NONE_TYPE in kinds:
        return True, tuple(k for k in kinds if k is not NONE_TYPE)
    else:
        return False, kinds