datatypes 0.12.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. datatypes/__init__.py +153 -0
  2. datatypes/collections/__init__.py +22 -0
  3. datatypes/collections/container.py +150 -0
  4. datatypes/collections/mapping.py +614 -0
  5. datatypes/collections/sequence.py +469 -0
  6. datatypes/command.py +572 -0
  7. datatypes/compat.py +205 -0
  8. datatypes/config/__init__.py +9 -0
  9. datatypes/config/environ.py +302 -0
  10. datatypes/config/parser.py +255 -0
  11. datatypes/copy.py +151 -0
  12. datatypes/csv.py +322 -0
  13. datatypes/datetime.py +888 -0
  14. datatypes/decorators/__init__.py +23 -0
  15. datatypes/decorators/base.py +343 -0
  16. datatypes/decorators/descriptor.py +389 -0
  17. datatypes/decorators/misc.py +118 -0
  18. datatypes/email.py +351 -0
  19. datatypes/enum.py +191 -0
  20. datatypes/event.py +207 -0
  21. datatypes/filetypes/__init__.py +12 -0
  22. datatypes/filetypes/html.py +556 -0
  23. datatypes/filetypes/toml.py +260 -0
  24. datatypes/http.py +723 -0
  25. datatypes/logging.py +384 -0
  26. datatypes/number.py +187 -0
  27. datatypes/parse.py +246 -0
  28. datatypes/path.py +3637 -0
  29. datatypes/profile.py +232 -0
  30. datatypes/reflection.py +1498 -0
  31. datatypes/server.py +408 -0
  32. datatypes/service.py +212 -0
  33. datatypes/string.py +1328 -0
  34. datatypes/token/__init__.py +16 -0
  35. datatypes/token/abnf.py +1704 -0
  36. datatypes/token/base.py +475 -0
  37. datatypes/token/word.py +259 -0
  38. datatypes/url.py +841 -0
  39. datatypes/utils.py +142 -0
  40. datatypes-0.12.0.dist-info/LICENSE.txt +21 -0
  41. datatypes-0.12.0.dist-info/METADATA +52 -0
  42. datatypes-0.12.0.dist-info/RECORD +44 -0
  43. datatypes-0.12.0.dist-info/WHEEL +5 -0
  44. datatypes-0.12.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,614 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Dict and Map like objects
