chanfig 0.0.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.
- chanfig/__init__.py +3 -0
- chanfig/config.py +1739 -0
- chanfig-0.0.0.dist-info/LICENSE +1 -0
- chanfig-0.0.0.dist-info/LICENSES/LICENSE.Apache2 +201 -0
- chanfig-0.0.0.dist-info/LICENSES/LICENSE.BSD2 +25 -0
- chanfig-0.0.0.dist-info/LICENSES/LICENSE.BSD3 +29 -0
- chanfig-0.0.0.dist-info/LICENSES/LICENSE.GPL2 +339 -0
- chanfig-0.0.0.dist-info/LICENSES/LICENSE.GPL3 +674 -0
- chanfig-0.0.0.dist-info/LICENSES/LICENSE.MIT +21 -0
- chanfig-0.0.0.dist-info/METADATA +178 -0
- chanfig-0.0.0.dist-info/RECORD +12 -0
- chanfig-0.0.0.dist-info/WHEEL +4 -0
chanfig/config.py
ADDED
|
@@ -0,0 +1,1739 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from argparse import ArgumentParser
|
|
5
|
+
from ast import literal_eval
|
|
6
|
+
from collections import OrderedDict as OrderedDict_
|
|
7
|
+
from collections.abc import Mapping
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from copy import copy, deepcopy
|
|
10
|
+
from functools import wraps
|
|
11
|
+
from json import dumps as json_dumps
|
|
12
|
+
from json import loads as json_loads
|
|
13
|
+
from os import PathLike
|
|
14
|
+
from os.path import splitext
|
|
15
|
+
from typing import IO, Any, Callable, Iterable, List, Optional, Sequence, Union
|
|
16
|
+
from warnings import warn
|
|
17
|
+
|
|
18
|
+
from typing_extensions import LiteralString # type: ignore
|
|
19
|
+
from yaml import SafeDumper, SafeLoader
|
|
20
|
+
from yaml import dump as yaml_dump
|
|
21
|
+
from yaml import load as yaml_load
|
|
22
|
+
|
|
23
|
+
PathStr = Union[PathLike, str, bytes, LiteralString]
|
|
24
|
+
File = Union[PathStr, IO]
|
|
25
|
+
|
|
26
|
+
YAML = ("yml", "yaml")
|
|
27
|
+
JSON = ("json",)
|
|
28
|
+
PYTHON = ("py",)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Dumper(SafeDumper): # pylint: disable=C0115
|
|
32
|
+
def increase_indent(self, flow: Optional[bool] = False, indentless: Optional[bool] = False):
|
|
33
|
+
return super().increase_indent(flow, indentless)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class FileError(ValueError): # pylint: disable=C0115
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ConfigParser(ArgumentParser): # pylint: disable=C0115
|
|
41
|
+
def parse(
|
|
42
|
+
self,
|
|
43
|
+
args: Optional[Sequence[str]] = None,
|
|
44
|
+
config: Optional[Config] = None,
|
|
45
|
+
default_config: Optional[str] = None,
|
|
46
|
+
) -> Config:
|
|
47
|
+
r"""
|
|
48
|
+
Parse the arguments for config.
|
|
49
|
+
There are three levels of config:
|
|
50
|
+
|
|
51
|
+
1. The base config parsed into the function,
|
|
52
|
+
2. The config file located at the path of default_config (if specified),
|
|
53
|
+
3. The config specified in arguments.
|
|
54
|
+
|
|
55
|
+
Higher levels override lower levels (i.e. 3 > 2 > 1).
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
args (Optional[Sequence[str]]): The arguments to parse. Defaults to sys.argv[1:].
|
|
59
|
+
config (Optional[Config]): The base config. Defaults to an empty Config.
|
|
60
|
+
default_config (Optional[str]): The path to a config file.
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
```python
|
|
64
|
+
>>> c = Config(a=0)
|
|
65
|
+
>>> c.to(dict)
|
|
66
|
+
{'a': 0}
|
|
67
|
+
>>> c.parse(['--a', '1', '--b', '2', '--c', '3']).to(dict)
|
|
68
|
+
{'a': 1, 'b': 2, 'c': 3}
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
if args is None:
|
|
74
|
+
args = sys.argv[1:]
|
|
75
|
+
for arg in args:
|
|
76
|
+
if arg.startswith("--") and args != "--":
|
|
77
|
+
arg = arg.split("=")[0]
|
|
78
|
+
if arg not in self._option_string_actions:
|
|
79
|
+
self.add_argument(arg)
|
|
80
|
+
if config is None:
|
|
81
|
+
config = Config()
|
|
82
|
+
path = getattr(config, default_config, None) if default_config is not None else None
|
|
83
|
+
if path is not None:
|
|
84
|
+
warn(f"Config has 'default_config={path}' specified, its values will override values in Config")
|
|
85
|
+
parsed, _ = self.parse_known_args(args)
|
|
86
|
+
if path is not None:
|
|
87
|
+
config = config.update(path) # type: ignore
|
|
88
|
+
config = config.update(vars(parsed)) # type: ignore
|
|
89
|
+
return config # type: ignore
|
|
90
|
+
|
|
91
|
+
parse_config = parse
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def identity(string):
|
|
95
|
+
"""
|
|
96
|
+
https://stackoverflow.com/questions/69896931/cant-pickle-local-object-argumentparser-init-locals-identity
|
|
97
|
+
"""
|
|
98
|
+
return string
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class Variable:
|
|
102
|
+
r"""
|
|
103
|
+
Mutable wrapper for immutable objects.
|
|
104
|
+
|
|
105
|
+
Example:
|
|
106
|
+
```python
|
|
107
|
+
>>> v = Variable(1)
|
|
108
|
+
>>> n = v
|
|
109
|
+
>>> v, n
|
|
110
|
+
(1, 1)
|
|
111
|
+
>>> v += 1
|
|
112
|
+
>>> v, n
|
|
113
|
+
(2, 2)
|
|
114
|
+
>>> v.value = 3
|
|
115
|
+
>>> v, n
|
|
116
|
+
(3, 3)
|
|
117
|
+
>>> v.set(4)
|
|
118
|
+
>>> v, n
|
|
119
|
+
(4, 4)
|
|
120
|
+
>>> v = 5
|
|
121
|
+
>>> v, n
|
|
122
|
+
(5, 4)
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
storage: List[Any]
|
|
128
|
+
|
|
129
|
+
def __init__(self, value):
|
|
130
|
+
self.storage = [value]
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def __class__(self):
|
|
134
|
+
return self.value.__class__
|
|
135
|
+
|
|
136
|
+
@__class__.setter
|
|
137
|
+
def __class__(self, value):
|
|
138
|
+
self.value.__class__ = value
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def value(self):
|
|
142
|
+
"""
|
|
143
|
+
actual object stored in the Variable
|
|
144
|
+
"""
|
|
145
|
+
return self.storage[0]
|
|
146
|
+
|
|
147
|
+
@value.setter
|
|
148
|
+
def value(self, value):
|
|
149
|
+
"""
|
|
150
|
+
assign value to object stored in the Variable
|
|
151
|
+
"""
|
|
152
|
+
self.storage[0] = self._get_value(value)
|
|
153
|
+
|
|
154
|
+
def get(self):
|
|
155
|
+
"""
|
|
156
|
+
alias of value
|
|
157
|
+
"""
|
|
158
|
+
return self.value
|
|
159
|
+
|
|
160
|
+
def set(self, value):
|
|
161
|
+
"""
|
|
162
|
+
alias of value.setter
|
|
163
|
+
"""
|
|
164
|
+
self.value = value
|
|
165
|
+
|
|
166
|
+
@staticmethod
|
|
167
|
+
def _get_value(obj):
|
|
168
|
+
if isinstance(obj, Variable):
|
|
169
|
+
return obj.value
|
|
170
|
+
return obj
|
|
171
|
+
|
|
172
|
+
def __getattr__(self, attr):
|
|
173
|
+
return getattr(self.value, attr)
|
|
174
|
+
|
|
175
|
+
def __lt__(self, other) -> bool:
|
|
176
|
+
return self.value < self._get_value(other)
|
|
177
|
+
|
|
178
|
+
def __le__(self, other) -> bool:
|
|
179
|
+
return self.value <= self._get_value(other)
|
|
180
|
+
|
|
181
|
+
def __eq__(self, other) -> bool:
|
|
182
|
+
return self.value == self._get_value(other)
|
|
183
|
+
|
|
184
|
+
def __ne__(self, other) -> bool:
|
|
185
|
+
return self.value != self._get_value(other)
|
|
186
|
+
|
|
187
|
+
def __ge__(self, other) -> bool:
|
|
188
|
+
return self.value >= self._get_value(other)
|
|
189
|
+
|
|
190
|
+
def __gt__(self, other) -> bool:
|
|
191
|
+
return self.value > self._get_value(other)
|
|
192
|
+
|
|
193
|
+
def __index__(self) -> int:
|
|
194
|
+
return int(self.value)
|
|
195
|
+
|
|
196
|
+
def __invert__(self):
|
|
197
|
+
return ~self.value
|
|
198
|
+
|
|
199
|
+
def __abs__(self):
|
|
200
|
+
return abs(self.value)
|
|
201
|
+
|
|
202
|
+
def __add__(self, other):
|
|
203
|
+
return Variable(self.value + self._get_value(other))
|
|
204
|
+
|
|
205
|
+
def __radd__(self, other):
|
|
206
|
+
return Variable(self._get_value(other) + self.value)
|
|
207
|
+
|
|
208
|
+
def __iadd__(self, other):
|
|
209
|
+
self.value += self._get_value(other)
|
|
210
|
+
return self
|
|
211
|
+
|
|
212
|
+
def __and__(self, other):
|
|
213
|
+
return Variable(self.value & self._get_value(other))
|
|
214
|
+
|
|
215
|
+
def __rand__(self, other):
|
|
216
|
+
return Variable(self._get_value(other) & self.value)
|
|
217
|
+
|
|
218
|
+
def __iand__(self, other):
|
|
219
|
+
self.value &= self._get_value(other)
|
|
220
|
+
return self
|
|
221
|
+
|
|
222
|
+
def __floordiv__(self, other):
|
|
223
|
+
return Variable(self.value // self._get_value(other))
|
|
224
|
+
|
|
225
|
+
def __rfloordiv__(self, other):
|
|
226
|
+
return Variable(self._get_value(other) // self.value)
|
|
227
|
+
|
|
228
|
+
def __ifloordiv__(self, other):
|
|
229
|
+
self.value //= self._get_value(other)
|
|
230
|
+
return self
|
|
231
|
+
|
|
232
|
+
def __mod__(self, other):
|
|
233
|
+
return Variable(self.value % self._get_value(other))
|
|
234
|
+
|
|
235
|
+
def __rmod__(self, other):
|
|
236
|
+
return Variable(self._get_value(other) % self.value)
|
|
237
|
+
|
|
238
|
+
def __imod__(self, other):
|
|
239
|
+
self.value %= self._get_value(other)
|
|
240
|
+
return self
|
|
241
|
+
|
|
242
|
+
def __mul__(self, other):
|
|
243
|
+
return Variable(self.value * self._get_value(other))
|
|
244
|
+
|
|
245
|
+
def __rmul__(self, other):
|
|
246
|
+
return Variable(self._get_value(other) * self.value)
|
|
247
|
+
|
|
248
|
+
def __imul__(self, other):
|
|
249
|
+
self.value *= self._get_value(other)
|
|
250
|
+
return self
|
|
251
|
+
|
|
252
|
+
def __matmul__(self, other):
|
|
253
|
+
return Variable(self.value @ self._get_value(other))
|
|
254
|
+
|
|
255
|
+
def __rmatmul__(self, other):
|
|
256
|
+
return Variable(self._get_value(other) @ self.value)
|
|
257
|
+
|
|
258
|
+
def __imatmul__(self, other):
|
|
259
|
+
self.value @= self._get_value(other)
|
|
260
|
+
return self
|
|
261
|
+
|
|
262
|
+
def __pow__(self, other):
|
|
263
|
+
return Variable(self.value ** self._get_value(other))
|
|
264
|
+
|
|
265
|
+
def __rpow__(self, other):
|
|
266
|
+
return Variable(self._get_value(other) ** self.value)
|
|
267
|
+
|
|
268
|
+
def __ipow__(self, other):
|
|
269
|
+
self.value **= self._get_value(other)
|
|
270
|
+
return self
|
|
271
|
+
|
|
272
|
+
def __truediv__(self, other):
|
|
273
|
+
return Variable(self.value / self._get_value(other))
|
|
274
|
+
|
|
275
|
+
def __rtruediv__(self, other):
|
|
276
|
+
return Variable(self._get_value(other) / self.value)
|
|
277
|
+
|
|
278
|
+
def __itruediv__(self, other):
|
|
279
|
+
self.value /= self._get_value(other)
|
|
280
|
+
return self
|
|
281
|
+
|
|
282
|
+
def __sub__(self, other):
|
|
283
|
+
return Variable(self.value - self._get_value(other))
|
|
284
|
+
|
|
285
|
+
def __rsub__(self, other):
|
|
286
|
+
return Variable(self._get_value(other) - self.value)
|
|
287
|
+
|
|
288
|
+
def __isub__(self, other):
|
|
289
|
+
self.value -= self._get_value(other)
|
|
290
|
+
return self
|
|
291
|
+
|
|
292
|
+
def __copy__(self):
|
|
293
|
+
return Variable(self.value)
|
|
294
|
+
|
|
295
|
+
def __deepcopy__(self, memo: Optional[Mapping] = None):
|
|
296
|
+
return Variable(copy(self.value))
|
|
297
|
+
|
|
298
|
+
def __repr__(self):
|
|
299
|
+
return repr(self.value)
|
|
300
|
+
|
|
301
|
+
def to(self, cls: Callable): # pylint: disable=C0103
|
|
302
|
+
r"""
|
|
303
|
+
Convert the value to a different type.
|
|
304
|
+
|
|
305
|
+
`convert` is an alias of this method.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
cls (Callable): The type to convert to.
|
|
309
|
+
|
|
310
|
+
Example:
|
|
311
|
+
```python
|
|
312
|
+
>>> id = Variable(1013)
|
|
313
|
+
>>> id.to(float)
|
|
314
|
+
1013.0
|
|
315
|
+
>>> id.to(str)
|
|
316
|
+
'1013.0'
|
|
317
|
+
|
|
318
|
+
```
|
|
319
|
+
"""
|
|
320
|
+
|
|
321
|
+
self.value = cls(self.value)
|
|
322
|
+
return self
|
|
323
|
+
|
|
324
|
+
def int(self) -> int:
|
|
325
|
+
r"""
|
|
326
|
+
Convert the value to a python default int.
|
|
327
|
+
|
|
328
|
+
Example:
|
|
329
|
+
```python
|
|
330
|
+
>>> id = Variable(1013.0)
|
|
331
|
+
>>> id.int()
|
|
332
|
+
1013
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
return self.to(int)
|
|
338
|
+
|
|
339
|
+
def float(self) -> float:
|
|
340
|
+
r"""
|
|
341
|
+
Convert the value to a python default float.
|
|
342
|
+
|
|
343
|
+
Example:
|
|
344
|
+
```python
|
|
345
|
+
>>> id = Variable(1013)
|
|
346
|
+
>>> id.float()
|
|
347
|
+
1013.0
|
|
348
|
+
|
|
349
|
+
```
|
|
350
|
+
"""
|
|
351
|
+
|
|
352
|
+
return self.to(float)
|
|
353
|
+
|
|
354
|
+
def str(self) -> str:
|
|
355
|
+
r"""
|
|
356
|
+
Convert the value to a python default float.
|
|
357
|
+
|
|
358
|
+
Example:
|
|
359
|
+
```python
|
|
360
|
+
>>> id = Variable(1013)
|
|
361
|
+
>>> id.str()
|
|
362
|
+
'1013'
|
|
363
|
+
|
|
364
|
+
```
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
return self.to(str)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class OrderedDict(OrderedDict_):
|
|
371
|
+
"""
|
|
372
|
+
Default OrderedDict with attribute-style access.
|
|
373
|
+
|
|
374
|
+
Works best with `Variable` objects.
|
|
375
|
+
|
|
376
|
+
Example:
|
|
377
|
+
```python
|
|
378
|
+
>>> d = OrderedDict()
|
|
379
|
+
>>> d.d = 1013
|
|
380
|
+
>>> d['d']
|
|
381
|
+
1013
|
|
382
|
+
>>> d['i'] = 1031
|
|
383
|
+
>>> d.i
|
|
384
|
+
1031
|
|
385
|
+
>>> d.a = Variable(1)
|
|
386
|
+
>>> d.b = d.a
|
|
387
|
+
>>> d.a, d.b
|
|
388
|
+
(1, 1)
|
|
389
|
+
>>> d.a += 1
|
|
390
|
+
>>> d.a, d.b
|
|
391
|
+
(2, 2)
|
|
392
|
+
>>> d.a = 3
|
|
393
|
+
>>> d.a, d.b
|
|
394
|
+
(3, 3)
|
|
395
|
+
>>> d.a = Variable('hello')
|
|
396
|
+
>>> d.a = d.a + ', world'
|
|
397
|
+
>>> d.b
|
|
398
|
+
'hello, world'
|
|
399
|
+
|
|
400
|
+
```
|
|
401
|
+
"""
|
|
402
|
+
|
|
403
|
+
default_factory: Optional[Callable]
|
|
404
|
+
indent: int = 2
|
|
405
|
+
|
|
406
|
+
def __init__(self, *args, default_factory: Optional[Callable] = None, **kwargs):
|
|
407
|
+
super().__init__()
|
|
408
|
+
if default_factory is not None:
|
|
409
|
+
if callable(default_factory):
|
|
410
|
+
self.setattr("default_factory", default_factory)
|
|
411
|
+
else:
|
|
412
|
+
raise TypeError(
|
|
413
|
+
f"default_factory={default_factory} should be of type Callable, but got {type(default_factory)}"
|
|
414
|
+
)
|
|
415
|
+
self.setattr("indent", 2)
|
|
416
|
+
self._init(*args, **kwargs)
|
|
417
|
+
self.init()
|
|
418
|
+
|
|
419
|
+
def _init(self, *args, **kwargs) -> None:
|
|
420
|
+
r"""
|
|
421
|
+
Initialise values from arguments for OrderedDict.
|
|
422
|
+
This method is called in `__init__`.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
*args: [(key1, value1), (key2, value2)].
|
|
426
|
+
**kwargs: {key1: value1, key2: value2}.
|
|
427
|
+
|
|
428
|
+
"""
|
|
429
|
+
for key, value in args:
|
|
430
|
+
self.set(key, value)
|
|
431
|
+
for key, value in kwargs.items():
|
|
432
|
+
self.set(key, value)
|
|
433
|
+
|
|
434
|
+
def init(self) -> None:
|
|
435
|
+
r"""
|
|
436
|
+
Setting values for OrderedDict.
|
|
437
|
+
This method is called in `__init__`.
|
|
438
|
+
You can overwrite this method instead of `__init__` as constructor to avoid call of `super().__init__()`
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
*args: [(key1, value1), (key2, value2)].
|
|
442
|
+
**kwargs: {key1: value1, key2: value2}.
|
|
443
|
+
|
|
444
|
+
"""
|
|
445
|
+
|
|
446
|
+
def get(self, name: str, default: Optional[Any] = None) -> Any:
|
|
447
|
+
r"""
|
|
448
|
+
Get value from OrderedDict.
|
|
449
|
+
|
|
450
|
+
`__getitem__` and `__getattr__` are alias of this method.
|
|
451
|
+
|
|
452
|
+
Note that default here will override the default_factory if specified.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
name (str): Key name.
|
|
456
|
+
default (Optional[Any]): Default value if name does not present.
|
|
457
|
+
|
|
458
|
+
Example:
|
|
459
|
+
```python
|
|
460
|
+
>>> d = OrderedDict(d=1013)
|
|
461
|
+
>>> d.get('d')
|
|
462
|
+
1013
|
|
463
|
+
>>> d['d']
|
|
464
|
+
1013
|
|
465
|
+
>>> d.d
|
|
466
|
+
1013
|
|
467
|
+
>>> d.get('f', 2)
|
|
468
|
+
2
|
|
469
|
+
>>> d.get('f')
|
|
470
|
+
Traceback (most recent call last):
|
|
471
|
+
KeyError: 'OrderedDict does not contain f'
|
|
472
|
+
|
|
473
|
+
```
|
|
474
|
+
"""
|
|
475
|
+
|
|
476
|
+
return super().__getitem__(name) if default is None else self.__missing__(name, default)
|
|
477
|
+
|
|
478
|
+
__getitem__ = get
|
|
479
|
+
__getattr__ = get
|
|
480
|
+
|
|
481
|
+
def getattr(self, name: str, default: Optional[Any] = None):
|
|
482
|
+
r"""
|
|
483
|
+
Get attribute of OrderedDict.
|
|
484
|
+
|
|
485
|
+
Note that if won't return value in the OrderedDict, nor will it create new one if default_factory is specified.
|
|
486
|
+
|
|
487
|
+
Args:
|
|
488
|
+
name (str): Key name.
|
|
489
|
+
default (Optional[Any]): Default value if name does not present.
|
|
490
|
+
|
|
491
|
+
Example:
|
|
492
|
+
```python
|
|
493
|
+
>>> d = OrderedDict(a=1, default_factory=list)
|
|
494
|
+
>>> d.getattr('default_factory')
|
|
495
|
+
<class 'list'>
|
|
496
|
+
>>> d.getattr('b', 2)
|
|
497
|
+
2
|
|
498
|
+
>>> d.getattr('a')
|
|
499
|
+
Traceback (most recent call last):
|
|
500
|
+
AttributeError: OrderedDict has no attribute a
|
|
501
|
+
|
|
502
|
+
```
|
|
503
|
+
"""
|
|
504
|
+
|
|
505
|
+
try:
|
|
506
|
+
try:
|
|
507
|
+
return self.__dict__[name]
|
|
508
|
+
except KeyError:
|
|
509
|
+
return self.__class__.__dict__[name]
|
|
510
|
+
except KeyError:
|
|
511
|
+
if default is not None:
|
|
512
|
+
return default
|
|
513
|
+
raise AttributeError(f"{self.__class__.__name__} has no attribute {name}") from None
|
|
514
|
+
|
|
515
|
+
def set(self, name: str, value: Any) -> None:
|
|
516
|
+
r"""
|
|
517
|
+
Set value of OrderedDict.
|
|
518
|
+
|
|
519
|
+
`__setitem__` and `__setattr__` are alias of this method.
|
|
520
|
+
|
|
521
|
+
Args:
|
|
522
|
+
name (str): Key name.
|
|
523
|
+
value (Any): Value to set.
|
|
524
|
+
|
|
525
|
+
Example:
|
|
526
|
+
```python
|
|
527
|
+
>>> d = OrderedDict()
|
|
528
|
+
>>> d.set('d', 1013)
|
|
529
|
+
>>> d.get('d')
|
|
530
|
+
1013
|
|
531
|
+
>>> d['d'] = 1031
|
|
532
|
+
>>> d.d
|
|
533
|
+
1031
|
|
534
|
+
>>> d.d = 'chang'
|
|
535
|
+
>>> d['d']
|
|
536
|
+
'chang'
|
|
537
|
+
|
|
538
|
+
```
|
|
539
|
+
"""
|
|
540
|
+
|
|
541
|
+
if isinstance(value, str):
|
|
542
|
+
try:
|
|
543
|
+
value = literal_eval(value)
|
|
544
|
+
except (TypeError, ValueError, SyntaxError):
|
|
545
|
+
pass
|
|
546
|
+
if name in self and isinstance(self[name], Variable):
|
|
547
|
+
self[name].set(value)
|
|
548
|
+
else:
|
|
549
|
+
super().__setitem__(name, value)
|
|
550
|
+
|
|
551
|
+
__setitem__ = set
|
|
552
|
+
__setattr__ = set
|
|
553
|
+
|
|
554
|
+
def setattr(self, name: str, value: Any):
|
|
555
|
+
r"""
|
|
556
|
+
Set attribute of OrderedDict.
|
|
557
|
+
Note that it won't alter values in the OrderedDict.
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
name (str): Key name.
|
|
561
|
+
value (Any): Value to set.
|
|
562
|
+
|
|
563
|
+
Example:
|
|
564
|
+
```python
|
|
565
|
+
>>> d = OrderedDict()
|
|
566
|
+
>>> d.setattr('attr', 'value')
|
|
567
|
+
>>> d.getattr('attr')
|
|
568
|
+
'value'
|
|
569
|
+
|
|
570
|
+
```
|
|
571
|
+
"""
|
|
572
|
+
|
|
573
|
+
self.__dict__[name] = value
|
|
574
|
+
|
|
575
|
+
def delete(self, name: str) -> None:
|
|
576
|
+
r"""
|
|
577
|
+
Remove value from OrderedDict.
|
|
578
|
+
|
|
579
|
+
`__delitem__`, `__delattr__` and `remove` are alias of this method.
|
|
580
|
+
|
|
581
|
+
Args:
|
|
582
|
+
name (str): Key name.
|
|
583
|
+
|
|
584
|
+
Example:
|
|
585
|
+
```python
|
|
586
|
+
>>> d = OrderedDict(d=1016, n='chang')
|
|
587
|
+
>>> d.d
|
|
588
|
+
1016
|
|
589
|
+
>>> d.n
|
|
590
|
+
'chang'
|
|
591
|
+
>>> d.delete('d')
|
|
592
|
+
>>> d.d
|
|
593
|
+
Traceback (most recent call last):
|
|
594
|
+
KeyError: 'OrderedDict does not contain d'
|
|
595
|
+
>>> del d.n
|
|
596
|
+
>>> d.n
|
|
597
|
+
Traceback (most recent call last):
|
|
598
|
+
KeyError: 'OrderedDict does not contain n'
|
|
599
|
+
>>> del d.f
|
|
600
|
+
Traceback (most recent call last):
|
|
601
|
+
KeyError: 'f'
|
|
602
|
+
|
|
603
|
+
```
|
|
604
|
+
"""
|
|
605
|
+
|
|
606
|
+
super().__delitem__(name)
|
|
607
|
+
|
|
608
|
+
__delitem__ = delete
|
|
609
|
+
__delattr__ = delete
|
|
610
|
+
remove = delete
|
|
611
|
+
|
|
612
|
+
def delattr(self, name: str) -> None:
|
|
613
|
+
r"""
|
|
614
|
+
Remove attribute of OrderedDict.
|
|
615
|
+
Note that it won't remove values in the OrderedDict.
|
|
616
|
+
|
|
617
|
+
Args:
|
|
618
|
+
name (str): Key name.
|
|
619
|
+
|
|
620
|
+
Example:
|
|
621
|
+
```python
|
|
622
|
+
>>> d = OrderedDict()
|
|
623
|
+
>>> d.setattr('name', 'chang')
|
|
624
|
+
>>> d.getattr('name')
|
|
625
|
+
'chang'
|
|
626
|
+
>>> d.delattr('name')
|
|
627
|
+
>>> d.getattr('name')
|
|
628
|
+
Traceback (most recent call last):
|
|
629
|
+
AttributeError: OrderedDict has no attribute name
|
|
630
|
+
|
|
631
|
+
```
|
|
632
|
+
"""
|
|
633
|
+
|
|
634
|
+
del self.__dict__[name]
|
|
635
|
+
|
|
636
|
+
def __missing__(self, name: str, default: Optional[Any] = None) -> Any:
|
|
637
|
+
r"""
|
|
638
|
+
Allow dict to have default value if it doesn't exist.
|
|
639
|
+
|
|
640
|
+
Args:
|
|
641
|
+
name (str): Key name.
|
|
642
|
+
default (Optional[Any]): Default value if name does not present.
|
|
643
|
+
|
|
644
|
+
Example:
|
|
645
|
+
```python
|
|
646
|
+
>>> d = OrderedDict(default_factory=list)
|
|
647
|
+
>>> d.n
|
|
648
|
+
[]
|
|
649
|
+
>>> d.get('d', 1031)
|
|
650
|
+
1031
|
|
651
|
+
>>> d.__missing__('d', 1031)
|
|
652
|
+
1031
|
|
653
|
+
|
|
654
|
+
```
|
|
655
|
+
"""
|
|
656
|
+
|
|
657
|
+
if default is None:
|
|
658
|
+
# default_factory might not in __dict__ and cannot be replaced with if self.getattr("default_factory")
|
|
659
|
+
if "default_factory" not in self.__dict__:
|
|
660
|
+
raise KeyError(f"{self.__class__.__name__} does not contain {name}")
|
|
661
|
+
default_factory = self.getattr("default_factory")
|
|
662
|
+
default = default_factory()
|
|
663
|
+
if isinstance(default, OrderedDict):
|
|
664
|
+
default.__dict__.update(self.__dict__)
|
|
665
|
+
super().__setitem__(name, default)
|
|
666
|
+
return default
|
|
667
|
+
|
|
668
|
+
def to(self, cls: Callable = dict) -> Mapping: # pylint: disable=C0103
|
|
669
|
+
r"""
|
|
670
|
+
Convert OrderedDict to other Mapping.
|
|
671
|
+
|
|
672
|
+
`to` and `dict` are alias of this method.
|
|
673
|
+
|
|
674
|
+
Args:
|
|
675
|
+
cls (Callable): Target class to be converted to. Defaults to dict.
|
|
676
|
+
|
|
677
|
+
Example:
|
|
678
|
+
```python
|
|
679
|
+
>>> d = OrderedDict(a=1, b=2, c=3)
|
|
680
|
+
>>> d.to(dict)
|
|
681
|
+
{'a': 1, 'b': 2, 'c': 3}
|
|
682
|
+
|
|
683
|
+
```
|
|
684
|
+
"""
|
|
685
|
+
|
|
686
|
+
return cls(**{k: v.value if isinstance(v, Variable) else v for k, v in self.items()})
|
|
687
|
+
|
|
688
|
+
convert = to
|
|
689
|
+
dict = to
|
|
690
|
+
|
|
691
|
+
def update(self, other: Union[Mapping, Iterable, PathStr]) -> OrderedDict: # type: ignore
|
|
692
|
+
r"""
|
|
693
|
+
Update OrderedDict values w.r.t. other.
|
|
694
|
+
|
|
695
|
+
`merge`, `merge_from_file`, and `union` are alias of this method.
|
|
696
|
+
|
|
697
|
+
Args:
|
|
698
|
+
other (Mapping | Iterable | PathStr): Other values to update.
|
|
699
|
+
|
|
700
|
+
Example:
|
|
701
|
+
```python
|
|
702
|
+
>>> d = OrderedDict(a=1, b=2, c=3)
|
|
703
|
+
>>> n = {'b': 'b', 'c': 'c', 'd': 'd'}
|
|
704
|
+
>>> d.update(n).to(dict)
|
|
705
|
+
{'a': 1, 'b': 'b', 'c': 'c', 'd': 'd'}
|
|
706
|
+
>>> l = [('c', 3), ('d', 4)]
|
|
707
|
+
>>> d.update(l).to(dict)
|
|
708
|
+
{'a': 1, 'b': 'b', 'c': 3, 'd': 4}
|
|
709
|
+
|
|
710
|
+
```
|
|
711
|
+
"""
|
|
712
|
+
|
|
713
|
+
if isinstance(other, (PathLike, str, bytes)):
|
|
714
|
+
other = self.load(other)
|
|
715
|
+
if isinstance(other, (Mapping,)):
|
|
716
|
+
for key, value in other.items():
|
|
717
|
+
if key in self and isinstance(self[key], (Mapping,)) and isinstance(value, (Mapping,)):
|
|
718
|
+
self[key].update(value)
|
|
719
|
+
else:
|
|
720
|
+
self[key] = value
|
|
721
|
+
elif isinstance(other, Iterable):
|
|
722
|
+
for key, value in other: # type: ignore
|
|
723
|
+
self[key] = value
|
|
724
|
+
return self
|
|
725
|
+
|
|
726
|
+
merge = update
|
|
727
|
+
merge_from_file = update
|
|
728
|
+
union = update
|
|
729
|
+
|
|
730
|
+
def difference(self, other: Union[Mapping, Iterable, PathStr]) -> OrderedDict:
|
|
731
|
+
r"""
|
|
732
|
+
Difference between OrderedDict values and other.
|
|
733
|
+
|
|
734
|
+
`diff` is an alias of this method.
|
|
735
|
+
|
|
736
|
+
Args:
|
|
737
|
+
other (Mapping | Iterable | PathStr): Other values to compare.
|
|
738
|
+
|
|
739
|
+
Example:
|
|
740
|
+
```python
|
|
741
|
+
>>> d = OrderedDict(a=1, b=2, c=3)
|
|
742
|
+
>>> n = {'b': 'b', 'c': 'c', 'd': 'd'}
|
|
743
|
+
>>> d.difference(n).to(dict)
|
|
744
|
+
{'b': 'b', 'c': 'c', 'd': 'd'}
|
|
745
|
+
>>> l = [('c', 3), ('d', 4)]
|
|
746
|
+
>>> d.difference(l).to(dict)
|
|
747
|
+
{'d': 4}
|
|
748
|
+
>>> d.difference(1)
|
|
749
|
+
Traceback (most recent call last):
|
|
750
|
+
TypeError: other=1 should be of type Mapping, Iterable or PathStr, but got <class 'int'>
|
|
751
|
+
|
|
752
|
+
```
|
|
753
|
+
"""
|
|
754
|
+
|
|
755
|
+
if isinstance(other, (PathLike, str, bytes)):
|
|
756
|
+
other = self.load(other)
|
|
757
|
+
if isinstance(other, (Mapping,)):
|
|
758
|
+
other = other.items()
|
|
759
|
+
if not isinstance(other, Iterable):
|
|
760
|
+
raise TypeError(f"other={other} should be of type Mapping, Iterable or PathStr, but got {type(other)}")
|
|
761
|
+
return self.empty(
|
|
762
|
+
**{key: value for key, value in other if key not in self or self[key] != value} # type: ignore
|
|
763
|
+
)
|
|
764
|
+
|
|
765
|
+
diff = difference
|
|
766
|
+
|
|
767
|
+
def intersection(self, other: Union[Mapping, Iterable, PathStr]) -> Mapping:
|
|
768
|
+
r"""
|
|
769
|
+
Intersection between OrderedDict values and other.
|
|
770
|
+
|
|
771
|
+
Args:
|
|
772
|
+
other (Mapping | Iterable | PathStr): Other values to join.
|
|
773
|
+
|
|
774
|
+
Example:
|
|
775
|
+
```python
|
|
776
|
+
>>> d = OrderedDict(a=1, b=2, c=3)
|
|
777
|
+
>>> n = {'b': 'b', 'c': 'c', 'd': 'd'}
|
|
778
|
+
>>> d.intersection(n).to(dict)
|
|
779
|
+
{}
|
|
780
|
+
>>> l = [('c', 3), ('d', 4)]
|
|
781
|
+
>>> d.intersection(l).to(dict)
|
|
782
|
+
{'c': 3}
|
|
783
|
+
>>> d.intersection(1)
|
|
784
|
+
Traceback (most recent call last):
|
|
785
|
+
TypeError: other=1 should be of type Mapping, Iterable or PathStr, but got <class 'int'>
|
|
786
|
+
|
|
787
|
+
```
|
|
788
|
+
"""
|
|
789
|
+
|
|
790
|
+
if isinstance(other, (PathLike, str, bytes)):
|
|
791
|
+
other = self.load(other)
|
|
792
|
+
if isinstance(other, (Mapping,)):
|
|
793
|
+
other = other.items()
|
|
794
|
+
if not isinstance(other, Iterable):
|
|
795
|
+
raise TypeError(f"other={other} should be of type Mapping, Iterable or PathStr, but got {type(other)}")
|
|
796
|
+
return self.empty(**{key: value for key, value in other if key in self and self[key] == value}) # type: ignore
|
|
797
|
+
|
|
798
|
+
def copy(self) -> OrderedDict:
|
|
799
|
+
r"""
|
|
800
|
+
Create a shallow copy of OrderedDict.
|
|
801
|
+
|
|
802
|
+
Example:
|
|
803
|
+
```python
|
|
804
|
+
>>> d = OrderedDict(a=[])
|
|
805
|
+
>>> d.setattr("name", "Chang")
|
|
806
|
+
>>> c = d.copy()
|
|
807
|
+
>>> c.to(dict)
|
|
808
|
+
{'a': []}
|
|
809
|
+
>>> d.a.append(1)
|
|
810
|
+
>>> c.to(dict)
|
|
811
|
+
{'a': [1]}
|
|
812
|
+
>>> c.getattr("name")
|
|
813
|
+
'Chang'
|
|
814
|
+
|
|
815
|
+
```
|
|
816
|
+
"""
|
|
817
|
+
|
|
818
|
+
return copy(self)
|
|
819
|
+
|
|
820
|
+
def deepcopy(self, memo: Optional[Mapping] = None) -> OrderedDict:
|
|
821
|
+
r"""
|
|
822
|
+
Create a deep copy of OrderedDict.
|
|
823
|
+
|
|
824
|
+
`clone` and `__deepcopy__` are alias of this method.
|
|
825
|
+
|
|
826
|
+
Example:
|
|
827
|
+
```python
|
|
828
|
+
>>> d = OrderedDict(a=[])
|
|
829
|
+
>>> d.setattr("name", "Chang")
|
|
830
|
+
>>> c = d.deepcopy()
|
|
831
|
+
>>> c.to(dict)
|
|
832
|
+
{'a': []}
|
|
833
|
+
>>> d.a.append(1)
|
|
834
|
+
>>> c.to(dict)
|
|
835
|
+
{'a': []}
|
|
836
|
+
>>> c.getattr("name")
|
|
837
|
+
'Chang'
|
|
838
|
+
|
|
839
|
+
```
|
|
840
|
+
"""
|
|
841
|
+
|
|
842
|
+
if memo is not None and id(self) in memo:
|
|
843
|
+
return memo[id(self)]
|
|
844
|
+
ret = self.empty_like()
|
|
845
|
+
for k, v in self.items():
|
|
846
|
+
if isinstance(v, OrderedDict):
|
|
847
|
+
ret[k] = v.deepcopy(memo=memo)
|
|
848
|
+
else:
|
|
849
|
+
ret[k] = deepcopy(v)
|
|
850
|
+
return ret
|
|
851
|
+
|
|
852
|
+
__deepcopy__ = deepcopy
|
|
853
|
+
|
|
854
|
+
clone = deepcopy
|
|
855
|
+
|
|
856
|
+
@classmethod
|
|
857
|
+
def empty(cls, *args, **kwargs):
|
|
858
|
+
r"""
|
|
859
|
+
Initialise an empty OrderedDict.
|
|
860
|
+
This method is helpful when you inheriting the OrderedDict with default values.
|
|
861
|
+
Use type(self)() in this case would copy the redundant default values.
|
|
862
|
+
|
|
863
|
+
Example:
|
|
864
|
+
```python
|
|
865
|
+
>>> d = OrderedDict(a=[])
|
|
866
|
+
>>> c = d.empty()
|
|
867
|
+
>>> c.to(dict)
|
|
868
|
+
{}
|
|
869
|
+
|
|
870
|
+
```
|
|
871
|
+
"""
|
|
872
|
+
|
|
873
|
+
empty = cls()
|
|
874
|
+
empty.clear()
|
|
875
|
+
empty._init(*args, **kwargs)
|
|
876
|
+
return empty
|
|
877
|
+
|
|
878
|
+
def empty_like(self, *args, **kwargs):
|
|
879
|
+
r"""
|
|
880
|
+
Initialise an empty copy of OrderedDict.
|
|
881
|
+
This method will preserve everything in __dict__.
|
|
882
|
+
|
|
883
|
+
Example:
|
|
884
|
+
```python
|
|
885
|
+
>>> d = OrderedDict(a=[])
|
|
886
|
+
>>> d.setattr("name", "Chang")
|
|
887
|
+
>>> c = d.empty_like()
|
|
888
|
+
>>> c.to(dict)
|
|
889
|
+
{}
|
|
890
|
+
>>> c.getattr("name")
|
|
891
|
+
'Chang'
|
|
892
|
+
|
|
893
|
+
```
|
|
894
|
+
"""
|
|
895
|
+
|
|
896
|
+
empty = self.empty(*args, **kwargs)
|
|
897
|
+
empty.__dict__.update(self.__dict__)
|
|
898
|
+
return empty
|
|
899
|
+
|
|
900
|
+
def json(self, file: File, *args, **kwargs) -> None:
|
|
901
|
+
r"""
|
|
902
|
+
Dump OrderedDict to json file.
|
|
903
|
+
|
|
904
|
+
This function calls `self.jsons()` to generate json string.
|
|
905
|
+
You may overwrite `jsons` in case something is not json serializable.
|
|
906
|
+
|
|
907
|
+
Example:
|
|
908
|
+
```python
|
|
909
|
+
>>> d = OrderedDict(a=1, b=2, c=3)
|
|
910
|
+
>>> d.json("example.json")
|
|
911
|
+
|
|
912
|
+
```
|
|
913
|
+
"""
|
|
914
|
+
with self.open(file, mode="w") as fp:
|
|
915
|
+
fp.write(self.jsons(*args, **kwargs))
|
|
916
|
+
|
|
917
|
+
@classmethod
|
|
918
|
+
def from_json(cls, file: File, *args, **kwargs) -> OrderedDict:
|
|
919
|
+
r"""
|
|
920
|
+
Construct OrderedDict from json file.
|
|
921
|
+
|
|
922
|
+
This function calls `self.from_jsons()` to constract object from json string.
|
|
923
|
+
You may overwrite `from_jsons` in case something is not json serializable.
|
|
924
|
+
|
|
925
|
+
Example:
|
|
926
|
+
```python
|
|
927
|
+
>>> d = OrderedDict.from_json('example.json')
|
|
928
|
+
>>> d.to(dict)
|
|
929
|
+
{'a': 1, 'b': 2, 'c': 3}
|
|
930
|
+
|
|
931
|
+
```
|
|
932
|
+
"""
|
|
933
|
+
with cls.open(file) as fp:
|
|
934
|
+
return cls.from_jsons(fp.read(), *args, **kwargs)
|
|
935
|
+
|
|
936
|
+
def jsons(self, *args, **kwargs) -> str:
|
|
937
|
+
r"""
|
|
938
|
+
Dump OrderedDict to json string.
|
|
939
|
+
|
|
940
|
+
Example:
|
|
941
|
+
```python
|
|
942
|
+
>>> d = OrderedDict(a=1, b=2, c=3)
|
|
943
|
+
>>> d.jsons()
|
|
944
|
+
'{\n "a": 1,\n "b": 2,\n "c": 3\n}'
|
|
945
|
+
|
|
946
|
+
```
|
|
947
|
+
"""
|
|
948
|
+
|
|
949
|
+
if "indent" not in kwargs:
|
|
950
|
+
kwargs["indent"] = self.getattr("indent")
|
|
951
|
+
return json_dumps(self.to(dict), *args, **kwargs)
|
|
952
|
+
|
|
953
|
+
@classmethod
|
|
954
|
+
def from_jsons(cls, string: str, *args, **kwargs) -> OrderedDict:
|
|
955
|
+
r"""
|
|
956
|
+
Construct OrderedDict from json string.
|
|
957
|
+
|
|
958
|
+
Example:
|
|
959
|
+
```python
|
|
960
|
+
>>> d = OrderedDict.from_jsons('{\n "a": 1,\n "b": 2,\n "c": 3\n}')
|
|
961
|
+
>>> d.to(dict)
|
|
962
|
+
{'a': 1, 'b': 2, 'c': 3}
|
|
963
|
+
|
|
964
|
+
```
|
|
965
|
+
"""
|
|
966
|
+
|
|
967
|
+
return cls(**json_loads(string, *args, **kwargs))
|
|
968
|
+
|
|
969
|
+
def yaml(self, file: File, *args, **kwargs) -> None:
|
|
970
|
+
r"""
|
|
971
|
+
Dump OrderedDict to yaml file.
|
|
972
|
+
|
|
973
|
+
This function calls `self.yamls()` to generate yaml string.
|
|
974
|
+
You may overwrite `yamls` in case something is not yaml serializable.
|
|
975
|
+
|
|
976
|
+
Example:
|
|
977
|
+
```python
|
|
978
|
+
>>> d = OrderedDict(a=1, b=2, c=3)
|
|
979
|
+
>>> d.yaml("example.yaml")
|
|
980
|
+
|
|
981
|
+
```
|
|
982
|
+
"""
|
|
983
|
+
with self.open(file, mode="w") as fp:
|
|
984
|
+
self.yamls(fp, *args, **kwargs)
|
|
985
|
+
|
|
986
|
+
@classmethod
|
|
987
|
+
def from_yaml(cls, file: File, *args, **kwargs) -> OrderedDict:
|
|
988
|
+
r"""
|
|
989
|
+
Construct OrderedDict from yaml file.
|
|
990
|
+
|
|
991
|
+
This function calls `self.from_yamls()` to constract object from yaml string.
|
|
992
|
+
You may overwrite `from_yamls` in case something is not yaml serializable.
|
|
993
|
+
|
|
994
|
+
```python
|
|
995
|
+
>>> d = OrderedDict.from_yaml('example.yaml')
|
|
996
|
+
>>> d.to(dict)
|
|
997
|
+
{'a': 1, 'b': 2, 'c': 3}
|
|
998
|
+
|
|
999
|
+
```
|
|
1000
|
+
"""
|
|
1001
|
+
with cls.open(file) as fp:
|
|
1002
|
+
return cls.from_yamls(fp.read(), *args, **kwargs)
|
|
1003
|
+
|
|
1004
|
+
def yamls(self, *args, **kwargs) -> str:
|
|
1005
|
+
r"""
|
|
1006
|
+
Dump OrderedDict to yaml string.
|
|
1007
|
+
|
|
1008
|
+
Example:
|
|
1009
|
+
```python
|
|
1010
|
+
>>> d = OrderedDict(a=1, b=2, c=3)
|
|
1011
|
+
>>> d.yamls()
|
|
1012
|
+
'a: 1\nb: 2\nc: 3\n'
|
|
1013
|
+
|
|
1014
|
+
```
|
|
1015
|
+
"""
|
|
1016
|
+
|
|
1017
|
+
if "Dumper" not in kwargs:
|
|
1018
|
+
kwargs["Dumper"] = Dumper
|
|
1019
|
+
if "indent" not in kwargs:
|
|
1020
|
+
kwargs["indent"] = self.getattr("indent")
|
|
1021
|
+
return yaml_dump(self.to(dict), *args, **kwargs) # type: ignore
|
|
1022
|
+
|
|
1023
|
+
@classmethod
|
|
1024
|
+
def from_yamls(cls, string: str, *args, **kwargs) -> OrderedDict:
|
|
1025
|
+
r"""
|
|
1026
|
+
Construct OrderedDict from yaml string.
|
|
1027
|
+
|
|
1028
|
+
Example:
|
|
1029
|
+
```python
|
|
1030
|
+
>>> d = OrderedDict.from_yamls('a: 1\nb: 2\nc: 3\n')
|
|
1031
|
+
>>> d.to(dict)
|
|
1032
|
+
{'a': 1, 'b': 2, 'c': 3}
|
|
1033
|
+
|
|
1034
|
+
```
|
|
1035
|
+
"""
|
|
1036
|
+
|
|
1037
|
+
if "Loader" not in kwargs:
|
|
1038
|
+
kwargs["Loader"] = SafeLoader
|
|
1039
|
+
return cls(**yaml_load(string, *args, **kwargs))
|
|
1040
|
+
|
|
1041
|
+
def dump(self, file: File, method: Optional[str] = None, *args, **kwargs) -> None: # pylint: disable=W1113
|
|
1042
|
+
r"""
|
|
1043
|
+
Dump OrderedDict to file.
|
|
1044
|
+
|
|
1045
|
+
Example:
|
|
1046
|
+
```python
|
|
1047
|
+
>>> d = OrderedDict(a=1, b=2, c=3)
|
|
1048
|
+
>>> d.dump("example.yaml")
|
|
1049
|
+
|
|
1050
|
+
```
|
|
1051
|
+
"""
|
|
1052
|
+
if method is None:
|
|
1053
|
+
if isinstance(file, IO):
|
|
1054
|
+
raise ValueError("method must be specified when dumping to file-like object")
|
|
1055
|
+
method = splitext(file)[-1][1:] # type: ignore
|
|
1056
|
+
extension = method.lower() # type: ignore
|
|
1057
|
+
if extension in YAML:
|
|
1058
|
+
return self.yaml(file=file, *args, **kwargs) # type: ignore
|
|
1059
|
+
if extension in JSON:
|
|
1060
|
+
return self.json(file=file, *args, **kwargs) # type: ignore
|
|
1061
|
+
raise FileError(f"file {file} should be in {JSON} or {YAML}, but got {extension}")
|
|
1062
|
+
|
|
1063
|
+
@classmethod
|
|
1064
|
+
def load(cls, file: File, method: Optional[str] = None, *args, **kwargs) -> OrderedDict: # pylint: disable=W1113
|
|
1065
|
+
"""
|
|
1066
|
+
Load OrderedDict from file.
|
|
1067
|
+
|
|
1068
|
+
Example:
|
|
1069
|
+
```python
|
|
1070
|
+
>>> d = OrderedDict.load("example.yaml")
|
|
1071
|
+
>>> d.to(dict)
|
|
1072
|
+
{'a': 1, 'b': 2, 'c': 3}
|
|
1073
|
+
|
|
1074
|
+
```
|
|
1075
|
+
"""
|
|
1076
|
+
|
|
1077
|
+
if method is None:
|
|
1078
|
+
if isinstance(file, IO):
|
|
1079
|
+
raise ValueError("method must be specified when loading from file-like object")
|
|
1080
|
+
method = splitext(file)[-1][1:] # type: ignore
|
|
1081
|
+
extension = method.lower() # type: ignore
|
|
1082
|
+
if extension in JSON:
|
|
1083
|
+
return cls.from_json(file, *args, **kwargs)
|
|
1084
|
+
if extension in YAML:
|
|
1085
|
+
return cls.from_yaml(file, *args, **kwargs)
|
|
1086
|
+
raise FileError("file {file} should be in {JSON} or {YAML}, but got {extension}")
|
|
1087
|
+
|
|
1088
|
+
@staticmethod
|
|
1089
|
+
@contextmanager
|
|
1090
|
+
def open(file: File, *args, **kwargs):
|
|
1091
|
+
"""
|
|
1092
|
+
open file IO from file path or file-like object.
|
|
1093
|
+
"""
|
|
1094
|
+
|
|
1095
|
+
if isinstance(file, (PathLike, str)):
|
|
1096
|
+
file = open(file, *args, **kwargs)
|
|
1097
|
+
try:
|
|
1098
|
+
yield file
|
|
1099
|
+
finally:
|
|
1100
|
+
file.close()
|
|
1101
|
+
elif isinstance(file, (IO,)):
|
|
1102
|
+
yield file
|
|
1103
|
+
else:
|
|
1104
|
+
raise TypeError(
|
|
1105
|
+
f"file={file} should be of type (str, os.PathLike) or (io.IOBase), but got {type(file)}" # type: ignore
|
|
1106
|
+
)
|
|
1107
|
+
|
|
1108
|
+
@staticmethod
|
|
1109
|
+
def extra_repr() -> str: # pylint: disable=C0116
|
|
1110
|
+
return ""
|
|
1111
|
+
|
|
1112
|
+
def __repr__(self):
|
|
1113
|
+
r"""
|
|
1114
|
+
Representation of OrderedDict.
|
|
1115
|
+
|
|
1116
|
+
Example:
|
|
1117
|
+
```python
|
|
1118
|
+
>>> d = OrderedDict(a=1, b=2, c=3)
|
|
1119
|
+
>>> repr(d)
|
|
1120
|
+
'OrderedDict(\n (a): 1\n (b): 2\n (c): 3\n)'
|
|
1121
|
+
|
|
1122
|
+
```
|
|
1123
|
+
"""
|
|
1124
|
+
|
|
1125
|
+
extra_lines = []
|
|
1126
|
+
extra_repr = self.extra_repr()
|
|
1127
|
+
# empty string will be split into list ['']
|
|
1128
|
+
if extra_repr:
|
|
1129
|
+
extra_lines = extra_repr.split("\n")
|
|
1130
|
+
child_lines = []
|
|
1131
|
+
for key, value in self.items():
|
|
1132
|
+
value_str = repr(value)
|
|
1133
|
+
value_str = self._add_indent(value_str)
|
|
1134
|
+
child_lines.append("(" + key + "): " + value_str)
|
|
1135
|
+
lines = extra_lines + child_lines
|
|
1136
|
+
|
|
1137
|
+
main_str = self.__class__.__name__ + "("
|
|
1138
|
+
if lines:
|
|
1139
|
+
# simple one-liner info, which most builtin Modules will use
|
|
1140
|
+
if len(extra_lines) == 1 and not child_lines:
|
|
1141
|
+
main_str += extra_lines[0]
|
|
1142
|
+
else:
|
|
1143
|
+
main_str += "\n " + "\n ".join(lines) + "\n"
|
|
1144
|
+
|
|
1145
|
+
main_str += ")"
|
|
1146
|
+
return main_str
|
|
1147
|
+
|
|
1148
|
+
def _add_indent(self, s):
|
|
1149
|
+
st = s.split("\n")
|
|
1150
|
+
# don't do anything for single-line stuff
|
|
1151
|
+
if len(st) == 1:
|
|
1152
|
+
return s
|
|
1153
|
+
first = st.pop(0)
|
|
1154
|
+
st = [(self.getattr("indent") * " ") + line for line in st]
|
|
1155
|
+
st = "\n".join(st)
|
|
1156
|
+
st = first + "\n" + st
|
|
1157
|
+
return st
|
|
1158
|
+
|
|
1159
|
+
def __setstate__(self, states, *args, **kwargs):
|
|
1160
|
+
for name, value in states.items():
|
|
1161
|
+
self.setattr(name, value)
|
|
1162
|
+
|
|
1163
|
+
|
|
1164
|
+
class NestedDict(OrderedDict):
|
|
1165
|
+
"""
|
|
1166
|
+
Nested Dict
|
|
1167
|
+
"""
|
|
1168
|
+
|
|
1169
|
+
convert_mapping: bool = False
|
|
1170
|
+
default_factory: Optional[Callable]
|
|
1171
|
+
default_mapping: Optional[Callable]
|
|
1172
|
+
delimiter: str = "."
|
|
1173
|
+
indent: int = 2
|
|
1174
|
+
|
|
1175
|
+
def __init__(self, *args, default_factory: Optional[Callable] = None, **kwargs):
|
|
1176
|
+
self.setattr("delimiter", ".")
|
|
1177
|
+
self.setattr("convert_mapping", False)
|
|
1178
|
+
self.setattr("default_mapping", NestedDict)
|
|
1179
|
+
super().__init__(*args, default_factory=default_factory, **kwargs)
|
|
1180
|
+
|
|
1181
|
+
def _init(self, *args, **kwargs) -> None:
|
|
1182
|
+
for key, value in args:
|
|
1183
|
+
self.set(key, value, convert_mapping=True)
|
|
1184
|
+
for key, value in kwargs.items():
|
|
1185
|
+
self.set(key, value, convert_mapping=True)
|
|
1186
|
+
|
|
1187
|
+
def get(self, name: str, default: Optional[Any] = None) -> Any:
|
|
1188
|
+
r"""
|
|
1189
|
+
Get value from NestedDict.
|
|
1190
|
+
|
|
1191
|
+
`__getitem__` and `__getattr__` are alias of this method.
|
|
1192
|
+
|
|
1193
|
+
Note that default here will override the default_factory if specified.
|
|
1194
|
+
|
|
1195
|
+
Args:
|
|
1196
|
+
name (str): Key name.
|
|
1197
|
+
default (Optional[Any]): Default value if name does not present.
|
|
1198
|
+
|
|
1199
|
+
Example:
|
|
1200
|
+
```python
|
|
1201
|
+
>>> d = NestedDict(default_factory=NestedDict)
|
|
1202
|
+
>>> d['i.d'] = 1013
|
|
1203
|
+
>>> d.get('i.d')
|
|
1204
|
+
1013
|
|
1205
|
+
>>> d['i.d']
|
|
1206
|
+
1013
|
|
1207
|
+
>>> d.i.d
|
|
1208
|
+
1013
|
|
1209
|
+
>>> d.get('c', 2)
|
|
1210
|
+
2
|
|
1211
|
+
>>> d.get('c')
|
|
1212
|
+
NestedDict()
|
|
1213
|
+
>>> d = NestedDict()
|
|
1214
|
+
>>> d.get('c')
|
|
1215
|
+
Traceback (most recent call last):
|
|
1216
|
+
KeyError: 'NestedDict does not contain c'
|
|
1217
|
+
|
|
1218
|
+
```
|
|
1219
|
+
"""
|
|
1220
|
+
|
|
1221
|
+
while self.getattr("delimiter") in name:
|
|
1222
|
+
name, rest = name.split(self.getattr("delimiter"), 1)
|
|
1223
|
+
self, name = self[name], rest # pylint: disable=W0642
|
|
1224
|
+
return super().get(name, default)
|
|
1225
|
+
|
|
1226
|
+
__getitem__ = get
|
|
1227
|
+
__getattr__ = get
|
|
1228
|
+
|
|
1229
|
+
def set( # pylint: disable=W0221
|
|
1230
|
+
self,
|
|
1231
|
+
name: str,
|
|
1232
|
+
value: Any,
|
|
1233
|
+
convert_mapping: Optional[bool] = None,
|
|
1234
|
+
) -> None:
|
|
1235
|
+
r"""
|
|
1236
|
+
Set value of NestedDict.
|
|
1237
|
+
|
|
1238
|
+
`__setitem__` and `__setattr__` are alias of this method.
|
|
1239
|
+
|
|
1240
|
+
Args:
|
|
1241
|
+
name (str): Key name.
|
|
1242
|
+
value (Any): Value to set.
|
|
1243
|
+
|
|
1244
|
+
Example:
|
|
1245
|
+
```python
|
|
1246
|
+
>>> d = NestedDict(default_factory=NestedDict)
|
|
1247
|
+
>>> d.set('i.d', 1013)
|
|
1248
|
+
>>> d.i.d
|
|
1249
|
+
1013
|
|
1250
|
+
>>> d.d.i = 1031
|
|
1251
|
+
>>> d['d.i']
|
|
1252
|
+
1031
|
|
1253
|
+
>>> d['n.l'] = 'chang'
|
|
1254
|
+
>>> d.n.l
|
|
1255
|
+
'chang'
|
|
1256
|
+
>>> d.f.n = 'liu'
|
|
1257
|
+
>>> d['f.n']
|
|
1258
|
+
'liu'
|
|
1259
|
+
>>> d.setattr('convert_mapping', True)
|
|
1260
|
+
>>> d.a.b = {'c': {'d': 1}, 'e.f' : 2}
|
|
1261
|
+
>>> d.a.b.c.d
|
|
1262
|
+
1
|
|
1263
|
+
>>> d.a.b.e.f
|
|
1264
|
+
2
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
```
|
|
1268
|
+
"""
|
|
1269
|
+
|
|
1270
|
+
default_mapping = self.getattr("default_mapping")
|
|
1271
|
+
if convert_mapping is None:
|
|
1272
|
+
convert_mapping = self.convert_mapping
|
|
1273
|
+
while self.getattr("delimiter") in name:
|
|
1274
|
+
name, rest = name.split(self.getattr("delimiter"), 1)
|
|
1275
|
+
if name not in self:
|
|
1276
|
+
if convert_mapping:
|
|
1277
|
+
super().__setitem__(name, default_mapping())
|
|
1278
|
+
else:
|
|
1279
|
+
self.__missing__(name)
|
|
1280
|
+
self, name = self[name], rest # pylint: disable=W0642
|
|
1281
|
+
if convert_mapping and isinstance(value, Mapping):
|
|
1282
|
+
value = default_mapping(**value)
|
|
1283
|
+
super().__setitem__(name, value)
|
|
1284
|
+
|
|
1285
|
+
__setitem__ = set
|
|
1286
|
+
__setattr__ = set
|
|
1287
|
+
|
|
1288
|
+
def pop(self, name: str, default: Optional[Any] = None) -> Any:
|
|
1289
|
+
r"""
|
|
1290
|
+
Pop value from NestedDict.
|
|
1291
|
+
|
|
1292
|
+
Args:
|
|
1293
|
+
name (str): Key name.
|
|
1294
|
+
default (Optional[Any]): Default value if name does not present.
|
|
1295
|
+
|
|
1296
|
+
Example:
|
|
1297
|
+
```python
|
|
1298
|
+
>>> d = NestedDict(default_factory=NestedDict)
|
|
1299
|
+
>>> d['i.d'] = 1013
|
|
1300
|
+
>>> d.pop('i.d')
|
|
1301
|
+
1013
|
|
1302
|
+
>>> d.pop('i.d', True)
|
|
1303
|
+
True
|
|
1304
|
+
>>> d.pop('i.d')
|
|
1305
|
+
Traceback (most recent call last):
|
|
1306
|
+
KeyError: 'd'
|
|
1307
|
+
|
|
1308
|
+
```
|
|
1309
|
+
"""
|
|
1310
|
+
|
|
1311
|
+
if self.getattr("delimiter") in name:
|
|
1312
|
+
name, rest = name.split(self.getattr("delimiter"), 1)
|
|
1313
|
+
if name not in self:
|
|
1314
|
+
raise KeyError(f"{self.__class__.__name__} does not contain {name}")
|
|
1315
|
+
return self[name].pop(rest, default)
|
|
1316
|
+
return super().pop(name, default) if default is not None else super().pop(name)
|
|
1317
|
+
|
|
1318
|
+
def all_keys(self):
|
|
1319
|
+
r"""
|
|
1320
|
+
Get all keys of NestedDict.
|
|
1321
|
+
|
|
1322
|
+
Example:
|
|
1323
|
+
```python
|
|
1324
|
+
>>> d = NestedDict(**{'a': 1, 'b': {'c': 2, 'd': 3}})
|
|
1325
|
+
>>> list(d.all_keys())
|
|
1326
|
+
['a', 'b.c', 'b.d']
|
|
1327
|
+
|
|
1328
|
+
```
|
|
1329
|
+
"""
|
|
1330
|
+
|
|
1331
|
+
@wraps(self.all_keys)
|
|
1332
|
+
def all_keys(self, prefix=""):
|
|
1333
|
+
for key, value in self.items():
|
|
1334
|
+
if prefix:
|
|
1335
|
+
key = prefix + self.getattr("delimiter") + key
|
|
1336
|
+
if isinstance(value, NestedDict):
|
|
1337
|
+
yield from all_keys(value, key)
|
|
1338
|
+
else:
|
|
1339
|
+
yield key
|
|
1340
|
+
|
|
1341
|
+
return all_keys(self)
|
|
1342
|
+
|
|
1343
|
+
def all_values(self):
|
|
1344
|
+
r"""
|
|
1345
|
+
Get all values of NestedDict.
|
|
1346
|
+
|
|
1347
|
+
Example:
|
|
1348
|
+
```python
|
|
1349
|
+
>>> d = NestedDict(**{'a': 1, 'b': {'c': 2, 'd': 3}})
|
|
1350
|
+
>>> list(d.all_values())
|
|
1351
|
+
[1, 2, 3]
|
|
1352
|
+
|
|
1353
|
+
```
|
|
1354
|
+
"""
|
|
1355
|
+
|
|
1356
|
+
for value in self.values():
|
|
1357
|
+
if isinstance(value, NestedDict):
|
|
1358
|
+
yield from value.all_values()
|
|
1359
|
+
else:
|
|
1360
|
+
yield value
|
|
1361
|
+
|
|
1362
|
+
def all_items(self):
|
|
1363
|
+
r"""
|
|
1364
|
+
Get all items of NestedDict.
|
|
1365
|
+
|
|
1366
|
+
Example:
|
|
1367
|
+
```python
|
|
1368
|
+
>>> d = NestedDict(**{'a': 1, 'b': {'c': 2, 'd': 3}})
|
|
1369
|
+
>>> list(d.all_items())
|
|
1370
|
+
[('a', 1), ('b.c', 2), ('b.d', 3)]
|
|
1371
|
+
|
|
1372
|
+
```
|
|
1373
|
+
"""
|
|
1374
|
+
|
|
1375
|
+
@wraps(self.all_items)
|
|
1376
|
+
def all_items(self, prefix=""):
|
|
1377
|
+
for key, value in self.items():
|
|
1378
|
+
if prefix:
|
|
1379
|
+
key = prefix + self.getattr("delimiter") + key
|
|
1380
|
+
if isinstance(value, NestedDict):
|
|
1381
|
+
yield from all_items(value, key)
|
|
1382
|
+
else:
|
|
1383
|
+
yield key, value
|
|
1384
|
+
|
|
1385
|
+
return all_items(self)
|
|
1386
|
+
|
|
1387
|
+
def apply(self, func: Callable) -> NestedDict:
|
|
1388
|
+
r"""
|
|
1389
|
+
Recursively apply a function to the object and its childrens.
|
|
1390
|
+
|
|
1391
|
+
Args:
|
|
1392
|
+
runc (Callable): Function to be applied to.
|
|
1393
|
+
|
|
1394
|
+
Example:
|
|
1395
|
+
```python
|
|
1396
|
+
>>> d = NestedDict()
|
|
1397
|
+
>>> d.a = NestedDict()
|
|
1398
|
+
>>> def func(d):
|
|
1399
|
+
... d.t = 1
|
|
1400
|
+
>>> d.apply(func).to(dict)
|
|
1401
|
+
{'a': {'t': 1}, 't': 1}
|
|
1402
|
+
|
|
1403
|
+
```
|
|
1404
|
+
"""
|
|
1405
|
+
|
|
1406
|
+
for value in self.values():
|
|
1407
|
+
if isinstance(value, NestedDict):
|
|
1408
|
+
value.apply(func)
|
|
1409
|
+
func(self)
|
|
1410
|
+
return self
|
|
1411
|
+
|
|
1412
|
+
def to(self, cls: Callable = dict) -> Mapping: # pylint: disable=C0103
|
|
1413
|
+
r"""
|
|
1414
|
+
Convert NestedDict to other Mapping.
|
|
1415
|
+
|
|
1416
|
+
`to` and `dict` are alias of this method.
|
|
1417
|
+
|
|
1418
|
+
Args:
|
|
1419
|
+
cls (Callable): Target class to be converted to.
|
|
1420
|
+
|
|
1421
|
+
Example:
|
|
1422
|
+
```python
|
|
1423
|
+
>>> d = NestedDict(default_factory=NestedDict, a=1, b=2, c=3)
|
|
1424
|
+
>>> d['i.d'] = 1013
|
|
1425
|
+
>>> d.to(dict)
|
|
1426
|
+
{'a': 1, 'b': 2, 'c': 3, 'i': {'d': 1013}}
|
|
1427
|
+
|
|
1428
|
+
```
|
|
1429
|
+
"""
|
|
1430
|
+
|
|
1431
|
+
ret = cls()
|
|
1432
|
+
for k, v in self.items():
|
|
1433
|
+
if isinstance(v, OrderedDict):
|
|
1434
|
+
v = v.convert(cls)
|
|
1435
|
+
ret[k] = v
|
|
1436
|
+
return ret
|
|
1437
|
+
|
|
1438
|
+
convert = to
|
|
1439
|
+
dict = to
|
|
1440
|
+
|
|
1441
|
+
|
|
1442
|
+
class DefaultDict(NestedDict):
|
|
1443
|
+
"""
|
|
1444
|
+
NestedDict with default_factory set to NestedDict by default.
|
|
1445
|
+
"""
|
|
1446
|
+
|
|
1447
|
+
def __init__(self, *args, default_factory: Optional[Callable] = None, **kwargs):
|
|
1448
|
+
if default_factory is None:
|
|
1449
|
+
default_factory = NestedDict
|
|
1450
|
+
super().__init__(*args, default_factory=default_factory, **kwargs)
|
|
1451
|
+
|
|
1452
|
+
|
|
1453
|
+
def frozen_check(func: Callable):
|
|
1454
|
+
"""
|
|
1455
|
+
Decorator check if the object is frozen.
|
|
1456
|
+
|
|
1457
|
+
"""
|
|
1458
|
+
|
|
1459
|
+
@wraps(func)
|
|
1460
|
+
def decorator(self, *args, **kwargs):
|
|
1461
|
+
if self.getattr("frozen"):
|
|
1462
|
+
raise ValueError("Attempting to alter a frozen config. Run config.defrost() to defrost first")
|
|
1463
|
+
return func(self, *args, **kwargs)
|
|
1464
|
+
|
|
1465
|
+
return decorator
|
|
1466
|
+
|
|
1467
|
+
|
|
1468
|
+
class Config(NestedDict):
|
|
1469
|
+
"""
|
|
1470
|
+
Basic Config
|
|
1471
|
+
"""
|
|
1472
|
+
|
|
1473
|
+
frozen: bool = False
|
|
1474
|
+
convert_mapping: bool = False
|
|
1475
|
+
default_factory: Optional[Callable]
|
|
1476
|
+
delimiter: str = "."
|
|
1477
|
+
indent: int = 2
|
|
1478
|
+
parser: ConfigParser
|
|
1479
|
+
|
|
1480
|
+
def __init__(self, *args, **kwargs):
|
|
1481
|
+
self.setattr("frozen", False)
|
|
1482
|
+
self.setattr("parser", ConfigParser())
|
|
1483
|
+
super().__init__(*args, default_factory=Config, **kwargs)
|
|
1484
|
+
self.setattr("convert_mapping", True)
|
|
1485
|
+
|
|
1486
|
+
@frozen_check
|
|
1487
|
+
def set(
|
|
1488
|
+
self,
|
|
1489
|
+
name: str,
|
|
1490
|
+
value: Any,
|
|
1491
|
+
convert_mapping: Optional[bool] = None,
|
|
1492
|
+
) -> None:
|
|
1493
|
+
r"""
|
|
1494
|
+
Set value of Config.
|
|
1495
|
+
|
|
1496
|
+
`__setitem__` and `__setattr__` are alias of this method.
|
|
1497
|
+
|
|
1498
|
+
Args:
|
|
1499
|
+
name (str): Key name.
|
|
1500
|
+
value (Any): Value to set.
|
|
1501
|
+
|
|
1502
|
+
Example:
|
|
1503
|
+
```python
|
|
1504
|
+
>>> c = Config()
|
|
1505
|
+
>>> c['i.d'] = 1013
|
|
1506
|
+
>>> c.i.d
|
|
1507
|
+
1013
|
|
1508
|
+
>>> c.freeze().to(dict)
|
|
1509
|
+
{'i': {'d': 1013}}
|
|
1510
|
+
>>> c['i.d'] = 1031
|
|
1511
|
+
Traceback (most recent call last):
|
|
1512
|
+
ValueError: Attempting to alter a frozen config. Run config.defrost() to defrost first
|
|
1513
|
+
>>> c.defrost().to(dict)
|
|
1514
|
+
{'i': {'d': 1013}}
|
|
1515
|
+
>>> c['i.d'] = 1031
|
|
1516
|
+
>>> c.i.d
|
|
1517
|
+
1031
|
|
1518
|
+
|
|
1519
|
+
```
|
|
1520
|
+
"""
|
|
1521
|
+
|
|
1522
|
+
return super().set(name, value, convert_mapping)
|
|
1523
|
+
|
|
1524
|
+
__setitem__ = set
|
|
1525
|
+
__setattr__ = set
|
|
1526
|
+
|
|
1527
|
+
@frozen_check
|
|
1528
|
+
def delete(self, name: str) -> None:
|
|
1529
|
+
r"""
|
|
1530
|
+
Remove value from Config.
|
|
1531
|
+
|
|
1532
|
+
`__delitem__`, `__delattr__` and `remove` are alias of this method.
|
|
1533
|
+
|
|
1534
|
+
Args:
|
|
1535
|
+
name (str): Key name.
|
|
1536
|
+
|
|
1537
|
+
Example:
|
|
1538
|
+
```python
|
|
1539
|
+
>>> d = OrderedDict(d=1016, n='chang')
|
|
1540
|
+
>>> d.d
|
|
1541
|
+
1016
|
|
1542
|
+
>>> d.n
|
|
1543
|
+
'chang'
|
|
1544
|
+
>>> d.delete('d')
|
|
1545
|
+
>>> d.d
|
|
1546
|
+
Traceback (most recent call last):
|
|
1547
|
+
KeyError: 'OrderedDict does not contain d'
|
|
1548
|
+
>>> del d.n
|
|
1549
|
+
>>> d.n
|
|
1550
|
+
Traceback (most recent call last):
|
|
1551
|
+
KeyError: 'OrderedDict does not contain n'
|
|
1552
|
+
>>> del d.f
|
|
1553
|
+
Traceback (most recent call last):
|
|
1554
|
+
KeyError: 'f'
|
|
1555
|
+
|
|
1556
|
+
```
|
|
1557
|
+
"""
|
|
1558
|
+
super().remove(name)
|
|
1559
|
+
|
|
1560
|
+
__delitem__ = delete
|
|
1561
|
+
__delattr__ = delete
|
|
1562
|
+
remove = delete
|
|
1563
|
+
|
|
1564
|
+
@frozen_check
|
|
1565
|
+
def pop(self, name: str, default: Optional[Any] = None) -> Any:
|
|
1566
|
+
r"""
|
|
1567
|
+
Pop value from Config.
|
|
1568
|
+
|
|
1569
|
+
Args:
|
|
1570
|
+
name (str): Key name.
|
|
1571
|
+
default (Optional[Any]): Default value if name does not present.
|
|
1572
|
+
|
|
1573
|
+
Example:
|
|
1574
|
+
```python
|
|
1575
|
+
>>> c = Config()
|
|
1576
|
+
>>> c['i.d'] = 1013
|
|
1577
|
+
>>> c.pop('i.d')
|
|
1578
|
+
1013
|
|
1579
|
+
>>> c.pop('i.d', True)
|
|
1580
|
+
True
|
|
1581
|
+
>>> c.freeze().to(dict)
|
|
1582
|
+
{'i': {}}
|
|
1583
|
+
>>> c['i.d'] = 1031
|
|
1584
|
+
Traceback (most recent call last):
|
|
1585
|
+
ValueError: Attempting to alter a frozen config. Run config.defrost() to defrost first
|
|
1586
|
+
>>> c.defrost().to(dict)
|
|
1587
|
+
{'i': {}}
|
|
1588
|
+
>>> c['i.d'] = 1031
|
|
1589
|
+
>>> c.pop('i.d')
|
|
1590
|
+
1031
|
|
1591
|
+
|
|
1592
|
+
```
|
|
1593
|
+
"""
|
|
1594
|
+
|
|
1595
|
+
return super().pop(name, default)
|
|
1596
|
+
|
|
1597
|
+
def freeze(self, recursive: Optional[bool] = True) -> Config:
|
|
1598
|
+
r"""
|
|
1599
|
+
Freeze the config.
|
|
1600
|
+
|
|
1601
|
+
`lock` is an alias of this method.
|
|
1602
|
+
|
|
1603
|
+
Args:
|
|
1604
|
+
recursive (Optional[bool]): freeze all sub-configs recursively. Defaults to True.
|
|
1605
|
+
|
|
1606
|
+
Example:
|
|
1607
|
+
```python
|
|
1608
|
+
>>> c = Config()
|
|
1609
|
+
>>> c.getattr('frozen')
|
|
1610
|
+
False
|
|
1611
|
+
>>> c.freeze().to(dict)
|
|
1612
|
+
{}
|
|
1613
|
+
>>> c.getattr('frozen')
|
|
1614
|
+
True
|
|
1615
|
+
|
|
1616
|
+
```
|
|
1617
|
+
"""
|
|
1618
|
+
|
|
1619
|
+
@wraps(self.freeze)
|
|
1620
|
+
def freeze(config: Config) -> None:
|
|
1621
|
+
config.setattr("frozen", True)
|
|
1622
|
+
|
|
1623
|
+
if recursive:
|
|
1624
|
+
self.apply(freeze)
|
|
1625
|
+
else:
|
|
1626
|
+
freeze(self)
|
|
1627
|
+
return self
|
|
1628
|
+
|
|
1629
|
+
lock = freeze
|
|
1630
|
+
|
|
1631
|
+
def defrost(self, recursive: Optional[bool] = True) -> Config:
|
|
1632
|
+
r"""
|
|
1633
|
+
Defrost the config.
|
|
1634
|
+
|
|
1635
|
+
`unlock` is an alias of this method.
|
|
1636
|
+
|
|
1637
|
+
Args:
|
|
1638
|
+
recursive (Optional[bool]): defrost all sub-configs recursively. Defaults to True.
|
|
1639
|
+
|
|
1640
|
+
Example:
|
|
1641
|
+
```python
|
|
1642
|
+
>>> c = Config()
|
|
1643
|
+
>>> c.getattr('frozen')
|
|
1644
|
+
False
|
|
1645
|
+
>>> c.freeze().to(dict)
|
|
1646
|
+
{}
|
|
1647
|
+
>>> c.getattr('frozen')
|
|
1648
|
+
True
|
|
1649
|
+
>>> c.defrost().to(dict)
|
|
1650
|
+
{}
|
|
1651
|
+
>>> c.getattr('frozen')
|
|
1652
|
+
False
|
|
1653
|
+
|
|
1654
|
+
```
|
|
1655
|
+
"""
|
|
1656
|
+
|
|
1657
|
+
@wraps(self.defrost)
|
|
1658
|
+
def defrost(config: Config) -> None:
|
|
1659
|
+
config.setattr("frozen", False)
|
|
1660
|
+
|
|
1661
|
+
if recursive:
|
|
1662
|
+
self.apply(defrost)
|
|
1663
|
+
else:
|
|
1664
|
+
defrost(self)
|
|
1665
|
+
return self
|
|
1666
|
+
|
|
1667
|
+
unlock = defrost
|
|
1668
|
+
|
|
1669
|
+
@contextmanager
|
|
1670
|
+
def unlocked(self):
|
|
1671
|
+
"""
|
|
1672
|
+
Context manager which temporarily unlocks the config.
|
|
1673
|
+
|
|
1674
|
+
Example:
|
|
1675
|
+
```python
|
|
1676
|
+
>>> c = Config()
|
|
1677
|
+
>>> c.freeze().to(dict)
|
|
1678
|
+
{}
|
|
1679
|
+
>>> with c.unlocked():
|
|
1680
|
+
... c['i.d'] = 1013
|
|
1681
|
+
>>> c.to(dict)
|
|
1682
|
+
{'i': {'d': 1013}}
|
|
1683
|
+
|
|
1684
|
+
```
|
|
1685
|
+
"""
|
|
1686
|
+
was_frozen = self.getattr("frozen")
|
|
1687
|
+
try:
|
|
1688
|
+
self.defrost()
|
|
1689
|
+
yield self
|
|
1690
|
+
finally:
|
|
1691
|
+
if was_frozen:
|
|
1692
|
+
self.freeze()
|
|
1693
|
+
|
|
1694
|
+
def parse(
|
|
1695
|
+
self,
|
|
1696
|
+
args: Optional[Iterable[str]] = None,
|
|
1697
|
+
default_config: Optional[str] = None,
|
|
1698
|
+
) -> Config:
|
|
1699
|
+
r"""
|
|
1700
|
+
Parse the arguments for config.
|
|
1701
|
+
There are three levels of config:
|
|
1702
|
+
|
|
1703
|
+
1. The base config parsed into the function,
|
|
1704
|
+
2. The config file located at the path of default_config (if specified),
|
|
1705
|
+
3. The config specified in arguments.
|
|
1706
|
+
|
|
1707
|
+
Higher levels override lower levels (i.e. 3 > 2 > 1).
|
|
1708
|
+
|
|
1709
|
+
Args:
|
|
1710
|
+
args (Optional[Sequence[str]]): The arguments to parse. Defaults to sys.argv[1:].
|
|
1711
|
+
default_config (Optional[str]): The path to a config file.
|
|
1712
|
+
|
|
1713
|
+
Example:
|
|
1714
|
+
```python
|
|
1715
|
+
>>> c = Config(a=0)
|
|
1716
|
+
>>> c.to(dict)
|
|
1717
|
+
{'a': 0}
|
|
1718
|
+
>>> c.parse(['--a', '1', '--b', '2', '--c', '3']).to(dict)
|
|
1719
|
+
{'a': 1, 'b': 2, 'c': 3}
|
|
1720
|
+
|
|
1721
|
+
```
|
|
1722
|
+
"""
|
|
1723
|
+
|
|
1724
|
+
return self.getattr("parser").parse(args, self, default_config)
|
|
1725
|
+
|
|
1726
|
+
parse_config = parse
|
|
1727
|
+
|
|
1728
|
+
def add_argument(self, *args, **kwargs) -> None:
|
|
1729
|
+
r"""
|
|
1730
|
+
Add an argument to the parser.
|
|
1731
|
+
"""
|
|
1732
|
+
|
|
1733
|
+
self.getattr("parser").add_argument(*args, **kwargs)
|
|
1734
|
+
|
|
1735
|
+
|
|
1736
|
+
if __name__ == "__main__":
|
|
1737
|
+
import doctest
|
|
1738
|
+
|
|
1739
|
+
doctest.testmod()
|