enhlib 0.0.1__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.
- enhlib-0.0.1/PKG-INFO +32 -0
- enhlib-0.0.1/enhlib/__init__.py +1 -0
- enhlib-0.0.1/enhlib/collections.py +448 -0
- enhlib-0.0.1/enhlib/contextlib.py +13 -0
- enhlib-0.0.1/enhlib/csv.py +264 -0
- enhlib-0.0.1/enhlib/functools.py +66 -0
- enhlib-0.0.1/enhlib/itertools.py +153 -0
- enhlib-0.0.1/enhlib/random.py +52 -0
- enhlib-0.0.1/enhlib/text.py +42 -0
- enhlib-0.0.1/enhlib/types.py +23 -0
- enhlib-0.0.1/enhlib.egg-info/PKG-INFO +32 -0
- enhlib-0.0.1/enhlib.egg-info/SOURCES.txt +14 -0
- enhlib-0.0.1/enhlib.egg-info/dependency_links.txt +1 -0
- enhlib-0.0.1/enhlib.egg-info/top_level.txt +1 -0
- enhlib-0.0.1/setup.cfg +4 -0
- enhlib-0.0.1/setup.py +72 -0
enhlib-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: enhlib
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, and NamedConstants
|
|
5
|
+
Home-page: https://github.com/ethanfurman/enhlib
|
|
6
|
+
Author: Ethan Furman
|
|
7
|
+
Author-email: ethan@stoneleaf.us
|
|
8
|
+
License: BSD License
|
|
9
|
+
Platform: UNKNOWN
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Topic :: Software Development
|
|
15
|
+
Classifier: Programming Language :: Python :: 2.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.4
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.5
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
27
|
+
Provides: enhlib
|
|
28
|
+
|
|
29
|
+
Enhanced Library -- extras beyond Python's stdlib
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = 0, 0, 1
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
from __future__ import division, print_function
|
|
2
|
+
|
|
3
|
+
from collections import OrderedDict
|
|
4
|
+
from .types import MISSING
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AttrDict(object):
|
|
8
|
+
"""
|
|
9
|
+
allows dictionary lookup using . notation
|
|
10
|
+
allows a default similar to defaultdict
|
|
11
|
+
iterations always ordered by key
|
|
12
|
+
"""
|
|
13
|
+
_internal = ['_illegal', '_keys', '_values', '_default', '_ordered']
|
|
14
|
+
_default = None
|
|
15
|
+
_ordered = True
|
|
16
|
+
_illegal = ()
|
|
17
|
+
_values = {}
|
|
18
|
+
_keys = []
|
|
19
|
+
|
|
20
|
+
def __init__(self, *args, **kwds):
|
|
21
|
+
"kwds is evaluated last"
|
|
22
|
+
if 'default' in kwds:
|
|
23
|
+
self._default = kwds.pop('default')
|
|
24
|
+
self._ordered = True
|
|
25
|
+
self._keys = []
|
|
26
|
+
self._values = _values = {}
|
|
27
|
+
self._illegal = _illegal = tuple([attr for attr in dir(_values) if attr[0] != '_'])
|
|
28
|
+
if self._default is None:
|
|
29
|
+
default_factory = lambda : False
|
|
30
|
+
else:
|
|
31
|
+
default_factory = self._default
|
|
32
|
+
for arg in args:
|
|
33
|
+
# first, see if it's a lone string
|
|
34
|
+
if isinstance(arg, basestring):
|
|
35
|
+
arg = [(arg, default_factory())]
|
|
36
|
+
elif isinstance(arg, tuple):
|
|
37
|
+
# had better be (name, value)
|
|
38
|
+
arg = [arg, ]
|
|
39
|
+
# next, see if it's a mapping
|
|
40
|
+
elif hasattr(arg, 'items'):
|
|
41
|
+
new_arg = arg.items()
|
|
42
|
+
if isinstance(arg, OrderedDict):
|
|
43
|
+
pass
|
|
44
|
+
elif isinstance(arg, AttrDict) and arg._ordered:
|
|
45
|
+
pass
|
|
46
|
+
else:
|
|
47
|
+
self._ordered = False
|
|
48
|
+
arg = new_arg
|
|
49
|
+
# now iterate over it
|
|
50
|
+
for item in arg:
|
|
51
|
+
if isinstance(item, basestring):
|
|
52
|
+
key, value = item, default_factory()
|
|
53
|
+
else:
|
|
54
|
+
key, value = item
|
|
55
|
+
if not isinstance(key, basestring):
|
|
56
|
+
raise ValueError('keys must be strings, but %r is %r' % (key, type(key)))
|
|
57
|
+
if key in _illegal:
|
|
58
|
+
raise ValueError('%r is a reserved word' % key)
|
|
59
|
+
_values[key] = value
|
|
60
|
+
if key not in self._keys:
|
|
61
|
+
self._keys.append(key)
|
|
62
|
+
if kwds:
|
|
63
|
+
self._ordered = False
|
|
64
|
+
_values.update(kwds)
|
|
65
|
+
self._keys = list(set(self._keys + list(kwds.keys())))
|
|
66
|
+
assert set(self._keys) == set(self._values.keys()), "%r is not equal to %r" % (self._keys, self._values.keys())
|
|
67
|
+
|
|
68
|
+
def __contains__(self, key):
|
|
69
|
+
return key in self._values
|
|
70
|
+
|
|
71
|
+
def __delitem__(self, name):
|
|
72
|
+
if name[0] == '_':
|
|
73
|
+
raise KeyError("illegal key name: %r" % name)
|
|
74
|
+
if name not in self._values:
|
|
75
|
+
raise KeyError("%s: no such key" % name)
|
|
76
|
+
self._values.pop(name)
|
|
77
|
+
self._keys.remove(name)
|
|
78
|
+
assert set(self._keys) == set(self._values.keys())
|
|
79
|
+
|
|
80
|
+
def __delattr__(self, name):
|
|
81
|
+
if name[0] == '_':
|
|
82
|
+
raise AttributeError("illegal key name: %r" % name)
|
|
83
|
+
if name not in self._values:
|
|
84
|
+
raise AttributeError("%s: no such key" % name)
|
|
85
|
+
self._values.pop(name)
|
|
86
|
+
self._keys.remove(name)
|
|
87
|
+
assert set(self._keys) == set(self._values.keys())
|
|
88
|
+
|
|
89
|
+
def __hash__(self):
|
|
90
|
+
return hash(tuple(sorted(self._keys)))
|
|
91
|
+
|
|
92
|
+
def __eq__(self, other):
|
|
93
|
+
try:
|
|
94
|
+
if isinstance(other, AttrDict):
|
|
95
|
+
other = other._values
|
|
96
|
+
elif not isinstance(other, dict):
|
|
97
|
+
return NotImplemented
|
|
98
|
+
return other == self._values
|
|
99
|
+
except UnicodeWarning:
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
def __ne__(self, other):
|
|
103
|
+
result = self == other
|
|
104
|
+
if result is NotImplemented:
|
|
105
|
+
return result
|
|
106
|
+
else:
|
|
107
|
+
return not result
|
|
108
|
+
|
|
109
|
+
def __getitem__(self, name):
|
|
110
|
+
if name in self._values:
|
|
111
|
+
return self._values[name]
|
|
112
|
+
elif self._default:
|
|
113
|
+
result = self._values[name] = self._default()
|
|
114
|
+
self._keys.append(name)
|
|
115
|
+
assert set(self._keys) == set(self._values.keys())
|
|
116
|
+
return result
|
|
117
|
+
else:
|
|
118
|
+
raise KeyError(name)
|
|
119
|
+
|
|
120
|
+
def __getattr__(self, name):
|
|
121
|
+
if name in self._values:
|
|
122
|
+
return self._values[name]
|
|
123
|
+
attr = getattr(self._values, name, None)
|
|
124
|
+
if attr is not None:
|
|
125
|
+
return attr
|
|
126
|
+
elif self._default:
|
|
127
|
+
result = self._values[name] = self._default()
|
|
128
|
+
self._keys.append(name)
|
|
129
|
+
assert set(self._keys) == set(self._values.keys())
|
|
130
|
+
return result
|
|
131
|
+
else:
|
|
132
|
+
raise AttributeError(name)
|
|
133
|
+
|
|
134
|
+
def __iter__(self):
|
|
135
|
+
if self._ordered:
|
|
136
|
+
return iter(self._keys)
|
|
137
|
+
else:
|
|
138
|
+
return iter(sorted(self._keys))
|
|
139
|
+
|
|
140
|
+
def __len__(self):
|
|
141
|
+
return len(self._values)
|
|
142
|
+
|
|
143
|
+
def __setitem__(self, name, value):
|
|
144
|
+
if name in self._internal:
|
|
145
|
+
object.__setattr__(self, name, value)
|
|
146
|
+
elif isinstance(name, basestring) and name[0:1] == name[-1:] == '_' and name not in ('__module__','__doc__'):
|
|
147
|
+
raise KeyError("illegal attribute name: %r" % name)
|
|
148
|
+
elif not isinstance(name, basestring):
|
|
149
|
+
raise ValueError('attribute names must be str, not %r' % type(name))
|
|
150
|
+
else:
|
|
151
|
+
if name not in self._keys:
|
|
152
|
+
self._keys.append(name)
|
|
153
|
+
self._values[name] = value
|
|
154
|
+
assert set(self._keys) == set(self._values.keys())
|
|
155
|
+
|
|
156
|
+
def __setattr__(self, name, value):
|
|
157
|
+
if name in self._internal:
|
|
158
|
+
object.__setattr__(self, name, value)
|
|
159
|
+
elif name in self._illegal or name[0:1] == name[-1:] == '_' and name not in ('__module__','__doc__'):
|
|
160
|
+
raise AttributeError("illegal attribute name: %r" % name)
|
|
161
|
+
elif not isinstance(name, basestring):
|
|
162
|
+
raise ValueError('attribute names must be str, not %r' % type(name))
|
|
163
|
+
else:
|
|
164
|
+
if name not in self._keys:
|
|
165
|
+
self._keys.append(name)
|
|
166
|
+
self._values[name] = value
|
|
167
|
+
assert set(self._keys) == set(self._values.keys()), "%r is not equal to %r" % (self._keys, self._values.keys())
|
|
168
|
+
|
|
169
|
+
def __repr__(self):
|
|
170
|
+
cls_name = self.__class__.__name__
|
|
171
|
+
if not self:
|
|
172
|
+
return "%s()" % cls_name
|
|
173
|
+
return "%s(%s)" % (cls_name, ', '.join(["%s=%r" % (k, self._values[k]) for k in self.keys()]))
|
|
174
|
+
|
|
175
|
+
def clear(self):
|
|
176
|
+
self._values.clear()
|
|
177
|
+
self._keys[:] = []
|
|
178
|
+
self._ordered = True
|
|
179
|
+
assert set(self._keys) == set(self._values.keys())
|
|
180
|
+
|
|
181
|
+
def copy(self):
|
|
182
|
+
result = self.__class__()
|
|
183
|
+
result._illegal = self._illegal
|
|
184
|
+
result._default = self._default
|
|
185
|
+
result._ordered = self._ordered
|
|
186
|
+
result._values.update(self._values.copy())
|
|
187
|
+
result._keys = self._keys[:]
|
|
188
|
+
return result
|
|
189
|
+
|
|
190
|
+
@classmethod
|
|
191
|
+
def fromkeys(cls, keys, value):
|
|
192
|
+
return cls([(k, value) for k in keys])
|
|
193
|
+
|
|
194
|
+
def items(self):
|
|
195
|
+
return [(k, self._values[k]) for k in self.keys()]
|
|
196
|
+
|
|
197
|
+
def keys(self):
|
|
198
|
+
if self._ordered:
|
|
199
|
+
return list(self._keys)
|
|
200
|
+
else:
|
|
201
|
+
return sorted(self._keys)
|
|
202
|
+
|
|
203
|
+
def pop(self, key, default=MISSING):
|
|
204
|
+
if default is MISSING:
|
|
205
|
+
value = self._values.pop(key)
|
|
206
|
+
else:
|
|
207
|
+
value = self._values.pop(key, default)
|
|
208
|
+
if key in self._keys:
|
|
209
|
+
self._keys.remove(key)
|
|
210
|
+
assert set(self._keys) == set(self._values.keys())
|
|
211
|
+
return value
|
|
212
|
+
|
|
213
|
+
def popitem(self):
|
|
214
|
+
k, v = self._values.popitem()
|
|
215
|
+
self._keys.remove(k)
|
|
216
|
+
assert set(self._keys) == set(self._values.keys())
|
|
217
|
+
return k, v
|
|
218
|
+
|
|
219
|
+
def setdefault(self, key, value=MISSING):
|
|
220
|
+
if key not in self._values:
|
|
221
|
+
self._keys.append(key)
|
|
222
|
+
if value is MISSING:
|
|
223
|
+
result = self._values.setdefault(key)
|
|
224
|
+
else:
|
|
225
|
+
result = self._values.setdefault(key, value)
|
|
226
|
+
assert set(self._keys) == set(self._values.keys())
|
|
227
|
+
return result
|
|
228
|
+
|
|
229
|
+
def update(self, items=(), **more_items):
|
|
230
|
+
before = len(self._values)
|
|
231
|
+
self._values.update(items, **more_items)
|
|
232
|
+
after = len(self._values)
|
|
233
|
+
if before != after:
|
|
234
|
+
self._keys = self._values.keys()
|
|
235
|
+
self._ordered = False
|
|
236
|
+
assert set(self._keys) == set(self._values.keys())
|
|
237
|
+
|
|
238
|
+
def updated(self, items=(), **more_items):
|
|
239
|
+
self.update(items, **more_items)
|
|
240
|
+
return self
|
|
241
|
+
|
|
242
|
+
def values(self):
|
|
243
|
+
return [self._values[k] for k in self.keys()]
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class BiDict(object):
|
|
247
|
+
"""
|
|
248
|
+
key <=> value (value must also be hashable)
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
def __init__(self, *args, **kwargs):
|
|
252
|
+
_dict = self._dict = dict()
|
|
253
|
+
original_keys = self._primary_keys = list()
|
|
254
|
+
for k, v in args:
|
|
255
|
+
if k not in original_keys:
|
|
256
|
+
original_keys.append(k)
|
|
257
|
+
_dict[k] = v
|
|
258
|
+
if v != k and v in _dict:
|
|
259
|
+
raise ValueError("%s:%s violates one-to-one mapping" % (k, v))
|
|
260
|
+
_dict[v] = k
|
|
261
|
+
for key, value in kwargs.items():
|
|
262
|
+
if key not in original_keys:
|
|
263
|
+
original_keys.append(key)
|
|
264
|
+
_dict[key] = value
|
|
265
|
+
if value != key and value in _dict:
|
|
266
|
+
raise ValueError("%s:%s violates one-to-one mapping" % (key, value))
|
|
267
|
+
_dict[value] = key
|
|
268
|
+
|
|
269
|
+
def __contains__(self, key):
|
|
270
|
+
return key in self._dict
|
|
271
|
+
|
|
272
|
+
def __delitem__(self, key):
|
|
273
|
+
_dict = self._dict
|
|
274
|
+
value = _dict[key]
|
|
275
|
+
del _dict[value]
|
|
276
|
+
if key != value:
|
|
277
|
+
del _dict[key]
|
|
278
|
+
target = (key, value)[value in self._primary_keys]
|
|
279
|
+
self._primary_keys.pop(self._primary_keys.index(target))
|
|
280
|
+
|
|
281
|
+
def __getitem__(self, key):
|
|
282
|
+
return self._dict.__getitem__(key)
|
|
283
|
+
|
|
284
|
+
def __iter__(self):
|
|
285
|
+
return iter(self._primary_keys)
|
|
286
|
+
|
|
287
|
+
def __len__(self):
|
|
288
|
+
return len(self._primary_keys)
|
|
289
|
+
|
|
290
|
+
def __setitem__(self, key, value):
|
|
291
|
+
_dict = self._dict
|
|
292
|
+
original_keys = self._primary_keys
|
|
293
|
+
if key in _dict:
|
|
294
|
+
mapping = key, _dict[key]
|
|
295
|
+
else:
|
|
296
|
+
mapping = ()
|
|
297
|
+
if value in _dict and value not in mapping:
|
|
298
|
+
raise ValueError("%s:%s violates one-to-one mapping" % (key, value))
|
|
299
|
+
if mapping:
|
|
300
|
+
k, v = mapping
|
|
301
|
+
del _dict[k]
|
|
302
|
+
if k != v:
|
|
303
|
+
del _dict[v]
|
|
304
|
+
target = (k, v)[v in original_keys]
|
|
305
|
+
original_keys.pop(original_keys.index(target))
|
|
306
|
+
_dict[key] = value
|
|
307
|
+
_dict[value] = key
|
|
308
|
+
original_keys.append(key)
|
|
309
|
+
|
|
310
|
+
def __repr__(self):
|
|
311
|
+
result = []
|
|
312
|
+
for key in self._primary_keys:
|
|
313
|
+
result.append(repr((key, self._dict[key])))
|
|
314
|
+
return "BiDict(%s)" % ', '.join(result)
|
|
315
|
+
|
|
316
|
+
def keys(self):
|
|
317
|
+
return self._primary_keys[:]
|
|
318
|
+
|
|
319
|
+
def items(self):
|
|
320
|
+
return [(k, self._dict[k]) for k in self._primary_keys]
|
|
321
|
+
|
|
322
|
+
def values(self):
|
|
323
|
+
return [self._dict[key] for key in self._primary_keys]
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class TransformDict(dict):
|
|
327
|
+
'''Dictionary that calls a transformation function when looking
|
|
328
|
+
up keys, but preserves the original keys.
|
|
329
|
+
|
|
330
|
+
>>> d = TransformDict(str.lower)
|
|
331
|
+
>>> d['Foo'] = 5
|
|
332
|
+
>>> d['foo'] == d['FOO'] == d['Foo'] == 5
|
|
333
|
+
True
|
|
334
|
+
>>> set(d.keys())
|
|
335
|
+
{'Foo'}
|
|
336
|
+
'''
|
|
337
|
+
|
|
338
|
+
__slots__ = ('_transform', '_original', '_data')
|
|
339
|
+
|
|
340
|
+
def __init__(self, transform, init_dict=None, **kwargs):
|
|
341
|
+
'''Create a new TransformDict with the given *transform* function.
|
|
342
|
+
*init_dict* and *kwargs* are optional initializers, as in the
|
|
343
|
+
dict constructor.
|
|
344
|
+
'''
|
|
345
|
+
if not callable(transform):
|
|
346
|
+
raise TypeError("expected a callable, got %r" % transform.__class__)
|
|
347
|
+
self._transform = transform
|
|
348
|
+
# transformed => original
|
|
349
|
+
self._original = {}
|
|
350
|
+
self._data = {}
|
|
351
|
+
if init_dict:
|
|
352
|
+
self.update(init_dict)
|
|
353
|
+
if kwargs:
|
|
354
|
+
self.update(kwargs)
|
|
355
|
+
|
|
356
|
+
def getitem(self, key):
|
|
357
|
+
'D.getitem(key) -> (stored key, value)'
|
|
358
|
+
transformed = self._transform(key)
|
|
359
|
+
original = self._original[transformed]
|
|
360
|
+
value = self._data[transformed]
|
|
361
|
+
return original, value
|
|
362
|
+
|
|
363
|
+
@property
|
|
364
|
+
def transform_func(self):
|
|
365
|
+
"This TransformDict's transformation function"
|
|
366
|
+
return self._transform
|
|
367
|
+
|
|
368
|
+
# Minimum set of methods required for MutableMapping
|
|
369
|
+
|
|
370
|
+
def __len__(self):
|
|
371
|
+
return len(self._data)
|
|
372
|
+
|
|
373
|
+
def __iter__(self):
|
|
374
|
+
return iter(self._original.values())
|
|
375
|
+
|
|
376
|
+
def __getitem__(self, key):
|
|
377
|
+
return self._data[self._transform(key)]
|
|
378
|
+
|
|
379
|
+
def __setitem__(self, key, value):
|
|
380
|
+
transformed = self._transform(key)
|
|
381
|
+
self._data[transformed] = value
|
|
382
|
+
self._original.setdefault(transformed, key)
|
|
383
|
+
|
|
384
|
+
def __delitem__(self, key):
|
|
385
|
+
transformed = self._transform(key)
|
|
386
|
+
del self._data[transformed]
|
|
387
|
+
del self._original[transformed]
|
|
388
|
+
|
|
389
|
+
# Methods overriden to mitigate the performance overhead.
|
|
390
|
+
|
|
391
|
+
def clear(self):
|
|
392
|
+
'D.clear() -> None. Remove all items from D.'
|
|
393
|
+
self._data.clear()
|
|
394
|
+
self._original.clear()
|
|
395
|
+
|
|
396
|
+
def __contains__(self, key):
|
|
397
|
+
return self._transform(key) in self._data
|
|
398
|
+
|
|
399
|
+
def get(self, key, default=None):
|
|
400
|
+
'D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.'
|
|
401
|
+
return self._data.get(self._transform(key), default)
|
|
402
|
+
|
|
403
|
+
def pop(self, key, default=MISSING):
|
|
404
|
+
'''D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
|
|
405
|
+
If key is not found, d is returned if given, otherwise KeyError is raised.
|
|
406
|
+
'''
|
|
407
|
+
transformed = self._transform(key)
|
|
408
|
+
if default is MISSING:
|
|
409
|
+
del self._original[transformed]
|
|
410
|
+
return self._data.pop(transformed)
|
|
411
|
+
else:
|
|
412
|
+
self._original.pop(transformed, None)
|
|
413
|
+
return self._data.pop(transformed, default)
|
|
414
|
+
|
|
415
|
+
def popitem(self):
|
|
416
|
+
'''D.popitem() -> (k, v), remove and return some (key, value) pair
|
|
417
|
+
as a 2-tuple; but raise KeyError if D is empty.
|
|
418
|
+
'''
|
|
419
|
+
transformed, value = self._data.popitem()
|
|
420
|
+
return self._original.pop(transformed), value
|
|
421
|
+
|
|
422
|
+
# Other methods
|
|
423
|
+
|
|
424
|
+
def copy(self):
|
|
425
|
+
'D.copy() -> a shallow copy of D'
|
|
426
|
+
other = self.__class__(self._transform)
|
|
427
|
+
other._original = self._original.copy()
|
|
428
|
+
other._data = self._data.copy()
|
|
429
|
+
return other
|
|
430
|
+
|
|
431
|
+
__copy__ = copy
|
|
432
|
+
|
|
433
|
+
def __getstate__(self):
|
|
434
|
+
return (self._transform, self._data, self._original)
|
|
435
|
+
|
|
436
|
+
def __setstate__(self, state):
|
|
437
|
+
self._transform, self._data, self._original = state
|
|
438
|
+
|
|
439
|
+
def __repr__(self):
|
|
440
|
+
try:
|
|
441
|
+
equiv = dict(self)
|
|
442
|
+
except TypeError:
|
|
443
|
+
# Some keys are unhashable, fall back on .items()
|
|
444
|
+
equiv = list(self.items())
|
|
445
|
+
return '%s(%r, %s)' % (self.__class__.__name__,
|
|
446
|
+
self._transform, repr(equiv))
|
|
447
|
+
|
|
448
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import division, print_function
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class suppress(object):
|
|
5
|
+
"suppresses first execption and exits the with block"
|
|
6
|
+
def __init__(self, *exceptions):
|
|
7
|
+
self.exceptions = exceptions
|
|
8
|
+
def __enter__(self):
|
|
9
|
+
pass
|
|
10
|
+
def __exit__(self, etype, val, tb):
|
|
11
|
+
return etype is None or issubclass(etype, self.exceptions)
|
|
12
|
+
|
|
13
|
+
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
from __future__ import division, print_function
|
|
2
|
+
|
|
3
|
+
from .collections import AttrDict
|
|
4
|
+
from .dbf import Date, DateTime, Time
|
|
5
|
+
from datetime import date, datetime, time
|
|
6
|
+
|
|
7
|
+
import codecs
|
|
8
|
+
|
|
9
|
+
enums = ()
|
|
10
|
+
try:
|
|
11
|
+
import aenum as _aenum
|
|
12
|
+
enums += _aenum.Enum,
|
|
13
|
+
except ImportError:
|
|
14
|
+
pass
|
|
15
|
+
try:
|
|
16
|
+
import enum as _enum
|
|
17
|
+
enums += _enum.Enum,
|
|
18
|
+
except ImportError:
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
dates = date, Date
|
|
22
|
+
times = time, Time
|
|
23
|
+
datetimes = datetime, DateTime
|
|
24
|
+
moments = dates + times + datetimes
|
|
25
|
+
|
|
26
|
+
class CSV(object):
|
|
27
|
+
"""
|
|
28
|
+
represents a .csv file
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, filename, mode='r', header=True, default_type=None, null=None):
|
|
32
|
+
if mode not in ('r','w'):
|
|
33
|
+
raise ValueError("mode must be 'r' or 'w', not %r" % (mode, ))
|
|
34
|
+
self.filename = filename
|
|
35
|
+
self.mode = mode
|
|
36
|
+
self.default_type = default_type
|
|
37
|
+
self.null = null
|
|
38
|
+
self.use_header = header
|
|
39
|
+
if mode == 'r':
|
|
40
|
+
with codecs.open(filename, mode='r', encoding='utf-8') as csv:
|
|
41
|
+
raw_data = csv.read().split('\n')
|
|
42
|
+
if header:
|
|
43
|
+
self.header = raw_data.pop(0).strip().split(',')
|
|
44
|
+
else:
|
|
45
|
+
self.header = []
|
|
46
|
+
self.data = [l.strip() for l in raw_data if l.strip()]
|
|
47
|
+
else:
|
|
48
|
+
self.header = []
|
|
49
|
+
self.data = []
|
|
50
|
+
if not header:
|
|
51
|
+
self.header = []
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def __enter__(self):
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
def __exit__(self, *args):
|
|
58
|
+
if args == (None, None, None) and self.mode == 'w':
|
|
59
|
+
self.save()
|
|
60
|
+
|
|
61
|
+
def __getitem__(self, index):
|
|
62
|
+
if isinstance(index, int):
|
|
63
|
+
return self.from_csv(self.data[index])
|
|
64
|
+
# better be a slice
|
|
65
|
+
lines = []
|
|
66
|
+
for line in self.data[index]:
|
|
67
|
+
lines.append(self.from_csv(line))
|
|
68
|
+
return lines
|
|
69
|
+
|
|
70
|
+
def __iter__(self):
|
|
71
|
+
"""
|
|
72
|
+
returns data rows (not header)
|
|
73
|
+
"""
|
|
74
|
+
for line in self.data:
|
|
75
|
+
yield self.from_csv(line)
|
|
76
|
+
|
|
77
|
+
def __len__(self):
|
|
78
|
+
return len(self.data)
|
|
79
|
+
|
|
80
|
+
def append(self, *values):
|
|
81
|
+
if isinstance(values[0], (list, tuple)):
|
|
82
|
+
values = tuple(values[0])
|
|
83
|
+
if self.header and len(values) != len(self.header):
|
|
84
|
+
raise ValueError('%d fields required, %d value(s) given' % (len(self.header), len(values)))
|
|
85
|
+
line = self.to_csv(*values)
|
|
86
|
+
new_values = self.from_csv(line)
|
|
87
|
+
if values != new_values:
|
|
88
|
+
print(len(values), len(new_values))
|
|
89
|
+
print(line)
|
|
90
|
+
print(values)
|
|
91
|
+
print(new_values)
|
|
92
|
+
raise ValueError
|
|
93
|
+
self.data.append(line)
|
|
94
|
+
|
|
95
|
+
def from_csv(self, line):
|
|
96
|
+
"""
|
|
97
|
+
returns a tuple of converted data from `line`
|
|
98
|
+
|
|
99
|
+
supported types:
|
|
100
|
+
|
|
101
|
+
date : nnnn-nn-nn
|
|
102
|
+
datetime : nnnn-nn-nn nn:nn:nn
|
|
103
|
+
time : nn:nn:nn
|
|
104
|
+
int : non-quoted number with no fractions
|
|
105
|
+
float : non-quoted number with fraction
|
|
106
|
+
bool : true, yes, on, t / false, no, off, f
|
|
107
|
+
unicode : "anything else"
|
|
108
|
+
"""
|
|
109
|
+
# break line into fields
|
|
110
|
+
fields = []
|
|
111
|
+
word = []
|
|
112
|
+
encap = False
|
|
113
|
+
parens = 0
|
|
114
|
+
skip_next = False
|
|
115
|
+
keep_next = False
|
|
116
|
+
for i, ch in enumerate(line):
|
|
117
|
+
if skip_next:
|
|
118
|
+
skip_next = False
|
|
119
|
+
continue
|
|
120
|
+
elif keep_next:
|
|
121
|
+
keep_next = False
|
|
122
|
+
word.append(ch)
|
|
123
|
+
continue
|
|
124
|
+
elif encap:
|
|
125
|
+
if ch == '"' and line[i+1:i+2] == '"':
|
|
126
|
+
word.append(ch)
|
|
127
|
+
skip_next = True
|
|
128
|
+
elif ch =='"' and line[i+1:i+2] in ('', ','):
|
|
129
|
+
word.append(ch)
|
|
130
|
+
encap = False
|
|
131
|
+
elif ch == '\\':
|
|
132
|
+
if line[i+1:i+2] == 'n':
|
|
133
|
+
word.append('\n')
|
|
134
|
+
skip_next = True
|
|
135
|
+
else:
|
|
136
|
+
keep_next = True
|
|
137
|
+
elif ch == '"':
|
|
138
|
+
raise ValueError(
|
|
139
|
+
'invalid char following ": <%s> (should be comma or double-quote)\n%r\n%s^'
|
|
140
|
+
% (ch, line, ' ' * i)
|
|
141
|
+
)
|
|
142
|
+
else:
|
|
143
|
+
word.append(ch)
|
|
144
|
+
elif ch == '(':
|
|
145
|
+
word.append(ch)
|
|
146
|
+
parens += 1
|
|
147
|
+
elif ch == ')':
|
|
148
|
+
word.append(ch)
|
|
149
|
+
parens -= 1
|
|
150
|
+
if parens < 0:
|
|
151
|
+
raise ValueError('unbalanced parentheses in:\n%r' % line)
|
|
152
|
+
else:
|
|
153
|
+
if ch == ',' and not parens:
|
|
154
|
+
fields.append(''.join(word))
|
|
155
|
+
word = []
|
|
156
|
+
elif ch == '"':
|
|
157
|
+
if word: # embedded " are not allowed
|
|
158
|
+
raise ValueError('embedded quotes not allowed:\n%s\n%s' % (line[:i], line))
|
|
159
|
+
encap = True
|
|
160
|
+
word.append(ch)
|
|
161
|
+
else:
|
|
162
|
+
word.append(ch)
|
|
163
|
+
if parens:
|
|
164
|
+
raise ValueError('unbalanced parentheses in:\n%r' % line)
|
|
165
|
+
# don't lose last field!
|
|
166
|
+
fields.append(''.join(word))
|
|
167
|
+
#
|
|
168
|
+
# convert fields to their data types
|
|
169
|
+
final = []
|
|
170
|
+
for i, field in enumerate(fields):
|
|
171
|
+
try:
|
|
172
|
+
if not field:
|
|
173
|
+
final.append(self.null)
|
|
174
|
+
elif field[0] == field[-1] == '"':
|
|
175
|
+
# simple string
|
|
176
|
+
final.append(field[1:-1])
|
|
177
|
+
elif field.lower() in ('true','yes','on','t'):
|
|
178
|
+
final.append(True)
|
|
179
|
+
elif field.lower() in ('false','no','off','f'):
|
|
180
|
+
final.append(False)
|
|
181
|
+
elif '-' in field and ':' in field:
|
|
182
|
+
final.append(dates.str_to_datetime(field, localtime=False))
|
|
183
|
+
elif '-' in field:
|
|
184
|
+
final.append(Date.strptime(field, '%Y-%m-%d'))
|
|
185
|
+
elif ':' in field:
|
|
186
|
+
final.append(Time.strptime(field, '%H:%M:%S'))
|
|
187
|
+
elif 'Many2One' in field:
|
|
188
|
+
final.append(eval(field))
|
|
189
|
+
elif 'Phone' in field:
|
|
190
|
+
final.append(eval(field))
|
|
191
|
+
else:
|
|
192
|
+
try:
|
|
193
|
+
final.append(int(field))
|
|
194
|
+
except ValueError:
|
|
195
|
+
final.append(float(field))
|
|
196
|
+
except ValueError:
|
|
197
|
+
if self.default_type is not None:
|
|
198
|
+
final.append(self.default_type(field))
|
|
199
|
+
else:
|
|
200
|
+
ve = ValueError('unable to determine datatype of <%r>' % (field, ))
|
|
201
|
+
ve.__cause__ = None
|
|
202
|
+
raise ve
|
|
203
|
+
return tuple(final)
|
|
204
|
+
|
|
205
|
+
def iter_map(self, header=None):
|
|
206
|
+
header = self.header or header
|
|
207
|
+
if not header:
|
|
208
|
+
raise ValueError('header needed for iter_map()')
|
|
209
|
+
for record in self:
|
|
210
|
+
yield AttrDict(zip(self.header, record))
|
|
211
|
+
|
|
212
|
+
def save(self, filename=None):
|
|
213
|
+
if filename is None:
|
|
214
|
+
filename = self.filename
|
|
215
|
+
with codecs.open(filename, mode='w', encoding='utf-8') as csv:
|
|
216
|
+
if self.header:
|
|
217
|
+
# write the header
|
|
218
|
+
csv.write(','.join(self.header) + '\n')
|
|
219
|
+
# write the data
|
|
220
|
+
for line in self.data:
|
|
221
|
+
csv.write(line + '\n')
|
|
222
|
+
|
|
223
|
+
def to_csv(self, *data):
|
|
224
|
+
"""
|
|
225
|
+
convert data to text and add to CSV.data
|
|
226
|
+
|
|
227
|
+
supported types:
|
|
228
|
+
|
|
229
|
+
unicode : "str and unicode"
|
|
230
|
+
date : nnnn-nn-nn
|
|
231
|
+
datetime : nnnn-nn-nn nn:nn:nn
|
|
232
|
+
time : nn:nn:nn
|
|
233
|
+
bool : true, yes, on, t / false, no, off, f
|
|
234
|
+
anything else : repr()
|
|
235
|
+
"""
|
|
236
|
+
line = []
|
|
237
|
+
for datum in data:
|
|
238
|
+
if datum is None or datum == '':
|
|
239
|
+
line.append('')
|
|
240
|
+
elif isinstance(datum, unicode):
|
|
241
|
+
datum = datum.replace('"','""').replace('\\','\\\\').replace('\n',r'\n')
|
|
242
|
+
line.append('"%s"' % datum)
|
|
243
|
+
elif isinstance(datum, dates.dates):
|
|
244
|
+
line.append(datum.strftime('%Y-%m-%d'))
|
|
245
|
+
elif isinstance(datum, dates.datetimes):
|
|
246
|
+
line.append(dates.datetime_to_str(datum))
|
|
247
|
+
elif isinstance(datum, dates.times):
|
|
248
|
+
line.append(datum.strftime('%H:%M:%S'))
|
|
249
|
+
elif isinstance(datum, bool):
|
|
250
|
+
line.append('ft'[datum])
|
|
251
|
+
elif isinstance(datum, SelectionEnum):
|
|
252
|
+
line.append(repr(datum.db))
|
|
253
|
+
elif isinstance(datum, enums):
|
|
254
|
+
line.append(repr(datum.value))
|
|
255
|
+
else:
|
|
256
|
+
line.append(repr(datum))
|
|
257
|
+
return ','.join(line)
|
|
258
|
+
|
|
259
|
+
def to_stream(self):
|
|
260
|
+
utf8 = codecs.getencoder('utf-8')
|
|
261
|
+
yield utf8(','.join(self.header) + '\n')
|
|
262
|
+
for line in self.data:
|
|
263
|
+
yield utf8(line + '\n')
|
|
264
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import division, print_function
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def nested_property(func):
|
|
6
|
+
"""
|
|
7
|
+
make defining properties simpler (from Mike Muller) [fget, fset, fdel]
|
|
8
|
+
|
|
9
|
+
@nested_property
|
|
10
|
+
def value():
|
|
11
|
+
"doc string here"
|
|
12
|
+
def fget(self):
|
|
13
|
+
pass
|
|
14
|
+
def fset(self, value):
|
|
15
|
+
pass
|
|
16
|
+
return locals()
|
|
17
|
+
"""
|
|
18
|
+
names = dict([(n, f) for n, f in func().items() if n in ('fset', 'fget', 'fdel')])
|
|
19
|
+
names['doc'] = func.__doc__
|
|
20
|
+
return property(**names)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
from functools import simplegeneric
|
|
25
|
+
except ImportError:
|
|
26
|
+
|
|
27
|
+
def simplegeneric(func):
|
|
28
|
+
"""Make a trivial single-dispatch generic function (from Python3.4 functools)"""
|
|
29
|
+
registry = {}
|
|
30
|
+
def wrapper(*args, **kw):
|
|
31
|
+
ob = args[0]
|
|
32
|
+
try:
|
|
33
|
+
cls = ob.__class__
|
|
34
|
+
except AttributeError:
|
|
35
|
+
cls = type(ob)
|
|
36
|
+
try:
|
|
37
|
+
mro = cls.__mro__
|
|
38
|
+
except AttributeError:
|
|
39
|
+
try:
|
|
40
|
+
class cls(cls, object):
|
|
41
|
+
pass
|
|
42
|
+
mro = cls.__mro__[1:]
|
|
43
|
+
except TypeError:
|
|
44
|
+
mro = object, # must be an ExtensionClass or some such :(
|
|
45
|
+
for t in mro:
|
|
46
|
+
if t in registry:
|
|
47
|
+
return registry[t](*args, **kw)
|
|
48
|
+
else:
|
|
49
|
+
return func(*args, **kw)
|
|
50
|
+
try:
|
|
51
|
+
wrapper.__name__ = func.__name__
|
|
52
|
+
except (TypeError, AttributeError):
|
|
53
|
+
pass # Python 2.3 doesn't allow functions to be renamed
|
|
54
|
+
|
|
55
|
+
def register(typ, func=None):
|
|
56
|
+
if func is None:
|
|
57
|
+
return lambda f: register(typ, f)
|
|
58
|
+
registry[typ] = func
|
|
59
|
+
return func
|
|
60
|
+
|
|
61
|
+
wrapper.__dict__ = func.__dict__
|
|
62
|
+
wrapper.__doc__ = func.__doc__
|
|
63
|
+
wrapper.register = register
|
|
64
|
+
return wrapper
|
|
65
|
+
|
|
66
|
+
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from __future__ import division, print_function
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
sys
|
|
5
|
+
|
|
6
|
+
def all_equal(iterator, test=None):
|
|
7
|
+
"""
|
|
8
|
+
If test is None do a straight equality test.
|
|
9
|
+
"""
|
|
10
|
+
it = iter(iterator)
|
|
11
|
+
if test is None:
|
|
12
|
+
try:
|
|
13
|
+
target = next(it)
|
|
14
|
+
test = lambda x: x == target
|
|
15
|
+
except StopIteration:
|
|
16
|
+
return True
|
|
17
|
+
return all(test(x) for x in it)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class xrange(object):
|
|
21
|
+
"""
|
|
22
|
+
Accepts arbitrary objects to use to produce sequence iterators.
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, start, stop=None, step=None, count=None, epsilon=None):
|
|
25
|
+
"""
|
|
26
|
+
range(7) -> start=0, stop=7, step=1, count=None
|
|
27
|
+
range(5, count=3) -> start=0, stop=5, step=1, count=3 (raises)
|
|
28
|
+
range(start=5, count=3) -> start=5, stop=None, step=1, count=3
|
|
29
|
+
range(1, 3, step=2) -> start=1, stop=3, step=2, count=None
|
|
30
|
+
"""
|
|
31
|
+
if stop is None and count is None:
|
|
32
|
+
# `start` value is actually `stop`
|
|
33
|
+
start, stop = None, start
|
|
34
|
+
if count is not None and count < 0:
|
|
35
|
+
raise ValueError('count must be a non-negative number')
|
|
36
|
+
# valid combinations:
|
|
37
|
+
# start stop step count notes
|
|
38
|
+
# x start=0, step=1
|
|
39
|
+
# x x start=0, step=1
|
|
40
|
+
# x step=1, infinite
|
|
41
|
+
# x x step=1
|
|
42
|
+
# x x infinite
|
|
43
|
+
# x x step=1
|
|
44
|
+
# x x x
|
|
45
|
+
# x x x step=1
|
|
46
|
+
# x x x
|
|
47
|
+
# x x x x
|
|
48
|
+
#
|
|
49
|
+
combo = [1 if a is not None else 0 for a in (start, stop, step, count)]
|
|
50
|
+
if combo not in (
|
|
51
|
+
[0, 1, 0, 0],
|
|
52
|
+
[0, 1, 1, 0],
|
|
53
|
+
[1, 0, 0, 0],
|
|
54
|
+
[1, 1, 0, 0],
|
|
55
|
+
[1, 0, 1, 0],
|
|
56
|
+
[1, 0, 0, 1],
|
|
57
|
+
[1, 1, 1, 0],
|
|
58
|
+
[1, 1, 0, 1],
|
|
59
|
+
[1, 0, 1, 1],
|
|
60
|
+
[1, 1, 1, 1],
|
|
61
|
+
):
|
|
62
|
+
args = [name for (name, present) in zip(('start','stop','step','count'), combo) if present]
|
|
63
|
+
raise TypeError('invalid combination of arguments: %s' % ', '.join(args))
|
|
64
|
+
if start is not None:
|
|
65
|
+
ref = type(start)
|
|
66
|
+
else:
|
|
67
|
+
ref = type(stop)
|
|
68
|
+
start = ref(0)
|
|
69
|
+
if step is None:
|
|
70
|
+
try:
|
|
71
|
+
step = ref(1)
|
|
72
|
+
if stop is not None and stop < start:
|
|
73
|
+
step = ref(-1)
|
|
74
|
+
except TypeError:
|
|
75
|
+
raise ValueError("step must be specified for type %r" % type(stop))
|
|
76
|
+
try:
|
|
77
|
+
if callable(step):
|
|
78
|
+
start + step(start, 1, start)
|
|
79
|
+
else:
|
|
80
|
+
start + step
|
|
81
|
+
except TypeError:
|
|
82
|
+
raise TypeError('unable to add %r to %r' % (step, start))
|
|
83
|
+
self.start = start
|
|
84
|
+
self.stop = stop
|
|
85
|
+
self.step = step
|
|
86
|
+
self.count = count
|
|
87
|
+
self.epsilon = epsilon
|
|
88
|
+
self.values = None
|
|
89
|
+
|
|
90
|
+
def __contains__(self, value):
|
|
91
|
+
if self.values is None:
|
|
92
|
+
self.values = list(self)
|
|
93
|
+
return value in self.values
|
|
94
|
+
|
|
95
|
+
def __iter__(self):
|
|
96
|
+
if self.values is not None:
|
|
97
|
+
return iter(self.values)
|
|
98
|
+
else:
|
|
99
|
+
return self._generate_values()
|
|
100
|
+
|
|
101
|
+
def __repr__(self):
|
|
102
|
+
values = []
|
|
103
|
+
if self.start:
|
|
104
|
+
values.append(repr(self.start))
|
|
105
|
+
if self.stop is not None:
|
|
106
|
+
values.append(repr(self.stop))
|
|
107
|
+
values.extend([
|
|
108
|
+
'%s=%r' % (k,v)
|
|
109
|
+
for k,v in (
|
|
110
|
+
('step', self.step),
|
|
111
|
+
('count', self.count),
|
|
112
|
+
('epsilon', self.epsilon),
|
|
113
|
+
)
|
|
114
|
+
if v is not None
|
|
115
|
+
])
|
|
116
|
+
return '%s(%s)' % (self.__class__.__name__, ', '.join(values))
|
|
117
|
+
|
|
118
|
+
def _generate_values(self):
|
|
119
|
+
start, stop, step, count, epsilon = self.start, self.stop, self.step, self.count, self.epsilon
|
|
120
|
+
i = -1
|
|
121
|
+
target = None
|
|
122
|
+
if stop is not None and epsilon is not None:
|
|
123
|
+
target = stop - epsilon, stop + epsilon
|
|
124
|
+
reverse = None
|
|
125
|
+
if stop is not None:
|
|
126
|
+
reverse = stop < start
|
|
127
|
+
while 'more values to yield':
|
|
128
|
+
if count is not None:
|
|
129
|
+
if count < 1:
|
|
130
|
+
break
|
|
131
|
+
count -= 1
|
|
132
|
+
i += 1
|
|
133
|
+
if i == 0:
|
|
134
|
+
value = start
|
|
135
|
+
else:
|
|
136
|
+
if callable(step):
|
|
137
|
+
value = step(start, i, value)
|
|
138
|
+
else:
|
|
139
|
+
value = start + i * step
|
|
140
|
+
if stop is not None:
|
|
141
|
+
if value == stop:
|
|
142
|
+
break
|
|
143
|
+
if reverse:
|
|
144
|
+
if value <= stop:
|
|
145
|
+
break
|
|
146
|
+
else:
|
|
147
|
+
if value >= stop:
|
|
148
|
+
break
|
|
149
|
+
if target is not None and target[0] <= value <= target[1]:
|
|
150
|
+
break
|
|
151
|
+
yield value
|
|
152
|
+
|
|
153
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# adapted from https://discuss.python.org/t/finding-a-bloc-star-provider/73918/40
|
|
2
|
+
#
|
|
3
|
+
# Very simple 16-bit PRNG, self-contained, and needing nothing fancier
|
|
4
|
+
# than 16x16->32 bit unsigned integer multiplication.
|
|
5
|
+
#
|
|
6
|
+
# We don't want much from this. After construction, 65536 consecutive
|
|
7
|
+
# calls to .get() will deliver each of the 65536 possible results once
|
|
8
|
+
# each, in a randomish order.
|
|
9
|
+
|
|
10
|
+
BITS = 16
|
|
11
|
+
PERIOD = 1 << BITS
|
|
12
|
+
MASK = PERIOD - 1
|
|
13
|
+
|
|
14
|
+
class TinyRand(object):
|
|
15
|
+
def __init__(self, seed=0):
|
|
16
|
+
self.seed(seed)
|
|
17
|
+
|
|
18
|
+
def __iter__(self):
|
|
19
|
+
return self
|
|
20
|
+
|
|
21
|
+
def __next__(self):
|
|
22
|
+
return self.get()
|
|
23
|
+
next == __next__
|
|
24
|
+
|
|
25
|
+
def seed(self, seed):
|
|
26
|
+
self.seed = seed & MASK
|
|
27
|
+
|
|
28
|
+
def get(self):
|
|
29
|
+
"""Return a random iot in range(2**16).
|
|
30
|
+
|
|
31
|
+
The period is 2**16, and each posdible result appears once
|
|
32
|
+
across the period.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# At heart this is the ordinary LCG
|
|
36
|
+
# state <- state * 43317 + 1 (modulo 2**16).
|
|
37
|
+
# It's strengthed a bit by returning the state xor'ed with a
|
|
38
|
+
# shifted copy of the state, a PCG-like trick that destroys the
|
|
39
|
+
# extreme regularity of the base LCG's low-order bits.
|
|
40
|
+
# 43317 came from a table of multipliers with "good" spectral
|
|
41
|
+
# scores,
|
|
42
|
+
self.seed = (self.seed * 43317 + 1) & MASK
|
|
43
|
+
return (self.seed >> 7) ^ self.seed
|
|
44
|
+
|
|
45
|
+
if __name__ == '__main__':
|
|
46
|
+
from scription import *
|
|
47
|
+
|
|
48
|
+
for i in ViewProgress(range(PERIOD)):
|
|
49
|
+
t = TinyRand(seed=i)
|
|
50
|
+
full = {t.get() for i in range(PERIOD)}
|
|
51
|
+
assert len(full) == PERIOD, "failed with seed %s" % i
|
|
52
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import division, print_function
|
|
2
|
+
|
|
3
|
+
from .types import MISSING
|
|
4
|
+
|
|
5
|
+
def translator(frm=u'', to=u'', delete=u'', keep=u'', strip=MISSING, compress=False):
|
|
6
|
+
# delete and keep are mutually exclusive
|
|
7
|
+
if delete and keep:
|
|
8
|
+
raise ValueError('cannot specify both keep and delete')
|
|
9
|
+
replacement = replacement_ord = None
|
|
10
|
+
if len(to) == 1:
|
|
11
|
+
if frm == u'':
|
|
12
|
+
replacement = to
|
|
13
|
+
replacement_ord = ord(to)
|
|
14
|
+
to = u''
|
|
15
|
+
else:
|
|
16
|
+
to = to * len(frm)
|
|
17
|
+
if len(to) != len(frm):
|
|
18
|
+
raise ValueError('frm and to should be equal lengths (or to should be a single character)')
|
|
19
|
+
uni_table = dict(
|
|
20
|
+
(ord(f), ord(t))
|
|
21
|
+
for f, t in zip(frm, to)
|
|
22
|
+
)
|
|
23
|
+
for ch in delete:
|
|
24
|
+
uni_table[ord(ch)] = None
|
|
25
|
+
def translate(s):
|
|
26
|
+
if isinstance(s, bytes):
|
|
27
|
+
s = s.decode('latin1')
|
|
28
|
+
s = s.translate(uni_table)
|
|
29
|
+
if keep:
|
|
30
|
+
remove_table = {}
|
|
31
|
+
for chr in set(s) - set(keep):
|
|
32
|
+
remove_table[ord(chr)] = replacement_ord
|
|
33
|
+
s = s.translate(remove_table)
|
|
34
|
+
if strip is not MISSING:
|
|
35
|
+
s = s.strip(strip)
|
|
36
|
+
if replacement and compress:
|
|
37
|
+
s = replacement.join([p for p in s.split(replacement) if p])
|
|
38
|
+
return s
|
|
39
|
+
return translate
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import division, print_function
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Sentinel(object):
|
|
5
|
+
"""
|
|
6
|
+
When you have to be absolutely sure your object is unique.
|
|
7
|
+
"""
|
|
8
|
+
def __init__(self, name, bool=None, doc=''):
|
|
9
|
+
self.name = name
|
|
10
|
+
self.__doc__ = doc
|
|
11
|
+
self.__bool = bool
|
|
12
|
+
|
|
13
|
+
def __repr__(self):
|
|
14
|
+
return "<%r>" % self.name
|
|
15
|
+
|
|
16
|
+
def __bool__(self):
|
|
17
|
+
if self.__bool is None:
|
|
18
|
+
raise NotImplementedError('Sentinel %r has no boolean value' % self.name)
|
|
19
|
+
return self.__bool
|
|
20
|
+
__nonzero__ = __bool__
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
MISSING = Sentinel('MISSING', doc="argument not provided by caller")
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: enhlib
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, and NamedConstants
|
|
5
|
+
Home-page: https://github.com/ethanfurman/enhlib
|
|
6
|
+
Author: Ethan Furman
|
|
7
|
+
Author-email: ethan@stoneleaf.us
|
|
8
|
+
License: BSD License
|
|
9
|
+
Platform: UNKNOWN
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Topic :: Software Development
|
|
15
|
+
Classifier: Programming Language :: Python :: 2.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.4
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.5
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
27
|
+
Provides: enhlib
|
|
28
|
+
|
|
29
|
+
Enhanced Library -- extras beyond Python's stdlib
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
setup.py
|
|
2
|
+
enhlib/__init__.py
|
|
3
|
+
enhlib/collections.py
|
|
4
|
+
enhlib/contextlib.py
|
|
5
|
+
enhlib/csv.py
|
|
6
|
+
enhlib/functools.py
|
|
7
|
+
enhlib/itertools.py
|
|
8
|
+
enhlib/random.py
|
|
9
|
+
enhlib/text.py
|
|
10
|
+
enhlib/types.py
|
|
11
|
+
enhlib.egg-info/PKG-INFO
|
|
12
|
+
enhlib.egg-info/SOURCES.txt
|
|
13
|
+
enhlib.egg-info/dependency_links.txt
|
|
14
|
+
enhlib.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
enhlib
|
enhlib-0.0.1/setup.cfg
ADDED
enhlib-0.0.1/setup.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
try:
|
|
2
|
+
import setuptools
|
|
3
|
+
except ImportError:
|
|
4
|
+
pass
|
|
5
|
+
setuptools # keep pyflakes happy
|
|
6
|
+
from distutils.core import setup
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
long_desc = '''\
|
|
10
|
+
Enhanced Library -- extras beyond Python's stdlib
|
|
11
|
+
|
|
12
|
+
'''
|
|
13
|
+
|
|
14
|
+
data = dict(
|
|
15
|
+
name='enhlib',
|
|
16
|
+
version='0.0.1',
|
|
17
|
+
url='https://github.com/ethanfurman/enhlib',
|
|
18
|
+
packages=['enhlib'],
|
|
19
|
+
package_data={
|
|
20
|
+
'enhlib' : [
|
|
21
|
+
'LICENSE',
|
|
22
|
+
'README.md',
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
include_package_data=True,
|
|
26
|
+
license='BSD License',
|
|
27
|
+
description="Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, and NamedConstants",
|
|
28
|
+
long_description=long_desc,
|
|
29
|
+
provides=['enhlib'],
|
|
30
|
+
author='Ethan Furman',
|
|
31
|
+
author_email='ethan@stoneleaf.us',
|
|
32
|
+
classifiers=[
|
|
33
|
+
'Development Status :: 5 - Production/Stable',
|
|
34
|
+
'Intended Audience :: Developers',
|
|
35
|
+
'License :: OSI Approved :: BSD License',
|
|
36
|
+
'Programming Language :: Python',
|
|
37
|
+
'Topic :: Software Development',
|
|
38
|
+
'Programming Language :: Python :: 2.7',
|
|
39
|
+
'Programming Language :: Python :: 3.3',
|
|
40
|
+
'Programming Language :: Python :: 3.4',
|
|
41
|
+
'Programming Language :: Python :: 3.5',
|
|
42
|
+
'Programming Language :: Python :: 3.6',
|
|
43
|
+
'Programming Language :: Python :: 3.7',
|
|
44
|
+
'Programming Language :: Python :: 3.8',
|
|
45
|
+
'Programming Language :: Python :: 3.9',
|
|
46
|
+
'Programming Language :: Python :: 3.10',
|
|
47
|
+
'Programming Language :: Python :: 3.11',
|
|
48
|
+
'Programming Language :: Python :: 3.12',
|
|
49
|
+
'Programming Language :: Python :: 3.13',
|
|
50
|
+
],
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
py2_only = ()
|
|
54
|
+
py3_only = ()
|
|
55
|
+
make = [ ]
|
|
56
|
+
|
|
57
|
+
if __name__ == '__main__':
|
|
58
|
+
if 'install' in sys.argv:
|
|
59
|
+
import os, sys
|
|
60
|
+
if sys.version_info[0] != 2:
|
|
61
|
+
for file in py2_only:
|
|
62
|
+
try:
|
|
63
|
+
os.unlink(file)
|
|
64
|
+
except OSError:
|
|
65
|
+
pass
|
|
66
|
+
if sys.version_info[0] != 3:
|
|
67
|
+
for file in py3_only:
|
|
68
|
+
try:
|
|
69
|
+
os.unlink(file)
|
|
70
|
+
except OSError:
|
|
71
|
+
pass
|
|
72
|
+
setup(**data)
|