4
+
5
+ https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping
6
+ """
7
+ from contextlib import contextmanager
8
+
9
+ from ..compat import *
10
+ from .sequence import PriorityQueue, Stack
11
+
12
+
13
+ class Pool(dict):
14
+ """Generic pool of some values bounded by size, this means when size is reached
15
+ then the least used item will be silently dropped from the pool.
16
+
17
+ This class gets interesting when you extend it and add __missing__() so you
18
+ can use this as kind of a hotcache of the maxsize most used items in the Pool
19
+
20
+ :Example:
21
+ class HotPool(Pool):
22
+ def __missing__(self, key):
23
+ value = get_some_value_here(key)
24
+ self[key] = value # save the found value at key so it will cache
25
+ return value
26
+ """
27
+ def __init__(self, maxsize=0):
28
+ super().__init__()
29
+ self.pq = PriorityQueue(maxsize, key=lambda value: value)
30
+
31
+ def __getitem__(self, key):
32
+ shuffle = key in self
33
+ value = super().__getitem__(key)
34
+
35
+ # since this is being accessed again, we move it to the end
36
+ if shuffle:
37
+ self.pq.put(key)
38
+
39
+ return value
40
+
41
+ def __setitem__(self, key, value):
42
+ super().__setitem__(key, value)
43
+ try:
44
+ self.pq.put(key)
45
+
46
+ except OverflowError:
47
+ self.popitem()
48
+ self.pq.put(key)
49
+
50
+ def popitem(self):
51
+ dead_val, dead_key, dead_priority = self.pq.popitem()
52
+ del self[dead_key]
53
+ return dead_key, dead_val
54
+
55
+ def pop(self, key, *args):
56
+ if key in self:
57
+ self.pq.remove(key)
58
+ return super().pop(key, *args)
59
+
60
+ def setdefault(self, key, value):
61
+ if key not in self:
62
+ self[key] = value
63
+
64
+ def get(self, key, default=None):
65
+ value = default
66
+ if key in self:
67
+ value = self[key]
68
+ return value
69
+
70
+ def update(self, d):
71
+ for k, v in d.items():
72
+ self[k] = v
73
+
74
+
75
+ class Dict(dict):
76
+ def __init__(self, *args, **kwargs):
77
+ if len(args) > 1:
78
+ super().__init__(args[0], **kwargs)
79
+ for arg in args[1:]:
80
+ self.update(arg)
81
+
82
+ else:
83
+ super().__init__(*args, **kwargs)
84
+
85
+ def ritems(self, *keys):
86
+ """Iterate through the dict and all sub dicts
87
+
88
+ :param *keys: str, if you only want to find and iterate certain keys
89
+ :yields: tuple, (list, mixed), the list is the key path, all the keys
90
+ it would take to get to this key (eg, ["foo", "bar"]) and mixed is
91
+ the value found at the end of the key path
92
+ """
93
+ def iterdict(d, keypath, keys):
94
+ for k, v in d.items():
95
+ kp = keypath + [k]
96
+ if k in keys:
97
+ yield kp, v
98
+
99
+ else:
100
+ if not keys:
101
+ yield kp, v
102
+
103
+ if isinstance(v, Mapping):
104
+ for kp, d in iterdict(v, kp, keys):
105
+ yield kp, d
106
+
107
+ keys = set(keys)
108
+ for kp, d in iterdict(self, [], keys):
109
+ yield kp, d
110
+
111
+ def rkeys(self, *keys):
112
+ """Iterate through all key paths
113
+
114
+ :param *keys: str, if you only want to find and iterate certain keys
115
+ :yields: list, the key paths
116
+ """
117
+ for kp, _ in self.ritems(*keys):
118
+ yield kp
119
+
120
+ def rvalues(self, *keys):
121
+ """Iterate through all values
122
+
123
+ :param *keys: str, if you only want to find and iterate certain keys
124
+ :yields: mixed, the found values
125
+ """
126
+ for _, v in self.ritems(*keys):
127
+ yield v
128
+
129
+ def rget(self, key, default=None):
130
+ """Return the first matching value found at key anywhere in the dict or
131
+ sub dicts
132
+
133
+ :param key: string, the key to find in this or any sub dicts
134
+ :param default: mixed, if key isn't found, return this value
135
+ :returns: mixed, the value found at the first key/subkey
136
+ """
137
+ v = default
138
+ for v in self.rvalues(key):
139
+ break
140
+ return v
141
+
142
+ def gets(self, keys, default=None):
143
+ """Check every key in the keys list, first found key is returned, if none
144
+ of the keys exist then return default
145
+
146
+ :Example:
147
+ d = Dict({
148
+ "foo": 1,
149
+ "bar": 2,
150
+ "che": 3,
151
+ })
152
+
153
+ d.get(["does", "not", "exist", "foo"], 5) # 1
154
+ d.get(["does", "bar", "exist", "foo"], 5) # 2
155
+
156
+ :param keys: list, a list of keys to check in the order they should be checked
157
+ :param *default: mixed, what to return if none of the keys exist
158
+ :returns: mixed, either the value of the first key that exists or default_val
159
+ """
160
+ for k in keys:
161
+ if k in self:
162
+ return self[k]
163
+ return default
164
+
165
+ def pops(self, keys, *default):
166
+ """Check every key in the keys list, first found key will be returned, if none
167
+ of the keys exist then return default, all keys in the keys list will be
168
+ popped, even the ones after a value is found
169
+
170
+ :Example:
171
+ d = Dict({
172
+ "foo": 1,
173
+ "bar": 2,
174
+ "che": 3,
175
+ })
176
+
177
+ d.pop(["foo"], 5) # 1
178
+ d.pop(["foo", "bar"], 5) # 2
179
+
180
+ :param keys: list, a list of keys to check in the order they should be checked
181
+ :param *default: mixed, what to return if none of the keys exist
182
+ :returns: mixed, either the value of the first key that exists or default_val
183
+ """
184
+ ret = None
185
+ key = None
186
+ for k in keys:
187
+ if k in self:
188
+ if key is None:
189
+ key = k
190
+ ret = self.pop(k)
191
+
192
+ else:
193
+ self.pop(k)
194
+
195
+ if key is None:
196
+ if default:
197
+ ret = default[0]
198
+ else:
199
+ raise KeyError(", ".join(keys))
200
+
201
+ return ret
202
+
203
+ def merge(self, other):
204
+ """Very similar to .update() but merges the dicts instead of overriding
205
+
206
+ :Example:
207
+ d = {
208
+ "foo": {
209
+ "bar": 1
210
+ }
211
+ }
212
+ d2 = {
213
+ "foo": {
214
+ "che": 2
215
+ }
216
+ }
217
+
218
+ d.merge(d2)
219
+ print(d["foo"]["che"]) # 2
220
+ print(d["foo"]["bar"]) # 1
221
+
222
+ :param other: Mapping, the other dict to merge into this dict, if self[key]
223
+ is not a Mapping, then other[key] will override, if both self[key] and
224
+ other[key] are Mapping instances then they will be merged
225
+ """
226
+ for k in other.keys():
227
+ if isinstance(other[k], Mapping) and (k in self):
228
+ if isinstance(self[k], Dict):
229
+ self[k].merge(other[k])
230
+
231
+ elif isinstance(self[k], Mapping):
232
+ self[k] = Dict(self[k])
233
+ self[k].merge(other[k])
234
+
235
+ else:
236
+ self[k] = other[k]
237
+
238
+ else:
239
+ self[k] = other[k]
240
+
241
+
242
+ class NormalizeDict(Dict):
243
+ """A normalizing dictionary, taken from herd.utils.NormalizeDict
244
+
245
+ You can override the .normalize_key and .normalize_value methods, which get
246
+ called anytime you set/get a value. You can check out the idict class for an
247
+ implementation that uses those methods to allow case-insensitive keys
248
+ """
249
+ def __init__(self, *args, **kwargs):
250
+ super(NormalizeDict, self).__init__()
251
+ self.update(*args, **kwargs)
252
+
253
+ def __setitem__(self, k, v):
254
+ k = self.normalize_key(k)
255
+ v = self.normalize_value(v)
256
+ return super(NormalizeDict, self).__setitem__(k, v)
257
+
258
+ def __delitem__(self, k):
259
+ k = self.normalize_key(k)
260
+ return super(NormalizeDict, self).__delitem__(k)
261
+
262
+ def __getitem__(self, k):
263
+ k = self.normalize_key(k)
264
+ return super(NormalizeDict, self).__getitem__(k)
265
+
266
+ def __contains__(self, k):
267
+ k = self.normalize_key(k)
268
+ return super(NormalizeDict, self).__contains__(k)
269
+
270
+ def setdefault(self, k, default=None):
271
+ k = self.normalize_key(k)
272
+ v = self.normalize_value(default)
273
+ return super(NormalizeDict, self).setdefault(k, v)
274
+
275
+ def update(self, *args, **kwargs):
276
+ # create temp dictionary so I don't have to mess with the arguments
277
+ d = dict(*args, **kwargs)
278
+ for k, v in d.items():
279
+ self[k] = v
280
+
281
+ def pop(self, k, *default):
282
+ k = self.normalize_key(k)
283
+ if default:
284
+ default = [self.normalize_value(default[0])]
285
+ return super(NormalizeDict, self).pop(k, *default)
286
+
287
+ def get(self, k, default=None):
288
+ k = self.normalize_key(k)
289
+ v = self.normalize_value(default)
290
+ return super(NormalizeDict, self).get(k, v)
291
+
292
+ def normalize_key(self, k):
293
+ return k
294
+
295
+ def normalize_value(self, v):
296
+ return v
297
+
298
+ def ritems(self, *keys):
299
+ keys = map(self.normalize_key, keys)
300
+ return super(NormalizeDict, self).ritems(*keys)
301
+
302
+
303
+ class idict(NormalizeDict):
304
+ """A case insensitive dictionary, adapted from herd.utils.NormalizeDict, naming
305
+ convention is meant to mimic python's use of i* for case-insensitive functions and
306
+ python's built-in dict class (which is why the classname is all lowercase)"""
307
+ def __init__(self, *args, **kwargs):
308
+ self.ikeys = {} # hold mappings from normalized keys to the actual key
309
+ super().__init__(*args, **kwargs)
310
+
311
+ def normalize_key(self, k):
312
+ nk = k.lower()
313
+ if nk in self.ikeys:
314
+ k = self.ikeys[nk]
315
+ self.ikeys[nk] = k
316
+ return k
317
+ Idict = idict
318
+ IDict = idict
319
+ iDict = idict
320
+
321
+
322
+ class Namespace(NormalizeDict):
323
+ """Allows both dictionary syntax (eg foo["keyname"]) and object syntax
324
+ (eg foo.keyname)
325
+ """
326
+ def __setattr__(self, k, v):
327
+ return self.__setitem__(k, v)
328
+
329
+ def __getattr__(self, k):
330
+ try:
331
+ return self.__getitem__(k)
332
+ except KeyError as e:
333
+ raise AttributeError(e) from e
334
+
335
+ def __delattr__(self, k):
336
+ return self.__delitem__(k)
337
+
338
+
339
+ class ContextNamespace(Namespace):
340
+ """A context aware namespace where you can override values in later contexts
341
+ and then revert back to the original context when the with statement is done
342
+
343
+ values are retrieved in LIFO order of the pushed contexts when cascade=True
344
+
345
+ Based on bang.config.Config moved here and expanded on 1-10-2023
346
+
347
+ Similar to a ChainMap:
348
+ https://docs.python.org/3/library/collections.html#chainmap-objects
349
+
350
+ :Example:
351
+ n = ContextNamespace()
352
+
353
+ n.foo = 1
354
+ with n.context("<CONTEXT NAME>"):
355
+ n.foo # 1
356
+ n.foo = 2
357
+ n.foo # 2
358
+ with n.context("<CONTEXT NAME 2>"):
359
+ n.foo # 2
360
+ n.foo = 3
361
+ n.foo #3
362
+
363
+ n.foo # 2
364
+
365
+ n.foo # 1
366
+
367
+ # you can also turn cascading off, so it switches contexts but doesn't
368
+ # cascade the values
369
+
370
+ n = ContextNamespace(cascade=False)
371
+
372
+ n.foo = 1
373
+ with n.context("<CONTEXT NAME>"):
374
+ "foo" in n.foo # False
375
+ n.foo = 2
376
+
377
+ n.foo # 1
378
+ """
379
+ context_class = Namespace
380
+ """Each context will be an instance of this class"""
381
+
382
+ def __init__(self, name="", cascade=True):
383
+ """
384
+ :param name: str, If you want to customize the default context name then
385
+ pass it in
386
+ :param cascade: bool, if True then gets cascade through the stack of
387
+ contexts, if False then this contexts completely switch
388
+ """
389
+ super().__init__()
390
+
391
+ # we set support properties directly on the __dict__ so __setattr__
392
+ # doesn't infinite loop, context properties can just be set normally
393
+
394
+ # a stack of the context names
395
+ self.__dict__["_context_names"] = Stack()
396
+ self.__dict__["_cascade"] = cascade
397
+ self.push_context(name)
398
+
399
+ def normalize_context_name(self, name):
400
+ """normalize the context name, this is meant to be customized in child
401
+ classes if needed
402
+
403
+ :param name: str, the context name
404
+ :returns: str, the name, normalized
405
+ """
406
+ return name
407
+
408
+ def push_context(self, name):
409
+ """push a context named name onto the context stack"""
410
+ name = self.normalize_context_name(name)
411
+
412
+ # this is where all the magic happens, the keys are the context names
413
+ # and the values are the set properties for that context,
414
+ super().setdefault(name, self.context_class())
415
+
416
+ self._context_names.push(name)
417
+
418
+ return name
419
+
420
+ def pop_context(self):
421
+ """Pop the last context from the stack"""
422
+ if len(self._context_names) > 1:
423
+ return self._context_names.pop()
424
+
425
+ def switch_context(self, name):
426
+ """Switch to context name, return the previous context_name"""
427
+ ret = self.pop_context()
428
+ self.push_context(name)
429
+ return ret
430
+
431
+ def clear_context(self, name):
432
+ """Completely clear the context"""
433
+ name = self.normalize_context_name(name)
434
+ self.get_context(name).clear()
435
+
436
+ def context_name(self):
437
+ """Get the current context name"""
438
+ return self._context_names.peek()
439
+
440
+ def context_names(self):
441
+ """yield all the _context_names taking into account the cascade setting"""
442
+ for context_name in self._context_names:
443
+ yield context_name
444
+
445
+ if not self._cascade:
446
+ break
447
+
448
+ def current_context(self):
449
+ """get the current context
450
+
451
+ :returns: self.context_class instance
452
+ """
453
+ return self.get_context(self.context_name())
454
+
455
+ def get_context(self, name):
456
+ """Get the context at name
457
+
458
+ :returns: self.context_class instance
459
+ """
460
+ name = self.normalize_context_name(name)
461
+ return super().__getitem__(name)
462
+
463
+ def is_context(self, name):
464
+ """Return True if name matches the current context name"""
465
+ return self.context_name() == self.normalize_context_name(name)
466
+
467
+ @contextmanager
468
+ def context(self, name, **kwargs):
469
+ """This is meant to be used with the "with ..." command, its purpose is
470
+ to make it easier to change the context and restore it back to the
471
+ previous context when it is done
472
+
473
+ :Example:
474
+ with instance.context("foo"):
475
+ # anything in this block will use the foo configuration
476
+ pass
477
+ # anything outside this block will *NOT* use the foo configuration
478
+ """
479
+ self.push_context(name)
480
+
481
+ # passed in values get set on the instance directly
482
+ for k, v in kwargs.items():
483
+ self[k] = v
484
+
485
+ yield self
486
+
487
+ self.pop_context()
488
+
489
+ def __setitem__(self, k, v):
490
+ k = self.normalize_key(k)
491
+ v = self.normalize_value(v)
492
+ self.current_context().__setitem__(k, v)
493
+
494
+ def __delitem__(self, k):
495
+ k = self.normalize_key(k)
496
+ self.current_context().__delitem__(k)
497
+
498
+ def __getitem__(self, k):
499
+ """Most of the context LIFO magic happens here, this will work through
500
+ the contexts looking for k"""
501
+ if k == "__missing__":
502
+ # we need to avoid infinite recursion, if __missing__ gets to here
503
+ # it doesn't exist so go ahead and fail fast
504
+ raise KeyError(k)
505
+
506
+ k = self.normalize_key(k)
507
+ for context_name in self.context_names():
508
+ try:
509
+ return self.get_context(context_name)[k]
510
+
511
+ except KeyError:
512
+ pass
513
+
514
+ # because we completely override __getitem__ we can't rely on the
515
+ # descriptor protocol to call __missing__ for us
516
+ try:
517
+ return self.__missing__(k)
518
+
519
+ except AttributeError as e:
520
+ raise KeyError(k)
521
+
522
+ def __contains__(self, k):
523
+ for context_name in self.context_names():
524
+ if k in self.get_context(context_name):
525
+ return True
526
+ return False
527
+
528
+ def __len__(self):
529
+ """Unlike a normal dict this is O(n)"""
530
+ return len(list(self.keys()))
531
+
532
+ def __reversed__(self):
533
+ return reversed(list(self.keys()))
534
+
535
+ def __ior__(self, other):
536
+ """self |= other"""
537
+ self.update(other)
538
+ return self
539
+
540
+ def __or__(self, other):
541
+ """self | other"""
542
+ d = self.context_class({i[0]: i[1] for i in self.items()})
543
+ d.update(other)
544
+ return d
545
+
546
+ def setdefault(self, k, v):
547
+ if k not in self:
548
+ self[k] = v
549
+
550
+ def get(self, k, default=None):
551
+ # we have to do it this way to play nice with __missing__
552
+ if k in self:
553
+ return self[k]
554
+ else:
555
+ return default
556
+
557
+ def pop(self, k, *default):
558
+ """Pop works a little differently than other key access methods, pop will
559
+ only return a value if it is actually in the current context, if k is not
560
+ in the current context then this will return default
561
+ """
562
+ k = self.normalize_key(k)
563
+ try:
564
+ d = self.current_context()
565
+ v = d[k]
566
+ del d[k]
567
+
568
+ except KeyError:
569
+ if default:
570
+ v = self.normalize_value(default[0])
571
+ else:
572
+ raise
573
+
574
+ return v
575
+
576
+ def popitem(self):
577
+ return self.current_context().popitem()
578
+
579
+ def clear(self):
580
+ return self.current_context().clear()
581
+
582
+ def copy(self):
583
+ """return a dict of all active values in the config at the moment"""
584
+ d = self.context_class()
585
+
586
+ if self._cascade:
587
+ for context_name in reversed(self._context_names):
588
+ d.update(self.get_context(context_name))
589
+
590
+ else:
591
+ d.update(self.current_context())
592
+
593
+ return d
594
+
595
+ def items(self):
596
+ seen_keys = set()
597
+ for context_name in self._context_names:
598
+ d = self.get_context(context_name)
599
+ for k, v in d.items():
600
+ if k not in seen_keys:
601
+ yield k, v
602
+ seen_keys.add(k)
603
+
604
+ if not self._cascade:
605
+ break
606
+
607
+ def keys(self):
608
+ for k, _ in self.items():
609
+ yield k
610
+
611
+ def values(self):
612
+ for _, v in self.items():
613
+ yield v
614
+