idict-schema 1.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.
- idict/__init__.py +26 -0
- idict/__main__.py +2 -0
- idict/exceptions.py +17 -0
- idict/idict.py +3 -0
- idict/utils.py +3 -0
- idict_schema-1.0.0.dist-info/METADATA +347 -0
- idict_schema-1.0.0.dist-info/RECORD +15 -0
- idict_schema-1.0.0.dist-info/WHEEL +5 -0
- idict_schema-1.0.0.dist-info/licenses/LICENSE +674 -0
- idict_schema-1.0.0.dist-info/top_level.txt +2 -0
- lib/__init__.py +22 -0
- lib/__main__.py +2 -0
- lib/exceptions.py +161 -0
- lib/idict.py +469 -0
- lib/utils.py +223 -0
idict/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from typing import TypeVar
|
|
3
|
+
|
|
4
|
+
from .idict import Idict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Stringifable(ABC):
|
|
8
|
+
"""
|
|
9
|
+
Stringifable
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __str__(self) -> str:
|
|
13
|
+
"""
|
|
14
|
+
__str__
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
str
|
|
18
|
+
"""
|
|
19
|
+
raise NotImplementedError()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
KT = TypeVar("KT", str, Stringifable)
|
|
23
|
+
KV = TypeVar("KV")
|
|
24
|
+
A = TypeVar("A", str, Stringifable)
|
|
25
|
+
|
|
26
|
+
__all__ = ["Idict", "KT", "KV", "A", "Stringifable"]
|
idict/__main__.py
ADDED
idict/exceptions.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from lib.exceptions import (
|
|
2
|
+
EllipsisException,
|
|
3
|
+
GeneralException,
|
|
4
|
+
KeyNotAllowedException,
|
|
5
|
+
KeyOnNonDictException,
|
|
6
|
+
MandatoryKeyValueException,
|
|
7
|
+
ValueNotAllowedException,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"EllipsisException",
|
|
12
|
+
"GeneralException",
|
|
13
|
+
"KeyNotAllowedException",
|
|
14
|
+
"KeyOnNonDictException",
|
|
15
|
+
"MandatoryKeyValueException",
|
|
16
|
+
"ValueNotAllowedException",
|
|
17
|
+
]
|
idict/idict.py
ADDED
idict/utils.py
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: idict-schema
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Extended defaultdict with declared structure and validation rules
|
|
5
|
+
Home-page: https://github.com/hadasbro/idict
|
|
6
|
+
Author: Slawomir Hadas
|
|
7
|
+
Author-email: slawekgithub1218@gmail.com
|
|
8
|
+
License: GPL-3.0-only
|
|
9
|
+
Project-URL: Source, https://github.com/hadasbro/idict
|
|
10
|
+
Project-URL: Issues, https://github.com/hadasbro/idict/issues
|
|
11
|
+
Keywords: dict,defaultdict,validation,schema
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.7
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: author-email
|
|
27
|
+
Dynamic: classifier
|
|
28
|
+
Dynamic: description
|
|
29
|
+
Dynamic: description-content-type
|
|
30
|
+
Dynamic: home-page
|
|
31
|
+
Dynamic: keywords
|
|
32
|
+
Dynamic: license
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
Dynamic: project-url
|
|
35
|
+
Dynamic: requires-python
|
|
36
|
+
Dynamic: summary
|
|
37
|
+
|
|
38
|
+
# I - Dict
|
|
39
|
+
|
|
40
|
+

|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
**Dictionary with declared structure in Python**
|
|
44
|
+
|
|
45
|
+
Tiny extension for [defaultdict](https://docs.python.org/3.3/library/collections.html#collections.defaultdict) Python's class.
|
|
46
|
+
Use this library if you need to ensure or force concrete
|
|
47
|
+
structure for your dictionary and use it as normal dictionary then.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
Install from PyPI:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install idict-schema
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
or from sources:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install .
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
After installation, import from:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from idict import Idict
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Overview
|
|
74
|
+
|
|
75
|
+
You can provide desired dictionary's structure and specify
|
|
76
|
+
action for non desired usage e.g. specify action if someone
|
|
77
|
+
uses unexpected key or value.
|
|
78
|
+
|
|
79
|
+
You can also specify required and/or default values for specified keys.
|
|
80
|
+
|
|
81
|
+
## 1.0 Contract
|
|
82
|
+
|
|
83
|
+
- Distribution name: `idict-schema`
|
|
84
|
+
- Import path: `idict` (`from idict import Idict`)
|
|
85
|
+
- `missing_keys = THROW` is enforced for `[] =`, `update`, `setdefault`, `|`, and `|=`
|
|
86
|
+
- Mandatory keys (`...`) are validated against the declared schema (`validate()`)
|
|
87
|
+
- `copy.copy(idict_obj)` is supported
|
|
88
|
+
- `pickle` serialization is currently not supported and raises a clear `TypeError`
|
|
89
|
+
- `hash_code()` returns Base64 of `repr(dict(...))` (it is not a cryptographic hash)
|
|
90
|
+
|
|
91
|
+
#### General usage
|
|
92
|
+
```python
|
|
93
|
+
from idict import Idict
|
|
94
|
+
|
|
95
|
+
desired_default_structure = {
|
|
96
|
+
'a': 1,
|
|
97
|
+
'c': None
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
options = {
|
|
101
|
+
"missing_keys": Idict.OPT.THROW,
|
|
102
|
+
"ellipsis_as_mandatory": True
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
my_dict = Idict(desired_default_structure, options)
|
|
106
|
+
|
|
107
|
+
my_dict['a'] = 1
|
|
108
|
+
|
|
109
|
+
print(repr(my_dict))
|
|
110
|
+
# {'a': 1, 'c': None}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### Options
|
|
114
|
+
|
|
115
|
+
**[ missing_keys ]** - action if unspecified key usage
|
|
116
|
+
```python
|
|
117
|
+
options = {
|
|
118
|
+
"missing_keys": Idict.OPT.ALLOW
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
possible options: `Idict.OPT.`
|
|
122
|
+
- `ALLOW` [default value] - allow all other keys and just add to the dictionary.
|
|
123
|
+
- `IGNORE` - ignore keys not available in specified interface and allow only specified keys. Always keep the same, desired structure. Ignore tries to change dictionary structure.
|
|
124
|
+
- `THROW` - throw an exception `KeyNotAllowedException` if someone tries to set key-value for unspecified key.
|
|
125
|
+
Throw an exception `ValueNotAllowedException` if someone tries to change specified key-value structure.
|
|
126
|
+
See examples below.
|
|
127
|
+
|
|
128
|
+
Key not allowed:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
my_dict = Idict({'a': None, 'b': {'bb1': None}}, {"missing_keys": Idict.OPT.THROW})
|
|
132
|
+
my_dict['non_existing_key'] = 123
|
|
133
|
+
|
|
134
|
+
'''
|
|
135
|
+
KeyNotAllowedException
|
|
136
|
+
[Key Error] You are trying to set value for key <non_existing> for your Idict
|
|
137
|
+
<your_dict:Idict> but this key is disallowed and doesnt exist in the provided interface
|
|
138
|
+
'''
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Trying to overwritte expected structure and set integer where dictionary should be:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
my_dict = Idict({'a': {'aa': {'aaa': 1}}}, options={"missing_keys": Idict.OPT.THROW})
|
|
145
|
+
my_dict['a']['aa'] = 123
|
|
146
|
+
|
|
147
|
+
'''
|
|
148
|
+
ValueNotAllowedException:
|
|
149
|
+
[Value Error] Trying to set value <123> for object , key <aa>
|
|
150
|
+
but in provided interface there is a dictionary under this key - {'aa': {'aaa': 1}}.
|
|
151
|
+
You cannot change expected objects structure
|
|
152
|
+
'''
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**[ ellipsis_as_mandatory ]** - [default = True] use Ellipsis (`...`) as a tag for mandatory keys.
|
|
156
|
+
If this option is settled to `True` then, upon validation, library will check if every key with `Ellipsis` value has new value settled.
|
|
157
|
+
Dictionary will be valid as long as every value tagged by `...` has new value. Otherwise `dict.validate()` will throw an excaption `MandatoryKeyValueException`.
|
|
158
|
+
```python
|
|
159
|
+
options = {
|
|
160
|
+
"ellipsis_as_mandatory": True
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
possible options:
|
|
164
|
+
- True
|
|
165
|
+
- False
|
|
166
|
+
|
|
167
|
+
Example:
|
|
168
|
+
```python
|
|
169
|
+
# use Ellipsis - ... as a tag for mandatory values
|
|
170
|
+
my_dict = Idict({'a': {'aa': None}, 'b': ...}, options={"ellipsis_as_mandatory": True})
|
|
171
|
+
|
|
172
|
+
my_dict.validate()
|
|
173
|
+
|
|
174
|
+
# THROWS:
|
|
175
|
+
# MandatoryKeyValueException: Key value for key [b]
|
|
176
|
+
# is mandatory. Set value for this key or set default value in provided interface.
|
|
177
|
+
|
|
178
|
+
my_dict['b'] = "anything"
|
|
179
|
+
|
|
180
|
+
my_dict.validate()
|
|
181
|
+
# OK, no exception, result = True
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
## Examples
|
|
187
|
+
|
|
188
|
+
+ Allow specified and unspecified keys:
|
|
189
|
+
|
|
190
|
+
Default structure and default values are specified in advance.
|
|
191
|
+
We allow other key-value pairs, not only specified ones.
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
my_dict = Idict({
|
|
195
|
+
'a': 1,
|
|
196
|
+
'b': 2,
|
|
197
|
+
'c': {
|
|
198
|
+
'cc': 3,
|
|
199
|
+
},
|
|
200
|
+
'd': {
|
|
201
|
+
'dd': {
|
|
202
|
+
'ddd': 4
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
# set/overwrite some values
|
|
208
|
+
my_dict['a'] = 11
|
|
209
|
+
my_dict['c']['cc'] = 33
|
|
210
|
+
my_dict['d']['dd']['new-value'] = 44
|
|
211
|
+
my_dict['e']['ee'] = 55
|
|
212
|
+
|
|
213
|
+
print(repr(my_dict))
|
|
214
|
+
# {'a': 11, 'b': 2, 'c': {'cc': 33}, 'd': {'dd': {'ddd': 4, 'new-value': 44}}, 'e': {'ee': 55}}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
+ Ignore unexpected keys:
|
|
219
|
+
|
|
220
|
+
Allow only specified structure, ignore all other key-value pairs.
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
my_dict = Idict({
|
|
224
|
+
'a': 1,
|
|
225
|
+
'c': {
|
|
226
|
+
'cc1': 3,
|
|
227
|
+
'cc2': 4,
|
|
228
|
+
}
|
|
229
|
+
}, {
|
|
230
|
+
"missing_keys": Idict.OPT.IGNORE,
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
# set/overwrite some values
|
|
234
|
+
my_dict['a'] = 11
|
|
235
|
+
my_dict['b'] = 22
|
|
236
|
+
my_dict['c']['cc1'] = 33
|
|
237
|
+
my_dict['d'] = 55
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
print(repr(my_dict))
|
|
241
|
+
# {'a': 11, 'c': {'cc1': 33, 'cc2': 4}}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
+ Allow only specified keys usage or throw an exception `KeyNotAllowedException`
|
|
245
|
+
|
|
246
|
+
Throw an exception (KeyNotAllowedException) in case of trying to set value for disallowed key:
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
dict1 = Idict({
|
|
250
|
+
'a': None,
|
|
251
|
+
'b': {
|
|
252
|
+
'bb1': None,
|
|
253
|
+
'bb2': None
|
|
254
|
+
}
|
|
255
|
+
}, {
|
|
256
|
+
"missing_keys": Idict.OPT.THROW
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
dict1['non_existing_key'] = "value"
|
|
261
|
+
except KeyNotAllowedException as ex:
|
|
262
|
+
print(ex)
|
|
263
|
+
# [Key Error] You are trying to set value
|
|
264
|
+
# for key for your Idict
|
|
265
|
+
# but this key is disallowed
|
|
266
|
+
# and doesnt exist in the provided interface
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
+ Changing default/provided default dictionary's structure
|
|
270
|
+
```python
|
|
271
|
+
desired_structure = {
|
|
272
|
+
'a': 'default value <a>',
|
|
273
|
+
'b': {
|
|
274
|
+
'bb1': 'default value ',
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
my_dict = Idict(desired_structure, {
|
|
279
|
+
"missing_keys": Idict.OPT.ALLOW
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
my_dict_ign = Idict(desired_structure, {
|
|
283
|
+
"missing_keys": Idict.OPT.IGNORE
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
my_dict_thr = Idict(desired_structure, {
|
|
287
|
+
"missing_keys": Idict.OPT.THROW
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
my_dict['b'] = 123
|
|
291
|
+
print(repr(my_dict))
|
|
292
|
+
# OK, result: {'a': 'default value <a>', 'b': 123}
|
|
293
|
+
|
|
294
|
+
my_dict_ign['b'] = 123
|
|
295
|
+
print(repr(my_dict_ign))
|
|
296
|
+
# illegal trying to change desired dictionary structure
|
|
297
|
+
# this operation will be ignored
|
|
298
|
+
# OK, no exception, result: {'a': 'default value ', 'b': {'bb1': 'default value '}}
|
|
299
|
+
|
|
300
|
+
my_dict_thr['b'] = 123
|
|
301
|
+
# illegal trying to change desired dictionary structure
|
|
302
|
+
# result: raise ValueNotAllowedException as below
|
|
303
|
+
|
|
304
|
+
# ValueNotAllowedException: [Value Error] Trying to set value <123>
|
|
305
|
+
# for object <your_dict:Idict>, key <b> but in provided interface there
|
|
306
|
+
# is a dictionary under this key - <interface>
|
|
307
|
+
# {'a': 'default value ', 'b': {'bb1': 'default value '}}.
|
|
308
|
+
# You cannot change expected objects structure
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
+ Validate dictionary, check mandatory keys.
|
|
313
|
+
|
|
314
|
+
Check if all mandatory keys (marked by `Ellipsis` - `...`) are overwritten/settled
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
my_dict1 = Idict({
|
|
318
|
+
'a': None,
|
|
319
|
+
'b': {
|
|
320
|
+
'bb1': None,
|
|
321
|
+
'bb2': ...
|
|
322
|
+
},
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
my_dict1.validate()
|
|
327
|
+
except MandatoryKeyValueException as e:
|
|
328
|
+
# Key value for key [b][bb2] is mandatory.
|
|
329
|
+
# Set value for this key or set default value in provided interface.
|
|
330
|
+
pass
|
|
331
|
+
|
|
332
|
+
my_dict1['b']['bb2'] = 123
|
|
333
|
+
|
|
334
|
+
print(repr(my_dict1))
|
|
335
|
+
# no Exception, all mandatory keys have value settled
|
|
336
|
+
# {'a': None, 'b': {'bb1': None, 'bb2': 123}}
|
|
337
|
+
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Authors
|
|
341
|
+
|
|
342
|
+
* **Slawomir Hadas** - *author* - [Github](https://github.com/hadasbro)
|
|
343
|
+
|
|
344
|
+
## See more
|
|
345
|
+
See `./tests` for more examples
|
|
346
|
+
|
|
347
|
+
---
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
idict/__init__.py,sha256=4RKh1RgFiVucie5LvABZ3zBNRzqFdI_YB_yBSF7jAlw,449
|
|
2
|
+
idict/__main__.py,sha256=HX4MrDFp3XQJC-6BmcAv_LjtfdQ5rN3NW-zEmKNW6t0,38
|
|
3
|
+
idict/exceptions.py,sha256=uPcG1sGmI33GGm6hfGpAVDi8hcwfbf0PwCNiknICKsg,399
|
|
4
|
+
idict/idict.py,sha256=LCGGhUgDyMOhcTZTKoz8naAiadVkiBYwUgWJ0OH_QV4,52
|
|
5
|
+
idict/utils.py,sha256=y-bDmmrncZswxTZAPEwmny4kX8QNEJshoKLZcPUx8Ac,52
|
|
6
|
+
idict_schema-1.0.0.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
7
|
+
lib/__init__.py,sha256=JwQQmerbfdlrDaznYPe-EsAGDl9zmQuOBRRIlhXgNpI,365
|
|
8
|
+
lib/__main__.py,sha256=tkZ_iuY7UZngMK-oaQCp920Zz6e1y2WdSbVxPTYo_DA,38
|
|
9
|
+
lib/exceptions.py,sha256=b43nvdTFzf7QvS_fL5XxqdrjXd6hdMMPR98bINJtIUQ,3561
|
|
10
|
+
lib/idict.py,sha256=LljiXiZluX38OFCxi2iBsQfqtjh3-GGDw2zSSlj3FAY,15673
|
|
11
|
+
lib/utils.py,sha256=uQuF1P4S8YhGi3kiZgUGIE3Ci0jwoRHSQ4R4hSVTxg0,6439
|
|
12
|
+
idict_schema-1.0.0.dist-info/METADATA,sha256=UoSPSye7oVsQMbuAj4oTMJdP6H-iVEprEbBod6W9O3A,10013
|
|
13
|
+
idict_schema-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
14
|
+
idict_schema-1.0.0.dist-info/top_level.txt,sha256=GtdMigkai9B0AEyjED3GFi-Grj97F4CBkNODAHUF5jA,10
|
|
15
|
+
idict_schema-1.0.0.dist-info/RECORD,,
|