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.
- datatypes/__init__.py +153 -0
- datatypes/collections/__init__.py +22 -0
- datatypes/collections/container.py +150 -0
- datatypes/collections/mapping.py +614 -0
- datatypes/collections/sequence.py +469 -0
- datatypes/command.py +572 -0
- datatypes/compat.py +205 -0
- datatypes/config/__init__.py +9 -0
- datatypes/config/environ.py +302 -0
- datatypes/config/parser.py +255 -0
- datatypes/copy.py +151 -0
- datatypes/csv.py +322 -0
- datatypes/datetime.py +888 -0
- datatypes/decorators/__init__.py +23 -0
- datatypes/decorators/base.py +343 -0
- datatypes/decorators/descriptor.py +389 -0
- datatypes/decorators/misc.py +118 -0
- datatypes/email.py +351 -0
- datatypes/enum.py +191 -0
- datatypes/event.py +207 -0
- datatypes/filetypes/__init__.py +12 -0
- datatypes/filetypes/html.py +556 -0
- datatypes/filetypes/toml.py +260 -0
- datatypes/http.py +723 -0
- datatypes/logging.py +384 -0
- datatypes/number.py +187 -0
- datatypes/parse.py +246 -0
- datatypes/path.py +3637 -0
- datatypes/profile.py +232 -0
- datatypes/reflection.py +1498 -0
- datatypes/server.py +408 -0
- datatypes/service.py +212 -0
- datatypes/string.py +1328 -0
- datatypes/token/__init__.py +16 -0
- datatypes/token/abnf.py +1704 -0
- datatypes/token/base.py +475 -0
- datatypes/token/word.py +259 -0
- datatypes/url.py +841 -0
- datatypes/utils.py +142 -0
- datatypes-0.12.0.dist-info/LICENSE.txt +21 -0
- datatypes-0.12.0.dist-info/METADATA +52 -0
- datatypes-0.12.0.dist-info/RECORD +44 -0
- datatypes-0.12.0.dist-info/WHEEL +5 -0
- 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
|
+
|