extensionmethods 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ from .extensions import extension
4
+
5
+ try:
6
+ __version__ = version("extensionmethods")
7
+ except PackageNotFoundError:
8
+ __version__ = "noinstall"
9
+
10
+ __all__ = [
11
+ "extension",
12
+ ]
@@ -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,7 @@
1
+ extensionmethods/__init__.py,sha256=GAyBMLVqiT4ASpVxDgxY17deN63Z47A5T7dqquM2unA,239
2
+ extensionmethods/extensions.py,sha256=tKudbhaUxfsQyUSJKnsnWYE6IPy8D1b0D-QYn3E0-F4,1349
3
+ extensionmethods-0.1.0.dist-info/licenses/LICENSE,sha256=PPj-m0UNCQgpVhkr-w1SAqbEUR8M0Mw3ANB4myKxt2E,1067
4
+ extensionmethods-0.1.0.dist-info/METADATA,sha256=k8pNs-Vuen9E1x381Jtr_7bcWfmTLqn-4IZrSpLVRJw,5194
5
+ extensionmethods-0.1.0.dist-info/WHEEL,sha256=YLJXdYXQ2FQ0Uqn2J-6iEIC-3iOey8lH3xCtvFLkd8Q,91
6
+ extensionmethods-0.1.0.dist-info/top_level.txt,sha256=t4mdhmA_07HZDmnLqfr-4PYjbe2sAkVJ3NULiaR6nx0,17
7
+ extensionmethods-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (81.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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 @@
1
+ extensionmethods