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 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
+ enhlib
enhlib-0.0.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
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)