swizzle 1.0.0__py3-none-any.whl → 2.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.
swizzle/__init__.py CHANGED
@@ -1,10 +1,255 @@
1
+ # Copyright (c) 2024 Jan T. Müller <mail@jantmueller.com>
2
+
3
+ import warnings
1
4
  from functools import wraps
2
- import sys
3
5
  import types
6
+ import builtins
7
+ import sys as _sys
8
+
9
+ from keyword import iskeyword as _iskeyword
10
+ from operator import itemgetter as _itemgetter
11
+ try:
12
+ from _collections import _tuplegetter
13
+ except ImportError:
14
+ _tuplegetter = lambda index, doc: property(_itemgetter(index), doc=doc)
15
+
16
+
17
+ __version__ = "2.1.0"
4
18
 
5
- __version__ = "1.0.0"
6
19
  MISSING = object()
7
20
 
21
+ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=None, arrange_names = None, seperator = None):
22
+ """
23
+ Create a custom named tuple class with swizzled attributes, allowing for rearranged field names
24
+ and customized attribute access.
25
+
26
+ This function generates a new subclass of `tuple` with named fields, similar to Python's
27
+ `collections.namedtuple`. However, it extends the functionality by allowing field names to be
28
+ rearranged, and attributes to be accessed with a customizable separator. The function also
29
+ provides additional safeguards for field naming and attribute access.
30
+
31
+ Args:
32
+ typename (str): The name of the new named tuple type.
33
+ field_names (sequence of str or str): A sequence of field names for the tuple. If given as
34
+ a single string, it will be split into separate field names.
35
+ rename (bool, optional): If True, invalid field names are automatically replaced with
36
+ positional names. Defaults to False.
37
+ defaults (sequence, optional): Default values for the fields. Defaults to None.
38
+ module (str, optional): The module name in which the named tuple is defined. Defaults to
39
+ the caller's module.
40
+ arrange_names (sequence of str, optional): A sequence of field names indicating the order
41
+ in which fields should be arranged in the resulting named tuple. This allows for fields
42
+ to be rearranged and, unlike standard `namedtuple`, can include duplicates. Defaults
43
+ to the order given in `field_names`.
44
+ separator (str, optional): A separator string that customizes the structure of attribute
45
+ access. If provided, this separator allows attributes to be accessed by combining field
46
+ names with the separator in between them. Defaults to no separator.
47
+
48
+ Returns:
49
+ type: A new subclass of `tuple` with named fields and customized attribute access.
50
+
51
+ Notes:
52
+ - The function is based on `collections.namedtuple` but with additional features such as
53
+ field rearrangement and swizzled attribute access.
54
+ - The `arrange_names` argument allows rearranging the field names, and it can include
55
+ duplicates, which is not possible in a standard `namedtuple`.
56
+ - The generated named tuple class includes methods like `_make`, `_replace`, `__repr__`,
57
+ `_asdict`, and `__getnewargs__`, partially customized to handle the rearranged field order.
58
+ - The `separator` argument enables a custom structure for attribute access, allowing for
59
+ combined attribute names based on the provided separator. If no separator is provided,
60
+ standard attribute access is used.
61
+
62
+ Example:
63
+ >>> Vector = swizzledtuple('Vector', 'x y z', arrange_names='y z x x')
64
+ >>> # Test the swizzle
65
+ >>> v = Vector(1, 2, 3)
66
+ >>> print(v) # Output: Vector(y=2, z=3, x=1, x=1)
67
+ >>> print(v.yzx) # Output: Vector(y=2, z=3, x=1)
68
+ >>> print(v.yzx.xxzyzz) # Output: Vector(x=1, x=1, z=3, y=2, z=3, z=3)
69
+ """
70
+
71
+ if isinstance(field_names, str):
72
+ field_names = field_names.replace(',', ' ').split()
73
+ field_names = list(map(str, field_names))
74
+ if arrange_names is not None:
75
+ if isinstance(arrange_names, str):
76
+ arrange_names = arrange_names.replace(',', ' ').split()
77
+ arrange_names = list(map(str, arrange_names))
78
+ assert set(arrange_names) == set(field_names), 'Arrangement must contain all field names'
79
+ else:
80
+ arrange_names = field_names.copy()
81
+
82
+
83
+ typename = _sys.intern(str(typename))
84
+
85
+ _dir = dir(tuple) + ['__match_args__', '__module__', '__slots__', '_asdict', '_field_defaults', '_fields', '_make', '_replace',]
86
+ if rename:
87
+ seen = set()
88
+ name_newname = {}
89
+ for index, name in enumerate(field_names):
90
+ if (not name.isidentifier()
91
+ or _iskeyword(name)
92
+ or name in _dir
93
+ or name in seen):
94
+ field_names[index] = f'_{index}'
95
+ name_newname[name] = field_names[index]
96
+ seen.add(name)
97
+ for index, name in enumerate(arrange_names):
98
+ arrange_names[index] = name_newname[name]
99
+
100
+ for name in [typename] + field_names:
101
+ if type(name) is not str:
102
+ raise TypeError('Type names and field names must be strings')
103
+ if not name.isidentifier():
104
+ raise ValueError('Type names and field names must be valid '
105
+ f'identifiers: {name!r}')
106
+ if _iskeyword(name):
107
+ raise ValueError('Type names and field names cannot be a '
108
+ f'keyword: {name!r}')
109
+ seen = set()
110
+ for name in field_names:
111
+ if name in _dir and not rename:
112
+ raise ValueError('Field names cannot be an attribute name which would shadow the namedtuple methods or attributes'
113
+ f'{name!r}')
114
+ if name in seen:
115
+ raise ValueError(f'Encountered duplicate field name: {name!r}')
116
+ seen.add(name)
117
+
118
+ arrange_indices = [field_names.index(name) for name in arrange_names]
119
+
120
+ def tuple_new(cls, iterable):
121
+ new = []
122
+ _iterable = list(iterable)
123
+ for index in arrange_indices:
124
+ new.append(_iterable[index])
125
+ return tuple.__new__(cls, new)
126
+
127
+ field_defaults = {}
128
+ if defaults is not None:
129
+ defaults = tuple(defaults)
130
+ if len(defaults) > len(field_names):
131
+ raise TypeError('Got more default values than field names')
132
+ field_defaults = dict(reversed(list(zip(reversed(field_names),
133
+ reversed(defaults)))))
134
+
135
+ field_names = tuple(map(_sys.intern, field_names))
136
+ arrange_names = tuple(map(_sys.intern, arrange_names))
137
+ num_fields = len(field_names)
138
+ num_arrange_fields = len(arrange_names)
139
+ arg_list = ', '.join(field_names)
140
+ if num_fields == 1:
141
+ arg_list += ','
142
+ repr_fmt = '(' + ', '.join(f'{name}=%r' for name in arrange_names) + ')'
143
+ _dict, _tuple, _len, _map, _zip = dict, tuple, len, map, zip
144
+
145
+ namespace = {
146
+ '_tuple_new': tuple_new,
147
+ '__builtins__': {},
148
+ '__name__': f'namedtuple_{typename}',
149
+ }
150
+ code = f'lambda _cls, {arg_list}: _tuple_new(_cls, ({arg_list}))'
151
+ __new__ = eval(code, namespace)
152
+ __new__.__name__ = '__new__'
153
+ __new__.__doc__ = f'Create new instance of {typename}({arg_list})'
154
+ if defaults is not None:
155
+ __new__.__defaults__ = defaults
156
+
157
+ @classmethod
158
+ def _make(cls, iterable):
159
+ result = tuple_new(cls, iterable)
160
+ if _len(result) != num_arrange_fields:
161
+ raise TypeError(f'Expected {num_arrange_fields} arguments, got {len(result)}')
162
+ return result
163
+
164
+ _make.__func__.__doc__ = (f'Make a new {typename} object from a sequence '
165
+ 'or iterable')
166
+
167
+ def _replace(self, /, **kwds):
168
+ def generator():
169
+ for name in field_names:
170
+ if name in kwds:
171
+ yield kwds.pop(name)
172
+ else:
173
+ yield getattr(self, name)
174
+
175
+ result = self._make(iter(generator()))
176
+ if kwds:
177
+ raise ValueError(f'Got unexpected field names: {list(kwds)!r}')
178
+ return result
179
+
180
+ _replace.__doc__ = (f'Return a new {typename} object replacing specified '
181
+ 'fields with new values')
182
+
183
+ def __repr__(self):
184
+ 'Return a nicely formatted representation string'
185
+ return self.__class__.__name__ + repr_fmt % self
186
+
187
+ def _asdict(self):
188
+ 'Return a new dict which maps field names to their values.'
189
+ return _dict(_zip(arrange_names, self))
190
+
191
+ def __getnewargs__(self):
192
+ 'Return self as a plain tuple. Used by copy and pickle.'
193
+ return _tuple(self)
194
+
195
+ @swizzle_attributes_retriever(separator=seperator, type = swizzledtuple)
196
+ def __getattribute__(self, attr_name):
197
+ return super(_tuple, self).__getattribute__(attr_name)
198
+
199
+
200
+
201
+ for method in (
202
+ __new__,
203
+ _make.__func__,
204
+ _replace,
205
+ __repr__,
206
+ _asdict,
207
+ __getnewargs__,
208
+ __getattribute__,
209
+ ):
210
+ method.__qualname__ = f'{typename}.{method.__name__}'
211
+
212
+ class_namespace = {
213
+ '__doc__': f'{typename}({arg_list})',
214
+ '__slots__': (),
215
+ '_fields': field_names,
216
+ '_field_defaults': field_defaults,
217
+ '__new__': __new__,
218
+ '_make': _make,
219
+ '_replace': _replace,
220
+ '__repr__': __repr__,
221
+ '_asdict': _asdict,
222
+ '__getnewargs__': __getnewargs__,
223
+ '__getattribute__': __getattribute__
224
+ }
225
+ seen = set()
226
+ for index, name in enumerate(arrange_names):
227
+ if name in seen:
228
+ continue
229
+ doc = _sys.intern(f'Alias for field number {index}')
230
+ class_namespace[name] = _tuplegetter(index, doc)
231
+ seen.add(name)
232
+
233
+ result = type(typename, (tuple,), class_namespace)
234
+
235
+ if module is None:
236
+ try:
237
+ module = _sys._getframemodulename(1) or '__main__'
238
+ except AttributeError:
239
+ try:
240
+ module = _sys._getframe(1).f_globals.get('__name__', '__main__')
241
+ except (AttributeError, ValueError):
242
+ pass
243
+ if module is not None:
244
+ result.__module__ = module
245
+
246
+ return result
247
+
248
+ # deprecated name
249
+ def swizzlednamedtuple(typename, field_names, *, rename=False, defaults=None, module=None, arrange_names = None, seperator = None):
250
+ warnings.warn("swizzlednamedtuple is deprecated, use swizzledtuple instead", DeprecationWarning, stacklevel=2)
251
+ return swizzledtuple(typename, field_names, rename=rename, defaults=defaults, module=module, arrange_names=arrange_names, seperator=seperator)
252
+
8
253
 
