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 +334 -52
- swizzle-2.1.0.dist-info/LICENSE +21 -0
- swizzle-2.1.0.dist-info/METADATA +128 -0
- swizzle-2.1.0.dist-info/RECORD +6 -0
- {swizzle-1.0.0.dist-info → swizzle-2.1.0.dist-info}/WHEEL +1 -1
- swizzle-1.0.0.dist-info/METADATA +0 -125
- swizzle-1.0.0.dist-info/RECORD +0 -5
- {swizzle-1.0.0.dist-info → swizzle-2.1.0.dist-info}/top_level.txt +0 -0
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
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,
|
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
|
84
|
-
|
85
|
-
|
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(
|
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
|
-
|
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
|
+
[](https://badge.fury.io/py/swizzle)
|
23
|
+
[](https://pepy.tech/project/swizzle)
|
24
|
+
[](https://github.com/janthmueller/swizzle/blob/main/LICENSE)
|
25
|
+
[](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,,
|
swizzle-1.0.0.dist-info/METADATA
DELETED
@@ -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)
|
swizzle-1.0.0.dist-info/RECORD
DELETED
@@ -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,,
|
File without changes
|