swizzle 2.2.1__py3-none-any.whl → 2.3.1__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 +254 -83
- swizzle/trie.py +61 -0
- {swizzle-2.2.1.dist-info → swizzle-2.3.1.dist-info}/METADATA +13 -2
- swizzle-2.3.1.dist-info/RECORD +7 -0
- {swizzle-2.2.1.dist-info → swizzle-2.3.1.dist-info}/WHEEL +1 -1
- swizzle-2.2.1.dist-info/RECORD +0 -6
- {swizzle-2.2.1.dist-info → swizzle-2.3.1.dist-info/licenses}/LICENSE +0 -0
- {swizzle-2.2.1.dist-info → swizzle-2.3.1.dist-info}/top_level.txt +0 -0
swizzle/__init__.py
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
# Copyright (c) 2024 Jan T. Müller <mail@jantmueller.com>
|
2
2
|
|
3
|
-
from functools import wraps
|
4
|
-
import types
|
5
3
|
import builtins
|
6
4
|
import sys as _sys
|
7
|
-
|
5
|
+
import types
|
6
|
+
import unicodedata
|
7
|
+
from enum import EnumType
|
8
|
+
from functools import wraps
|
8
9
|
from keyword import iskeyword as _iskeyword
|
9
10
|
from operator import itemgetter as _itemgetter
|
11
|
+
|
12
|
+
from .trie import Trie
|
13
|
+
|
10
14
|
try:
|
11
15
|
from _collections import _tuplegetter
|
12
16
|
except ImportError:
|
@@ -14,11 +18,21 @@ except ImportError:
|
|
14
18
|
|
15
19
|
_type = builtins.type
|
16
20
|
|
17
|
-
__version__ = "2.
|
21
|
+
__version__ = "2.3.1"
|
18
22
|
|
19
23
|
MISSING = object()
|
20
24
|
|
21
|
-
|
25
|
+
|
26
|
+
def swizzledtuple(
|
27
|
+
typename,
|
28
|
+
field_names,
|
29
|
+
*,
|
30
|
+
rename=False,
|
31
|
+
defaults=None,
|
32
|
+
module=None,
|
33
|
+
arrange_names=None,
|
34
|
+
sep=None,
|
35
|
+
):
|
22
36
|
"""
|
23
37
|
Create a custom named tuple class with swizzled attributes, allowing for rearranged field names
|
24
38
|
and customized attribute access.
|
@@ -41,9 +55,13 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
41
55
|
in which fields should be arranged in the resulting named tuple. This allows for fields
|
42
56
|
to be rearranged and, unlike standard `namedtuple`, can include duplicates. Defaults
|
43
57
|
to the order given in `field_names`.
|
44
|
-
sep (str, optional): A separator string
|
45
|
-
|
46
|
-
|
58
|
+
sep (str, optional): A separator string used to control how attribute names are constructed.
|
59
|
+
If provided, fields will be joined using this separator to create compound attribute names.
|
60
|
+
Defaults to None.
|
61
|
+
|
62
|
+
Special case: If all field names have the same length `n` after optional renaming,
|
63
|
+
and `sep` is still None, then `sep` is automatically set to `"+n"` (e.g. "+2").
|
64
|
+
This indicates that names should be split every `n` characters for improved performance.
|
47
65
|
|
48
66
|
Returns:
|
49
67
|
type: A new subclass of `tuple` with named fields and customized attribute access.
|
@@ -69,29 +87,41 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
69
87
|
"""
|
70
88
|
|
71
89
|
if isinstance(field_names, str):
|
72
|
-
field_names = field_names.replace(
|
90
|
+
field_names = field_names.replace(",", " ").split()
|
73
91
|
field_names = list(map(str, field_names))
|
74
92
|
if arrange_names is not None:
|
75
93
|
if isinstance(arrange_names, str):
|
76
|
-
arrange_names = arrange_names.replace(
|
94
|
+
arrange_names = arrange_names.replace(",", " ").split()
|
77
95
|
arrange_names = list(map(str, arrange_names))
|
78
|
-
assert set(arrange_names) == set(
|
96
|
+
assert set(arrange_names) == set(
|
97
|
+
field_names
|
98
|
+
), "Arrangement must contain all field names"
|
79
99
|
else:
|
80
100
|
arrange_names = field_names.copy()
|
81
101
|
|
82
|
-
|
83
102
|
typename = _sys.intern(str(typename))
|
84
103
|
|
85
|
-
_dir = dir(tuple) + [
|
104
|
+
_dir = dir(tuple) + [
|
105
|
+
"__match_args__",
|
106
|
+
"__module__",
|
107
|
+
"__slots__",
|
108
|
+
"_asdict",
|
109
|
+
"_field_defaults",
|
110
|
+
"_fields",
|
111
|
+
"_make",
|
112
|
+
"_replace",
|
113
|
+
]
|
86
114
|
if rename:
|
87
115
|
seen = set()
|
88
116
|
name_newname = {}
|
89
117
|
for index, name in enumerate(field_names):
|
90
|
-
if (
|
118
|
+
if (
|
119
|
+
not name.isidentifier()
|
91
120
|
or _iskeyword(name)
|
92
121
|
or name in _dir
|
93
|
-
or name in seen
|
94
|
-
|
122
|
+
or name in seen
|
123
|
+
):
|
124
|
+
field_names[index] = f"_{index}"
|
95
125
|
name_newname[name] = field_names[index]
|
96
126
|
seen.add(name)
|
97
127
|
for index, name in enumerate(arrange_names):
|
@@ -99,20 +129,24 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
99
129
|
|
100
130
|
for name in [typename] + field_names:
|
101
131
|
if type(name) is not str:
|
102
|
-
raise TypeError(
|
132
|
+
raise TypeError("Type names and field names must be strings")
|
103
133
|
if not name.isidentifier():
|
104
|
-
raise ValueError(
|
105
|
-
|
134
|
+
raise ValueError(
|
135
|
+
"Type names and field names must be valid " f"identifiers: {name!r}"
|
136
|
+
)
|
106
137
|
if _iskeyword(name):
|
107
|
-
raise ValueError(
|
108
|
-
|
138
|
+
raise ValueError(
|
139
|
+
"Type names and field names cannot be a " f"keyword: {name!r}"
|
140
|
+
)
|
109
141
|
seen = set()
|
110
142
|
for name in field_names:
|
111
|
-
if name in _dir
|
112
|
-
raise ValueError(
|
113
|
-
|
143
|
+
if name in _dir:
|
144
|
+
raise ValueError(
|
145
|
+
"Field names cannot be an attribute name which would shadow the namedtuple methods or attributes"
|
146
|
+
f"{name!r}"
|
147
|
+
)
|
114
148
|
if name in seen:
|
115
|
-
raise ValueError(f
|
149
|
+
raise ValueError(f"Encountered duplicate field name: {name!r}")
|
116
150
|
seen.add(name)
|
117
151
|
|
118
152
|
arrange_indices = [field_names.index(name) for name in arrange_names]
|
@@ -128,29 +162,30 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
128
162
|
if defaults is not None:
|
129
163
|
defaults = tuple(defaults)
|
130
164
|
if len(defaults) > len(field_names):
|
131
|
-
raise TypeError(
|
132
|
-
field_defaults = dict(
|
133
|
-
|
165
|
+
raise TypeError("Got more default values than field names")
|
166
|
+
field_defaults = dict(
|
167
|
+
reversed(list(zip(reversed(field_names), reversed(defaults))))
|
168
|
+
)
|
134
169
|
|
135
170
|
field_names = tuple(map(_sys.intern, field_names))
|
136
171
|
arrange_names = tuple(map(_sys.intern, arrange_names))
|
137
172
|
num_fields = len(field_names)
|
138
173
|
num_arrange_fields = len(arrange_names)
|
139
|
-
arg_list =
|
174
|
+
arg_list = ", ".join(field_names)
|
140
175
|
if num_fields == 1:
|
141
|
-
arg_list +=
|
142
|
-
repr_fmt =
|
176
|
+
arg_list += ","
|
177
|
+
repr_fmt = "(" + ", ".join(f"{name}=%r" for name in arrange_names) + ")"
|
143
178
|
_dict, _tuple, _len, _map, _zip = dict, tuple, len, map, zip
|
144
179
|
|
145
180
|
namespace = {
|
146
|
-
|
147
|
-
|
148
|
-
|
181
|
+
"_tuple_new": tuple_new,
|
182
|
+
"__builtins__": {},
|
183
|
+
"__name__": f"swizzledtuple_{typename}",
|
149
184
|
}
|
150
|
-
code = f
|
185
|
+
code = f"lambda _cls, {arg_list}: _tuple_new(_cls, ({arg_list}))"
|
151
186
|
__new__ = eval(code, namespace)
|
152
|
-
__new__.__name__ =
|
153
|
-
__new__.__doc__ = f
|
187
|
+
__new__.__name__ = "__new__"
|
188
|
+
__new__.__doc__ = f"Create new instance of {typename}({arg_list})"
|
154
189
|
if defaults is not None:
|
155
190
|
__new__.__defaults__ = defaults
|
156
191
|
|
@@ -158,11 +193,14 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
158
193
|
def _make(cls, iterable):
|
159
194
|
result = tuple_new(cls, iterable)
|
160
195
|
if _len(result) != num_arrange_fields:
|
161
|
-
raise TypeError(
|
196
|
+
raise TypeError(
|
197
|
+
f"Expected {num_arrange_fields} arguments, got {len(result)}"
|
198
|
+
)
|
162
199
|
return result
|
163
200
|
|
164
|
-
_make.__func__.__doc__ = (
|
165
|
-
|
201
|
+
_make.__func__.__doc__ = (
|
202
|
+
f"Make a new {typename} object from a sequence " "or iterable"
|
203
|
+
)
|
166
204
|
|
167
205
|
def _replace(self, /, **kwds):
|
168
206
|
def generator():
|
@@ -174,29 +212,57 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
174
212
|
|
175
213
|
result = self._make(iter(generator()))
|
176
214
|
if kwds:
|
177
|
-
raise ValueError(f
|
215
|
+
raise ValueError(f"Got unexpected field names: {list(kwds)!r}")
|
178
216
|
return result
|
179
217
|
|
180
|
-
_replace.__doc__ = (
|
181
|
-
|
218
|
+
_replace.__doc__ = (
|
219
|
+
f"Return a new {typename} object replacing specified " "fields with new values"
|
220
|
+
)
|
182
221
|
|
183
222
|
def __repr__(self):
|
184
|
-
|
223
|
+
"Return a nicely formatted representation string"
|
185
224
|
return self.__class__.__name__ + repr_fmt % self
|
186
225
|
|
187
226
|
def _asdict(self):
|
188
|
-
|
227
|
+
"Return a new dict which maps field names to their values."
|
189
228
|
return _dict(_zip(arrange_names, self))
|
190
229
|
|
191
230
|
def __getnewargs__(self):
|
192
|
-
|
231
|
+
"Return self as a plain tuple. Used by copy and pickle."
|
193
232
|
return _tuple(self)
|
194
233
|
|
195
|
-
@swizzle_attributes_retriever(sep=sep, type =
|
234
|
+
@swizzle_attributes_retriever(sep=sep, type=swizzledtuple, only_attrs=field_names)
|
196
235
|
def __getattribute__(self, attr_name):
|
197
236
|
return super(_tuple, self).__getattribute__(attr_name)
|
198
237
|
|
238
|
+
def __getitem__(self, index):
|
239
|
+
if not isinstance(index, slice):
|
240
|
+
return _tuple.__getitem__(self, index)
|
199
241
|
|
242
|
+
selected_indices = arrange_indices[index]
|
243
|
+
selected_values = _tuple.__getitem__(self, index)
|
244
|
+
|
245
|
+
seen = set()
|
246
|
+
filtered = [
|
247
|
+
(i, v, field_names[i])
|
248
|
+
for i, v in zip(selected_indices, selected_values)
|
249
|
+
if not (i in seen or seen.add(i))
|
250
|
+
]
|
251
|
+
|
252
|
+
if filtered:
|
253
|
+
_, filtered_values, filtered_names = zip(*filtered)
|
254
|
+
else:
|
255
|
+
filtered_values, filtered_names = (), ()
|
256
|
+
|
257
|
+
return swizzledtuple(
|
258
|
+
typename,
|
259
|
+
filtered_names,
|
260
|
+
rename=rename,
|
261
|
+
defaults=filtered_values,
|
262
|
+
module=module,
|
263
|
+
arrange_names=arrange_names[index],
|
264
|
+
sep=sep,
|
265
|
+
)()
|
200
266
|
|
201
267
|
for method in (
|
202
268
|
__new__,
|
@@ -206,27 +272,29 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
206
272
|
_asdict,
|
207
273
|
__getnewargs__,
|
208
274
|
__getattribute__,
|
275
|
+
__getitem__,
|
209
276
|
):
|
210
|
-
method.__qualname__ = f
|
277
|
+
method.__qualname__ = f"{typename}.{method.__name__}"
|
211
278
|
|
212
279
|
class_namespace = {
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
280
|
+
"__doc__": f"{typename}({arg_list})",
|
281
|
+
"__slots__": (),
|
282
|
+
"_fields": field_names,
|
283
|
+
"_field_defaults": field_defaults,
|
284
|
+
"__new__": __new__,
|
285
|
+
"_make": _make,
|
286
|
+
"_replace": _replace,
|
287
|
+
"__repr__": __repr__,
|
288
|
+
"_asdict": _asdict,
|
289
|
+
"__getnewargs__": __getnewargs__,
|
290
|
+
"__getattribute__": __getattribute__,
|
291
|
+
"__getitem__": __getitem__,
|
224
292
|
}
|
225
293
|
seen = set()
|
226
294
|
for index, name in enumerate(arrange_names):
|
227
295
|
if name in seen:
|
228
296
|
continue
|
229
|
-
doc = _sys.intern(f
|
297
|
+
doc = _sys.intern(f"Alias for field number {index}")
|
230
298
|
class_namespace[name] = _tuplegetter(index, doc)
|
231
299
|
seen.add(name)
|
232
300
|
|
@@ -234,10 +302,10 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
234
302
|
|
235
303
|
if module is None:
|
236
304
|
try:
|
237
|
-
module = _sys._getframemodulename(1) or
|
305
|
+
module = _sys._getframemodulename(1) or "__main__"
|
238
306
|
except AttributeError:
|
239
307
|
try:
|
240
|
-
module = _sys._getframe(1).f_globals.get(
|
308
|
+
module = _sys._getframe(1).f_globals.get("__name__", "__main__")
|
241
309
|
except (AttributeError, ValueError):
|
242
310
|
pass
|
243
311
|
if module is not None:
|
@@ -245,27 +313,69 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
245
313
|
|
246
314
|
return result
|
247
315
|
|
316
|
+
|
248
317
|
# Helper function to split a string based on a sep
|
249
318
|
def split_string(string, sep):
|
250
|
-
if sep ==
|
251
|
-
|
319
|
+
if sep[0] == "+":
|
320
|
+
n = int(sep)
|
321
|
+
return [string[i : i + n] for i in range(0, len(string), n)]
|
252
322
|
else:
|
253
323
|
return string.split(sep)
|
254
324
|
|
325
|
+
|
255
326
|
# Helper function to collect attribute retrieval functions from a class or meta-class
|
256
327
|
def collect_attribute_functions(cls):
|
257
328
|
funcs = []
|
258
|
-
if hasattr(cls,
|
329
|
+
if hasattr(cls, "__getattribute__"):
|
259
330
|
funcs.append(cls.__getattribute__)
|
260
|
-
if hasattr(cls,
|
331
|
+
if hasattr(cls, "__getattr__"):
|
261
332
|
funcs.append(cls.__getattr__)
|
262
333
|
if not funcs:
|
263
|
-
raise AttributeError(
|
334
|
+
raise AttributeError(
|
335
|
+
"No __getattr__ or __getattribute__ found on the class or meta-class"
|
336
|
+
)
|
264
337
|
return funcs
|
265
338
|
|
339
|
+
|
266
340
|
# Function to combine multiple attribute retrieval functions
|
267
341
|
|
268
|
-
|
342
|
+
|
343
|
+
def is_valid_sep(s):
|
344
|
+
# if not s:
|
345
|
+
# return False
|
346
|
+
if s[0] == "+" and s[1:].isdigit():
|
347
|
+
return True
|
348
|
+
for ch in s:
|
349
|
+
if ch == "_":
|
350
|
+
continue
|
351
|
+
cat = unicodedata.category(ch)
|
352
|
+
if not (cat.startswith("L") or cat == "Nd"):
|
353
|
+
return False
|
354
|
+
return True
|
355
|
+
|
356
|
+
|
357
|
+
def swizzle_attributes_retriever(
|
358
|
+
attribute_funcs=None, sep=None, type=swizzledtuple, only_attrs=None
|
359
|
+
):
|
360
|
+
trie = None
|
361
|
+
|
362
|
+
if sep == "":
|
363
|
+
sep = "+1" # for backwards compatibility, remove on next version
|
364
|
+
|
365
|
+
if sep is None and only_attrs:
|
366
|
+
only_attrs_length = set(len(fname) for fname in only_attrs)
|
367
|
+
if len(only_attrs_length) == 1:
|
368
|
+
sep = f"+{next(iter(only_attrs_length))}"
|
369
|
+
else:
|
370
|
+
trie = Trie(only_attrs)
|
371
|
+
|
372
|
+
if sep is not None and not is_valid_sep(sep):
|
373
|
+
raise ValueError(
|
374
|
+
f"Invalid value for sep: {sep!r}. Must be either:"
|
375
|
+
" (1) a non-empty string containing only letters, digits, or underscores, "
|
376
|
+
"or (2) a pattern of the form '+N' where N is a positive integer."
|
377
|
+
)
|
378
|
+
|
269
379
|
def _swizzle_attributes_retriever(attribute_funcs):
|
270
380
|
if not isinstance(attribute_funcs, list):
|
271
381
|
attribute_funcs = [attribute_funcs]
|
@@ -292,11 +402,25 @@ def swizzle_attributes_retriever(attribute_funcs=None, sep=None, type = swizzled
|
|
292
402
|
attr_parts = split_string(attr_name, sep)
|
293
403
|
arranged_names = attr_parts
|
294
404
|
for part in attr_parts:
|
405
|
+
if only_attrs and part not in only_attrs:
|
406
|
+
raise AttributeError(
|
407
|
+
f"Attribute {part} is not part of an allowed field for swizzling"
|
408
|
+
)
|
295
409
|
attribute = retrieve_attribute(obj, part)
|
296
410
|
if attribute is not MISSING:
|
297
411
|
matched_attributes.append(attribute)
|
298
412
|
else:
|
299
|
-
raise AttributeError(f"No matching attribute found for
|
413
|
+
raise AttributeError(f"No matching attribute found for {part}")
|
414
|
+
elif only_attrs:
|
415
|
+
arranged_names = trie.split_longest_prefix(attr_name)
|
416
|
+
if arranged_names is None:
|
417
|
+
raise AttributeError(f"No matching attribute found for {attr_name}")
|
418
|
+
for name in arranged_names:
|
419
|
+
attribute = retrieve_attribute(obj, name)
|
420
|
+
if attribute is not MISSING:
|
421
|
+
matched_attributes.append(attribute)
|
422
|
+
else:
|
423
|
+
raise AttributeError(f"No matching attribute found for {name}")
|
300
424
|
else:
|
301
425
|
# No sep provided, attempt to match substrings
|
302
426
|
i = 0
|
@@ -312,7 +436,9 @@ def swizzle_attributes_retriever(attribute_funcs=None, sep=None, type = swizzled
|
|
312
436
|
match_found = True
|
313
437
|
break
|
314
438
|
if not match_found:
|
315
|
-
raise AttributeError(
|
439
|
+
raise AttributeError(
|
440
|
+
f"No matching attribute found for substring: {attr_name[i:]}"
|
441
|
+
)
|
316
442
|
|
317
443
|
if type == swizzledtuple:
|
318
444
|
field_names = set(arranged_names)
|
@@ -323,11 +449,15 @@ def swizzle_attributes_retriever(attribute_funcs=None, sep=None, type = swizzled
|
|
323
449
|
elif hasattr(obj, "__class__"):
|
324
450
|
if hasattr(obj.__class__, "__name__"):
|
325
451
|
name = obj.__class__.__name__
|
326
|
-
result = type(
|
452
|
+
result = type(
|
453
|
+
name,
|
454
|
+
field_names,
|
455
|
+
arrange_names=arranged_names,
|
456
|
+
sep=sep,
|
457
|
+
)
|
327
458
|
result = result(*field_values)
|
328
459
|
return result
|
329
460
|
|
330
|
-
|
331
461
|
return type(matched_attributes)
|
332
462
|
|
333
463
|
return retrieve_swizzled_attributes
|
@@ -337,28 +467,66 @@ def swizzle_attributes_retriever(attribute_funcs=None, sep=None, type = swizzled
|
|
337
467
|
else:
|
338
468
|
return _swizzle_attributes_retriever
|
339
469
|
|
340
|
-
|
341
|
-
def swizzle(cls=None, meta=False, sep=None, type =
|
470
|
+
|
471
|
+
def swizzle(cls=None, meta=False, sep=None, type=tuple, only_attrs=None):
|
472
|
+
|
473
|
+
def preserve_metadata(
|
474
|
+
target,
|
475
|
+
source,
|
476
|
+
keys=("__name__", "__qualname__", "__doc__", "__module__", "__annotations__"),
|
477
|
+
):
|
478
|
+
for key in keys:
|
479
|
+
if hasattr(source, key):
|
480
|
+
try:
|
481
|
+
setattr(target, key, getattr(source, key))
|
482
|
+
except (TypeError, AttributeError):
|
483
|
+
pass # some attributes may be read-only
|
342
484
|
|
343
485
|
def class_decorator(cls):
|
344
486
|
# Collect attribute retrieval functions from the class
|
345
487
|
attribute_funcs = collect_attribute_functions(cls)
|
346
488
|
|
347
489
|
# Apply the swizzling to the class's attribute retrieval
|
348
|
-
setattr(
|
490
|
+
setattr(
|
491
|
+
cls,
|
492
|
+
attribute_funcs[-1].__name__,
|
493
|
+
swizzle_attributes_retriever(attribute_funcs, sep, type, only_attrs),
|
494
|
+
)
|
349
495
|
|
350
496
|
# Handle meta-class swizzling if requested
|
351
497
|
if meta:
|
352
498
|
meta_cls = _type(cls)
|
353
|
-
|
354
|
-
|
499
|
+
|
500
|
+
class SwizzledMetaType(meta_cls):
|
501
|
+
pass
|
502
|
+
|
503
|
+
if meta_cls == EnumType:
|
504
|
+
|
505
|
+
def cfem_dummy(*args, **kwargs):
|
355
506
|
pass
|
356
|
-
meta_cls = SwizzledMetaType
|
357
|
-
cls = meta_cls(cls.__name__, cls.__bases__, dict(cls.__dict__))
|
358
507
|
|
359
|
-
|
360
|
-
|
508
|
+
cfem = SwizzledMetaType._check_for_existing_members_
|
509
|
+
SwizzledMetaType._check_for_existing_members_ = cfem_dummy
|
510
|
+
|
511
|
+
class SwizzledClass(cls, metaclass=SwizzledMetaType):
|
512
|
+
pass
|
513
|
+
|
514
|
+
if meta_cls == EnumType:
|
515
|
+
SwizzledMetaType._check_for_existing_members_ = cfem
|
516
|
+
|
517
|
+
# Preserve metadata on swizzled meta and class
|
518
|
+
preserve_metadata(SwizzledMetaType, meta_cls)
|
519
|
+
preserve_metadata(SwizzledClass, cls)
|
361
520
|
|
521
|
+
meta_cls = SwizzledMetaType
|
522
|
+
cls = SwizzledClass
|
523
|
+
|
524
|
+
meta_funcs = collect_attribute_functions(meta_cls)
|
525
|
+
setattr(
|
526
|
+
meta_cls,
|
527
|
+
meta_funcs[-1].__name__,
|
528
|
+
swizzle_attributes_retriever(meta_funcs, sep, type, only_attrs),
|
529
|
+
)
|
362
530
|
return cls
|
363
531
|
|
364
532
|
if cls is None:
|
@@ -372,7 +540,10 @@ class Swizzle(types.ModuleType):
|
|
372
540
|
types.ModuleType.__init__(self, __name__)
|
373
541
|
self.__dict__.update(_sys.modules[__name__].__dict__)
|
374
542
|
|
375
|
-
def __call__(
|
376
|
-
|
543
|
+
def __call__(
|
544
|
+
self, cls=None, meta=False, sep=None, type=swizzledtuple, only_attrs=None
|
545
|
+
):
|
546
|
+
return swizzle(cls, meta, sep, type, only_attrs)
|
547
|
+
|
377
548
|
|
378
549
|
_sys.modules[__name__] = Swizzle()
|
swizzle/trie.py
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
class TrieNode:
|
2
|
+
def __init__(self):
|
3
|
+
self.children = {}
|
4
|
+
self.is_end = False
|
5
|
+
|
6
|
+
def add(self, word):
|
7
|
+
node = self
|
8
|
+
for char in word:
|
9
|
+
if char not in node.children:
|
10
|
+
node.children[char] = TrieNode()
|
11
|
+
node = node.children[char]
|
12
|
+
node.is_end = True
|
13
|
+
|
14
|
+
def step(self, char):
|
15
|
+
return self.children.get(char)
|
16
|
+
|
17
|
+
|
18
|
+
class Trie:
|
19
|
+
def __init__(self, words=None):
|
20
|
+
self.root = TrieNode()
|
21
|
+
if words:
|
22
|
+
for word in words:
|
23
|
+
self.add(word)
|
24
|
+
|
25
|
+
def add(self, word):
|
26
|
+
self.root.add(word)
|
27
|
+
|
28
|
+
def split_longest_prefix(self, query):
|
29
|
+
result = []
|
30
|
+
length = len(query)
|
31
|
+
i = 0
|
32
|
+
while i < length:
|
33
|
+
node = self.root
|
34
|
+
longest_end = -1
|
35
|
+
j = i
|
36
|
+
children = node.children # cache children dict
|
37
|
+
while j < length:
|
38
|
+
next_node = children.get(query[j])
|
39
|
+
if not next_node:
|
40
|
+
break
|
41
|
+
node = next_node
|
42
|
+
children = node.children # update cache
|
43
|
+
j += 1
|
44
|
+
if node.is_end:
|
45
|
+
longest_end = j
|
46
|
+
if longest_end == -1:
|
47
|
+
return None
|
48
|
+
result.append(query[i:longest_end])
|
49
|
+
i = longest_end
|
50
|
+
return result
|
51
|
+
|
52
|
+
|
53
|
+
if __name__ == "__main__":
|
54
|
+
words = ["mango", "man", "go", "bat", "manbat"]
|
55
|
+
trie = Trie(words)
|
56
|
+
|
57
|
+
print(trie.split_longest_prefix("mangomanmanbat")) # ['mango', 'man', 'bat']
|
58
|
+
print(trie.split_longest_prefix("manbat")) # ['manbat']
|
59
|
+
print(trie.split_longest_prefix("mango")) # ['mango']
|
60
|
+
print(trie.split_longest_prefix("mangoman")) # ['mango', 'man']
|
61
|
+
print(trie.split_longest_prefix("unknownword")) # None
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: swizzle
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.3.1
|
4
4
|
Summary: Swizzle enables the retrieval of multiple attributes, similar to swizzling in computer graphics.
|
5
5
|
Home-page: https://github.com/janthmueller/swizzle
|
6
6
|
Author: Jan T. Müller
|
@@ -17,6 +17,17 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
17
|
Requires-Python: >=3.6
|
18
18
|
Description-Content-Type: text/markdown
|
19
19
|
License-File: LICENSE
|
20
|
+
Dynamic: author
|
21
|
+
Dynamic: author-email
|
22
|
+
Dynamic: classifier
|
23
|
+
Dynamic: description
|
24
|
+
Dynamic: description-content-type
|
25
|
+
Dynamic: home-page
|
26
|
+
Dynamic: license
|
27
|
+
Dynamic: license-file
|
28
|
+
Dynamic: project-url
|
29
|
+
Dynamic: requires-python
|
30
|
+
Dynamic: summary
|
20
31
|
|
21
32
|
# Swizzle
|
22
33
|
|
@@ -0,0 +1,7 @@
|
|
1
|
+
swizzle/__init__.py,sha256=8lOReILgZiQ38ez_BTRsVTbWbHJ2cHfQM9edkU6voOQ,19996
|
2
|
+
swizzle/trie.py,sha256=JutRoUHut2kbNJZ8hmHMCaXP8QHGl7KXzLv2rRVFrb0,1791
|
3
|
+
swizzle-2.3.1.dist-info/licenses/LICENSE,sha256=WDAegKWtl3rZUiN-SQ2FEQQwEFxlM_jEKQyJRJawJXo,1070
|
4
|
+
swizzle-2.3.1.dist-info/METADATA,sha256=QYsGAWzGAB3i25i6zZZOqHM7PkIbMmEqNleLNOr-ttk,5684
|
5
|
+
swizzle-2.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
6
|
+
swizzle-2.3.1.dist-info/top_level.txt,sha256=XFSQti81x2zM0zAMCY1YD0lqB1eSg5my9BB03uFgCic,8
|
7
|
+
swizzle-2.3.1.dist-info/RECORD,,
|
swizzle-2.2.1.dist-info/RECORD
DELETED
@@ -1,6 +0,0 @@
|
|
1
|
-
swizzle/__init__.py,sha256=_clk7y4NCO9KY-I-iSNsLc6iT8ylLcNPD4aiqoun1NE,15304
|
2
|
-
swizzle-2.2.1.dist-info/LICENSE,sha256=WDAegKWtl3rZUiN-SQ2FEQQwEFxlM_jEKQyJRJawJXo,1070
|
3
|
-
swizzle-2.2.1.dist-info/METADATA,sha256=vsupVSVUEg4uC3ZodBYPaLyUhvVPSXr2AgG9om0WMlY,5450
|
4
|
-
swizzle-2.2.1.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
5
|
-
swizzle-2.2.1.dist-info/top_level.txt,sha256=XFSQti81x2zM0zAMCY1YD0lqB1eSg5my9BB03uFgCic,8
|
6
|
-
swizzle-2.2.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|