9
254
  # Helper function to split a string based on a separator
10
255
  def split_string(string, separator):
@@ -25,64 +270,101 @@ def collect_attribute_functions(cls):
25
270
  return funcs
26
271
 
27
272
  # Function to combine multiple attribute retrieval functions
28
- def swizzle_attributes_retriever(attribute_funcs, separator=None):
29
- def retrieve_attribute(obj, attr_name):
30
- for func in attribute_funcs:
31
- try:
32
- return func(obj, attr_name)
33
- except AttributeError:
34
- continue
35
- return MISSING
36
-
37
- @wraps(attribute_funcs[-1])
38
- def retrieve_swizzled_attributes(obj, attr_name):
39
- # Attempt to find an exact attribute match
40
- attribute = retrieve_attribute(obj, attr_name)
41
- if attribute is not MISSING:
42
- return attribute
43
-
44
- matched_attributes = []
45
-
46
- # If a separator is provided, split the name accordingly
47
- if separator is not None:
48
- attr_parts = split_string(attr_name, separator)
49
- for part in attr_parts:
50
- attribute = retrieve_attribute(obj, part)
51
- if attribute is not MISSING:
52
- matched_attributes.append(attribute)
53
- else:
54
- # No separator provided, attempt to match substrings
55
- i = 0
56
- while i < len(attr_name):
57
- match_found = False
58
- for j in range(len(attr_name), i, -1):
59
- substring = attr_name[i:j]
60
- attribute = retrieve_attribute(obj, substring)
273
+
274
+ def swizzle_attributes_retriever(attribute_funcs=None, separator=None, type = tuple):
275
+ def _swizzle_attributes_retriever(attribute_funcs):
276
+ if not isinstance(attribute_funcs, list):
277
+ attribute_funcs = [attribute_funcs]
278
+
279
+ def retrieve_attribute(obj, attr_name):
280
+ for func in attribute_funcs:
281
+ try:
282
+ return func(obj, attr_name)
283
+ except AttributeError:
284
+ continue
285
+ return MISSING
286
+
287
+ @wraps(attribute_funcs[-1])
288
+ def retrieve_swizzled_attributes(obj, attr_name):
289
+ # Attempt to find an exact attribute match
290
+ attribute = retrieve_attribute(obj, attr_name)
291
+ if attribute is not MISSING:
292
+ return attribute
293
+
294
+ matched_attributes = []
295
+ arranged_names = []
296
+ # If a separator is provided, split the name accordingly
297
+ if separator is not None:
298
+ attr_parts = split_string(attr_name, separator)
299
+ arranged_names = attr_parts
300
+ for part in attr_parts:
301
+ attribute = retrieve_attribute(obj, part)
61
302
  if attribute is not MISSING:
62
303
  matched_attributes.append(attribute)
63
- i = j # Move index to end of the matched substring
64
- match_found = True
65
- break
66
- if not match_found:
67
- raise AttributeError(f"No matching attribute found for substring: {attr_name[i:]}")
304
+ else:
305
+ # No separator provided, attempt to match substrings
306
+ i = 0
307
+ while i < len(attr_name):
308
+ match_found = False
309
+ for j in range(len(attr_name), i, -1):
310
+ substring = attr_name[i:j]
311
+ attribute = retrieve_attribute(obj, substring)
312
+ if attribute is not MISSING:
313
+ matched_attributes.append(attribute)
314
+ arranged_names.append(substring)
315
+ i = j # Move index to end of the matched substring
316
+ match_found = True
317
+ break
318
+ if not match_found:
319
+ raise AttributeError(f"No matching attribute found for substring: {attr_name[i:]}")
320
+
321
+ if type == swizzledtuple or type == swizzlednamedtuple:
322
+ field_names = set(arranged_names)
323
+ field_values = [retrieve_attribute(obj, name) for name in field_names]
324
+ name = "swizzledtuple"
325
+ if hasattr(obj, "__name__"):
326
+ name = obj.__name__
327
+ elif hasattr(obj, "__class__"):
328
+ if hasattr(obj.__class__, "__name__"):
329
+ name = obj.__class__.__name__
330
+ result = type(name, field_names, arrange_names=arranged_names)
331
+ result = result(*field_values)
332
+ return result
68
333
 
