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.
@@ -0,0 +1,513 @@
1
+ """
2
+ NDArray
3
+ =======
4
+ """
5
+ # src/typingkit/_typed/ndarray.py
6
+
7
+ # pyright: reportPrivateUsage = false
8
+
9
+ import builtins
10
+ from types import GenericAlias, UnionType
11
+ from typing import (
12
+ Any,
13
+ Iterator,
14
+ Literal,
15
+ NoReturn,
16
+ Protocol,
17
+ Self,
18
+ TypeAlias,
19
+ TypeVar,
20
+ TypeVarTuple,
21
+ cast,
22
+ get_args,
23
+ get_origin,
24
+ overload,
25
+ )
26
+ from typing import Literal as L
27
+
28
+ import numpy as np
29
+ import numpy._typing as npt_
30
+ import numpy.typing as npt
31
+
32
+ ## Typings
33
+
34
+ _ShapeRest = TypeVarTuple("_ShapeRest")
35
+
36
+ # `numpy` privates
37
+ _Shape: TypeAlias = tuple[int, ...]
38
+ _AnyShape: TypeAlias = tuple[Any, ...]
39
+
40
+ _ShapeT_co = TypeVar("_ShapeT_co", bound=_Shape, default=_AnyShape, covariant=True)
41
+ _DTypeT_co = TypeVar("_DTypeT_co", bound=np.dtype, default=np.dtype, covariant=True)
42
+
43
+ _ScalarT_co = TypeVar("_ScalarT_co", bound=np.generic, default=Any, covariant=True)
44
+ _NonObjectScalarT = TypeVar(
45
+ "_NonObjectScalarT",
46
+ bound=np.bool | np.number | np.flexible | np.datetime64 | np.timedelta64,
47
+ )
48
+
49
+ _ArrayT_co = TypeVar("_ArrayT_co", bound=np.ndarray, covariant=True)
50
+
51
+
52
+ class _SupportsArray(Protocol[_ArrayT_co]):
53
+ def __array__(self, /) -> _ArrayT_co: ...
54
+
55
+
56
+ ## Exceptions
57
+
58
+
59
+ class ShapeError(Exception):
60
+ """Raised when array shape doesn't match expected shape."""
61
+
62
+
63
+ class RankError(ShapeError):
64
+ """Raised when array rank doesn't match expected dimensions."""
65
+
66
+
67
+ class DimensionError(ShapeError):
68
+ """Raised when some dimension doesn't match expected."""
69
+
70
+
71
+ class DTypeError(Exception):
72
+ """Raised when array dtype doesn't match expected dtype."""
73
+
74
+
75
+ ## Runtime validation
76
+
77
+
78
+ def _validate_dtype(
79
+ expected: GenericAlias | TypeVar | type[np.dtype], actual: np.dtype
80
+ ) -> None:
81
+ """
82
+ Validate dtype at runtime.
83
+
84
+ ### References:
85
+ - https://numpy.org/doc/stable/reference/arrays.dtypes.html#checking-the-data-type
86
+ """
87
+
88
+ # ~TypeVar
89
+ if isinstance(expected, TypeVar):
90
+ # [TODO] Verify bounds, contraints, default
91
+ return None
92
+
93
+ # np.dtype[...]
94
+ if isinstance(expected, GenericAlias):
95
+ if get_origin(expected) is np.dtype:
96
+ if not (args := get_args(expected)):
97
+ return None
98
+ assert len(args) == 1
99
+ exp = args[0]
100
+
101
+ # np.dtype[Any]
102
+ if exp is Any:
103
+ return None
104
+
105
+ # np.dtype[~TypeVar]
106
+ elif isinstance(exp, TypeVar):
107
+ # [TODO] Verify bounds, contraints, default
108
+ return None
109
+
110
+ # np.dtype[A | B | ...]
111
+ elif get_origin(exp) is UnionType:
112
+ args = get_args(exp)
113
+ for arg in args:
114
+ if actual == arg:
115
+ break
116
+ if isinstance(arg, TypeVar):
117
+ return None
118
+ else:
119
+ raise DTypeError(f"expected {exp}, got {actual}")
120
+
121
+ # np.dtype[<subclass of np.generic>]
122
+ else:
123
+ if actual != exp:
124
+ raise DTypeError(f"expected {exp.__name__}, got {actual}")
125
+ else:
126
+ # [TODO]: Handle typing.Annotated
127
+ raise TypeError(f"Invalid dtype specification. {expected} is not a dtype")
128
+
129
+ # <class np.dtype>
130
+ if expected is np.dtype:
131
+ return None
132
+
133
+ return None # Fallback
134
+
135
+
136
+ def _resolve_shape(args: _AnyShape) -> _AnyShape:
137
+ from typingkit._typed.dimexpr import _resolve_dim
138
+
139
+ # [TODO]: Handle TypeAliasType
140
+ return tuple(_resolve_dim(arg) for arg in args)
141
+
142
+
143
+ def _validate_shape(expected: _AnyShape, actual: _Shape) -> None:
144
+ """Validate shapes at runtime."""
145
+
146
+ # tuple[T, ...] (variadic shape)
147
+ is_variadic = len(expected) == 2 and expected[1] is Ellipsis
148
+
149
+ ## Rank enforcement
150
+ # In the variadic case, rank is not enforced.
151
+ if not is_variadic:
152
+ if len(expected) != len(actual):
153
+ raise RankError(f"Expected {len(expected)} dimensions, got {len(actual)}")
154
+
155
+ ## Shape enforcement
156
+
157
+ if is_variadic:
158
+ dim_specs = tuple([expected[0]] * len(actual))
159
+ else:
160
+ dim_specs = expected
161
+
162
+ bindings = dict[TypeVar, tuple[int, int]]()
163
+ # store: TypeVar -> (first_index, first_value)
164
+
165
+ for idx, (exp, act) in enumerate(zip(dim_specs, actual)):
166
+ origin = get_origin(exp)
167
+
168
+ # Literal
169
+ if origin is Literal:
170
+ args = get_args(exp)
171
+ if act not in args:
172
+ expected_str = f"{args[0]}" if len(args) == 1 else f"one of {set(args)}"
173
+ raise DimensionError(
174
+ f"Dimension {idx}: expected {expected_str}, got {act}"
175
+ )
176
+
177
+ # TypeVar
178
+ elif isinstance(exp, TypeVar):
179
+ if exp in bindings:
180
+ prev_index, prev_value = bindings[exp]
181
+ if prev_value != act:
182
+ raise ShapeError(
183
+ f"Inconsistent dimensions.\n"
184
+ f"Found Dimension {prev_index} to be {prev_value} and Dimension {idx} to be {act}"
185
+ f" but both were constrained to the same TypeVar {exp}."
186
+ )
187
+ else:
188
+ bindings[exp] = (idx, act)
189
+
190
+ # (Concrete) int
191
+ elif isinstance(exp, int):
192
+ if exp != act:
193
+ raise ShapeError(f"Shape mismatch: expected {expected}, got {actual}")
194
+
195
+ # Relaxed dimension
196
+ elif exp is int or exp is Any:
197
+ continue
198
+
199
+
200
+ def _validate_shape_against_contexts(shape_spec: _AnyShape, actual: _Shape) -> None:
201
+ """Validate shape against active TypeVar contexts (class-level and method-level)."""
202
+ from typingkit._typed.context import (
203
+ _active_class_context,
204
+ _method_typevar_context,
205
+ )
206
+
207
+ method_context = _method_typevar_context.get()
208
+ class_context = _active_class_context.get()
209
+ typevar_bindings = dict[TypeVar, int]()
210
+ for idx, dim in enumerate(shape_spec):
211
+ if not isinstance(dim, TypeVar):
212
+ continue
213
+ if idx >= len(actual):
214
+ continue
215
+ actual_dim = actual[idx]
216
+ # Check method_context first before class_context
217
+ expected_dim = method_context.get(dim) or class_context.get(dim)
218
+ if expected_dim is not None and actual_dim != expected_dim:
219
+ raise ShapeError(
220
+ f"TypeVar {dim} mismatch at dimension {idx}: "
221
+ f"expected {expected_dim}, got {actual_dim}"
222
+ )
223
+ # Consistency check
224
+ if dim in typevar_bindings:
225
+ if actual_dim != typevar_bindings[dim]:
226
+ raise ShapeError(
227
+ f"TypeVar {dim} inconsistent: dimension {idx} is {actual_dim}, "
228
+ f"but previous occurrence required {typevar_bindings[dim]}"
229
+ )
230
+ else:
231
+ typevar_bindings[dim] = actual_dim
232
+
233
+
234
+ ## Typed NDArray
235
+ class TypedNDArray(np.ndarray[_ShapeT_co, _DTypeT_co]):
236
+ """Generic `numpy.ndarray` subclass with static shape typing and runtime shape validation."""
237
+
238
+ @classmethod
239
+ def __class_getitem__(cls, item: Any, /) -> GenericAlias:
240
+ # [HACK] Misuses __class_getitem__
241
+ # See https://docs.python.org/3/reference/datamodel.html#the-purpose-of-class-getitem
242
+
243
+ # This method is called when using `TypedNDArray` with generics as in `TypedNDArray[...]`.
244
+ # The arguments can be just a Shape GenericAlias such as `tuple[...]`,
245
+ # which is allowed since `_DTypeT_co` TypeVar defines a default.
246
+ # OR it is a Shape GenericAlias and a DType GenericAlias as `tuple[...], np.dtype[...]`
247
+ # which is passed in to `__class_getitem__` as a `tuple[GenericAlias, GenericAlias]`.
248
+ # Any other case would result in a static error already.
249
+
250
+ # We defer the arguments to `_TypedNDArrayGenericAlias` which is a subclass of `GenericAlias`. It has two roles:
251
+ # 1. Support handling further partial binding just as the type system expects.
252
+ # This is done through `_TypedNDArrayGenericAlias.__getitem__` which just ensures that
253
+ # `_TypedNDArrayGenericAlias` wraps the `GenericAlias` when partial binding.
254
+ # 2. Transfer the control back to `TypedNDArray` when its `_TypedNDArrayGenericAlias.__call__` method is called,
255
+ # which should then invoke `TypedNDArray.__new__`.
256
+ # Additionally we can use the bindings here to perform runtime validation.
257
+
258
+ ga = super().__class_getitem__(item)
259
+ return _TypedNDArrayGenericAlias.from_generic_alias(ga)
260
+
261
+ # [FIXME]: Can we skip this method? It just uses
262
+ # `np.asarray(object, dtype=dtype).view(cls)`
263
+ # all of which are regular `numpy`.
264
+ @overload
265
+ def __new__( # type: ignore[misc]
266
+ cls,
267
+ object: np._ArrayT,
268
+ dtype: None = None,
269
+ *,
270
+ copy: bool | np._CopyMode | None = True,
271
+ order: np._OrderKACF = "K",
272
+ subok: L[True],
273
+ ndmin: int = 0,
274
+ like: npt_._SupportsArrayFunc | None = None,
275
+ ) -> np._ArrayT: ...
276
+ @overload
277
+ def __new__( # type: ignore[misc]
278
+ cls,
279
+ object: _SupportsArray[np._ArrayT],
280
+ dtype: None = None,
281
+ *,
282
+ copy: bool | np._CopyMode | None = True,
283
+ order: np._OrderKACF = "K",
284
+ subok: L[True],
285
+ ndmin: L[0] = 0,
286
+ like: npt_._SupportsArrayFunc | None = None,
287
+ ) -> np._ArrayT: ...
288
+ @overload
289
+ def __new__(
290
+ cls,
291
+ object: npt_._ArrayLike[np._ScalarT],
292
+ dtype: None = None,
293
+ *,
294
+ copy: bool | np._CopyMode | None = True,
295
+ order: np._OrderKACF = "K",
296
+ subok: bool = False,
297
+ ndmin: int = 0,
298
+ like: npt_._SupportsArrayFunc | None = None,
299
+ ) -> "TypedNDArray[_ShapeT_co, np.dtype[np._ScalarT]]": ...
300
+ @overload
301
+ # NOTE: This is prolly the best we can do without HKTs
302
+ def __new__(
303
+ cls,
304
+ object: Any,
305
+ dtype: npt_._DTypeLike[np._ScalarT],
306
+ *,
307
+ copy: bool | np._CopyMode | None = True,
308
+ order: np._OrderKACF = "K",
309
+ subok: bool = False,
310
+ ndmin: int = 0,
311
+ like: npt_._SupportsArrayFunc | None = None,
312
+ ) -> "TypedNDArray[_ShapeT_co, np.dtype[np._ScalarT]]": ...
313
+ @overload
314
+ def __new__(
315
+ cls,
316
+ object: Any,
317
+ dtype: npt.DTypeLike | None = None,
318
+ *,
319
+ copy: bool | np._CopyMode | None = True,
320
+ order: np._OrderKACF = "K",
321
+ subok: bool = False,
322
+ ndmin: int = 0,
323
+ like: npt_._SupportsArrayFunc | None = None,
324
+ ) -> "TypedNDArray[_ShapeT_co, np.dtype[Any]]": ...
325
+ #
326
+ def __new__( # type: ignore[misc]
327
+ cls,
328
+ object: npt.ArrayLike,
329
+ dtype: npt.DTypeLike | None = None,
330
+ # NOTE: This is prolly the best we can do without HKTs
331
+ *,
332
+ copy: bool | np._CopyMode | None = True,
333
+ order: np._OrderKACF = "K",
334
+ subok: bool = False,
335
+ ndmin: int = 0,
336
+ like: npt_._SupportsArrayFunc | None = None,
337
+ ) -> Self:
338
+ # Overrides base; This doesn't follow `numpy.ndarray.__new__`,
339
+ # but rather tries to mimick `numpy.array(...)`;
340
+
341
+ arr = np.array(
342
+ object, dtype, copy=copy, order=order, subok=subok, ndmin=ndmin, like=like
343
+ )
344
+ # The regular `numpy.ndarray` machinery is put to use here,
345
+ # basically making `TypedNDArray` just a type wrapper around it, just as intended.
346
+
347
+ # The `.view(...)` method should be used when subclassing `numpy.ndarray`.
348
+ obj = arr.view(cls)
349
+ obj = cast(Self, obj) # pyright: ignore[reportUnnecessaryCast] # pyrefly: ignore [redundant-cast]
350
+ return obj
351
+
352
+ def __array_finalize__(self, obj: npt.NDArray[Any] | None, /) -> None:
353
+ if obj is None:
354
+ return
355
+
356
+ def __repr__(self) -> str:
357
+ return str(np.asarray(self).__repr__())
358
+
359
+ @overload # == 0D --> Not iterable
360
+ def __iter__(self: "TypedNDArray[tuple[()], _DTypeT_co]", /) -> NoReturn: ...
361
+ @overload # == 1-d & dtype[T \ object_]
362
+ def __iter__(
363
+ self: "TypedNDArray[tuple[int], np.dtype[_NonObjectScalarT]]", /
364
+ ) -> Iterator[_NonObjectScalarT]: ...
365
+ @overload # == 1-d & StringDType
366
+ def __iter__(
367
+ self: "TypedNDArray[tuple[int], np.dtypes.StringDType]", /
368
+ ) -> Iterator[str]: ...
369
+ @overload # >= 1D
370
+ # Currently, TypeVarTuple doesn't support bounds/constraints,
371
+ # which we'd want to bound/constrain to `_ShapeT_co` := `tuple[int, ...]`.
372
+ # We can relax `_ShapeT_co` to `tuple[Any, ...]` which would make the following overload type fine,
373
+ # but would relax bounds for each dimension, which is a trade off.
374
+ # So we just allow the following ~unsafely typed overload, which is just complained here
375
+ # and shouldn't affect when using `__iter__` in downstream code, hopefully.
376
+ def __iter__(
377
+ self: "TypedNDArray[tuple[int, *_ShapeRest], _DTypeT_co]", # type: ignore[type-var] # ty: ignore[unused-ignore-comment]
378
+ /,
379
+ ) -> Iterator["TypedNDArray[tuple[*_ShapeRest], _DTypeT_co]"]: ... # type: ignore[type-var] # ty: ignore[unused-ignore-comment]
380
+ @overload # ?-d
381
+ # Not required, but can keep as a fallback
382
+ def __iter__(self, /) -> Iterator[Any]: ... # pyright: ignore[reportOverlappingOverload]
383
+ #
384
+ def __iter__(self, /) -> Iterator[Any]: # pyright: ignore[reportIncompatibleMethodOverride]
385
+ return super().__iter__()
386
+
387
+ @overload
388
+ def astype(
389
+ self,
390
+ dtype: npt_._DTypeLike[np._ScalarT],
391
+ order: np._OrderKACF = ...,
392
+ casting: np._CastingKind = ...,
393
+ subok: builtins.bool = ...,
394
+ copy: builtins.bool | np._CopyMode = ...,
395
+ ) -> "TypedNDArray[_ShapeT_co, np.dtype[np._ScalarT]]": ...
396
+ @overload
397
+ def astype(
398
+ self,
399
+ dtype: npt_.DTypeLike | None,
400
+ order: np._OrderKACF = ...,
401
+ casting: np._CastingKind = ...,
402
+ subok: builtins.bool = ...,
403
+ copy: builtins.bool | np._CopyMode = ...,
404
+ ) -> "TypedNDArray[_ShapeT_co, np.dtype]": ...
405
+ #
406
+ def astype(
407
+ self,
408
+ dtype: npt_.DTypeLike | None,
409
+ order: np._OrderKACF = "K",
410
+ casting: np._CastingKind = "unsafe",
411
+ subok: builtins.bool = True,
412
+ copy: builtins.bool | np._CopyMode = True,
413
+ ):
414
+ return super().astype(
415
+ dtype=dtype, order=order, casting=casting, subok=subok, copy=copy
416
+ )
417
+
418
+ def flatten(
419
+ self, /, order: np._OrderKACF = "C"
420
+ ) -> "TypedNDArray[tuple[int], _DTypeT_co]":
421
+ return super().flatten(order=order) # type: ignore[return-value]
422
+
423
+ @overload # type: ignore
424
+ def __getitem__(
425
+ self: "TypedNDArray[tuple[int], np.dtype[_ScalarT_co]]", key: int
426
+ ) -> _ScalarT_co: ...
427
+ @overload
428
+ def __getitem__(
429
+ self: "TypedNDArray[tuple[int, *_ShapeRest], _DTypeT_co]", # type: ignore[type-var] # ty: ignore[unused-ignore-comment]
430
+ key: int,
431
+ ) -> "TypedNDArray[tuple[*_ShapeRest], _DTypeT_co]": ... # type: ignore[type-var] # ty: ignore[unused-ignore-comment]
432
+ @overload
433
+ def __getitem__(
434
+ self: "TypedNDArray[tuple[int, *_ShapeRest], _DTypeT_co]", # type: ignore[type-var] # ty: ignore[unused-ignore-comment]
435
+ key: slice,
436
+ ) -> "TypedNDArray[tuple[int, *_ShapeRest], _DTypeT_co]": ... # type: ignore[type-var] # ty: ignore[unused-ignore-comment]
437
+ @overload
438
+ def __getitem__(
439
+ self: "TypedNDArray[tuple[int, *_ShapeRest], np.dtype[_ScalarT_co]]", # type: ignore[type-var] # ty: ignore[unused-ignore-comment]
440
+ key: tuple[int, *_ShapeRest],
441
+ ) -> _ScalarT_co: ...
442
+ @overload
443
+ def __getitem__(
444
+ self: "TypedNDArray[tuple[int, int, *_ShapeRest], _DTypeT_co]", # type: ignore[type-var] # ty: ignore[unused-ignore-comment]
445
+ key: tuple[int, slice],
446
+ ) -> "TypedNDArray[tuple[int, *_ShapeRest], _DTypeT_co]": ... # type: ignore[type-var] # ty: ignore[unused-ignore-comment]
447
+ @overload
448
+ def __getitem__(
449
+ self: "TypedNDArray[tuple[int, int, int, *_ShapeRest], _DTypeT_co]", # type: ignore[type-var] # ty: ignore[unused-ignore-comment]
450
+ key: tuple[int, int],
451
+ ) -> "TypedNDArray[tuple[int, *_ShapeRest], _DTypeT_co]": ... # type: ignore[type-var] # ty: ignore[unused-ignore-comment]
452
+ @overload
453
+ def __getitem__(self, key: Any) -> Any: ...
454
+ #
455
+ def __getitem__(self, key: Any) -> Any: # pyright: ignore[reportIncompatibleMethodOverride]
456
+ return super().__getitem__(key) # pyright: ignore[reportUnknownVariableType]
457
+
458
+
459
+ ## Deferred shape binding
460
+
461
+
462
+ class _TypedNDArrayGenericAlias(GenericAlias):
463
+ """
464
+ Deferred TypedNDArray constructor for shapes with TypeVars.
465
+ Enables progressive type specialization, behaving like a type-level curry.
466
+ """
467
+
468
+ @classmethod
469
+ def from_generic_alias(cls, alias: GenericAlias) -> Self:
470
+ return cls(alias.__origin__, alias.__args__) # pyright: ignore[reportArgumentType]
471
+
472
+ def __getitem__(self, typeargs: Any) -> Self:
473
+ ga = super().__getitem__(typeargs)
474
+ return type(self).from_generic_alias(ga)
475
+
476
+ def __call__(
477
+ self,
478
+ object: npt.ArrayLike,
479
+ dtype: npt.DTypeLike | None = None,
480
+ *,
481
+ copy: bool | np._CopyMode | None = True,
482
+ order: np._OrderKACF = "K",
483
+ subok: bool = False,
484
+ ndmin: int = 0,
485
+ like: npt_._SupportsArrayFunc | None = None,
486
+ ) -> TypedNDArray:
487
+ # [NOTE] Should mimick `TypedNDArray.__new__` signature
488
+
489
+ base = cast(type[TypedNDArray], get_origin(self))
490
+ args = get_args(self)
491
+
492
+ if len(args) == 2:
493
+ shape_spec, dtype_spec = args
494
+ elif len(args) == 1:
495
+ (shape_spec,) = args
496
+ dtype_spec = _DTypeT_co.__default__
497
+ # The `dtype_spec` default here should match the default in `_DTypeT_co`.
498
+ else:
499
+ raise TypeError
500
+
501
+ # Create `numpy.ndarray` object
502
+ arr = base(
503
+ object, dtype, copy=copy, order=order, subok=subok, ndmin=ndmin, like=like
504
+ )
505
+
506
+ # Runtime validations
507
+ shape_args = _resolve_shape(get_args(shape_spec))
508
+ arr_shape, arr_dtype = arr.shape, arr.dtype
509
+ _validate_shape(shape_args, arr_shape)
510
+ _validate_shape_against_contexts(shape_args, arr_shape)
511
+ _validate_dtype(dtype_spec, arr_dtype)
512
+
513
+ return arr.view(TypedNDArray)
typingkit/py.typed ADDED
File without changes
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.3
2
+ Name: typingkit
3
+ Version: 0.2.2
4
+ Summary: Python strong typing suite, along with Typed NumPy: Static shape typing and runtime shape validation.
5
+ Author: Ashrith Sagar
6
+ Author-email: Ashrith Sagar <ashrith9sagar@gmail.com>
7
+ Requires-Dist: numpy>=2.2
8
+ Requires-Python: >=3.13
9
+ Description-Content-Type: text/markdown
10
+
11
+ # typingkit
12
+
13
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
14
+
15
+ Python strong typing suite, along with Typed NumPy: Static shape typing and runtime shape validation.
16
+
17
+ > [!WARNING]
18
+ > Experimental & WIP.
19
+ > See [USAGE.md](USAGE.md) for more details.
20
+
21
+ ## Installation
22
+
23
+ <details>
24
+
25
+ <summary>Install uv (optional, recommended)</summary>
26
+
27
+ Install [`uv`](https://docs.astral.sh/uv/), if not already.
28
+ Check [here](https://docs.astral.sh/uv/getting-started/installation/) for installation instructions.
29
+
30
+ It is recommended to use `uv`, as it will automatically install the dependencies in a virtual environment.
31
+ If you don't want to use `uv`, skip to the next step.
32
+
33
+ **TL;DR: Just run**
34
+
35
+ ```shell
36
+ curl -LsSf https://astral.sh/uv/install.sh | sh
37
+ ```
38
+
39
+ </details>
40
+
41
+ <details>
42
+
43
+ <summary>Install the package</summary>
44
+
45
+ The dependencies are listed in the [pyproject.toml](pyproject.toml) file.
46
+ At present, the only required dependency is `numpy`.
47
+
48
+ Install the package from the PyPI release:
49
+
50
+ ```shell
51
+ # Using uv
52
+ uv add typingkit
53
+
54
+ # Or with pip
55
+ pip3 install typingkit
56
+ ```
57
+
58
+ To install from the latest commit:
59
+
60
+ ```shell
61
+ uv add git+https://github.com/AshrithSagar/typingkit.git@main
62
+ ```
63
+
64
+ </details>
65
+
66
+ ## Usage
67
+
68
+ ```python
69
+ from typing import TypeVar
70
+
71
+ from typingkit._typed.ndarray import TypedNDArray
72
+
73
+ # Shape variables are just regular TypeVar's
74
+ N = TypeVar("N", bound=int, default=int)
75
+ M = TypeVar("M", bound=int, default=int)
76
+
77
+ # Create aliases such as these, or use TypedNDArray directly
78
+ Vector = TypedNDArray[tuple[N]]
79
+ Matrix = TypedNDArray[tuple[M, N]]
80
+
81
+ v1 = Vector([1, 2, 3]) # Passes
82
+ v2 = Vector([4, 5, 6, 7]) # Also passes
83
+
84
+ v3 = TypedNDArray[tuple[int]]([[8, 9]])
85
+ # Fails, since expected 1D array but passed in a 2D array
86
+ ```
87
+
88
+ See [USAGE.md](USAGE.md) for more details.
89
+
90
+ ## License
91
+
92
+ This project falls under the [MIT License](LICENSE).
@@ -0,0 +1,14 @@
1
+ typingkit/__init__.py,sha256=nKaGmvJYFnD1rpYKWA459zUKzK1zneEHJn_sQp6KGLY,98
2
+ typingkit/_typed/__init__.py,sha256=ZFYm5nYxMFO2nPOM6aA-XRucU5HrbMXHmozFagopz7A,154
3
+ typingkit/_typed/_debug.py,sha256=X8riENmHb_G0QPicX0gaLdG5lxzyMelvdQK7L69jzu4,1154
4
+ typingkit/_typed/context.py,sha256=zl_WBhOvQOB8N-4weLG_EIuVd3srUkdNoJwS_BUjOWI,6334
5
+ typingkit/_typed/dimexpr.py,sha256=HoCRL7HUwMloTnKuWdS2ftwifMyiHv7f5I3Yxqggqac,3827
6
+ typingkit/_typed/factory.py,sha256=rcDQG0ihFpS34XQ6Sp_15ARqaTNYf0Gm7uLi8UKcjRM,2216
7
+ typingkit/_typed/generics.py,sha256=-gMilWx86rP5g_HT76PPaCYMCDLF-M1MAbEWof9w97M,1646
8
+ typingkit/_typed/helpers.py,sha256=mxrx4LasGTZ5aB37khrhMJaHcqFMM_gSRUJsQV4qcSg,12386
9
+ typingkit/_typed/list.py,sha256=q-peH3y72imqBauoab25lxCC6l3W0q8AqwvyUsFh2PM,6174
10
+ typingkit/_typed/ndarray.py,sha256=GX-YufpLmDN3Tb4PWX4Ku997x3Vc5Na780M9SNi7UXQ,18254
11
+ typingkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ typingkit-0.2.2.dist-info/WHEEL,sha256=Uo4e6VmJM8J_cLBTtiWBRVQkc1yUEkw94xaL0B_lH6c,80
13
+ typingkit-0.2.2.dist-info/METADATA,sha256=OCVD8GeirbjUY2Oekqlft9zp9Esi9SEZzHnhpBjbnKU,2258
14
+ typingkit-0.2.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.10.7
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any