typingkit 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- typingkit/__init__.py +11 -0
- typingkit/_typed/__init__.py +11 -0
- typingkit/_typed/_debug.py +37 -0
- typingkit/_typed/context.py +203 -0
- typingkit/_typed/dimexpr.py +154 -0
- typingkit/_typed/factory.py +71 -0
- typingkit/_typed/generics.py +50 -0
- typingkit/_typed/helpers.py +248 -0
- typingkit/_typed/list.py +206 -0
- typingkit/_typed/ndarray.py +513 -0
- typingkit/py.typed +0 -0
- typingkit-0.2.2.dist-info/METADATA +92 -0
- typingkit-0.2.2.dist-info/RECORD +14 -0
- typingkit-0.2.2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helpers for TypedNDArray
|
|
3
|
+
=======
|
|
4
|
+
"""
|
|
5
|
+
# src/typingkit/_typed/helpers.py
|
|
6
|
+
|
|
7
|
+
from typing import Literal, TypeAlias, TypeVar
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from typingkit._typed.ndarray import TypedNDArray
|
|
12
|
+
|
|
13
|
+
## Helpers
|
|
14
|
+
|
|
15
|
+
# Literal type aliases for small integers
|
|
16
|
+
ZERO: TypeAlias = Literal[0]
|
|
17
|
+
"""Literal type for the integer `0`."""
|
|
18
|
+
ONE: TypeAlias = Literal[1]
|
|
19
|
+
"""Literal type for the integer `1`."""
|
|
20
|
+
TWO: TypeAlias = Literal[2]
|
|
21
|
+
"""Literal type for the integer `2`."""
|
|
22
|
+
THREE: TypeAlias = Literal[3]
|
|
23
|
+
"""Literal type for the integer `3`."""
|
|
24
|
+
FOUR: TypeAlias = Literal[4]
|
|
25
|
+
"""Literal type for the integer `4`."""
|
|
26
|
+
FIVE: TypeAlias = Literal[5]
|
|
27
|
+
"""Literal type for the integer `5`."""
|
|
28
|
+
SIX: TypeAlias = Literal[6]
|
|
29
|
+
"""Literal type for the integer `6`."""
|
|
30
|
+
SEVEN: TypeAlias = Literal[7]
|
|
31
|
+
"""Literal type for the integer `7`."""
|
|
32
|
+
EIGHT: TypeAlias = Literal[8]
|
|
33
|
+
"""Literal type for the integer `8`."""
|
|
34
|
+
NINE: TypeAlias = Literal[9]
|
|
35
|
+
"""Literal type for the integer `9`."""
|
|
36
|
+
TEN: TypeAlias = Literal[10]
|
|
37
|
+
"""Literal type for the integer `10`."""
|
|
38
|
+
|
|
39
|
+
# TypeVars
|
|
40
|
+
Dim1 = TypeVar("Dim1", bound=int, default=int)
|
|
41
|
+
Dim2 = TypeVar("Dim2", bound=int, default=int)
|
|
42
|
+
Dim3 = TypeVar("Dim3", bound=int, default=int)
|
|
43
|
+
Dim4 = TypeVar("Dim4", bound=int, default=int)
|
|
44
|
+
K = TypeVar("K", bound=int, default=int)
|
|
45
|
+
L = TypeVar("L", bound=int, default=int)
|
|
46
|
+
M = TypeVar("M", bound=int, default=int)
|
|
47
|
+
N = TypeVar("N", bound=int, default=int)
|
|
48
|
+
|
|
49
|
+
# Shape type aliases
|
|
50
|
+
Shape0D: TypeAlias = tuple[()]
|
|
51
|
+
"""A tuple representing a 0D shape, i.e., `()`."""
|
|
52
|
+
Shape1D: TypeAlias = tuple[Dim1]
|
|
53
|
+
"""A tuple representing a 1D shape, i.e., `(N,)`."""
|
|
54
|
+
Shape2D: TypeAlias = tuple[Dim1, Dim2]
|
|
55
|
+
"""A tuple representing a 2D shape, i.e., `(M, N)`."""
|
|
56
|
+
Shape3D: TypeAlias = tuple[Dim1, Dim2, Dim3]
|
|
57
|
+
"""A tuple representing a 3D shape, i.e., shape `(L, M, N)`."""
|
|
58
|
+
Shape4D: TypeAlias = tuple[Dim1, Dim2, Dim3, Dim4]
|
|
59
|
+
"""A tuple representing a 4D shape, i.e., shape `(K, L, M, N)`."""
|
|
60
|
+
ShapeND: TypeAlias = tuple[int, ...]
|
|
61
|
+
"""A tuple representing a ND shape, i.e., shape `(N, ...)`."""
|
|
62
|
+
|
|
63
|
+
# DType aliases
|
|
64
|
+
DType = TypeVar("DType", bound=np.dtype, default=np.dtype, covariant=True)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
## Array type aliases
|
|
68
|
+
|
|
69
|
+
# Arrays based on dimensionality
|
|
70
|
+
Array0D: TypeAlias = TypedNDArray[Shape0D, DType]
|
|
71
|
+
"""A `numpy.ndarray` of shape `()` and dtype `DType`."""
|
|
72
|
+
Array1D: TypeAlias = TypedNDArray[Shape1D[Dim1], DType]
|
|
73
|
+
"""A `numpy.ndarray` of shape `(N,)` and dtype `DType`."""
|
|
74
|
+
Array2D: TypeAlias = TypedNDArray[Shape2D[Dim1, Dim2], DType]
|
|
75
|
+
"""A `numpy.ndarray` of shape `(M, N)` and dtype `DType`."""
|
|
76
|
+
Array3D: TypeAlias = TypedNDArray[Shape3D[Dim1, Dim2, Dim3], DType]
|
|
77
|
+
"""A `numpy.ndarray` of shape `(L, M, N)` and dtype `DType`."""
|
|
78
|
+
Array4D: TypeAlias = TypedNDArray[Shape4D[Dim1, Dim2, Dim3, Dim4], DType]
|
|
79
|
+
"""A `numpy.ndarray` of shape `(K, L, M, N)` and dtype `DType`."""
|
|
80
|
+
ArrayND: TypeAlias = TypedNDArray[ShapeND, DType]
|
|
81
|
+
"""A `numpy.ndarray` of shape `(N, ...)` and dtype `DType`."""
|
|
82
|
+
|
|
83
|
+
# 1D Arrays with specific small sizes
|
|
84
|
+
Array2: TypeAlias = TypedNDArray[tuple[TWO], DType]
|
|
85
|
+
"""A `numpy.ndarray` of shape `(2,)` and dtype `DType`."""
|
|
86
|
+
Array3: TypeAlias = TypedNDArray[tuple[THREE], DType]
|
|
87
|
+
"""A `numpy.ndarray` of shape `(3,)` and dtype `DType`."""
|
|
88
|
+
Array4: TypeAlias = TypedNDArray[tuple[FOUR], DType]
|
|
89
|
+
"""A `numpy.ndarray` of shape `(4,)` and dtype `DType`."""
|
|
90
|
+
ArrayN: TypeAlias = TypedNDArray[tuple[N], DType] # Same as Array1D
|
|
91
|
+
"""A `numpy.ndarray` of shape `(N,)` and dtype `DType`."""
|
|
92
|
+
|
|
93
|
+
# 2D Arrays with specific small sizes
|
|
94
|
+
Array2x2: TypeAlias = TypedNDArray[tuple[TWO, TWO], DType]
|
|
95
|
+
"""A `numpy.ndarray` of shape `(2, 2)` and dtype `DType`."""
|
|
96
|
+
Array2x3: TypeAlias = TypedNDArray[tuple[TWO, THREE], DType]
|
|
97
|
+
"""A `numpy.ndarray` of shape `(2, 3)` and dtype `DType`."""
|
|
98
|
+
Array2x4: TypeAlias = TypedNDArray[tuple[TWO, FOUR], DType]
|
|
99
|
+
"""A `numpy.ndarray` of shape `(2, 4)` and dtype `DType`."""
|
|
100
|
+
Array2xN: TypeAlias = TypedNDArray[tuple[TWO, N], DType]
|
|
101
|
+
"""A `numpy.ndarray` of shape `(2, N)` and dtype `DType`."""
|
|
102
|
+
|
|
103
|
+
Array3x2: TypeAlias = TypedNDArray[tuple[THREE, TWO], DType]
|
|
104
|
+
"""A `numpy.ndarray` of shape `(3, 2)` and dtype `DType`."""
|
|
105
|
+
Array3x3: TypeAlias = TypedNDArray[tuple[THREE, THREE], DType]
|
|
106
|
+
"""A `numpy.ndarray` of shape `(3, 3)` and dtype `DType`."""
|
|
107
|
+
Array3x4: TypeAlias = TypedNDArray[tuple[THREE, FOUR], DType]
|
|
108
|
+
"""A `numpy.ndarray` of shape `(3, 4)` with the default `dtype."""
|
|
109
|
+
Array3xN: TypeAlias = TypedNDArray[tuple[THREE, N], DType]
|
|
110
|
+
"""A `numpy.ndarray` of shape `(3, N)` and dtype `DType`."""
|
|
111
|
+
|
|
112
|
+
Array4x2: TypeAlias = TypedNDArray[tuple[FOUR, TWO], DType]
|
|
113
|
+
"""A `numpy.ndarray` of shape `(4, 2)` and dtype `DType`."""
|
|
114
|
+
Array4x3: TypeAlias = TypedNDArray[tuple[FOUR, THREE], DType]
|
|
115
|
+
"""A `numpy.ndarray` of shape `(4, 3)` and dtype `DType`."""
|
|
116
|
+
Array4x4: TypeAlias = TypedNDArray[tuple[FOUR, FOUR], DType]
|
|
117
|
+
"""A `numpy.ndarray` of shape `(4, 4)` with the default `dtype."""
|
|
118
|
+
Array4xN: TypeAlias = TypedNDArray[tuple[FOUR, N], DType]
|
|
119
|
+
"""A `numpy.ndarray` of shape `(4, N)` and dtype `DType`."""
|
|
120
|
+
|
|
121
|
+
ArrayNx2: TypeAlias = TypedNDArray[tuple[N, TWO], DType]
|
|
122
|
+
"""A `numpy.ndarray` of shape `(N, 2)` and dtype `DType`."""
|
|
123
|
+
ArrayNx3: TypeAlias = TypedNDArray[tuple[N, THREE], DType]
|
|
124
|
+
"""A `numpy.ndarray` of shape `(N, 3)` and dtype `DType`."""
|
|
125
|
+
ArrayNx4: TypeAlias = TypedNDArray[tuple[N, FOUR], DType]
|
|
126
|
+
"""A `numpy.ndarray` of shape `(N, 4)` and dtype `DType`."""
|
|
127
|
+
|
|
128
|
+
# 3D Arrays with specific small sizes
|
|
129
|
+
Array2x2x2: TypeAlias = TypedNDArray[tuple[TWO, TWO, TWO], DType]
|
|
130
|
+
"""A `numpy.ndarray` of shape `(2, 2, 2)` and dtype `DType`."""
|
|
131
|
+
Array2x2x3: TypeAlias = TypedNDArray[tuple[TWO, TWO, THREE], DType]
|
|
132
|
+
"""A `numpy.ndarray` of shape `(2, 2, 3)` and dtype `DType`."""
|
|
133
|
+
Array2x2x4: TypeAlias = TypedNDArray[tuple[TWO, TWO, FOUR], DType]
|
|
134
|
+
"""A `numpy.ndarray` of shape `(2, 2, 4)` and dtype `DType`."""
|
|
135
|
+
Array2x2xN: TypeAlias = TypedNDArray[tuple[TWO, TWO, N], DType]
|
|
136
|
+
"""A `numpy.ndarray` of shape `(2, 2, N)` and dtype `DType`."""
|
|
137
|
+
|
|
138
|
+
Array2x3x2: TypeAlias = TypedNDArray[tuple[TWO, THREE, TWO], DType]
|
|
139
|
+
"""A `numpy.ndarray` of shape `(2, 3, 2)` and dtype `DType`."""
|
|
140
|
+
Array2x3x3: TypeAlias = TypedNDArray[tuple[TWO, THREE, THREE], DType]
|
|
141
|
+
"""A `numpy.ndarray` of shape `(2, 3, 3)` and dtype `DType`."""
|
|
142
|
+
Array2x3x4: TypeAlias = TypedNDArray[tuple[TWO, THREE, FOUR], DType]
|
|
143
|
+
"""A `numpy.ndarray` of shape `(2, 3, 4)` and dtype `DType`."""
|
|
144
|
+
Array2x3xN: TypeAlias = TypedNDArray[tuple[TWO, THREE, N], DType]
|
|
145
|
+
"""A `numpy.ndarray` of shape `(2, 3, N)` and dtype `DType`."""
|
|
146
|
+
|
|
147
|
+
Array2x4x2: TypeAlias = TypedNDArray[tuple[TWO, FOUR, TWO], DType]
|
|
148
|
+
"""A `numpy.ndarray` of shape `(2, 4, 2)` and dtype `DType`."""
|
|
149
|
+
Array2x4x3: TypeAlias = TypedNDArray[tuple[TWO, FOUR, THREE], DType]
|
|
150
|
+
"""A `numpy.ndarray` of shape `(2, 4, 3)` and dtype `DType`."""
|
|
151
|
+
Array2x4x4: TypeAlias = TypedNDArray[tuple[TWO, FOUR, FOUR], DType]
|
|
152
|
+
"""A `numpy.ndarray` of shape `(2, 4, 4)` and dtype `DType`."""
|
|
153
|
+
Array2x4xN: TypeAlias = TypedNDArray[tuple[TWO, FOUR, N], DType]
|
|
154
|
+
"""A `numpy.ndarray` of shape `(2, 4, N)` and dtype `DType`."""
|
|
155
|
+
|
|
156
|
+
Array2xNx2: TypeAlias = TypedNDArray[tuple[TWO, N, TWO], DType]
|
|
157
|
+
"""A `numpy.ndarray` of shape `(2, N, 2)` and dtype `DType`."""
|
|
158
|
+
Array2xNx3: TypeAlias = TypedNDArray[tuple[TWO, N, THREE], DType]
|
|
159
|
+
"""A `numpy.ndarray` of shape `(2, N, 3)` and dtype `DType`."""
|
|
160
|
+
Array2xNx4: TypeAlias = TypedNDArray[tuple[TWO, N, FOUR], DType]
|
|
161
|
+
"""A `numpy.ndarray` of shape `(2, N, 4)` and dtype `DType`."""
|
|
162
|
+
|
|
163
|
+
Array3x2x2: TypeAlias = TypedNDArray[tuple[THREE, TWO, TWO], DType]
|
|
164
|
+
"""A `numpy.ndarray` of shape `(3, 2, 2)` and dtype `DType`."""
|
|
165
|
+
Array3x2x3: TypeAlias = TypedNDArray[tuple[THREE, TWO, THREE], DType]
|
|
166
|
+
"""A `numpy.ndarray` of shape `(3, 2, 3)` and dtype `DType`."""
|
|
167
|
+
Array3x2x4: TypeAlias = TypedNDArray[tuple[THREE, TWO, FOUR], DType]
|
|
168
|
+
"""A `numpy.ndarray` of shape `(3, 2, 4)` and dtype `DType`."""
|
|
169
|
+
Array3x2xN: TypeAlias = TypedNDArray[tuple[THREE, TWO, N], DType]
|
|
170
|
+
"""A `numpy.ndarray` of shape `(3, 2, N)` and dtype `DType`."""
|
|
171
|
+
|
|
172
|
+
Array3x3x2: TypeAlias = TypedNDArray[tuple[THREE, THREE, TWO], DType]
|
|
173
|
+
"""A `numpy.ndarray` of shape `(3, 3, 2)` and dtype `DType`."""
|
|
174
|
+
Array3x3x3: TypeAlias = TypedNDArray[tuple[THREE, THREE, THREE], DType]
|
|
175
|
+
"""A `numpy.ndarray` of shape `(3, 3, 3)` and dtype `DType`."""
|
|
176
|
+
Array3x3x4: TypeAlias = TypedNDArray[tuple[THREE, THREE, FOUR], DType]
|
|
177
|
+
"""A `numpy.ndarray` of shape `(3, 3, 4)` and dtype `DType`."""
|
|
178
|
+
Array3x3xN: TypeAlias = TypedNDArray[tuple[THREE, THREE, N], DType]
|
|
179
|
+
"""A `numpy.ndarray` of shape `(3, 3, N)` and dtype `DType`."""
|
|
180
|
+
|
|
181
|
+
Array3x4x2: TypeAlias = TypedNDArray[tuple[THREE, FOUR, TWO], DType]
|
|
182
|
+
"""A `numpy.ndarray` of shape `(3, 4, 2)` and dtype `DType`."""
|
|
183
|
+
Array3x4x3: TypeAlias = TypedNDArray[tuple[THREE, FOUR, THREE], DType]
|
|
184
|
+
"""A `numpy.ndarray` of shape `(3, 4, 3)` and dtype `DType`."""
|
|
185
|
+
Array3x4x4: TypeAlias = TypedNDArray[tuple[THREE, FOUR, FOUR], DType]
|
|
186
|
+
"""A `numpy.ndarray` of shape `(3, 4, 4)` and dtype `DType`."""
|
|
187
|
+
Array3x4xN: TypeAlias = TypedNDArray[tuple[THREE, FOUR, N], DType]
|
|
188
|
+
"""A `numpy.ndarray` of shape `(3, 4, N)` and dtype `DType`."""
|
|
189
|
+
|
|
190
|
+
Array3xNx2: TypeAlias = TypedNDArray[tuple[THREE, N, TWO], DType]
|
|
191
|
+
"""A `numpy.ndarray` of shape `(3, N, 2)` and dtype `DType`."""
|
|
192
|
+
Array3xNx3: TypeAlias = TypedNDArray[tuple[THREE, N, THREE], DType]
|
|
193
|
+
"""A `numpy.ndarray` of shape `(3, N, 3)` and dtype `DType`."""
|
|
194
|
+
Array3xNx4: TypeAlias = TypedNDArray[tuple[THREE, N, FOUR], DType]
|
|
195
|
+
"""A `numpy.ndarray` of shape `(3, N, 4)` and dtype `DType`."""
|
|
196
|
+
|
|
197
|
+
Array4x2x2: TypeAlias = TypedNDArray[tuple[FOUR, TWO, TWO], DType]
|
|
198
|
+
"""A `numpy.ndarray` of shape `(4, 2, 2)` and dtype `DType`."""
|
|
199
|
+
Array4x2x3: TypeAlias = TypedNDArray[tuple[FOUR, TWO, THREE], DType]
|
|
200
|
+
"""A `numpy.ndarray` of shape `(4, 2, 3)` and dtype `DType`."""
|
|
201
|
+
Array4x2x4: TypeAlias = TypedNDArray[tuple[FOUR, TWO, FOUR], DType]
|
|
202
|
+
"""A `numpy.ndarray` of shape `(4, 2, 4)` and dtype `DType`."""
|
|
203
|
+
Array4x2xN: TypeAlias = TypedNDArray[tuple[FOUR, TWO, N], DType]
|
|
204
|
+
"""A `numpy.ndarray` of shape `(4, 2, N)` and dtype `DType`."""
|
|
205
|
+
|
|
206
|
+
Array4x3x2: TypeAlias = TypedNDArray[tuple[FOUR, THREE, TWO], DType]
|
|
207
|
+
"""A `numpy.ndarray` of shape `(4, 3, 2)` and dtype `DType`."""
|
|
208
|
+
Array4x3x3: TypeAlias = TypedNDArray[tuple[FOUR, THREE, THREE], DType]
|
|
209
|
+
"""A `numpy.ndarray` of shape `(4, 3, 3)` and dtype `DType`."""
|
|
210
|
+
Array4x3x4: TypeAlias = TypedNDArray[tuple[FOUR, THREE, FOUR], DType]
|
|
211
|
+
"""A `numpy.ndarray` of shape `(4, 3, 4)` and dtype `DType`."""
|
|
212
|
+
Array4x3xN: TypeAlias = TypedNDArray[tuple[FOUR, THREE, N], DType]
|
|
213
|
+
"""A `numpy.ndarray` of shape `(4, 3, N)` and dtype `DType`."""
|
|
214
|
+
|
|
215
|
+
Array4x4x2: TypeAlias = TypedNDArray[tuple[FOUR, FOUR, TWO], DType]
|
|
216
|
+
"""A `numpy.ndarray` of shape `(4, 4, 2)` and dtype `DType`."""
|
|
217
|
+
Array4x4x3: TypeAlias = TypedNDArray[tuple[FOUR, FOUR, THREE], DType]
|
|
218
|
+
"""A `numpy.ndarray` of shape `(4, 4, 3)` and dtype `DType`."""
|
|
219
|
+
Array4x4x4: TypeAlias = TypedNDArray[tuple[FOUR, FOUR, FOUR], DType]
|
|
220
|
+
"""A `numpy.ndarray` of shape `(4, 4, 4)` and dtype `DType`."""
|
|
221
|
+
Array4x4xN: TypeAlias = TypedNDArray[tuple[FOUR, FOUR, N], DType]
|
|
222
|
+
"""A `numpy.ndarray` of shape `(4, 4, N)` and dtype `DType`."""
|
|
223
|
+
|
|
224
|
+
Array4xNx2: TypeAlias = TypedNDArray[tuple[FOUR, N, TWO], DType]
|
|
225
|
+
"""A `numpy.ndarray` of shape `(4, N, 2)` and dtype `DType`."""
|
|
226
|
+
Array4xNx3: TypeAlias = TypedNDArray[tuple[FOUR, N, THREE], DType]
|
|
227
|
+
"""A `numpy.ndarray` of shape `(4, N, 3)` and dtype `DType`."""
|
|
228
|
+
Array4xNx4: TypeAlias = TypedNDArray[tuple[FOUR, N, FOUR], DType]
|
|
229
|
+
"""A `numpy.ndarray` of shape `(4, N, 4)` and dtype `DType`."""
|
|
230
|
+
|
|
231
|
+
ArrayNx2x2: TypeAlias = TypedNDArray[tuple[N, TWO, TWO], DType]
|
|
232
|
+
"""A `numpy.ndarray` of shape `(N, 2, 2)` and dtype `DType`."""
|
|
233
|
+
ArrayNx2x3: TypeAlias = TypedNDArray[tuple[N, TWO, THREE], DType]
|
|
234
|
+
"""A `numpy.ndarray` of shape `(N, 2, 3)` and dtype `DType`."""
|
|
235
|
+
ArrayNx2x4: TypeAlias = TypedNDArray[tuple[N, TWO, FOUR], DType]
|
|
236
|
+
"""A `numpy.ndarray` of shape `(N, 2, 4)` and dtype `DType`."""
|
|
237
|
+
ArrayNx3x2: TypeAlias = TypedNDArray[tuple[N, THREE, TWO], DType]
|
|
238
|
+
"""A `numpy.ndarray` of shape `(N, 3, 2)` and dtype `DType`."""
|
|
239
|
+
ArrayNx3x3: TypeAlias = TypedNDArray[tuple[N, THREE, THREE], DType]
|
|
240
|
+
"""A `numpy.ndarray` of shape `(N, 3, 3)` and dtype `DType`."""
|
|
241
|
+
ArrayNx3x4: TypeAlias = TypedNDArray[tuple[N, THREE, FOUR], DType]
|
|
242
|
+
"""A `numpy.ndarray` of shape `(N, 3, 4)` and dtype `DType`."""
|
|
243
|
+
ArrayNx4x2: TypeAlias = TypedNDArray[tuple[N, FOUR, TWO], DType]
|
|
244
|
+
"""A `numpy.ndarray` of shape `(N, 4, 2)` and dtype `DType`."""
|
|
245
|
+
ArrayNx4x3: TypeAlias = TypedNDArray[tuple[N, FOUR, THREE], DType]
|
|
246
|
+
"""A `numpy.ndarray` of shape `(N, 4, 3)` and dtype `DType`."""
|
|
247
|
+
ArrayNx4x4: TypeAlias = TypedNDArray[tuple[N, FOUR, FOUR], DType]
|
|
248
|
+
"""A `numpy.ndarray` of shape `(N, 4, 4)` and dtype `DType`."""
|
typingkit/_typed/list.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TypedList
|
|
3
|
+
=======
|
|
4
|
+
"""
|
|
5
|
+
# src/typingkit/_typed/list.py
|
|
6
|
+
|
|
7
|
+
import copy
|
|
8
|
+
import numbers
|
|
9
|
+
from collections.abc import Iterable, Sequence
|
|
10
|
+
from types import GenericAlias, NoneType, UnionType
|
|
11
|
+
from typing import Any, Callable, Literal, Self, TypeVar, cast, get_args, get_origin
|
|
12
|
+
|
|
13
|
+
from typingkit._typed.generics import RuntimeGeneric
|
|
14
|
+
|
|
15
|
+
## Typings
|
|
16
|
+
|
|
17
|
+
Length = TypeVar("Length", bound=int, default=int)
|
|
18
|
+
Item = TypeVar("Item", default=Any)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Exceptions
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LengthError(Exception):
|
|
25
|
+
"""Raised when list length doesn't match expected length."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ItemError(Exception):
|
|
29
|
+
"""Raised when list item type doesn't match expected item type."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## Runtime validation
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TypedListConfig:
|
|
36
|
+
VALIDATE_LENGTH: bool = True
|
|
37
|
+
VALIDATE_ITEM: bool = True
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def enable_all(cls):
|
|
41
|
+
cls.VALIDATE_LENGTH = True
|
|
42
|
+
cls.VALIDATE_ITEM = True
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def disable_all(cls):
|
|
46
|
+
cls.VALIDATE_LENGTH = False
|
|
47
|
+
cls.VALIDATE_ITEM = False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _is_assignable(value: Any, expected_type: type) -> bool:
|
|
51
|
+
if expected_type is Any:
|
|
52
|
+
return True
|
|
53
|
+
if expected_type is complex:
|
|
54
|
+
return isinstance(value, numbers.Complex)
|
|
55
|
+
if expected_type is float:
|
|
56
|
+
return isinstance(value, numbers.Real)
|
|
57
|
+
if expected_type is int:
|
|
58
|
+
return isinstance(value, numbers.Integral)
|
|
59
|
+
try:
|
|
60
|
+
origin = get_origin(expected_type)
|
|
61
|
+
if origin is not None:
|
|
62
|
+
return isinstance(value, origin)
|
|
63
|
+
return isinstance(value, expected_type)
|
|
64
|
+
except Exception:
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _resolve_length(length: Any) -> Any:
|
|
69
|
+
# ~TypeVar
|
|
70
|
+
if isinstance(length, TypeVar):
|
|
71
|
+
# [TODO]: How should typing.NoDefault be handled?
|
|
72
|
+
length = _resolve_length(length.__default__)
|
|
73
|
+
|
|
74
|
+
origin = get_origin(length)
|
|
75
|
+
|
|
76
|
+
# Literal[A, B, ...]
|
|
77
|
+
if origin is Literal:
|
|
78
|
+
length = set(get_args(length))
|
|
79
|
+
|
|
80
|
+
# Union[A, B, ...]
|
|
81
|
+
if origin is UnionType:
|
|
82
|
+
resolved = (_resolve_length(arg) for arg in get_args(length))
|
|
83
|
+
result = set[Any]()
|
|
84
|
+
for r in resolved:
|
|
85
|
+
if isinstance(r, set):
|
|
86
|
+
result |= r
|
|
87
|
+
else:
|
|
88
|
+
result.add(r)
|
|
89
|
+
return result
|
|
90
|
+
|
|
91
|
+
return length
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _validate_length(object: Sequence[Item], length: Any) -> None:
|
|
95
|
+
if not TypedListConfig.VALIDATE_LENGTH:
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
length = _resolve_length(length)
|
|
99
|
+
|
|
100
|
+
# type[Any]
|
|
101
|
+
if length is Any:
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
# type[int]; Including <subclass of int>
|
|
105
|
+
if isinstance(length, type) and issubclass(length, int):
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
# Should already be disallowed statically, here we just raise a runtime error
|
|
109
|
+
if length is NoneType:
|
|
110
|
+
raise LengthError("Invalid length")
|
|
111
|
+
# [TODO]: Others
|
|
112
|
+
|
|
113
|
+
# [NOTE]: From a statical perspective, being strict,
|
|
114
|
+
# prefer Literal[~int] over ~int, although we allow it here, for now.
|
|
115
|
+
|
|
116
|
+
actual = len(object)
|
|
117
|
+
if isinstance(length, set):
|
|
118
|
+
if len(length) > 1: # pyright: ignore[reportUnknownArgumentType]
|
|
119
|
+
for arg in length: # pyright: ignore[reportUnknownVariableType]
|
|
120
|
+
if isinstance(arg, int):
|
|
121
|
+
if arg == actual:
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
## [TODO]: Similar to the outer validation, so prolly can refactor through a recursive function call
|
|
125
|
+
# type[Any]
|
|
126
|
+
if arg is Any:
|
|
127
|
+
break
|
|
128
|
+
|
|
129
|
+
# type[int]; Including <subclass of int>
|
|
130
|
+
if isinstance(arg, type) and issubclass(arg, int):
|
|
131
|
+
break
|
|
132
|
+
else:
|
|
133
|
+
raise LengthError(
|
|
134
|
+
f"Length mismatch: expected one of {length}, got {actual}"
|
|
135
|
+
)
|
|
136
|
+
elif len(length) == 1: # pyright: ignore[reportUnknownArgumentType]
|
|
137
|
+
# Defer to the single case below
|
|
138
|
+
length = length.pop() # pyright: ignore[reportUnknownVariableType]
|
|
139
|
+
else: # len(length) == 0
|
|
140
|
+
# This case should prolly never arise? Strictly speaking, statically.
|
|
141
|
+
return None
|
|
142
|
+
# (Concrete) int
|
|
143
|
+
if isinstance(length, int):
|
|
144
|
+
# This case is just for a minimal error message, we could already handle
|
|
145
|
+
# it in the `set` case above, provided `_resolve_length` resolves it accordingly.
|
|
146
|
+
if actual != length:
|
|
147
|
+
raise LengthError(f"Length mismatch: expected {length}, got {actual}")
|
|
148
|
+
|
|
149
|
+
return None # Fallback
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _validate_item(object: Iterable[Item], item_type: Any) -> None:
|
|
153
|
+
if not TypedListConfig.VALIDATE_ITEM:
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
for index, item in enumerate(object):
|
|
157
|
+
if not _is_assignable(item, item_type):
|
|
158
|
+
raise ItemError(
|
|
159
|
+
f"Item type mismatch: expected {item_type.__name__}, got {type(item).__name__} at index {index}"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
## TypedList
|
|
164
|
+
class TypedList(RuntimeGeneric[Length, Item], list[Item]):
|
|
165
|
+
@classmethod
|
|
166
|
+
def __pre_new__(cls, alias: GenericAlias, *args: Any, **kwargs: Any) -> Self:
|
|
167
|
+
# Create `list` object
|
|
168
|
+
obj = super().__pre_new__(alias, *args, **kwargs)
|
|
169
|
+
|
|
170
|
+
## Runtime validations
|
|
171
|
+
typeargs = get_args(alias)
|
|
172
|
+
if len(typeargs) == 2:
|
|
173
|
+
length, item_type = typeargs
|
|
174
|
+
elif len(typeargs) == 1:
|
|
175
|
+
(length,) = typeargs
|
|
176
|
+
item_type = Item.__default__ # type: ignore[misc]
|
|
177
|
+
# The `item_type` default here should match the default in `Item`.
|
|
178
|
+
else:
|
|
179
|
+
raise TypeError
|
|
180
|
+
_validate_length(obj, length)
|
|
181
|
+
_validate_item(obj, item_type)
|
|
182
|
+
|
|
183
|
+
return obj
|
|
184
|
+
|
|
185
|
+
def __len__(self) -> Length:
|
|
186
|
+
return cast(Length, super().__len__())
|
|
187
|
+
|
|
188
|
+
def copy(self) -> Self:
|
|
189
|
+
return type(self)(super().copy())
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def length(self) -> Length:
|
|
193
|
+
return self.__len__()
|
|
194
|
+
|
|
195
|
+
@classmethod
|
|
196
|
+
def full(
|
|
197
|
+
cls: "type[TypedList[Length, Item]]",
|
|
198
|
+
length: Length,
|
|
199
|
+
fill_value: Item | Callable[[int], Item],
|
|
200
|
+
) -> "TypedList[Length, Item]":
|
|
201
|
+
data: list[Item]
|
|
202
|
+
if callable(fill_value):
|
|
203
|
+
data = [cast(Item, fill_value(i)) for i in range(length)]
|
|
204
|
+
else:
|
|
205
|
+
data = [copy.deepcopy(fill_value) for _ in range(length)]
|
|
206
|
+
return cls(data)
|