69
- return tuple(matched_attributes)
70
334
 
71
- return retrieve_swizzled_attributes
335
+ return type(matched_attributes)
336
+
337
+ return retrieve_swizzled_attributes
338
+
339
+ if attribute_funcs is not None:
340
+ return _swizzle_attributes_retriever(attribute_funcs)
341
+ else:
342
+ return _swizzle_attributes_retriever
72
343
 
73
344
  # Decorator function to enable swizzling for a class
74
- def swizzle(cls=None, use_meta=False, separator=None):
345
+ def swizzle(cls=None, meta=False, separator=None, type = tuple, **kwargs):
346
+
347
+ if 'use_meta' in kwargs:
348
+ warnings.warn("The 'use_meta' argument is deprecated and will be removed in a future version. Use 'meta' instead.", DeprecationWarning, stacklevel=2)
349
+ meta = kwargs.pop('use_meta')
350
+ if '_type' in kwargs:
351
+ warnings.warn("The '_type' argument is deprecated and will be removed in a future version. Use 'type' instead.", DeprecationWarning, stacklevel=2)
352
+ type = kwargs.pop('_type')
353
+
354
+ _type = builtins.type
355
+
75
356
  def class_decorator(cls):
76
357
  # Collect attribute retrieval functions from the class
77
358
  attribute_funcs = collect_attribute_functions(cls)
78
359
 
79
360
  # Apply the swizzling to the class's attribute retrieval
80
- setattr(cls, attribute_funcs[-1].__name__, swizzle_attributes_retriever(attribute_funcs, separator))
361
+ setattr(cls, attribute_funcs[-1].__name__, swizzle_attributes_retriever(attribute_funcs, separator, type))
81
362
 
