extensionmethods 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.
- extensionmethods-0.1.0/.github/workflows/build-test-publish.yml +133 -0
- extensionmethods-0.1.0/.gitignore +10 -0
- extensionmethods-0.1.0/.pre-commit-config.yaml +11 -0
- extensionmethods-0.1.0/.python-version +1 -0
- extensionmethods-0.1.0/.vscode/extensions.json +5 -0
- extensionmethods-0.1.0/.vscode/settings.json +12 -0
- extensionmethods-0.1.0/LICENSE +21 -0
- extensionmethods-0.1.0/PKG-INFO +229 -0
- extensionmethods-0.1.0/README.md +214 -0
- extensionmethods-0.1.0/pyproject.toml +33 -0
- extensionmethods-0.1.0/setup.cfg +4 -0
- extensionmethods-0.1.0/src/extensionmethods/__init__.py +12 -0
- extensionmethods-0.1.0/src/extensionmethods/extensions.py +44 -0
- extensionmethods-0.1.0/src/extensionmethods.egg-info/PKG-INFO +229 -0
- extensionmethods-0.1.0/src/extensionmethods.egg-info/SOURCES.txt +17 -0
- extensionmethods-0.1.0/src/extensionmethods.egg-info/dependency_links.txt +1 -0
- extensionmethods-0.1.0/src/extensionmethods.egg-info/top_level.txt +1 -0
- extensionmethods-0.1.0/tests/test_extensions.py +53 -0
- extensionmethods-0.1.0/uv.lock +221 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
name: Build, unittest and publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
tags:
|
|
8
|
+
- "v[0-9]+.[0-9]+.[0-9]+"
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
env:
|
|
12
|
+
UV_VERSION: "0.9.7"
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build:
|
|
16
|
+
name: Build package
|
|
17
|
+
runs-on: "ubuntu-latest"
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- name: Checkout repository
|
|
21
|
+
uses: actions/checkout@v4
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0 # required for setuptools-scm
|
|
24
|
+
|
|
25
|
+
- name: Install uv
|
|
26
|
+
uses: astral-sh/setup-uv@v6
|
|
27
|
+
with:
|
|
28
|
+
version: ${{ env.UV_VERSION }}
|
|
29
|
+
|
|
30
|
+
- name: Set up Python
|
|
31
|
+
run: uv python install
|
|
32
|
+
|
|
33
|
+
- name: Install the project
|
|
34
|
+
run: uv sync --locked --dev
|
|
35
|
+
|
|
36
|
+
- name: Build packages
|
|
37
|
+
run: uv build --all-packages
|
|
38
|
+
|
|
39
|
+
- name: Store the distribution packages
|
|
40
|
+
uses: actions/upload-artifact@v5
|
|
41
|
+
with:
|
|
42
|
+
name: python-package-distributions
|
|
43
|
+
path: dist/
|
|
44
|
+
|
|
45
|
+
test:
|
|
46
|
+
name: Test package
|
|
47
|
+
runs-on: "ubuntu-latest"
|
|
48
|
+
needs:
|
|
49
|
+
- build
|
|
50
|
+
strategy:
|
|
51
|
+
matrix:
|
|
52
|
+
python-version:
|
|
53
|
+
- "3.11"
|
|
54
|
+
- "3.12"
|
|
55
|
+
- "3.13"
|
|
56
|
+
|
|
57
|
+
steps:
|
|
58
|
+
- name: Checkout repository
|
|
59
|
+
uses: actions/checkout@v4
|
|
60
|
+
|
|
61
|
+
- name: Install uv
|
|
62
|
+
uses: astral-sh/setup-uv@v6
|
|
63
|
+
with:
|
|
64
|
+
version: ${{ env.UV_VERSION }}
|
|
65
|
+
python-version: ${{ matrix.python-version }}
|
|
66
|
+
|
|
67
|
+
- name: Set up Python
|
|
68
|
+
run: uv python install
|
|
69
|
+
|
|
70
|
+
- name: Download all the dists
|
|
71
|
+
uses: actions/download-artifact@v6
|
|
72
|
+
with:
|
|
73
|
+
name: python-package-distributions
|
|
74
|
+
path: dist/
|
|
75
|
+
|
|
76
|
+
- name: Install package
|
|
77
|
+
run: |
|
|
78
|
+
uv venv
|
|
79
|
+
uv pip install dist/*.whl
|
|
80
|
+
uv pip install pytest
|
|
81
|
+
|
|
82
|
+
- name: Test with pytest
|
|
83
|
+
run: |
|
|
84
|
+
uv run pytest
|
|
85
|
+
|
|
86
|
+
publish-testpypi:
|
|
87
|
+
name: Publish to TestPyPI
|
|
88
|
+
if: startsWith(github.ref, 'refs/tags/') # only publish on tag pushes
|
|
89
|
+
needs:
|
|
90
|
+
- test
|
|
91
|
+
runs-on: ubuntu-latest
|
|
92
|
+
|
|
93
|
+
environment:
|
|
94
|
+
name: testpypi
|
|
95
|
+
url: https://test.pypi.org/p/extensionmethods
|
|
96
|
+
|
|
97
|
+
permissions:
|
|
98
|
+
id-token: write # IMPORTANT: mandatory for trusted publishing
|
|
99
|
+
|
|
100
|
+
steps:
|
|
101
|
+
- name: Download all the dists
|
|
102
|
+
uses: actions/download-artifact@v6
|
|
103
|
+
with:
|
|
104
|
+
name: python-package-distributions
|
|
105
|
+
path: dist/
|
|
106
|
+
- name: Publish distribution to TestPyPI
|
|
107
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
108
|
+
with:
|
|
109
|
+
repository-url: https://test.pypi.org/legacy/
|
|
110
|
+
verbose: true
|
|
111
|
+
|
|
112
|
+
publish-pypi:
|
|
113
|
+
name: Publish to PyPI
|
|
114
|
+
if: startsWith(github.ref, 'refs/tags/') # only publish on tag pushes
|
|
115
|
+
needs:
|
|
116
|
+
- publish-testpypi
|
|
117
|
+
runs-on: ubuntu-latest
|
|
118
|
+
|
|
119
|
+
environment:
|
|
120
|
+
name: pypi
|
|
121
|
+
url: https://pypi.org/p/extensionmethods
|
|
122
|
+
|
|
123
|
+
permissions:
|
|
124
|
+
id-token: write # IMPORTANT: mandatory for trusted publishing
|
|
125
|
+
|
|
126
|
+
steps:
|
|
127
|
+
- name: Download all the dists
|
|
128
|
+
uses: actions/download-artifact@v6
|
|
129
|
+
with:
|
|
130
|
+
name: python-package-distributions
|
|
131
|
+
path: dist/
|
|
132
|
+
- name: Publish to PyPI
|
|
133
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"python.analysis.typeCheckingMode": "standard",
|
|
3
|
+
"editor.codeActionsOnSave": {
|
|
4
|
+
"source.fixAll": "always",
|
|
5
|
+
"source.organizeImports.ruff": "always"
|
|
6
|
+
},
|
|
7
|
+
"python.testing.pytestArgs": [
|
|
8
|
+
"tests"
|
|
9
|
+
],
|
|
10
|
+
"python.testing.unittestEnabled": false,
|
|
11
|
+
"python.testing.pytestEnabled": true
|
|
12
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pim Mostert
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: extensionmethods
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author-email: Pim Mostert <pim.mostert@pimmostert.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Pim-Mostert/extensionmethods
|
|
8
|
+
Project-URL: Issues, https://github.com/Pim-Mostert/extensionmethods/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# extensionmethods
|
|
17
|
+
|
|
18
|
+
Mimics C#-style extension methods in Python.
|
|
19
|
+
|
|
20
|
+
`extensionmethods` is a tiny package that lets you “attach” functions to existing types without modifying their source code, enabling method-like syntax, better chaining, and cleaner separation of optional dependencies.
|
|
21
|
+
|
|
22
|
+
- [extensionmethods](#extensionmethods)
|
|
23
|
+
- [Example usage](#example-usage)
|
|
24
|
+
- [Type safety and type checking](#type-safety-and-type-checking)
|
|
25
|
+
- [Installation](#installation)
|
|
26
|
+
- [Why use extension methods?](#why-use-extension-methods)
|
|
27
|
+
- [Readability through chaining](#readability-through-chaining)
|
|
28
|
+
- [Modularity and dependency isolation](#modularity-and-dependency-isolation)
|
|
29
|
+
- [Known caveats](#known-caveats)
|
|
30
|
+
- [IDE type hints may be misleading](#ide-type-hints-may-be-misleading)
|
|
31
|
+
- [Uses the `|` operator (`__ror__`)](#uses-the--operator-__ror__)
|
|
32
|
+
- [License](#license)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## Example usage
|
|
36
|
+
|
|
37
|
+
In C#, you can add methods to existing types:
|
|
38
|
+
|
|
39
|
+
```csharp
|
|
40
|
+
public static class IntExtensions
|
|
41
|
+
{
|
|
42
|
+
public static int Double(this int x)
|
|
43
|
+
{
|
|
44
|
+
return x * 2;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
int result = 7.Double();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
You get method syntax without modifying `int`.
|
|
52
|
+
|
|
53
|
+
With the `extensionmethods` package you can achieve similiar functionality like so:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from extensionmethods import extension
|
|
57
|
+
|
|
58
|
+
@extension(to=int)
|
|
59
|
+
def double(x: int) -> int:
|
|
60
|
+
return x * 2
|
|
61
|
+
|
|
62
|
+
result = 7 | double()
|
|
63
|
+
print(result) # 14
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
With parameters:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
@extension(to=int)
|
|
70
|
+
def add_then_multiply(x: int, to_add: int, to_multiply: int) -> int:
|
|
71
|
+
return (x + to_add) * to_multiply
|
|
72
|
+
|
|
73
|
+
result = 7 | add_then_multiply(11, 3)
|
|
74
|
+
print(result) # 54
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The value on the left side becomes the first argument of the function.
|
|
78
|
+
|
|
79
|
+
## Type safety and type checking
|
|
80
|
+
|
|
81
|
+
The extension methods are type-aware.
|
|
82
|
+
|
|
83
|
+
When you declare an extension, you bind it to a specific type:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
@extension(to=int)
|
|
87
|
+
def double(x: int) -> int:
|
|
88
|
+
return x * 2
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This gives you safety at two levels:
|
|
92
|
+
|
|
93
|
+
- **IDE / static type checking**
|
|
94
|
+
Type checkers and editors can detect incorrect usage:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
"hello" | double() # type error
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Your IDE (e.g. VS Code) can flag this because the extension is declared for `int`, not `str`.
|
|
101
|
+
|
|
102
|
+
- **Runtime enforcement**
|
|
103
|
+
|
|
104
|
+
Even if type checking is bypassed, the library validates the type at runtime:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
>>> "hello" | double()
|
|
108
|
+
TypeError: Extension 'double' can only be used on 'int', not 'str'
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Installation
|
|
112
|
+
|
|
113
|
+
Using **pip**:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pip install extensionmethods
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Using **uv**:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
uv pip install extensionmethods
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Why use extension methods?
|
|
126
|
+
|
|
127
|
+
### Readability through chaining
|
|
128
|
+
|
|
129
|
+
Instead of nested calls:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
result = normalize(scale(center(data)))
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
You can express the same flow step-by-step:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
result = data | center() | scale() | normalize()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
This reads left-to-right and mirrors how data is conceptually transformed.
|
|
142
|
+
|
|
143
|
+
### Modularity and dependency isolation
|
|
144
|
+
|
|
145
|
+
Suppose you maintain a core class:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
class Dataset:
|
|
149
|
+
...
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
You want export helpers:
|
|
153
|
+
|
|
154
|
+
- `to_pandas()`
|
|
155
|
+
- `to_numpy()`
|
|
156
|
+
- `to_torch()`
|
|
157
|
+
|
|
158
|
+
If you put these methods directly on `Dataset`, your core package must depend on `pandas`, `numpy`, and `torch`.
|
|
159
|
+
|
|
160
|
+
Instead, keep the core dependency-free:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
# core package
|
|
164
|
+
class Dataset:
|
|
165
|
+
...
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Then provide optional extensions:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
# dataset_pandas package
|
|
172
|
+
import pandas as pd
|
|
173
|
+
from extensionmethods import extension
|
|
174
|
+
from core import Dataset
|
|
175
|
+
|
|
176
|
+
@extension(to=Dataset)
|
|
177
|
+
def to_pandas(ds: Dataset) -> pd.DataFrame:
|
|
178
|
+
...
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Usage:
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
import dataset_pandas # registers the extension
|
|
185
|
+
|
|
186
|
+
df = dataset | to_pandas()
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Now:
|
|
190
|
+
|
|
191
|
+
- The core package has zero heavy dependencies
|
|
192
|
+
- Users only install what they need
|
|
193
|
+
- Functionality stays logically grouped
|
|
194
|
+
|
|
195
|
+
## Known caveats
|
|
196
|
+
|
|
197
|
+
### IDE type hints may be misleading
|
|
198
|
+
|
|
199
|
+
Editors like VS Code may show hover/type information for the decorator wrapper, not the original function.
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
@extension(to=int)
|
|
203
|
+
def double(x: int) -> int:
|
|
204
|
+
return x * 2
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Hovering `double` may not show the expected signature `(x: int) -> int`, but instead `(function) double: ExtensionDecoratorFactory[int]`.
|
|
208
|
+
|
|
209
|
+
### Uses the `|` operator (`__ror__`)
|
|
210
|
+
|
|
211
|
+
The system works by overriding the right-side bitwise OR operator.
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
result = value | extension_call()
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
This only works if the left-hand type does not fully consume the `|` operator itself.
|
|
218
|
+
|
|
219
|
+
For example, sets already use `|`:
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
{1, 2} | {3} # set union
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
If a type defines its own `__or__` in a way that prevents fallback to `__ror__`, the extension method will not run.
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
This project is licensed under the MIT License. See the `LICENSE` file for details.
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# extensionmethods
|
|
2
|
+
|
|
3
|
+
Mimics C#-style extension methods in Python.
|
|
4
|
+
|
|
5
|
+
`extensionmethods` is a tiny package that lets you “attach” functions to existing types without modifying their source code, enabling method-like syntax, better chaining, and cleaner separation of optional dependencies.
|
|
6
|
+
|
|
7
|
+
- [extensionmethods](#extensionmethods)
|
|
8
|
+
- [Example usage](#example-usage)
|
|
9
|
+
- [Type safety and type checking](#type-safety-and-type-checking)
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Why use extension methods?](#why-use-extension-methods)
|
|
12
|
+
- [Readability through chaining](#readability-through-chaining)
|
|
13
|
+
- [Modularity and dependency isolation](#modularity-and-dependency-isolation)
|
|
14
|
+
- [Known caveats](#known-caveats)
|
|
15
|
+
- [IDE type hints may be misleading](#ide-type-hints-may-be-misleading)
|
|
16
|
+
- [Uses the `|` operator (`__ror__`)](#uses-the--operator-__ror__)
|
|
17
|
+
- [License](#license)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## Example usage
|
|
21
|
+
|
|
22
|
+
In C#, you can add methods to existing types:
|
|
23
|
+
|
|
24
|
+
```csharp
|
|
25
|
+
public static class IntExtensions
|
|
26
|
+
{
|
|
27
|
+
public static int Double(this int x)
|
|
28
|
+
{
|
|
29
|
+
return x * 2;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
int result = 7.Double();
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
You get method syntax without modifying `int`.
|
|
37
|
+
|
|
38
|
+
With the `extensionmethods` package you can achieve similiar functionality like so:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from extensionmethods import extension
|
|
42
|
+
|
|
43
|
+
@extension(to=int)
|
|
44
|
+
def double(x: int) -> int:
|
|
45
|
+
return x * 2
|
|
46
|
+
|
|
47
|
+
result = 7 | double()
|
|
48
|
+
print(result) # 14
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
With parameters:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
@extension(to=int)
|
|
55
|
+
def add_then_multiply(x: int, to_add: int, to_multiply: int) -> int:
|
|
56
|
+
return (x + to_add) * to_multiply
|
|
57
|
+
|
|
58
|
+
result = 7 | add_then_multiply(11, 3)
|
|
59
|
+
print(result) # 54
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The value on the left side becomes the first argument of the function.
|
|
63
|
+
|
|
64
|
+
## Type safety and type checking
|
|
65
|
+
|
|
66
|
+
The extension methods are type-aware.
|
|
67
|
+
|
|
68
|
+
When you declare an extension, you bind it to a specific type:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
@extension(to=int)
|
|
72
|
+
def double(x: int) -> int:
|
|
73
|
+
return x * 2
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This gives you safety at two levels:
|
|
77
|
+
|
|
78
|
+
- **IDE / static type checking**
|
|
79
|
+
Type checkers and editors can detect incorrect usage:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
"hello" | double() # type error
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Your IDE (e.g. VS Code) can flag this because the extension is declared for `int`, not `str`.
|
|
86
|
+
|
|
87
|
+
- **Runtime enforcement**
|
|
88
|
+
|
|
89
|
+
Even if type checking is bypassed, the library validates the type at runtime:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
>>> "hello" | double()
|
|
93
|
+
TypeError: Extension 'double' can only be used on 'int', not 'str'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Installation
|
|
97
|
+
|
|
98
|
+
Using **pip**:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install extensionmethods
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Using **uv**:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
uv pip install extensionmethods
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Why use extension methods?
|
|
111
|
+
|
|
112
|
+
### Readability through chaining
|
|
113
|
+
|
|
114
|
+
Instead of nested calls:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
result = normalize(scale(center(data)))
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
You can express the same flow step-by-step:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
result = data | center() | scale() | normalize()
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This reads left-to-right and mirrors how data is conceptually transformed.
|
|
127
|
+
|
|
128
|
+
### Modularity and dependency isolation
|
|
129
|
+
|
|
130
|
+
Suppose you maintain a core class:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
class Dataset:
|
|
134
|
+
...
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
You want export helpers:
|
|
138
|
+
|
|
139
|
+
- `to_pandas()`
|
|
140
|
+
- `to_numpy()`
|
|
141
|
+
- `to_torch()`
|
|
142
|
+
|
|
143
|
+
If you put these methods directly on `Dataset`, your core package must depend on `pandas`, `numpy`, and `torch`.
|
|
144
|
+
|
|
145
|
+
Instead, keep the core dependency-free:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
# core package
|
|
149
|
+
class Dataset:
|
|
150
|
+
...
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Then provide optional extensions:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
# dataset_pandas package
|
|
157
|
+
import pandas as pd
|
|
158
|
+
from extensionmethods import extension
|
|
159
|
+
from core import Dataset
|
|
160
|
+
|
|
161
|
+
@extension(to=Dataset)
|
|
162
|
+
def to_pandas(ds: Dataset) -> pd.DataFrame:
|
|
163
|
+
...
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Usage:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
import dataset_pandas # registers the extension
|
|
170
|
+
|
|
171
|
+
df = dataset | to_pandas()
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Now:
|
|
175
|
+
|
|
176
|
+
- The core package has zero heavy dependencies
|
|
177
|
+
- Users only install what they need
|
|
178
|
+
- Functionality stays logically grouped
|
|
179
|
+
|
|
180
|
+
## Known caveats
|
|
181
|
+
|
|
182
|
+
### IDE type hints may be misleading
|
|
183
|
+
|
|
184
|
+
Editors like VS Code may show hover/type information for the decorator wrapper, not the original function.
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
@extension(to=int)
|
|
188
|
+
def double(x: int) -> int:
|
|
189
|
+
return x * 2
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Hovering `double` may not show the expected signature `(x: int) -> int`, but instead `(function) double: ExtensionDecoratorFactory[int]`.
|
|
193
|
+
|
|
194
|
+
### Uses the `|` operator (`__ror__`)
|
|
195
|
+
|
|
196
|
+
The system works by overriding the right-side bitwise OR operator.
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
result = value | extension_call()
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
This only works if the left-hand type does not fully consume the `|` operator itself.
|
|
203
|
+
|
|
204
|
+
For example, sets already use `|`:
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
{1, 2} | {3} # set union
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
If a type defines its own `__or__` in a way that prevents fallback to `__ror__`, the extension method will not run.
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
This project is licensed under the MIT License. See the `LICENSE` file for details.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "extensionmethods"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Pim Mostert", email = "pim.mostert@pimmostert.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"Operating System :: OS Independent",
|
|
13
|
+
]
|
|
14
|
+
license = "MIT"
|
|
15
|
+
license-files = ["LICEN[CS]E*"]
|
|
16
|
+
dependencies = []
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
Homepage = "https://github.com/Pim-Mostert/extensionmethods"
|
|
20
|
+
Issues = "https://github.com/Pim-Mostert/extensionmethods/issues"
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["setuptools", "wheel", "setuptools-scm"]
|
|
24
|
+
build-backend = "setuptools.build_meta"
|
|
25
|
+
|
|
26
|
+
[dependency-groups]
|
|
27
|
+
dev = [
|
|
28
|
+
"pre-commit>=4.5.1",
|
|
29
|
+
"pytest>=9.0.2",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[tool.setuptools_scm]
|
|
33
|
+
tag_regex = 'v(\d+\.\d+\.\d+)$' # vX.Y.Z (major, minor, patch)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
from typing import Any, Callable, Generic, Type, TypeVar
|
|
3
|
+
|
|
4
|
+
T = TypeVar("T")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ExtensionDecorator(Generic[T]):
|
|
8
|
+
def __init__(self, func: Callable[..., Any], args, kwargs, to: Type[T]):
|
|
9
|
+
self._func = func
|
|
10
|
+
self._to = to
|
|
11
|
+
self._args = args
|
|
12
|
+
self._kwargs = kwargs
|
|
13
|
+
|
|
14
|
+
def __call__(self, _: Any) -> Any:
|
|
15
|
+
raise NotImplementedError("Don't call an extension method directly.")
|
|
16
|
+
|
|
17
|
+
def __ror__(self, other: T) -> Any:
|
|
18
|
+
if not isinstance(other, self._to):
|
|
19
|
+
raise TypeError(
|
|
20
|
+
f"Extension '{self._func.__name__}' can only be used on '{self._to.__name__}', "
|
|
21
|
+
f"not '{type(other).__name__}'"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
return self._func(other, *self._args, **self._kwargs)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ExtensionDecoratorFactory(Generic[T]):
|
|
28
|
+
def __init__(self, func: Callable[..., Any], to: Type[T]):
|
|
29
|
+
self._func = func
|
|
30
|
+
self._to = to
|
|
31
|
+
|
|
32
|
+
wraps(func)(self)
|
|
33
|
+
|
|
34
|
+
def __call__(self, *args, **kwargs) -> ExtensionDecorator[T]:
|
|
35
|
+
return ExtensionDecorator(self._func, args, kwargs, self._to)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def extension(
|
|
39
|
+
*, to: Type[T]
|
|
40
|
+
) -> Callable[[Callable[..., Any]], ExtensionDecoratorFactory[T]]:
|
|
41
|
+
def wrapper(func: Callable[..., Any]) -> ExtensionDecoratorFactory[T]:
|
|
42
|
+
return ExtensionDecoratorFactory(func, to)
|
|
43
|
+
|
|
44
|
+
return wrapper
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: extensionmethods
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author-email: Pim Mostert <pim.mostert@pimmostert.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Pim-Mostert/extensionmethods
|
|
8
|
+
Project-URL: Issues, https://github.com/Pim-Mostert/extensionmethods/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# extensionmethods
|
|
17
|
+
|
|
18
|
+
Mimics C#-style extension methods in Python.
|
|
19
|
+
|
|
20
|
+
`extensionmethods` is a tiny package that lets you “attach” functions to existing types without modifying their source code, enabling method-like syntax, better chaining, and cleaner separation of optional dependencies.
|
|
21
|
+
|
|
22
|
+
- [extensionmethods](#extensionmethods)
|
|
23
|
+
- [Example usage](#example-usage)
|
|
24
|
+
- [Type safety and type checking](#type-safety-and-type-checking)
|
|
25
|
+
- [Installation](#installation)
|
|
26
|
+
- [Why use extension methods?](#why-use-extension-methods)
|
|
27
|
+
- [Readability through chaining](#readability-through-chaining)
|
|
28
|
+
- [Modularity and dependency isolation](#modularity-and-dependency-isolation)
|
|
29
|
+
- [Known caveats](#known-caveats)
|
|
30
|
+
- [IDE type hints may be misleading](#ide-type-hints-may-be-misleading)
|
|
31
|
+
- [Uses the `|` operator (`__ror__`)](#uses-the--operator-__ror__)
|
|
32
|
+
- [License](#license)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## Example usage
|
|
36
|
+
|
|
37
|
+
In C#, you can add methods to existing types:
|
|
38
|
+
|
|
39
|
+
```csharp
|
|
40
|
+
public static class IntExtensions
|
|
41
|
+
{
|
|
42
|
+
public static int Double(this int x)
|
|
43
|
+
{
|
|
44
|
+
return x * 2;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
int result = 7.Double();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
You get method syntax without modifying `int`.
|
|
52
|
+
|
|
53
|
+
With the `extensionmethods` package you can achieve similiar functionality like so:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from extensionmethods import extension
|
|
57
|
+
|
|
58
|
+
@extension(to=int)
|
|
59
|
+
def double(x: int) -> int:
|
|
60
|
+
return x * 2
|
|
61
|
+
|
|
62
|
+
result = 7 | double()
|
|
63
|
+
print(result) # 14
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
With parameters:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
@extension(to=int)
|
|
70
|
+
def add_then_multiply(x: int, to_add: int, to_multiply: int) -> int:
|
|
71
|
+
return (x + to_add) * to_multiply
|
|
72
|
+
|
|
73
|
+
result = 7 | add_then_multiply(11, 3)
|
|
74
|
+
print(result) # 54
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The value on the left side becomes the first argument of the function.
|
|
78
|
+
|
|
79
|
+
## Type safety and type checking
|
|
80
|
+
|
|
81
|
+
The extension methods are type-aware.
|
|
82
|
+
|
|
83
|
+
When you declare an extension, you bind it to a specific type:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
@extension(to=int)
|
|
87
|
+
def double(x: int) -> int:
|
|
88
|
+
return x * 2
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This gives you safety at two levels:
|
|
92
|
+
|
|
93
|
+
- **IDE / static type checking**
|
|
94
|
+
Type checkers and editors can detect incorrect usage:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
"hello" | double() # type error
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Your IDE (e.g. VS Code) can flag this because the extension is declared for `int`, not `str`.
|
|
101
|
+
|
|
102
|
+
- **Runtime enforcement**
|
|
103
|
+
|
|
104
|
+
Even if type checking is bypassed, the library validates the type at runtime:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
>>> "hello" | double()
|
|
108
|
+
TypeError: Extension 'double' can only be used on 'int', not 'str'
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Installation
|
|
112
|
+
|
|
113
|
+
Using **pip**:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pip install extensionmethods
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Using **uv**:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
uv pip install extensionmethods
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Why use extension methods?
|
|
126
|
+
|
|
127
|
+
### Readability through chaining
|
|
128
|
+
|
|
129
|
+
Instead of nested calls:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
result = normalize(scale(center(data)))
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
You can express the same flow step-by-step:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
result = data | center() | scale() | normalize()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
This reads left-to-right and mirrors how data is conceptually transformed.
|
|
142
|
+
|
|
143
|
+
### Modularity and dependency isolation
|
|
144
|
+
|
|
145
|
+
Suppose you maintain a core class:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
class Dataset:
|
|
149
|
+
...
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
You want export helpers:
|
|
153
|
+
|
|
154
|
+
- `to_pandas()`
|
|
155
|
+
- `to_numpy()`
|
|
156
|
+
- `to_torch()`
|
|
157
|
+
|
|
158
|
+
If you put these methods directly on `Dataset`, your core package must depend on `pandas`, `numpy`, and `torch`.
|
|
159
|
+
|
|
160
|
+
Instead, keep the core dependency-free:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
# core package
|
|
164
|
+
class Dataset:
|
|
165
|
+
...
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Then provide optional extensions:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
# dataset_pandas package
|
|
172
|
+
import pandas as pd
|
|
173
|
+
from extensionmethods import extension
|
|
174
|
+
from core import Dataset
|
|
175
|
+
|
|
176
|
+
@extension(to=Dataset)
|
|
177
|
+
def to_pandas(ds: Dataset) -> pd.DataFrame:
|
|
178
|
+
...
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Usage:
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
import dataset_pandas # registers the extension
|
|
185
|
+
|
|
186
|
+
df = dataset | to_pandas()
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Now:
|
|
190
|
+
|
|
191
|
+
- The core package has zero heavy dependencies
|
|
192
|
+
- Users only install what they need
|
|
193
|
+
- Functionality stays logically grouped
|
|
194
|
+
|
|
195
|
+
## Known caveats
|
|
196
|
+
|
|
197
|
+
### IDE type hints may be misleading
|
|
198
|
+
|
|
199
|
+
Editors like VS Code may show hover/type information for the decorator wrapper, not the original function.
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
@extension(to=int)
|
|
203
|
+
def double(x: int) -> int:
|
|
204
|
+
return x * 2
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Hovering `double` may not show the expected signature `(x: int) -> int`, but instead `(function) double: ExtensionDecoratorFactory[int]`.
|
|
208
|
+
|
|
209
|
+
### Uses the `|` operator (`__ror__`)
|
|
210
|
+
|
|
211
|
+
The system works by overriding the right-side bitwise OR operator.
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
result = value | extension_call()
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
This only works if the left-hand type does not fully consume the `|` operator itself.
|
|
218
|
+
|
|
219
|
+
For example, sets already use `|`:
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
{1, 2} | {3} # set union
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
If a type defines its own `__or__` in a way that prevents fallback to `__ror__`, the extension method will not run.
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
This project is licensed under the MIT License. See the `LICENSE` file for details.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
.gitignore
|
|
2
|
+
.pre-commit-config.yaml
|
|
3
|
+
.python-version
|
|
4
|
+
LICENSE
|
|
5
|
+
README.md
|
|
6
|
+
pyproject.toml
|
|
7
|
+
uv.lock
|
|
8
|
+
.github/workflows/build-test-publish.yml
|
|
9
|
+
.vscode/extensions.json
|
|
10
|
+
.vscode/settings.json
|
|
11
|
+
src/extensionmethods/__init__.py
|
|
12
|
+
src/extensionmethods/extensions.py
|
|
13
|
+
src/extensionmethods.egg-info/PKG-INFO
|
|
14
|
+
src/extensionmethods.egg-info/SOURCES.txt
|
|
15
|
+
src/extensionmethods.egg-info/dependency_links.txt
|
|
16
|
+
src/extensionmethods.egg-info/top_level.txt
|
|
17
|
+
tests/test_extensions.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
extensionmethods
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from extensionmethods import extension
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_extension():
|
|
7
|
+
# Assign
|
|
8
|
+
@extension(to=int)
|
|
9
|
+
def double(x: int) -> int:
|
|
10
|
+
return x * 2
|
|
11
|
+
|
|
12
|
+
# Act
|
|
13
|
+
output = 7 | double()
|
|
14
|
+
|
|
15
|
+
# Assert
|
|
16
|
+
assert output == 7 * 2
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_extension_single_parameter():
|
|
20
|
+
# Assign
|
|
21
|
+
@extension(to=int)
|
|
22
|
+
def add(x: int, to_add: int) -> int:
|
|
23
|
+
return x + to_add
|
|
24
|
+
|
|
25
|
+
# Act
|
|
26
|
+
output = 7 | add(11)
|
|
27
|
+
|
|
28
|
+
# Assert
|
|
29
|
+
assert 7 + 11 == output
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_extension_two_parameters():
|
|
33
|
+
# Assign
|
|
34
|
+
@extension(to=int)
|
|
35
|
+
def add_then_multiply(x: int, to_add: int, to_multiply: int) -> int:
|
|
36
|
+
return (x + to_add) * to_multiply
|
|
37
|
+
|
|
38
|
+
# Act
|
|
39
|
+
output = 7 | add_then_multiply(11, 3)
|
|
40
|
+
|
|
41
|
+
# Assert
|
|
42
|
+
assert (7 + 11) * 3 == output
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_extension_wrong_type():
|
|
46
|
+
# Assign
|
|
47
|
+
@extension(to=str)
|
|
48
|
+
def upper(x: str) -> str:
|
|
49
|
+
return x.upper()
|
|
50
|
+
|
|
51
|
+
# Act
|
|
52
|
+
with pytest.raises(TypeError):
|
|
53
|
+
_ = 1 | upper() # pyright: ignore[reportOperatorIssue]
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 3
|
|
3
|
+
requires-python = ">=3.11"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "cfgv"
|
|
7
|
+
version = "3.5.0"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" }
|
|
10
|
+
wheels = [
|
|
11
|
+
{ url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[[package]]
|
|
15
|
+
name = "colorama"
|
|
16
|
+
version = "0.4.6"
|
|
17
|
+
source = { registry = "https://pypi.org/simple" }
|
|
18
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
|
19
|
+
wheels = [
|
|
20
|
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[[package]]
|
|
24
|
+
name = "distlib"
|
|
25
|
+
version = "0.4.0"
|
|
26
|
+
source = { registry = "https://pypi.org/simple" }
|
|
27
|
+
sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" }
|
|
28
|
+
wheels = [
|
|
29
|
+
{ url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[[package]]
|
|
33
|
+
name = "extensionmethods"
|
|
34
|
+
source = { editable = "." }
|
|
35
|
+
|
|
36
|
+
[package.dev-dependencies]
|
|
37
|
+
dev = [
|
|
38
|
+
{ name = "pre-commit" },
|
|
39
|
+
{ name = "pytest" },
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[package.metadata]
|
|
43
|
+
|
|
44
|
+
[package.metadata.requires-dev]
|
|
45
|
+
dev = [
|
|
46
|
+
{ name = "pre-commit", specifier = ">=4.5.1" },
|
|
47
|
+
{ name = "pytest", specifier = ">=9.0.2" },
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[[package]]
|
|
51
|
+
name = "filelock"
|
|
52
|
+
version = "3.20.3"
|
|
53
|
+
source = { registry = "https://pypi.org/simple" }
|
|
54
|
+
sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" }
|
|
55
|
+
wheels = [
|
|
56
|
+
{ url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" },
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[[package]]
|
|
60
|
+
name = "identify"
|
|
61
|
+
version = "2.6.16"
|
|
62
|
+
source = { registry = "https://pypi.org/simple" }
|
|
63
|
+
sdist = { url = "https://files.pythonhosted.org/packages/5b/8d/e8b97e6bd3fb6fb271346f7981362f1e04d6a7463abd0de79e1fda17c067/identify-2.6.16.tar.gz", hash = "sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980", size = 99360, upload-time = "2026-01-12T18:58:58.201Z" }
|
|
64
|
+
wheels = [
|
|
65
|
+
{ url = "https://files.pythonhosted.org/packages/b8/58/40fbbcefeda82364720eba5cf2270f98496bdfa19ea75b4cccae79c698e6/identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0", size = 99202, upload-time = "2026-01-12T18:58:56.627Z" },
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[[package]]
|
|
69
|
+
name = "iniconfig"
|
|
70
|
+
version = "2.3.0"
|
|
71
|
+
source = { registry = "https://pypi.org/simple" }
|
|
72
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
|
73
|
+
wheels = [
|
|
74
|
+
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
[[package]]
|
|
78
|
+
name = "nodeenv"
|
|
79
|
+
version = "1.10.0"
|
|
80
|
+
source = { registry = "https://pypi.org/simple" }
|
|
81
|
+
sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" }
|
|
82
|
+
wheels = [
|
|
83
|
+
{ url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" },
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
[[package]]
|
|
87
|
+
name = "packaging"
|
|
88
|
+
version = "26.0"
|
|
89
|
+
source = { registry = "https://pypi.org/simple" }
|
|
90
|
+
sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
|
|
91
|
+
wheels = [
|
|
92
|
+
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
[[package]]
|
|
96
|
+
name = "platformdirs"
|
|
97
|
+
version = "4.5.1"
|
|
98
|
+
source = { registry = "https://pypi.org/simple" }
|
|
99
|
+
sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" }
|
|
100
|
+
wheels = [
|
|
101
|
+
{ url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" },
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
[[package]]
|
|
105
|
+
name = "pluggy"
|
|
106
|
+
version = "1.6.0"
|
|
107
|
+
source = { registry = "https://pypi.org/simple" }
|
|
108
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
|
109
|
+
wheels = [
|
|
110
|
+
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
[[package]]
|
|
114
|
+
name = "pre-commit"
|
|
115
|
+
version = "4.5.1"
|
|
116
|
+
source = { registry = "https://pypi.org/simple" }
|
|
117
|
+
dependencies = [
|
|
118
|
+
{ name = "cfgv" },
|
|
119
|
+
{ name = "identify" },
|
|
120
|
+
{ name = "nodeenv" },
|
|
121
|
+
{ name = "pyyaml" },
|
|
122
|
+
{ name = "virtualenv" },
|
|
123
|
+
]
|
|
124
|
+
sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" }
|
|
125
|
+
wheels = [
|
|
126
|
+
{ url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" },
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
[[package]]
|
|
130
|
+
name = "pygments"
|
|
131
|
+
version = "2.19.2"
|
|
132
|
+
source = { registry = "https://pypi.org/simple" }
|
|
133
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
|
134
|
+
wheels = [
|
|
135
|
+
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
[[package]]
|
|
139
|
+
name = "pytest"
|
|
140
|
+
version = "9.0.2"
|
|
141
|
+
source = { registry = "https://pypi.org/simple" }
|
|
142
|
+
dependencies = [
|
|
143
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
144
|
+
{ name = "iniconfig" },
|
|
145
|
+
{ name = "packaging" },
|
|
146
|
+
{ name = "pluggy" },
|
|
147
|
+
{ name = "pygments" },
|
|
148
|
+
]
|
|
149
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
|
|
150
|
+
wheels = [
|
|
151
|
+
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
[[package]]
|
|
155
|
+
name = "pyyaml"
|
|
156
|
+
version = "6.0.3"
|
|
157
|
+
source = { registry = "https://pypi.org/simple" }
|
|
158
|
+
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
|
|
159
|
+
wheels = [
|
|
160
|
+
{ url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" },
|
|
161
|
+
{ url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" },
|
|
162
|
+
{ url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" },
|
|
163
|
+
{ url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" },
|
|
164
|
+
{ url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" },
|
|
165
|
+
{ url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" },
|
|
166
|
+
{ url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" },
|
|
167
|
+
{ url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" },
|
|
168
|
+
{ url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" },
|
|
169
|
+
{ url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
|
|
170
|
+
{ url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
|
|
171
|
+
{ url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
|
|
172
|
+
{ url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
|
|
173
|
+
{ url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
|
|
174
|
+
{ url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
|
|
175
|
+
{ url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
|
|
176
|
+
{ url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
|
|
177
|
+
{ url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
|
|
178
|
+
{ url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
|
|
179
|
+
{ url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
|
|
180
|
+
{ url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
|
|
181
|
+
{ url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
|
|
182
|
+
{ url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
|
|
183
|
+
{ url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
|
|
184
|
+
{ url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
|
|
185
|
+
{ url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
|
|
186
|
+
{ url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
|
|
187
|
+
{ url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
|
|
188
|
+
{ url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
|
|
189
|
+
{ url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
|
|
190
|
+
{ url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
|
|
191
|
+
{ url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
|
|
192
|
+
{ url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
|
|
193
|
+
{ url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
|
|
194
|
+
{ url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
|
|
195
|
+
{ url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
|
|
196
|
+
{ url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
|
|
197
|
+
{ url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
|
|
198
|
+
{ url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
|
|
199
|
+
{ url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
|
|
200
|
+
{ url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
|
|
201
|
+
{ url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
|
|
202
|
+
{ url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
|
|
203
|
+
{ url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
|
|
204
|
+
{ url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
|
|
205
|
+
{ url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
|
|
206
|
+
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
[[package]]
|
|
210
|
+
name = "virtualenv"
|
|
211
|
+
version = "20.36.1"
|
|
212
|
+
source = { registry = "https://pypi.org/simple" }
|
|
213
|
+
dependencies = [
|
|
214
|
+
{ name = "distlib" },
|
|
215
|
+
{ name = "filelock" },
|
|
216
|
+
{ name = "platformdirs" },
|
|
217
|
+
]
|
|
218
|
+
sdist = { url = "https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba", size = 6032239, upload-time = "2026-01-09T18:21:01.296Z" }
|
|
219
|
+
wheels = [
|
|
220
|
+
{ url = "https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f", size = 6008258, upload-time = "2026-01-09T18:20:59.425Z" },
|
|
221
|
+
]
|