typing-arguments 0.1.0__tar.gz
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.
- typing_arguments-0.1.0/.github/dependabot.yml +16 -0
- typing_arguments-0.1.0/.github/workflows/lint.yml +24 -0
- typing_arguments-0.1.0/.github/workflows/release.yml +26 -0
- typing_arguments-0.1.0/.github/workflows/test.yml +26 -0
- typing_arguments-0.1.0/.gitignore +13 -0
- typing_arguments-0.1.0/.pre-commit-config.yaml +26 -0
- typing_arguments-0.1.0/.python-version +1 -0
- typing_arguments-0.1.0/LICENSE +20 -0
- typing_arguments-0.1.0/PKG-INFO +158 -0
- typing_arguments-0.1.0/README.md +127 -0
- typing_arguments-0.1.0/commitlint.config.js +12 -0
- typing_arguments-0.1.0/justfile +46 -0
- typing_arguments-0.1.0/nurfile +50 -0
- typing_arguments-0.1.0/pkg-version.py +24 -0
- typing_arguments-0.1.0/pyproject.toml +33 -0
- typing_arguments-0.1.0/tests/__init__.py +0 -0
- typing_arguments-0.1.0/tests/test_generic_arguments.py +161 -0
- typing_arguments-0.1.0/tox.ini +13 -0
- typing_arguments-0.1.0/typing-arguments.iml +9 -0
- typing_arguments-0.1.0/typing_arguments/__init__.py +4 -0
- typing_arguments-0.1.0/typing_arguments/generic_arguments.py +235 -0
- typing_arguments-0.1.0/typing_arguments/py.typed +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: "github-actions"
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: "weekly"
|
|
7
|
+
assignees:
|
|
8
|
+
- "ddanier"
|
|
9
|
+
- package-ecosystem: "pip"
|
|
10
|
+
directory: "/"
|
|
11
|
+
schedule:
|
|
12
|
+
interval: "weekly"
|
|
13
|
+
ignore:
|
|
14
|
+
- dependency-name: "pydantic"
|
|
15
|
+
assignees:
|
|
16
|
+
- "ddanier"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: "LINT: Run ruff & pyright"
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
pull_request:
|
|
5
|
+
schedule:
|
|
6
|
+
- cron: '0 7 * * 1'
|
|
7
|
+
jobs:
|
|
8
|
+
lint:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- name: Install uv
|
|
13
|
+
uses: astral-sh/setup-uv@v4
|
|
14
|
+
- name: Set up Python
|
|
15
|
+
uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.x"
|
|
18
|
+
- name: Install dependencies
|
|
19
|
+
run: |
|
|
20
|
+
uv sync --group dev
|
|
21
|
+
- name: Lint with ruff & pyright
|
|
22
|
+
run: |
|
|
23
|
+
uv run ruff check typing_arguments tests
|
|
24
|
+
uv run pyright typing_arguments
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: "RELEASE: Upload Python Package to PyPI"
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types: [published]
|
|
5
|
+
jobs:
|
|
6
|
+
release:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
permissions:
|
|
9
|
+
id-token: write
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- name: Install uv
|
|
13
|
+
uses: astral-sh/setup-uv@v4
|
|
14
|
+
- name: Set up Python
|
|
15
|
+
uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: '3.x'
|
|
18
|
+
- name: Install dependencies
|
|
19
|
+
run: |
|
|
20
|
+
uv sync
|
|
21
|
+
- name: Build package
|
|
22
|
+
run: uv build
|
|
23
|
+
- name: Publish package
|
|
24
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
25
|
+
with:
|
|
26
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: "TEST: Run pytest using tox"
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
pull_request:
|
|
5
|
+
schedule:
|
|
6
|
+
- cron: '0 7 * * 1'
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
python-version: ["3.9", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- name: Install uv
|
|
16
|
+
uses: astral-sh/setup-uv@v4
|
|
17
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
18
|
+
uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: |
|
|
23
|
+
uv sync --group dev
|
|
24
|
+
- name: Test with pytest
|
|
25
|
+
run: |
|
|
26
|
+
uv run tox -e 'py'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v5.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: end-of-file-fixer
|
|
6
|
+
- id: check-added-large-files
|
|
7
|
+
- id: check-merge-conflict
|
|
8
|
+
- id: check-docstring-first
|
|
9
|
+
- id: debug-statements
|
|
10
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
11
|
+
rev: v0.8.0
|
|
12
|
+
hooks:
|
|
13
|
+
- id: ruff
|
|
14
|
+
args: [--fix, --exit-non-zero-on-fix]
|
|
15
|
+
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
|
|
16
|
+
rev: v9.18.0
|
|
17
|
+
hooks:
|
|
18
|
+
- id: commitlint
|
|
19
|
+
stages: [commit-msg]
|
|
20
|
+
additional_dependencies:
|
|
21
|
+
- "@commitlint/config-conventional"
|
|
22
|
+
default_stages:
|
|
23
|
+
- pre-commit
|
|
24
|
+
default_install_hook_types:
|
|
25
|
+
- pre-commit
|
|
26
|
+
- commit-msg
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.10
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
Copyright (c) 2024 TEAM23 GmbH
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
18
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
19
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
|
20
|
+
OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: typing-arguments
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Store references of your typing arguments to be available at runtime.
|
|
5
|
+
Project-URL: Repository, https://github.com/team23/typing-arguments
|
|
6
|
+
Author-email: TEAM23 GmbH <info@team23.de>
|
|
7
|
+
License: The MIT License (MIT)
|
|
8
|
+
Copyright (c) 2024 TEAM23 GmbH
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
21
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
22
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
23
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
24
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
25
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
|
26
|
+
OR OTHER DEALINGS IN THE SOFTWARE.
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Provides-Extra: pydantic
|
|
29
|
+
Requires-Dist: pydantic<3.0.0,>=2.0.0; extra == 'pydantic'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# `typing-arguments`
|
|
33
|
+
|
|
34
|
+
Typing arguments using the `Generic` base class in python are great, but they lack the ability to
|
|
35
|
+
easily access the type arguments at runtime. This library provides a mixin class that can be used
|
|
36
|
+
to make type arguments available in the class and its instances.
|
|
37
|
+
|
|
38
|
+
This is also true for classes based on `pydantic.BaseModel` for pydantic > 2.x.
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
Just use `pip install typing-arguments` to install the library.
|
|
43
|
+
|
|
44
|
+
**Note:** `typing-arguments` is tested on Python `3.10`, `3.11`, `3.12` and `3.13`. This is also ensured running
|
|
45
|
+
all tests on all those versions using `tox`.
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
### Quick Example
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
T1 = TypeVar("T1")
|
|
53
|
+
T2 = TypeVar("T2", bound="SomeBaseClass")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Something(
|
|
57
|
+
GenericArgumentsMixin,
|
|
58
|
+
Generic[T1, T2],
|
|
59
|
+
):
|
|
60
|
+
t1 = typing_arg(T1)
|
|
61
|
+
t2 = typing_arg(T2)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
ConcreteClass = Something[str, SomeBaseClassChild]
|
|
65
|
+
ConcreteClass.t1 # str
|
|
66
|
+
ConcreteClass.t2 # SomeBaseClassChild
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Hint:** You may also use this with pydantic models:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
T1 = TypeVar("T1")
|
|
73
|
+
T2 = TypeVar("T2", bound="SomeBaseClass")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class SomethingModel(
|
|
77
|
+
GenericArgumentsMixin,
|
|
78
|
+
BaseModel,
|
|
79
|
+
Generic[T1, T2],
|
|
80
|
+
):
|
|
81
|
+
t1: ClassVar = typing_arg(T1)
|
|
82
|
+
t2: ClassVar = typing_arg(T2)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
ConcreteClassModel = SomethingModel[str, SomeBaseClassChild]
|
|
86
|
+
ConcreteClassModel.t1 # str
|
|
87
|
+
ConcreteClassModel.t2 # SomeBaseClassChild
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Internally `GenericArgumentsMixin` will create a new attribute `__typing_arguments__`
|
|
91
|
+
inside the class and its instances. This attribute is a dictionary mapping the type
|
|
92
|
+
variables to their concrete types. This is useful if you want to access the type
|
|
93
|
+
arguments in a generic way.
|
|
94
|
+
|
|
95
|
+
The `typing_arg` function is a helper function to make the type arguments available
|
|
96
|
+
in the class and its instances using a nicely named attribute. This is just a
|
|
97
|
+
convenience function, as you can also access the type arguments directly from the
|
|
98
|
+
`__typing_arguments__` attribute.
|
|
99
|
+
|
|
100
|
+
**Note:** If you are using pydantic models you should use the `ClassVar` annotation
|
|
101
|
+
to ensure pydantic will not try to catch and validate the type arguments as normal
|
|
102
|
+
model fields.
|
|
103
|
+
|
|
104
|
+
You may also mix different generic base classes like so:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
T1 = TypeVar("T1")
|
|
108
|
+
T2 = TypeVar("T2", bound="SomeBaseClass")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class Base1(
|
|
112
|
+
GenericArgumentsMixin,
|
|
113
|
+
Generic[T1],
|
|
114
|
+
):
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class Base2(
|
|
119
|
+
GenericArgumentsMixin,
|
|
120
|
+
Generic[T2],
|
|
121
|
+
):
|
|
122
|
+
t2 = typing_arg(T2)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class Something(
|
|
126
|
+
Base1[str],
|
|
127
|
+
Base2[SomeBaseClassChild],
|
|
128
|
+
):
|
|
129
|
+
t1 = typing_arg(T1)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
Something.t1 # str
|
|
133
|
+
Something.t2 # SomeBaseClassChild
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
In this example `Base1` and `Base2` are both generic base classes. `Base1` only
|
|
137
|
+
defines a type argument `T1` and `Base2` only defines a type argument `T2`. The
|
|
138
|
+
`Something` class inherits from both `Base1` and `Base2`. Note that `Base1` does
|
|
139
|
+
not define a simple accessor like `t1` using `typing_arg`, while `Base2` does. This
|
|
140
|
+
is not a problem and can be later added by `Something` using `typing_arg` as well.
|
|
141
|
+
|
|
142
|
+
You may encounter issues using the `typing_arg` function when using type validator
|
|
143
|
+
like mypy or your IDE. If so you might need to use `cast` to tell the type checker
|
|
144
|
+
you are sure about what you are doing. For example:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
T1 = TypeVar("T1", bound="SomeBaseClass")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class Something(
|
|
151
|
+
GenericArgumentsMixin,
|
|
152
|
+
Generic[T1],
|
|
153
|
+
):
|
|
154
|
+
t1 = cast(type[SomeBaseClass], typing_arg(T1))
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Note:** You will still need to use `ClassVar` when using pydantic models. This
|
|
158
|
+
might result in using the same type twice (inside `ClassVar` and `cast`).
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# `typing-arguments`
|
|
2
|
+
|
|
3
|
+
Typing arguments using the `Generic` base class in python are great, but they lack the ability to
|
|
4
|
+
easily access the type arguments at runtime. This library provides a mixin class that can be used
|
|
5
|
+
to make type arguments available in the class and its instances.
|
|
6
|
+
|
|
7
|
+
This is also true for classes based on `pydantic.BaseModel` for pydantic > 2.x.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Just use `pip install typing-arguments` to install the library.
|
|
12
|
+
|
|
13
|
+
**Note:** `typing-arguments` is tested on Python `3.10`, `3.11`, `3.12` and `3.13`. This is also ensured running
|
|
14
|
+
all tests on all those versions using `tox`.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
### Quick Example
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
T1 = TypeVar("T1")
|
|
22
|
+
T2 = TypeVar("T2", bound="SomeBaseClass")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Something(
|
|
26
|
+
GenericArgumentsMixin,
|
|
27
|
+
Generic[T1, T2],
|
|
28
|
+
):
|
|
29
|
+
t1 = typing_arg(T1)
|
|
30
|
+
t2 = typing_arg(T2)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
ConcreteClass = Something[str, SomeBaseClassChild]
|
|
34
|
+
ConcreteClass.t1 # str
|
|
35
|
+
ConcreteClass.t2 # SomeBaseClassChild
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Hint:** You may also use this with pydantic models:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
T1 = TypeVar("T1")
|
|
42
|
+
T2 = TypeVar("T2", bound="SomeBaseClass")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SomethingModel(
|
|
46
|
+
GenericArgumentsMixin,
|
|
47
|
+
BaseModel,
|
|
48
|
+
Generic[T1, T2],
|
|
49
|
+
):
|
|
50
|
+
t1: ClassVar = typing_arg(T1)
|
|
51
|
+
t2: ClassVar = typing_arg(T2)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
ConcreteClassModel = SomethingModel[str, SomeBaseClassChild]
|
|
55
|
+
ConcreteClassModel.t1 # str
|
|
56
|
+
ConcreteClassModel.t2 # SomeBaseClassChild
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Internally `GenericArgumentsMixin` will create a new attribute `__typing_arguments__`
|
|
60
|
+
inside the class and its instances. This attribute is a dictionary mapping the type
|
|
61
|
+
variables to their concrete types. This is useful if you want to access the type
|
|
62
|
+
arguments in a generic way.
|
|
63
|
+
|
|
64
|
+
The `typing_arg` function is a helper function to make the type arguments available
|
|
65
|
+
in the class and its instances using a nicely named attribute. This is just a
|
|
66
|
+
convenience function, as you can also access the type arguments directly from the
|
|
67
|
+
`__typing_arguments__` attribute.
|
|
68
|
+
|
|
69
|
+
**Note:** If you are using pydantic models you should use the `ClassVar` annotation
|
|
70
|
+
to ensure pydantic will not try to catch and validate the type arguments as normal
|
|
71
|
+
model fields.
|
|
72
|
+
|
|
73
|
+
You may also mix different generic base classes like so:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
T1 = TypeVar("T1")
|
|
77
|
+
T2 = TypeVar("T2", bound="SomeBaseClass")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class Base1(
|
|
81
|
+
GenericArgumentsMixin,
|
|
82
|
+
Generic[T1],
|
|
83
|
+
):
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class Base2(
|
|
88
|
+
GenericArgumentsMixin,
|
|
89
|
+
Generic[T2],
|
|
90
|
+
):
|
|
91
|
+
t2 = typing_arg(T2)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class Something(
|
|
95
|
+
Base1[str],
|
|
96
|
+
Base2[SomeBaseClassChild],
|
|
97
|
+
):
|
|
98
|
+
t1 = typing_arg(T1)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
Something.t1 # str
|
|
102
|
+
Something.t2 # SomeBaseClassChild
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
In this example `Base1` and `Base2` are both generic base classes. `Base1` only
|
|
106
|
+
defines a type argument `T1` and `Base2` only defines a type argument `T2`. The
|
|
107
|
+
`Something` class inherits from both `Base1` and `Base2`. Note that `Base1` does
|
|
108
|
+
not define a simple accessor like `t1` using `typing_arg`, while `Base2` does. This
|
|
109
|
+
is not a problem and can be later added by `Something` using `typing_arg` as well.
|
|
110
|
+
|
|
111
|
+
You may encounter issues using the `typing_arg` function when using type validator
|
|
112
|
+
like mypy or your IDE. If so you might need to use `cast` to tell the type checker
|
|
113
|
+
you are sure about what you are doing. For example:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
T1 = TypeVar("T1", bound="SomeBaseClass")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class Something(
|
|
120
|
+
GenericArgumentsMixin,
|
|
121
|
+
Generic[T1],
|
|
122
|
+
):
|
|
123
|
+
t1 = cast(type[SomeBaseClass], typing_arg(T1))
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Note:** You will still need to use `ClassVar` when using pydantic models. This
|
|
127
|
+
might result in using the same type twice (inside `ClassVar` and `cast`).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
// See https://github.com/conventional-changelog/commitlint/blob/master/%40commitlint/config-conventional/index.js
|
|
3
|
+
extends: ['@commitlint/config-conventional'],
|
|
4
|
+
// Own rules
|
|
5
|
+
rules: {
|
|
6
|
+
'subject-case': [
|
|
7
|
+
2,
|
|
8
|
+
'never',
|
|
9
|
+
['start-case', 'pascal-case', 'upper-case'],
|
|
10
|
+
],
|
|
11
|
+
},
|
|
12
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
default:
|
|
2
|
+
just --list
|
|
3
|
+
|
|
4
|
+
[unix]
|
|
5
|
+
_install-pre-commit:
|
|
6
|
+
#!/usr/bin/env bash
|
|
7
|
+
if ( which pre-commit > /dev/null 2>&1 )
|
|
8
|
+
then
|
|
9
|
+
pre-commit install --install-hooks
|
|
10
|
+
else
|
|
11
|
+
echo "-----------------------------------------------------------------"
|
|
12
|
+
echo "pre-commit is not installed - cannot enable pre-commit hooks!"
|
|
13
|
+
echo "Recommendation: Install pre-commit ('brew install pre-commit')."
|
|
14
|
+
echo "-----------------------------------------------------------------"
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
[windows]
|
|
18
|
+
_install-pre-commit:
|
|
19
|
+
#!powershell.exe
|
|
20
|
+
Write-Host "Please ensure pre-commit hooks are installed using 'pre-commit install --install-hooks'"
|
|
21
|
+
|
|
22
|
+
install: (uv "sync") && _install-pre-commit
|
|
23
|
+
|
|
24
|
+
update: (uv "sync")
|
|
25
|
+
|
|
26
|
+
uv *args:
|
|
27
|
+
uv {{args}}
|
|
28
|
+
|
|
29
|
+
test *args: (uv "run" "pytest" "--cov=typing_arguments" "--cov-report" "term-missing:skip-covered" args)
|
|
30
|
+
|
|
31
|
+
test-all: (uv "run" "tox")
|
|
32
|
+
|
|
33
|
+
ruff *args: (uv "run" "ruff" "check" "typing_arguments" "tests" args)
|
|
34
|
+
|
|
35
|
+
pyright *args: (uv "run" "pyright" "typing_arguments" args)
|
|
36
|
+
|
|
37
|
+
lint: ruff pyright
|
|
38
|
+
|
|
39
|
+
publish: (uv "publish" "--build")
|
|
40
|
+
|
|
41
|
+
release version: (uv "run" "pkg-version.py" version)
|
|
42
|
+
git add pyproject.toml
|
|
43
|
+
git commit -m "release: 🔖 v$(uv run --quiet pkg-version.py)" --no-verify
|
|
44
|
+
git tag "v$(uv run --quiet pkg-version.py)"
|
|
45
|
+
git push
|
|
46
|
+
git push --tags
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# FILE GENERATED BY nurify COMMAND
|
|
2
|
+
|
|
3
|
+
def --wrapped "nur _install-pre-commit" [...args] {
|
|
4
|
+
^just "_install-pre-commit" ...$args
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
def --wrapped "nur default" [...args] {
|
|
8
|
+
^just "default" ...$args
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
def --wrapped "nur install" [...args] {
|
|
12
|
+
^just "install" ...$args
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
def --wrapped "nur lint" [...args] {
|
|
16
|
+
^just "lint" ...$args
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
def --wrapped "nur publish" [...args] {
|
|
20
|
+
^just "publish" ...$args
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def --wrapped "nur pyright" [...args] {
|
|
24
|
+
^just "pyright" ...$args
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
def --wrapped "nur release" [...args] {
|
|
28
|
+
^just "release" ...$args
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def --wrapped "nur ruff" [...args] {
|
|
32
|
+
^just "ruff" ...$args
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
def --wrapped "nur test" [...args] {
|
|
36
|
+
^just "test" ...$args
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def --wrapped "nur test-all" [...args] {
|
|
40
|
+
^just "test-all" ...$args
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
def --wrapped "nur update" [...args] {
|
|
44
|
+
^just "update" ...$args
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
def --wrapped "nur uv" [...args] {
|
|
48
|
+
^just "uv" ...$args
|
|
49
|
+
}
|
|
50
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
import tomlkit
|
|
4
|
+
|
|
5
|
+
if __name__ == "__main__":
|
|
6
|
+
if len(sys.argv) not in (1, 2):
|
|
7
|
+
raise RuntimeError("Usage: python pkg-version.py [version]")
|
|
8
|
+
|
|
9
|
+
if len(sys.argv) == 2:
|
|
10
|
+
version = sys.argv[1]
|
|
11
|
+
|
|
12
|
+
with open("pyproject.toml", "rb") as f:
|
|
13
|
+
pyproject_toml = tomlkit.parse(f.read())
|
|
14
|
+
|
|
15
|
+
pyproject_toml["project"]["version"] = version
|
|
16
|
+
|
|
17
|
+
with open("pyproject.toml", "w") as f:
|
|
18
|
+
f.write(tomlkit.dumps(pyproject_toml))
|
|
19
|
+
else:
|
|
20
|
+
with open("pyproject.toml", "rb") as f:
|
|
21
|
+
pyproject_toml = tomlkit.parse(f.read())
|
|
22
|
+
|
|
23
|
+
version = pyproject_toml["project"]["version"]
|
|
24
|
+
print(version)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "typing-arguments"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Store references of your typing arguments to be available at runtime."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{name = "TEAM23 GmbH", email = "info@team23.de"},
|
|
8
|
+
]
|
|
9
|
+
license = {file = "LICENSE"}
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = []
|
|
12
|
+
|
|
13
|
+
[project.urls]
|
|
14
|
+
Repository = "https://github.com/team23/typing-arguments"
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
pydantic = [
|
|
18
|
+
"pydantic>=2.0.0,<3.0.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[dependency-groups]
|
|
22
|
+
dev = [
|
|
23
|
+
"pydantic>=2.10.1",
|
|
24
|
+
"pyright>=1.1.389",
|
|
25
|
+
"pytest>=8.3.3",
|
|
26
|
+
"pytest-cov>=6.0.0",
|
|
27
|
+
"ruff>=0.8.0",
|
|
28
|
+
"tox>=4.23.2",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["hatchling"]
|
|
33
|
+
build-backend = "hatchling.build"
|
|
File without changes
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
from typing import ClassVar, Generic, TypeVar
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
from typing_arguments import GenericArgumentsMixin, typing_arg
|
|
7
|
+
|
|
8
|
+
T1 = TypeVar("T1")
|
|
9
|
+
T2 = TypeVar("T2")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PlainGeneric(GenericArgumentsMixin, Generic[T1, T2]):
|
|
13
|
+
t1 = typing_arg(T1)
|
|
14
|
+
t2 = typing_arg(T2)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PlainGenericChild(PlainGeneric[str, int]):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PlainGenericGrandChild(PlainGenericChild):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_plain_generic():
|
|
26
|
+
type_alias = PlainGeneric[str, int]
|
|
27
|
+
|
|
28
|
+
assert type_alias.__typing_arguments__ == {T1: str, T2: int}
|
|
29
|
+
assert type_alias.t1 is str
|
|
30
|
+
assert type_alias.t2 is int
|
|
31
|
+
assert type_alias().t1 is str
|
|
32
|
+
assert type_alias().t2 is int
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_plain_generic_raises_exception_if_not_typed():
|
|
36
|
+
assert PlainGeneric.__typing_arguments__ == {}
|
|
37
|
+
with pytest.raises(TypeError):
|
|
38
|
+
PlainGeneric.t1
|
|
39
|
+
with pytest.raises(TypeError):
|
|
40
|
+
PlainGeneric.t2
|
|
41
|
+
with pytest.raises(TypeError):
|
|
42
|
+
PlainGeneric().t1
|
|
43
|
+
with pytest.raises(TypeError):
|
|
44
|
+
PlainGeneric().t2
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_plain_generic_raises_exception_if_base_class_is_not_generic():
|
|
48
|
+
class NotGeneric(GenericArgumentsMixin):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
with pytest.raises(TypeError):
|
|
52
|
+
NotGeneric[str, int]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_plain_generic_raises_exception_if_you_try_to_pass_typevars_to_mixin():
|
|
56
|
+
with pytest.raises(TypeError):
|
|
57
|
+
class TypeVarsPassedToMixin(
|
|
58
|
+
GenericArgumentsMixin[T1, T2],
|
|
59
|
+
Generic[T1, T2],
|
|
60
|
+
):
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_plain_generic_raises_exception_if_you_miss_generic_base_class():
|
|
65
|
+
class GenericWithoutBase(
|
|
66
|
+
GenericArgumentsMixin,
|
|
67
|
+
):
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
with pytest.raises(TypeError):
|
|
71
|
+
GenericWithoutBase[str, int]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_plain_generic_raises_exception_if_wrong_arguments_count():
|
|
75
|
+
with pytest.raises(TypeError):
|
|
76
|
+
PlainGeneric[str]
|
|
77
|
+
with pytest.raises(TypeError):
|
|
78
|
+
PlainGeneric[str, int, str]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_plain_generic_raises_exception_if_using_typing_arg_on_non_mixin_class():
|
|
82
|
+
class Something(Generic[T1]):
|
|
83
|
+
t1 = typing_arg(T1)
|
|
84
|
+
|
|
85
|
+
with pytest.raises(TypeError):
|
|
86
|
+
Something[str].t1
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_plain_generic_child():
|
|
90
|
+
assert PlainGenericChild.__typing_arguments__ == {T1: str, T2: int}
|
|
91
|
+
assert PlainGenericChild.t1 is str
|
|
92
|
+
assert PlainGenericChild.t2 is int
|
|
93
|
+
assert PlainGenericChild().t1 is str
|
|
94
|
+
assert PlainGenericChild().t2 is int
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_plain_generic__grand_child():
|
|
98
|
+
assert PlainGenericGrandChild.__typing_arguments__ == {T1: str, T2: int}
|
|
99
|
+
assert PlainGenericGrandChild.t1 is str
|
|
100
|
+
assert PlainGenericGrandChild.t2 is int
|
|
101
|
+
assert PlainGenericGrandChild().t1 is str
|
|
102
|
+
assert PlainGenericGrandChild().t2 is int
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class PydanticModel(GenericArgumentsMixin, BaseModel, Generic[T1, T2]):
|
|
106
|
+
t1: ClassVar = typing_arg(T1)
|
|
107
|
+
t2: ClassVar = typing_arg(T2)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class PydanticModelChild(PydanticModel[str, int]):
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_pydantic_model():
|
|
115
|
+
type_alias = PydanticModel[str, int]
|
|
116
|
+
|
|
117
|
+
assert type_alias.__typing_arguments__ == {T1: str, T2: int}
|
|
118
|
+
assert type_alias.t1 is str
|
|
119
|
+
assert type_alias.t2 is int
|
|
120
|
+
assert type_alias().t1 is str
|
|
121
|
+
assert type_alias().t2 is int
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_pydantic_model_child():
|
|
125
|
+
assert PydanticModelChild.__typing_arguments__ == {T1: str, T2: int}
|
|
126
|
+
assert PydanticModelChild.t1 is str
|
|
127
|
+
assert PydanticModelChild.t2 is int
|
|
128
|
+
assert PydanticModelChild().t1 is str
|
|
129
|
+
assert PydanticModelChild().t2 is int
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class Base1(GenericArgumentsMixin, Generic[T1]):
|
|
133
|
+
t1 = typing_arg(T1)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class Base2(GenericArgumentsMixin, Generic[T2]):
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class MultiBaseGeneric(Base1[str], Base2[int]):
|
|
141
|
+
t2 = typing_arg(T2)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class MultiBaseGenericChild(MultiBaseGeneric):
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_multi_base():
|
|
149
|
+
assert MultiBaseGeneric.__typing_arguments__ == {T1: str, T2: int}
|
|
150
|
+
assert MultiBaseGeneric.t1 is str
|
|
151
|
+
assert MultiBaseGeneric.t2 is int
|
|
152
|
+
assert MultiBaseGeneric().t1 is str
|
|
153
|
+
assert MultiBaseGeneric().t2 is int
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_multi_base_child():
|
|
157
|
+
assert MultiBaseGenericChild.__typing_arguments__ == {T1: str, T2: int}
|
|
158
|
+
assert MultiBaseGenericChild.t1 is str
|
|
159
|
+
assert MultiBaseGenericChild.t2 is int
|
|
160
|
+
assert MultiBaseGenericChild().t1 is str
|
|
161
|
+
assert MultiBaseGenericChild().t2 is int
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="PYTHON_MODULE" version="4">
|
|
3
|
+
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
4
|
+
<exclude-output />
|
|
5
|
+
<content url="file://$MODULE_DIR$" />
|
|
6
|
+
<orderEntry type="jdk" jdkName="Python 3.13 (python-libs:typing-arguments)" jdkType="Python SDK" />
|
|
7
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
8
|
+
</component>
|
|
9
|
+
</module>
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generic method to make type arguments of generic models available in the class.
|
|
3
|
+
|
|
4
|
+
Example:
|
|
5
|
+
-------
|
|
6
|
+
```python
|
|
7
|
+
T1 = TypeVar("T1")
|
|
8
|
+
T2 = TypeVar("T2", bound="SomeBaseClass")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Something(
|
|
12
|
+
GenericArgumentsMixin,
|
|
13
|
+
Generic[T1, T2],
|
|
14
|
+
):
|
|
15
|
+
t1 = typing_arg(T1)
|
|
16
|
+
t2 = typing_arg(T2)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
ConcreteClass = Something[str, SomeBaseClassChild]
|
|
20
|
+
ConcreteClass.t1 # str
|
|
21
|
+
ConcreteClass.t2 # SomeBaseClassChild
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
You may also use this with pydantic models:
|
|
25
|
+
```python
|
|
26
|
+
T1 = TypeVar("T1")
|
|
27
|
+
T2 = TypeVar("T2", bound="SomeBaseClass")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SomethingModel(
|
|
31
|
+
GenericArgumentsMixin,
|
|
32
|
+
BaseModel,
|
|
33
|
+
Generic[T1, T2],
|
|
34
|
+
):
|
|
35
|
+
t1: ClassVar = typing_arg(T1)
|
|
36
|
+
t2: ClassVar = typing_arg(T2)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
ConcreteClassModel = SomethingModel[str, SomeBaseClassChild]
|
|
40
|
+
ConcreteClassModel.t1 # str
|
|
41
|
+
ConcreteClassModel.t2 # SomeBaseClassChild
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Internally `GenericArgumentsMixin` will create a new attribute `__typing_arguments__`
|
|
45
|
+
inside the class and its instances. This attribute is a dictionary mapping the type
|
|
46
|
+
variables to their concrete types. This is useful if you want to access the type
|
|
47
|
+
arguments in a generic way.
|
|
48
|
+
|
|
49
|
+
The `typing_arg` function is a helper function to make the type arguments available
|
|
50
|
+
in the class and its instances using a nicely named attribute. This is just a
|
|
51
|
+
convenience function, as you can also access the type arguments directly from the
|
|
52
|
+
`__typing_arguments__` attribute.
|
|
53
|
+
|
|
54
|
+
**Note:** If you are using pydantic models you should use the `ClassVar` annotation
|
|
55
|
+
to ensure pydantic will not try to catch and validate the type arguments as normal
|
|
56
|
+
model fields.
|
|
57
|
+
|
|
58
|
+
You may also mix different generic base classes like so:
|
|
59
|
+
```python
|
|
60
|
+
T1 = TypeVar("T1")
|
|
61
|
+
T2 = TypeVar("T2", bound="SomeBaseClass")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Base1(
|
|
65
|
+
GenericArgumentsMixin,
|
|
66
|
+
Generic[T1],
|
|
67
|
+
):
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Base2(
|
|
72
|
+
GenericArgumentsMixin,
|
|
73
|
+
Generic[T2],
|
|
74
|
+
):
|
|
75
|
+
t2 = typing_arg(T2)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Something(
|
|
79
|
+
Base1[str],
|
|
80
|
+
Base2[SomeBaseClassChild],
|
|
81
|
+
):
|
|
82
|
+
t1 = typing_arg(T1)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
Something.t1 # str
|
|
86
|
+
Something.t2 # SomeBaseClassChild
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
In this example `Base1` and `Base2` are both generic base classes. `Base1` only
|
|
90
|
+
defines a type argument `T1` and `Base2` only defines a type argument `T2`. The
|
|
91
|
+
`Something` class inherits from both `Base1` and `Base2`. Note that `Base1` does
|
|
92
|
+
not define a simple accessor like `t1` using `typing_arg`, while `Base2` does. This
|
|
93
|
+
is not a problem and can be later added by `Something` using `typing_arg` as well.
|
|
94
|
+
|
|
95
|
+
You may encounter issues using the `typing_arg` function when using type validator
|
|
96
|
+
like mypy or your IDE. If so you might need to use `cast` to tell the type checker
|
|
97
|
+
you are sure about what you are doing. For example:
|
|
98
|
+
```python
|
|
99
|
+
T1 = TypeVar("T1", bound="SomeBaseClass")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class Something(
|
|
103
|
+
GenericArgumentsMixin,
|
|
104
|
+
Generic[T1],
|
|
105
|
+
):
|
|
106
|
+
t1 = cast(type[SomeBaseClass], typing_arg(T1))
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Note:** You will still need to use `ClassVar` when using pydantic models. This
|
|
110
|
+
might result in using the same type twice (inside `ClassVar` and `cast`).
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
import functools
|
|
114
|
+
import types
|
|
115
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast
|
|
116
|
+
from typing import _GenericAlias as TypingGenericAlias # pyright: ignore[reportAttributeAccessIssue]
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
from pydantic import BaseModel as _PydanticBaseModel # pyright: ignore[reportAssignmentType]
|
|
120
|
+
except ImportError: # pragma: no cover
|
|
121
|
+
# Provide fake pydantic base model
|
|
122
|
+
class _PydanticBaseModel:
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
GenericTrackerMixinT = TypeVar("GenericTrackerMixinT")
|
|
126
|
+
|
|
127
|
+
TYPING_ATTRIBUTE_NAME = "__typing_arguments__"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class GenericArgumentsMixin:
|
|
131
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
132
|
+
# Same as TYPING_ATTRIBUTE_NAME
|
|
133
|
+
__typing_arguments__: dict[TypeVar, type[Any]]
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
@functools.lru_cache(maxsize=None, typed=True)
|
|
137
|
+
def __class_getitem__(
|
|
138
|
+
cls: type[GenericTrackerMixinT],
|
|
139
|
+
params: type[Any] | tuple[type[Any], ...],
|
|
140
|
+
) -> type[Any]:
|
|
141
|
+
if (
|
|
142
|
+
cls is GenericArgumentsMixin
|
|
143
|
+
and params
|
|
144
|
+
):
|
|
145
|
+
raise TypeError(
|
|
146
|
+
'Type parameters should be placed on typing.Generic, '
|
|
147
|
+
'not GenericArgumentsMixin',
|
|
148
|
+
)
|
|
149
|
+
if Generic not in cls.__bases__:
|
|
150
|
+
raise TypeError(
|
|
151
|
+
'Cannot provide type arguments to a non-generic class, must '
|
|
152
|
+
'inherit from typing.Generic first',
|
|
153
|
+
)
|
|
154
|
+
if not isinstance(params, tuple):
|
|
155
|
+
params = (params,)
|
|
156
|
+
|
|
157
|
+
base_cls = cls
|
|
158
|
+
if issubclass(cls, _PydanticBaseModel):
|
|
159
|
+
base_cls = super().__class_getitem__(params) # pyright: ignore[reportAttributeAccessIssue]
|
|
160
|
+
|
|
161
|
+
if len(cls.__parameters__) != len(params): # pyright: ignore[reportAttributeAccessIssue]
|
|
162
|
+
raise TypeError(
|
|
163
|
+
f'Type {cls.__name__} expects {len(cls.__parameters__)} ' # pyright: ignore[reportAttributeAccessIssue]
|
|
164
|
+
f'parameters, got {len(params)}',
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
typing_args = dict(zip(cls.__parameters__, params, strict=True)) # pyright: ignore[reportAttributeAccessIssue]
|
|
168
|
+
if hasattr(cls, TYPING_ATTRIBUTE_NAME):
|
|
169
|
+
typing_args = {
|
|
170
|
+
**getattr(cls, TYPING_ATTRIBUTE_NAME),
|
|
171
|
+
**typing_args,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
typed_cls = cast(
|
|
175
|
+
type[GenericArgumentsMixin],
|
|
176
|
+
types.new_class(
|
|
177
|
+
f"Typed{cls.__name__}",
|
|
178
|
+
(base_cls,),
|
|
179
|
+
{},
|
|
180
|
+
lambda ns: ns.update({TYPING_ATTRIBUTE_NAME: typing_args}),
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if issubclass(cls, _PydanticBaseModel):
|
|
185
|
+
return typed_cls
|
|
186
|
+
|
|
187
|
+
typed_alias = TypingGenericAlias(typed_cls, params)
|
|
188
|
+
setattr(typed_alias, TYPING_ATTRIBUTE_NAME, typing_args)
|
|
189
|
+
return typed_alias
|
|
190
|
+
|
|
191
|
+
def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None:
|
|
192
|
+
super().__init_subclass__(*args, **kwargs)
|
|
193
|
+
|
|
194
|
+
if not hasattr(cls, TYPING_ATTRIBUTE_NAME):
|
|
195
|
+
setattr(cls, TYPING_ATTRIBUTE_NAME, {})
|
|
196
|
+
|
|
197
|
+
base_typing_args = {}
|
|
198
|
+
typing_args = getattr(cls, TYPING_ATTRIBUTE_NAME)
|
|
199
|
+
for base in reversed(cls.__bases__):
|
|
200
|
+
if hasattr(base, TYPING_ATTRIBUTE_NAME):
|
|
201
|
+
base_typing_args.update(getattr(base, TYPING_ATTRIBUTE_NAME))
|
|
202
|
+
|
|
203
|
+
setattr(
|
|
204
|
+
cls, TYPING_ATTRIBUTE_NAME, {
|
|
205
|
+
**base_typing_args,
|
|
206
|
+
**typing_args,
|
|
207
|
+
},
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class typing_arg: # noqa
|
|
212
|
+
__slots__ = ("type_argument",)
|
|
213
|
+
|
|
214
|
+
def __init__(self, type_argument: TypeVar, /) -> None:
|
|
215
|
+
self.type_argument = type_argument
|
|
216
|
+
|
|
217
|
+
def __get__(
|
|
218
|
+
self,
|
|
219
|
+
obj: GenericArgumentsMixin,
|
|
220
|
+
obj_class: type[GenericArgumentsMixin] | None = None,
|
|
221
|
+
) -> type[Any]:
|
|
222
|
+
if obj_class is None: # pragma: no cover
|
|
223
|
+
obj_class = obj.__class__
|
|
224
|
+
|
|
225
|
+
if not hasattr(obj_class, TYPING_ATTRIBUTE_NAME):
|
|
226
|
+
raise TypeError(
|
|
227
|
+
f"{obj_class} seems not be be using GenericArgumentsMixin or "
|
|
228
|
+
f"no arguments were provided",
|
|
229
|
+
)
|
|
230
|
+
if self.type_argument not in getattr(obj_class, TYPING_ATTRIBUTE_NAME):
|
|
231
|
+
raise TypeError(
|
|
232
|
+
f"Type argument {self.type_argument} not found in {obj_class}",
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
return getattr(obj_class, TYPING_ATTRIBUTE_NAME)[self.type_argument]
|
|
File without changes
|