flinventory 0.3.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.
- flinventory/__init__.py +8 -0
- flinventory/__main__.py +45 -0
- flinventory/box.py +289 -0
- flinventory/constant.py +285 -0
- flinventory/datacleanup.py +349 -0
- flinventory/defaulted_data.py +552 -0
- flinventory/generate_labels.py +214 -0
- flinventory/inventory_io.py +295 -0
- flinventory/location.py +455 -0
- flinventory/sign.py +160 -0
- flinventory/signprinter_latex.py +471 -0
- flinventory/thing.py +145 -0
- flinventory/thingtemplate_latex/.gitignore +6 -0
- flinventory/thingtemplate_latex/dummyImage.jpg +0 -0
- flinventory/thingtemplate_latex/sign.tex +26 -0
- flinventory/thingtemplate_latex/signlist-footer.tex +1 -0
- flinventory/thingtemplate_latex/signlist-header.tex +12 -0
- flinventory/thingtemplate_latex/signs-example.tex +95 -0
- flinventory-0.3.0.dist-info/METADATA +63 -0
- flinventory-0.3.0.dist-info/RECORD +23 -0
- flinventory-0.3.0.dist-info/WHEEL +4 -0
- flinventory-0.3.0.dist-info/entry_points.txt +4 -0
- flinventory-0.3.0.dist-info/licenses/LICENSE +626 -0
@@ -0,0 +1,552 @@
|
|
1
|
+
#! /usr/bin/env python3
|
2
|
+
"""Data structures for objects with fallback.
|
3
|
+
|
4
|
+
This module provides a dict-like data structure, maybe later
|
5
|
+
also other data structures, that can provide information
|
6
|
+
from a different default data storage if it does not provide
|
7
|
+
it itself but is still editable.
|
8
|
+
|
9
|
+
To keep the special cases at bay, the supported data types are limited:
|
10
|
+
- keys: str, tuple(str, language key (which is a str as well))
|
11
|
+
- values: str, int, float, bool, None, list of the simple ones
|
12
|
+
- when list is accessed, always tuple is returned
|
13
|
+
- list-altering functions are supplied
|
14
|
+
"""
|
15
|
+
|
16
|
+
import logging
|
17
|
+
|
18
|
+
import collections
|
19
|
+
|
20
|
+
from typing import Any, Iterable, Union, Iterator, cast, Callable
|
21
|
+
|
22
|
+
from . import constant
|
23
|
+
|
24
|
+
IMMUTABLE_TYPES = (str, int, float, bool, type(None))
|
25
|
+
Immutable = Union[*IMMUTABLE_TYPES]
|
26
|
+
Key = Union[str, tuple[str, str]]
|
27
|
+
"""Type of keys of DefaultedDict"""
|
28
|
+
Value = Union[Immutable, list[Immutable]]
|
29
|
+
"""Type of values of DefaultedDict"""
|
30
|
+
Data = collections.abc.Mapping[Key, Value]
|
31
|
+
"""Type of data that can be used as default and starting data."""
|
32
|
+
SuperDict = collections.abc.MutableMapping[Key, Value]
|
33
|
+
"""Super type of DefaultedDict."""
|
34
|
+
|
35
|
+
|
36
|
+
class DefaultedDict(SuperDict):
|
37
|
+
"""A dict that returns information from a default dict if needed.
|
38
|
+
|
39
|
+
The DefaultedDict is not subclassed from dict or UserDict so that
|
40
|
+
we have to think for every functionality how it should be implemented.
|
41
|
+
|
42
|
+
For simplicity only some value types are allowed:
|
43
|
+
- immutable values
|
44
|
+
- lists of immutable values
|
45
|
+
|
46
|
+
For simplicity only some key types are allowed:
|
47
|
+
- str
|
48
|
+
- tuple[str, str|int] for translated keys (where ints are converted to language keys)
|
49
|
+
|
50
|
+
"""
|
51
|
+
|
52
|
+
def __init__(
|
53
|
+
self,
|
54
|
+
data: collections.abc.Mapping[Key, Value],
|
55
|
+
default: collections.abc.Mapping[Key, Value],
|
56
|
+
non_defaulted: Iterable[str],
|
57
|
+
translated: Iterable[str],
|
58
|
+
lists: Iterable[str],
|
59
|
+
default_order: Iterable[str],
|
60
|
+
options: constant.Options,
|
61
|
+
):
|
62
|
+
"""Creates a defaulted dict.
|
63
|
+
|
64
|
+
Args:
|
65
|
+
data: data at the beginning. Is converted as if all elements where inserted one by one.
|
66
|
+
default: other dict where to draw information from where this dict has none.
|
67
|
+
Use empty dict if no default values exist.
|
68
|
+
non_defaulted: keys that should not use data from default
|
69
|
+
translated: keys that are used as a tuple (this_key, language) for translated values
|
70
|
+
lists: keys (and tuples of (this_key, language) of list value
|
71
|
+
default_order: default order of keys when iterating or turning into regular dict
|
72
|
+
"""
|
73
|
+
self._logger = logging.getLogger(__name__)
|
74
|
+
self._default = default
|
75
|
+
self._non_defaulted = tuple(non_defaulted)
|
76
|
+
self._translated = tuple(translated)
|
77
|
+
self._lists = tuple(lists)
|
78
|
+
self._default_order = tuple(default_order)
|
79
|
+
self._options = options
|
80
|
+
self._data = {}
|
81
|
+
for key, value in data.items():
|
82
|
+
self[key] = value # type checking and so on
|
83
|
+
if isinstance(data, DefaultedDict):
|
84
|
+
logging.getLogger(__name__).debug(
|
85
|
+
f"Check that it's correctly saved: {data!r} -> {self!r}."
|
86
|
+
)
|
87
|
+
|
88
|
+
@property
|
89
|
+
def default(self):
|
90
|
+
"""The default values."""
|
91
|
+
return self._default
|
92
|
+
|
93
|
+
@property
|
94
|
+
def non_defaulted(self) -> tuple[str, ...]:
|
95
|
+
"""Tuple of keys that do not use default."""
|
96
|
+
return tuple(self._non_defaulted)
|
97
|
+
|
98
|
+
@property
|
99
|
+
def translated(self) -> tuple[str, ...]:
|
100
|
+
"""Tuple of keys that should be called as a (key, language_key)."""
|
101
|
+
return tuple(self._translated)
|
102
|
+
|
103
|
+
@property
|
104
|
+
def lists(self) -> tuple[str, ...]:
|
105
|
+
"""Tuple of keys that hold lists."""
|
106
|
+
return tuple(self._lists)
|
107
|
+
|
108
|
+
@property
|
109
|
+
def default_order(self) -> tuple[str]:
|
110
|
+
"""Default order of keys. Not changable."""
|
111
|
+
return tuple(self._default_order)
|
112
|
+
|
113
|
+
def best(self, translated_key, **backup):
|
114
|
+
"""For a translated key, get the 'best' value.
|
115
|
+
|
116
|
+
That is: get the value for the language that is highest in the options.languages list.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
translated_key: key for which a value is looked for. Must be in self.translated.
|
120
|
+
**backup: if the key-word-only argument 'backup' is given, use this as the default value
|
121
|
+
If no such argument is given, raise KeyError
|
122
|
+
Raises:
|
123
|
+
KeyError: if no arguments are given and no value could be found
|
124
|
+
"""
|
125
|
+
assert translated_key in self.translated
|
126
|
+
for language in self._options.languages:
|
127
|
+
try:
|
128
|
+
return self[translated_key, language]
|
129
|
+
except KeyError:
|
130
|
+
pass
|
131
|
+
for key in self:
|
132
|
+
# also searches in default
|
133
|
+
if isinstance(key, tuple) and len(key) == 2 and key[0] == translated_key:
|
134
|
+
return self[key]
|
135
|
+
if "backup" in backup:
|
136
|
+
return backup["backup"]
|
137
|
+
raise KeyError(f"No {translated_key} in {self!r} known.")
|
138
|
+
|
139
|
+
@staticmethod
|
140
|
+
def _key_in_category(key: Key, category: Iterable[str]) -> bool:
|
141
|
+
"""Is the key in one of the categories (lists, non_defaulted, translated)?
|
142
|
+
|
143
|
+
In case of tuple only regard the first element since the second is a language key.
|
144
|
+
"""
|
145
|
+
return key in category or (isinstance(key, tuple) and key[0] in category)
|
146
|
+
|
147
|
+
def interpret_number_language(self, key: Union[Key, tuple[str, int]]) -> Key:
|
148
|
+
"""Transform key in case of type (translatable, number). Otherwise return as is."""
|
149
|
+
try:
|
150
|
+
return (key[0], self._options.languages[key[1]])
|
151
|
+
# checks implicitly:
|
152
|
+
# and isinstance(key, tuple)
|
153
|
+
# and len(key) > 1
|
154
|
+
# and isinstance(key[1], int)
|
155
|
+
except (IndexError, TypeError):
|
156
|
+
return key
|
157
|
+
|
158
|
+
def get_conversion(self, key: Key) -> Callable[[Any], Any]:
|
159
|
+
"""Choose the correct conversion from saved value to given value based on key.
|
160
|
+
|
161
|
+
Can be tuple for lists or identity for everything else.
|
162
|
+
"""
|
163
|
+
return (
|
164
|
+
tuple if DefaultedDict._key_in_category(key, self._lists) else lambda x: x
|
165
|
+
)
|
166
|
+
|
167
|
+
def __getitem__(self, key: Union[Key, tuple[str, int]]) -> Value:
|
168
|
+
"""Give item for ["key"] syntax. Use default is needed.
|
169
|
+
|
170
|
+
Returns:
|
171
|
+
in order of preference:
|
172
|
+
- internally saved value
|
173
|
+
- value in default
|
174
|
+
- in case of translated keys: dictionary language_key : value, also including default
|
175
|
+
values. Note that changing this dictionary does not change the value of self.
|
176
|
+
Does not include translated values for language codes that are not listed
|
177
|
+
in options.
|
178
|
+
Raises:
|
179
|
+
KeyError: if key does not exist and default has not key either
|
180
|
+
or key is in self.non_defaulted. Also raises KeyError if a translated key
|
181
|
+
is queried and no translations are available (no empty dict is given in this case).
|
182
|
+
"""
|
183
|
+
key = self.interpret_number_language(key)
|
184
|
+
conversion = self.get_conversion(key)
|
185
|
+
|
186
|
+
try:
|
187
|
+
return conversion(self._data[key])
|
188
|
+
except KeyError as key_error:
|
189
|
+
if DefaultedDict._key_in_category(key, self._non_defaulted):
|
190
|
+
raise KeyError(f"Default not used for {key}") from key_error
|
191
|
+
try:
|
192
|
+
return conversion(self._default[key])
|
193
|
+
except KeyError:
|
194
|
+
if key in self._translated:
|
195
|
+
translations = {}
|
196
|
+
for lang_key in self._options.languages:
|
197
|
+
try:
|
198
|
+
translations[lang_key] = conversion(self[(key, lang_key)])
|
199
|
+
except KeyError:
|
200
|
+
# does not exist
|
201
|
+
pass
|
202
|
+
if translations:
|
203
|
+
return translations
|
204
|
+
# else:
|
205
|
+
raise KeyError(f"{key} has no value.") from key_error
|
206
|
+
|
207
|
+
def get_undefaulted(self, key: Key, **backup: Value) -> Value:
|
208
|
+
"""Get a value but without resorting to "backup".
|
209
|
+
|
210
|
+
Args:
|
211
|
+
key: the key for which the value is looked for.
|
212
|
+
Can be (translatable, nr) which is interpreted as in __getitem__.
|
213
|
+
Cannot be translatable without language key.
|
214
|
+
(If that is necessary in the same way as for [key], it needs to be implemented.)
|
215
|
+
backup: optional keyword-only argument "backup". Used if no value is saved.
|
216
|
+
If not given, KeyError is raised.
|
217
|
+
Returns:
|
218
|
+
internalData.get(key, backup) or internalData[key]
|
219
|
+
"""
|
220
|
+
key = self.interpret_number_language(key)
|
221
|
+
conversion = self.get_conversion(key)
|
222
|
+
try:
|
223
|
+
return conversion(self._data[key])
|
224
|
+
except KeyError:
|
225
|
+
if "backup" in backup:
|
226
|
+
return backup["backup"]
|
227
|
+
raise
|
228
|
+
|
229
|
+
def __setitem__(
|
230
|
+
self, key: Union[Key, tuple[str, int]], value: Union[Value, dict[str, Value]]
|
231
|
+
) -> None:
|
232
|
+
"""Sets a value.
|
233
|
+
|
234
|
+
Checks the type: if it should be a list, then check that it is.
|
235
|
+
Otherwise, check that it is Immutable.
|
236
|
+
|
237
|
+
If language is needed but not given,
|
238
|
+
use first language in options if a list or single value is given,
|
239
|
+
set all languages if dict is given.
|
240
|
+
If language is given as an integer, use the language at this index.
|
241
|
+
Args:
|
242
|
+
key: new key
|
243
|
+
value: new value. If key in self.translated, must be dict.
|
244
|
+
Raises:
|
245
|
+
AssertionError: in case of wrong types. This is chosen to give
|
246
|
+
a confident caller the possibility to optimize this away.
|
247
|
+
TypeError: if value should be a list but is not iterable
|
248
|
+
Value Error: if value is not a dict for key in translated.
|
249
|
+
Setting individual values is probably preferable.
|
250
|
+
"""
|
251
|
+
if isinstance(key, str) and any(
|
252
|
+
key.endswith("_" + (found_language := language))
|
253
|
+
for language in self._options.languages
|
254
|
+
):
|
255
|
+
main_key = key[: -len("_" + found_language)]
|
256
|
+
if main_key in self._translated:
|
257
|
+
new_key = (main_key, found_language)
|
258
|
+
key = new_key
|
259
|
+
else:
|
260
|
+
self._logger.warning(
|
261
|
+
f"debug: found {key} ({found_language}) but {main_key} "
|
262
|
+
"should not be translated"
|
263
|
+
)
|
264
|
+
### up until here could be deleted when data is converted
|
265
|
+
|
266
|
+
if key in self._translated:
|
267
|
+
assert isinstance(key, str)
|
268
|
+
# must be str, not (str, language) since _translated has only str
|
269
|
+
# therefore the following recursive calls do not create an infinite
|
270
|
+
# recursion
|
271
|
+
if isinstance(value, dict):
|
272
|
+
for language, subvalue in value.items():
|
273
|
+
self[key, language] = subvalue
|
274
|
+
else:
|
275
|
+
self[key, self._options.languages[0]] = value
|
276
|
+
return # otherwise the original key would be used below
|
277
|
+
|
278
|
+
if DefaultedDict._key_in_category(key, self._lists) and isinstance(
|
279
|
+
value, IMMUTABLE_TYPES
|
280
|
+
):
|
281
|
+
value = [value]
|
282
|
+
|
283
|
+
##### Datatype assertions
|
284
|
+
if DefaultedDict._key_in_category(key, self._lists):
|
285
|
+
# str is iterable, so to forbid it, it needs to be handled differently
|
286
|
+
assert not isinstance(
|
287
|
+
value, str
|
288
|
+
), f"Key {key} is only storing lists, not strings like {value}."
|
289
|
+
try:
|
290
|
+
value = list(value)
|
291
|
+
except TypeError:
|
292
|
+
assert (
|
293
|
+
False
|
294
|
+
), f"Key {key} is only taking storing lists, so supply some iterable, not {value}."
|
295
|
+
assert all(
|
296
|
+
isinstance(mutable := element, IMMUTABLE_TYPES) for element in value
|
297
|
+
), f"{mutable} of type {type(mutable)} is not allowed in list in our dicts."
|
298
|
+
else:
|
299
|
+
assert isinstance(
|
300
|
+
value, IMMUTABLE_TYPES
|
301
|
+
), f"Values for key {key} must be immutable, not {type(value)} as {value} is."
|
302
|
+
###### Datatype assertions end
|
303
|
+
|
304
|
+
if DefaultedDict._key_in_category(key, self._translated):
|
305
|
+
# cannot be simple str since we checked that at beginning
|
306
|
+
assert isinstance(
|
307
|
+
key, tuple
|
308
|
+
), "If a translatable key is given, it must be a tuple of length 2."
|
309
|
+
assert (
|
310
|
+
len(key) == 2
|
311
|
+
), "If a translatable key is given, it must be a tuple of length 2."
|
312
|
+
try:
|
313
|
+
key = key[0], self._options.languages[cast(int, key[1])]
|
314
|
+
except TypeError: # probably key[1] is str, not int.
|
315
|
+
pass # it's fine
|
316
|
+
except IndexError:
|
317
|
+
assert False, f"There are not {cast(int, key[1]) + 1} many languages."
|
318
|
+
assert isinstance(key[1], str), "Second part of key must be integer or str"
|
319
|
+
else:
|
320
|
+
assert isinstance(key, str), f"Key {key} has to be a string but."
|
321
|
+
self._data[key] = value
|
322
|
+
|
323
|
+
def to_jsonable_data(self) -> dict[str, Any]:
|
324
|
+
"""Returns values without resorting to default.
|
325
|
+
|
326
|
+
Intended to be used for saving to file.
|
327
|
+
|
328
|
+
Returns:
|
329
|
+
dict with
|
330
|
+
key: value for non-translated keys
|
331
|
+
key: { lang: value } for translated keys
|
332
|
+
"""
|
333
|
+
# make sure that all keys in saveable will be strings:
|
334
|
+
assert all(
|
335
|
+
((non_str := key) not in self._translated and isinstance(key, str))
|
336
|
+
or (
|
337
|
+
isinstance(key, tuple)
|
338
|
+
and len(key) == 2
|
339
|
+
and isinstance(key[0], str)
|
340
|
+
and isinstance(key[1], str)
|
341
|
+
and key[0] in self._translated
|
342
|
+
)
|
343
|
+
for key in self._data
|
344
|
+
), f"{non_str} is an invalid key. Only strings (and tuple for translated keys) allowed."
|
345
|
+
|
346
|
+
saveable = {}
|
347
|
+
for key, value in self._data.items():
|
348
|
+
if isinstance(key, tuple):
|
349
|
+
assert (
|
350
|
+
len(key) == 2
|
351
|
+
), f"Somehow a weird tuple key was introduced: {key}."
|
352
|
+
if key[0] in saveable:
|
353
|
+
saveable[key[0]][key[1]] = value
|
354
|
+
else:
|
355
|
+
saveable[key[0]] = {key[1]: value}
|
356
|
+
else:
|
357
|
+
saveable[key] = value
|
358
|
+
assert all(
|
359
|
+
isinstance(saveable[mutable := key], IMMUTABLE_TYPES)
|
360
|
+
or key in self._lists
|
361
|
+
or key in self._translated
|
362
|
+
for key in saveable
|
363
|
+
), (
|
364
|
+
f"Value {saveable[mutable]} of type {type(saveable[mutable])}"
|
365
|
+
f" for key {mutable} is mutable but should not be."
|
366
|
+
)
|
367
|
+
assert all(
|
368
|
+
key not in self._lists
|
369
|
+
or key in self._translated
|
370
|
+
or (
|
371
|
+
isinstance(saveable[not_list := key], list)
|
372
|
+
and all(isinstance(element, IMMUTABLE_TYPES) for element in value)
|
373
|
+
)
|
374
|
+
for key, value in saveable.items()
|
375
|
+
), (
|
376
|
+
f"Value {saveable[not_list]} for key {not_list} "
|
377
|
+
"is not a list or some element is mutable."
|
378
|
+
)
|
379
|
+
assert all(
|
380
|
+
(wrong := key) in self._lists
|
381
|
+
or key not in self._translated
|
382
|
+
or (
|
383
|
+
isinstance(value, dict)
|
384
|
+
and all(isinstance(value[subkey], IMMUTABLE_TYPES) for subkey in value)
|
385
|
+
)
|
386
|
+
for key, value in saveable.items()
|
387
|
+
), (
|
388
|
+
f"Value {saveable[wrong]} for key {wrong} is somehow wrong. "
|
389
|
+
"Should be dict of immutable values."
|
390
|
+
)
|
391
|
+
assert all(
|
392
|
+
(wrong := key) not in self._lists
|
393
|
+
or key not in self._translated
|
394
|
+
or (
|
395
|
+
isinstance(value, dict)
|
396
|
+
and all(
|
397
|
+
isinstance(value[subkey], list)
|
398
|
+
and all(
|
399
|
+
isinstance(value, IMMUTABLE_TYPES) for value in value[subkey]
|
400
|
+
)
|
401
|
+
for subkey in value
|
402
|
+
)
|
403
|
+
)
|
404
|
+
for key, value in saveable.items()
|
405
|
+
), (
|
406
|
+
f"Value {saveable[wrong]} for key {wrong} is somehow wrong. "
|
407
|
+
"Should be dict of lists of immutable values."
|
408
|
+
)
|
409
|
+
|
410
|
+
assert all(
|
411
|
+
isinstance(wrong_key := element, str)
|
412
|
+
and (
|
413
|
+
isinstance(wrong_type := value, IMMUTABLE_TYPES)
|
414
|
+
or (
|
415
|
+
isinstance(value, list)
|
416
|
+
and all(isinstance(subvalue, IMMUTABLE_TYPES) for subvalue in value)
|
417
|
+
)
|
418
|
+
or (
|
419
|
+
isinstance(value, dict)
|
420
|
+
and all(
|
421
|
+
isinstance(subkey, str)
|
422
|
+
and (
|
423
|
+
isinstance(sub_value, IMMUTABLE_TYPES)
|
424
|
+
or (
|
425
|
+
isinstance(sub_value, list)
|
426
|
+
and all(
|
427
|
+
isinstance(sub_sub_value, IMMUTABLE_TYPES)
|
428
|
+
for sub_sub_value in sub_value
|
429
|
+
)
|
430
|
+
)
|
431
|
+
)
|
432
|
+
for subkey, sub_value in value.items()
|
433
|
+
)
|
434
|
+
)
|
435
|
+
)
|
436
|
+
for element, value in saveable.items()
|
437
|
+
), f"{wrong_key} = {wrong_type} occurs in json output. Not allowed."
|
438
|
+
|
439
|
+
# todo: ensure order of self._default_order
|
440
|
+
return saveable
|
441
|
+
|
442
|
+
def __delitem__(self, key: Key):
|
443
|
+
"""Removes an item.
|
444
|
+
|
445
|
+
Allows removing all translated values.
|
446
|
+
|
447
|
+
Note that [key] might still return something since default values are not deleted.
|
448
|
+
Note that (str, int)-type keys as in getitem and setitem are not supported.
|
449
|
+
|
450
|
+
Raises:
|
451
|
+
KeyError: if key is not valid.
|
452
|
+
"""
|
453
|
+
assert (
|
454
|
+
not isinstance(key, tuple) or len(key) == 2 and isinstance(key[1], str)
|
455
|
+
), f"(str, int) as {key} keys are not supported by del yet."
|
456
|
+
if key in self._translated:
|
457
|
+
del_something = False
|
458
|
+
for other_key in set(
|
459
|
+
k for k in self._data if isinstance(k, tuple) and k[0] == key
|
460
|
+
):
|
461
|
+
# set() copies keys. If we directly iterate
|
462
|
+
# over self._data, we get RuntimeError: dictionary changed size during iteration
|
463
|
+
del self._data[other_key]
|
464
|
+
del_something = True
|
465
|
+
if not del_something:
|
466
|
+
raise KeyError(key)
|
467
|
+
else:
|
468
|
+
del self._data[key]
|
469
|
+
|
470
|
+
def items(self) -> Iterable[tuple[Key, Value]]:
|
471
|
+
"""Iterate over all directly saved key-value pairs.
|
472
|
+
|
473
|
+
Do not include values only saved in the default.
|
474
|
+
"""
|
475
|
+
# custom __getitem__ is not used but that's fine because we
|
476
|
+
# do not want default values
|
477
|
+
# and also giving the original lists is fine since they are
|
478
|
+
# not from the default and therefore fine to edit
|
479
|
+
return self._data.items()
|
480
|
+
|
481
|
+
def all_items(self):
|
482
|
+
"""Iterator over all key-value pairs saved in self and default.
|
483
|
+
|
484
|
+
No performance improvement over iterating over self and using [key].
|
485
|
+
"""
|
486
|
+
for key in iter(self):
|
487
|
+
yield key, self[key]
|
488
|
+
|
489
|
+
def get(self, key, default=None):
|
490
|
+
"""Get value without KeyError, instead with default.
|
491
|
+
|
492
|
+
Returns:
|
493
|
+
value saved for key itself if it exists
|
494
|
+
otherwise value saved in objects default for key if it exists
|
495
|
+
otherwise argument default
|
496
|
+
"""
|
497
|
+
try:
|
498
|
+
return self[key]
|
499
|
+
except KeyError:
|
500
|
+
return default
|
501
|
+
|
502
|
+
def __contains__(self, key: Key):
|
503
|
+
"""Return if self[key] would give something, possibly from default."""
|
504
|
+
try:
|
505
|
+
_ = self[key]
|
506
|
+
except KeyError:
|
507
|
+
return False
|
508
|
+
return True
|
509
|
+
|
510
|
+
def __iter__(self) -> Iterator[Key]:
|
511
|
+
"""Iterate over keys with values in self and defaults.
|
512
|
+
|
513
|
+
Each translation of a translated key is given separately.
|
514
|
+
"""
|
515
|
+
already_sent = set()
|
516
|
+
for key in self._data:
|
517
|
+
already_sent.add(key)
|
518
|
+
yield key
|
519
|
+
for key in self.default:
|
520
|
+
if key not in already_sent and not self._key_in_category(
|
521
|
+
key, self._non_defaulted
|
522
|
+
):
|
523
|
+
already_sent.add(key) # should not be necessary, but to make sure
|
524
|
+
yield key
|
525
|
+
# else: go to next
|
526
|
+
|
527
|
+
def __keys__(self) -> set[Key]:
|
528
|
+
"""iter(self) as a set."""
|
529
|
+
return set(iter(self))
|
530
|
+
|
531
|
+
def __str__(self):
|
532
|
+
"""Give string representation of data directly saved. (Without default values)"""
|
533
|
+
return f"{self._data}"
|
534
|
+
|
535
|
+
def __repr__(self):
|
536
|
+
"""Give string representation of data including special keys and defaults."""
|
537
|
+
return f"""DefaultedDict(
|
538
|
+
data={self._data!r},
|
539
|
+
lists={self.lists!r},
|
540
|
+
translated={self.translated!r},
|
541
|
+
non_defaulted={self.non_defaulted!r},
|
542
|
+
default={repr(self.default).replace('\n', '\n ')},
|
543
|
+
languages={self._options.languages!r}
|
544
|
+
"""
|
545
|
+
|
546
|
+
def __len__(self):
|
547
|
+
"""Give amount of keys with values directly in self.
|
548
|
+
|
549
|
+
Ignore keys available in default.
|
550
|
+
Translated keys are counted as often as they have translations.
|
551
|
+
"""
|
552
|
+
return len(self._data)
|