82
363
  # Handle meta-class swizzling if requested
83
- if use_meta:
84
- meta_cls = type(cls)
85
- if meta_cls == type:
364
+ if meta:
365
+ print(cls)
366
+ meta_cls = _type(cls)
367
+ if meta_cls == _type:
86
368
  class SwizzledMetaType(meta_cls):
87
369
  pass
88
370
  meta_cls = SwizzledMetaType
@@ -91,7 +373,7 @@ def swizzle(cls=None, use_meta=False, separator=None):
91
373
  cls = meta_cls(cls.__name__, cls.__bases__, dict(cls.__dict__))
92
374
 
93
375
  meta_funcs = collect_attribute_functions(meta_cls)
94
- setattr(meta_cls, meta_funcs[-1].__name__, swizzle_attributes_retriever(meta_funcs, separator))
376
+ setattr(meta_cls, meta_funcs[-1].__name__, swizzle_attributes_retriever(meta_funcs, separator, type))
95
377
 
96
378
  return cls
97
379
 
@@ -104,9 +386,9 @@ def swizzle(cls=None, use_meta=False, separator=None):
104
386
  class Swizzle(types.ModuleType):
105
387
  def __init__(self):
106
388
  types.ModuleType.__init__(self, __name__)
107
- self.__dict__.update(sys.modules[__name__].__dict__)
389
+ self.__dict__.update(_sys.modules[__name__].__dict__)
108
390
 
109
- def __call__(self, cls=None, meta=False, sep = None):
110
- return swizzle(cls, meta, sep)
391
+ def __call__(self, cls=None, meta=False, sep = None, type = swizzledtuple):
392
+ return swizzle(cls, meta, sep, type)
111
393
 
112
- sys.modules[__name__] = Swizzle()
394
+ _sys.modules[__name__] = Swizzle()
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Jan T. Mueller
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,128 @@
1
+ Metadata-Version: 2.1
2
+ Name: swizzle
3
+ Version: 2.1.0
4
+ Summary: Swizzle enables the retrieval of multiple attributes, similar to swizzling in computer graphics.
5
+ Home-page: https://github.com/janthmueller/swizzle
6
+ Author: Jan T. Müller
7
+ Author-email: mail@jantmueller.com
8
+ License: MIT
9
+ Project-URL: Documentation, https://github.com/janthmueller/swizzle/blob/main/README.md
10
+ Project-URL: Source, https://github.com/janthmueller/swizzle
11
+ Project-URL: Tracker, https://github.com/janthmueller/swizzle/issues
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Requires-Python: >=3.6
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+
21
+ # Swizzle
22
+ [![PyPI version](https://badge.fury.io/py/swizzle.svg?kill_cache=1)](https://badge.fury.io/py/swizzle)
23
+ [![Downloads](https://pepy.tech/badge/swizzle?kill_cache=1)](https://pepy.tech/project/swizzle)
24
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/janthmueller/swizzle/blob/main/LICENSE)
25
+ [![GitHub Stars](https://img.shields.io/github/stars/janthmueller/swizzle.svg?kill_cache=1)](https://github.com/janthmueller/swizzle/stargazers)
26
+
27
+ **Swizzle** for Python enhances attribute lookup methods to facilitate dynamic and flexible retrieval of multiple attributes based on specified arrangements of their names.
28
+ > **Update v2:**
29
+ > Introducing `swizzledtuple` , a new function that allows you to create swizzled named tuples. This feature is inspired by the `namedtuple` function from the [collections module](https://docs.python.org/3/library/collections.html#collections.namedtuple) and provides a concise way to define swizzled tuples.
30
+ > ```python
31
+ > from swizzle import swizzledtuple
32
+ >
33
+ > Vector = swizzledtuple('Vector', 'x y z', arrange_names = "y z x x")
34
+ >
35
+ > # Test the swizzle
36
+ > v = Vector(1, 2, 3)
37
+ > print(v) # Output: Vector(y=2, z=3, x=1, x=1)
38
+ > print(v.yzx) # Output: Vector(y = 2, z = 3, x = 1)
39
+ > print(v.yzx.xxzyzz) # Output: Vector(x=1, x=1, z=3, y=2, z=3, z=3)
40
+ >```
41
+
42
+ ### Swizzle Decorator:
43
+
44
+ ```python
45
+ import swizzle
46
+
47
+ @swizzle
48
+ class Vector:
49
+ def __init__(self, x, y, z):
50
+ self.x = x
51
+ self.y = y
52
+ self.z = z
53
+
54
+ # Test the swizzle
55
+ print(Vector(1, 2, 3).yzx) # Output: Vector(y = 2, z = 3, x = 1)
56
+ ```
57
+
58
+
59
+ ## Installation
60
+ ### From PyPI
61
+ ```bash
62
+ pip install swizzle
63
+ ```
64
+ ### From GitHub
65
+ ```bash
66
+ pip install git+https://github.com/janthmueller/swizzle.git
67
+ ```
68
+
69
+ ## Further Examples
70
+
71
+ ### Using `swizzle` with `dataclass`
72
+
73
+ ```python
74
+ import swizzle
75
+ from dataclasses import dataclass
76
+
77
+ @swizzle
78
+ @dataclass
79
+ class Vector:
80
+ x: int
81
+ y: int
82
+ z: int
83
+
84
+ # Test the swizzle
85
+ print(Vector(1, 2, 3).yzx) # Output: Vector(y = 2, z = 3, x = 1)
86
+ ```
87
+
88
+ ### Using `swizzle` with `IntEnum`
89
+
90
+ ```python
91
+ import swizzle
92
+ from enum import IntEnum
93
+
94
+ @swizzle(meta=True)
95
+ class Vector(IntEnum):
96
+ X = 1
97
+ Y = 2
98
+ Z = 3
99
+
100
+ # Test the swizzle
101
+ print(Vector.YXZ) # Output: Vector(Y=<Vector.Y: 2>, X=<Vector.X: 1>, Z=<Vector.Z: 3>)
102
+ ```
103
+ Setting the `meta` argument to `True` in the swizzle decorator extends the `getattr` behavior of the metaclass, enabling attribute swizzling directly on the class itself.
104
+
105
+
106
+ ### Sequential matching
107
+ Attributes are matched from left to right, starting with the longest substring match. This behavior can be controlled by the `seperator` argument in the swizzle decorator.
108
+ ```python
109
+ import swizzle
110
+
111
+ @swizzle(meta=True)
112
+ class Vector:
113
+ x = 1
114
+ y = 2
115
+ z = 3
116
+ xy = 4
117
+ yz = 5
118
+ xz = 6
119
+ xyz = 7
120
+
121
+ # Test the swizzle
122
+ print(Vector.xz) # Output: 6
123
+ print(Vector.yz) # Output: 5
124
+ print(Vector.xyyz) # Output: Vector(xy=4, yz=5)
125
+ print(Vector.xyzx) # Output: Vector(xyz=7, x=1)
126
+ ```
127
+
128
+
@@ -0,0 +1,6 @@
1
+ swizzle/__init__.py,sha256=124893c7gC4KJhrpFAGOuOJb5TVBGSUPyZPm0wilsKQ,16394
2
+ swizzle-2.1.0.dist-info/LICENSE,sha256=WDAegKWtl3rZUiN-SQ2FEQQwEFxlM_jEKQyJRJawJXo,1070
3
+ swizzle-2.1.0.dist-info/METADATA,sha256=x1iq2dpfhqBWHYdn0MeqqL3vqa9Jw-Fb6lt3PITDjfs,3825
4
+ swizzle-2.1.0.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
5
+ swizzle-2.1.0.dist-info/top_level.txt,sha256=XFSQti81x2zM0zAMCY1YD0lqB1eSg5my9BB03uFgCic,8
6
+ swizzle-2.1.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (72.1.0)
2
+ Generator: setuptools (72.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,125 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: swizzle
3
- Version: 1.0.0
4
- Summary: The Swizzle Decorator enables the retrieval of multiple attributes, similar to swizzling in computer graphics.
5
- Home-page: https://github.com/janthmueller/swizzle
6
- Author: Jan T. Müller
7
- Author-email: mail@jantmueller.com
8
- License: MIT
9
- Project-URL: Documentation, https://github.com/janthmueller/swizzle/blob/main/README.md
10
- Project-URL: Source, https://github.com/janthmueller/swizzle
11
- Project-URL: Tracker, https://github.com/janthmueller/swizzle/issues
12
- Classifier: Intended Audience :: Developers
13
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
- Classifier: License :: OSI Approved :: MIT License
15
- Classifier: Operating System :: OS Independent
16
- Classifier: Programming Language :: Python :: 3 :: Only
17
- Requires-Python: >=3.6
18
- Description-Content-Type: text/markdown
19
-
20
- # Swizzle Decorator
21
-
22
- The **Swizzle Decorator** for Python enhances attribute lookup methods (`__getattr__` or `__getattribute__`) to facilitate dynamic and flexible retrieval of multiple attributes based on specified arrangements of their names. This concept is reminiscent of swizzling in computer graphics, where it allows efficient access to components of vectors or coordinates in various orders:
23
-
24
- ```python
25
- import swizzle
26
-
27
- @swizzle
28
- class Vector:
29
- def __init__(self, x, y, z):
30
- self.x = x
31
- self.y = y
32
- self.z = z
33
-
34
- print(Vector(1, 2, 3).yzx) # Output: (2, 3, 1)
35
- ```
36
-
37
- ## Installation
38
- ### From PyPI
39
- ```bash
40
- pip install swizzle
41
- ```
42
- ### From GitHub
43
- ```bash
44
- pip install git+https://github.com/janthmueller/swizzle.git
45
- ```
46
-
47
- ## Further Examples
48
-
49
- ### Using `swizzle` with `dataclass`
50
-
51
- ```python
52
- import swizzle
53
- from dataclasses import dataclass
54
-
55
- @swizzle
56
- @dataclass
57
- class XYZ:
58
- x: int
59
- y: int
60
- z: int
61
-
62
- # Test the swizzle
63
- xyz = XYZ(1, 2, 3)
64
- print(xyz.yzx) # Output: (2, 3, 1)
65
- ```
66
-
67
- ### Using `swizzle` with `IntEnum`
68
-
69
- ```python
70
- import swizzle
71
- from enum import IntEnum
72
-
73
- @swizzle(meta=True)
74
- class XYZ(IntEnum):
75
- X = 1
76
- Y = 2
77
- Z = 3
78
-
79
- # Test the swizzle
80
- print(XYZ.YXZ) # Output: (<XYZ.Y: 2>, <XYZ.X: 1>, <XYZ.Z: 3>)
81
- ```
82
- Setting the `meta` argument to `True` in the swizzle decorator extends the `getattr` behavior of the metaclass, enabling attribute swizzling directly on the class itself.
83
-
84
- ### Using `swizzle` with `NamedTuple`
85
-
86
- ```python
87
- import swizzle
88
- from typing import NamedTuple
89
-
90
- @swizzle
91
- class XYZ(NamedTuple):
92
- x: int
93
- y: int
94
- z: int
95
-
96
- # Test the swizzle
97
- xyz = XYZ(1, 2, 3)
98
- print(xyz.yzx) # Output: (2, 3, 1)
99
- ```
100
-
101
-
102
- ### Sequential matching
103
- Attributes are matched from left to right, starting with the longest substring match.
104
- ```python
105
- import swizzle
106
-
107
- @swizzle(meta=True)
108
- class Test:
109
- x = 1
110
- y = 2
111
- z = 3
112
- xy = 4
113
- yz = 5
114
- xz = 6
115
- xyz = 7
116
-
117
- # Test the swizzle
118
- print(Test.xz) # Output: 6
119
- print(Test.yz) # Output: 5
120
- print(Test.xyyz) # Output: (4, 5)
121
- print(Test.xyzx) # Output: (7, 1)
122
- ```
123
-
124
- ## To Do
125
- - [ ] Swizzle for method args (swizzle+partial)
@@ -1,5 +0,0 @@
1
- swizzle/__init__.py,sha256=9LHl0nK4LB9aEm4264zzhCLL_yF6_2k3-hHdORzapVU,4151
2
- swizzle-1.0.0.dist-info/METADATA,sha256=B8VrYh-Lm_Ixg_J-GrVdNAbpOP3oR6ouRVUSLii8izI,2963
3
- swizzle-1.0.0.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
4
- swizzle-1.0.0.dist-info/top_level.txt,sha256=XFSQti81x2zM0zAMCY1YD0lqB1eSg5my9BB03uFgCic,8
5
- swizzle-1.0.0.dist-info/RECORD,,