apytizer 0.0.1a0__py3-none-any.whl → 0.0.1b2__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.
- apytizer/__init__.py +2 -12
- apytizer/adapters/__init__.py +2 -3
- apytizer/adapters/transport_adapter.py +91 -0
- apytizer/apis/__init__.py +6 -0
- apytizer/apis/abstract_api.py +36 -0
- apytizer/apis/web_api.py +460 -0
- apytizer/connections/__init__.py +6 -0
- apytizer/connections/abstract_connection.py +28 -0
- apytizer/connections/http_connection.py +431 -0
- apytizer/decorators/__init__.py +5 -5
- apytizer/decorators/caching.py +60 -9
- apytizer/decorators/chunking.py +105 -0
- apytizer/decorators/connection.py +55 -20
- apytizer/decorators/json.py +70 -12
- apytizer/decorators/pagination.py +50 -32
- apytizer/endpoints/__init__.py +6 -0
- apytizer/endpoints/abstract_endpoint.py +38 -0
- apytizer/endpoints/web_endpoint.py +519 -0
- apytizer/engines/__init__.py +6 -0
- apytizer/engines/abstract_engine.py +45 -0
- apytizer/engines/http_engine.py +171 -0
- apytizer/errors.py +34 -0
- apytizer/factories/__init__.py +5 -0
- apytizer/factories/abstract_factory.py +17 -0
- apytizer/http_methods.py +34 -0
- apytizer/managers/__init__.py +12 -0
- apytizer/managers/abstract_manager.py +80 -0
- apytizer/managers/base_manager.py +116 -0
- apytizer/mappers/__init__.py +6 -0
- apytizer/mappers/abstract_mapper.py +48 -0
- apytizer/mappers/base_mapper.py +78 -0
- apytizer/media_types.py +118 -0
- apytizer/models/__init__.py +6 -0
- apytizer/models/abstract_model.py +119 -0
- apytizer/models/base_model.py +85 -0
- apytizer/protocols.py +38 -0
- apytizer/repositories/__init__.py +6 -0
- apytizer/repositories/abstract_repository.py +81 -0
- apytizer/repositories/managed_repository.py +92 -0
- apytizer/routes/__init__.py +6 -0
- apytizer/routes/abstract_route.py +32 -0
- apytizer/routes/base_route.py +138 -0
- apytizer/sessions/__init__.py +33 -0
- apytizer/sessions/abstract_session.py +63 -0
- apytizer/sessions/requests_session.py +125 -0
- apytizer/states/__init__.py +6 -0
- apytizer/states/abstract_state.py +71 -0
- apytizer/states/local_state.py +99 -0
- apytizer/utils/__init__.py +9 -4
- apytizer/utils/caching.py +39 -0
- apytizer/utils/dictionaries.py +376 -0
- apytizer/utils/errors.py +104 -0
- apytizer/utils/iterables.py +91 -0
- apytizer/utils/objects.py +145 -0
- apytizer/utils/strings.py +69 -0
- apytizer/utils/typing.py +29 -0
- apytizer-0.0.1b2.dist-info/METADATA +41 -0
- apytizer-0.0.1b2.dist-info/RECORD +60 -0
- {apytizer-0.0.1a0.dist-info → apytizer-0.0.1b2.dist-info}/WHEEL +1 -2
- apytizer/abstracts/__init__.py +0 -8
- apytizer/abstracts/api.py +0 -147
- apytizer/abstracts/endpoint.py +0 -177
- apytizer/abstracts/model.py +0 -50
- apytizer/abstracts/session.py +0 -39
- apytizer/adapters/transport.py +0 -40
- apytizer/base/__init__.py +0 -8
- apytizer/base/api.py +0 -510
- apytizer/base/endpoint.py +0 -443
- apytizer/base/model.py +0 -119
- apytizer/utils/generate_key.py +0 -18
- apytizer/utils/merge.py +0 -19
- apytizer-0.0.1a0.dist-info/METADATA +0 -27
- apytizer-0.0.1a0.dist-info/RECORD +0 -25
- apytizer-0.0.1a0.dist-info/top_level.txt +0 -1
- {apytizer-0.0.1a0.dist-info → apytizer-0.0.1b2.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/utils/dictionaries.py
|
|
3
|
+
|
|
4
|
+
# Standard Library Imports
|
|
5
|
+
from collections import ChainMap
|
|
6
|
+
import functools
|
|
7
|
+
from typing import Any
|
|
8
|
+
from typing import Collection
|
|
9
|
+
from typing import Dict
|
|
10
|
+
from typing import Hashable
|
|
11
|
+
from typing import List
|
|
12
|
+
from typing import Optional
|
|
13
|
+
from typing import Set
|
|
14
|
+
from typing import TypeVar
|
|
15
|
+
from typing import Union
|
|
16
|
+
|
|
17
|
+
# Local Imports
|
|
18
|
+
from .errors import raise_for_instance
|
|
19
|
+
from .typing import allinstance
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"deep_get",
|
|
23
|
+
"deep_set",
|
|
24
|
+
"iter_get",
|
|
25
|
+
"iter_set",
|
|
26
|
+
"omit",
|
|
27
|
+
"pick",
|
|
28
|
+
"merge",
|
|
29
|
+
"remap_keys",
|
|
30
|
+
"remove_nulls",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
# Custom types:
|
|
34
|
+
K = TypeVar("K")
|
|
35
|
+
V = TypeVar("V")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def deep_get(
|
|
39
|
+
__d: Dict[Hashable, Any],
|
|
40
|
+
/,
|
|
41
|
+
keys: str,
|
|
42
|
+
default: Optional[object] = None,
|
|
43
|
+
) -> Any:
|
|
44
|
+
"""Get value from nested dictionary object.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
__d: Dictionary object.
|
|
48
|
+
keys: String of keys seperated by periods.
|
|
49
|
+
default (optional): Default if value not found. Default ``None``.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Value of key in nested dictionary object.
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
TypeError: when argument is not an instance of 'dict'.
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def _get(data: Dict[str, Any], key: str) -> Any:
|
|
60
|
+
"""Get value of key from dictionary.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
data: Dictionary object.
|
|
64
|
+
key: Key for which to get value.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Value.
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
result = data.get(key, default)
|
|
72
|
+
except AttributeError: # if data is `None`
|
|
73
|
+
return default
|
|
74
|
+
else:
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
raise_for_instance(__d, (dict, ChainMap))
|
|
78
|
+
raise_for_instance(keys, str)
|
|
79
|
+
|
|
80
|
+
value = functools.reduce(_get, keys.split("."), __d)
|
|
81
|
+
return value
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def deep_set(
|
|
85
|
+
__d: Dict[Hashable, Any],
|
|
86
|
+
/,
|
|
87
|
+
keys: Union[List[str], str],
|
|
88
|
+
value: Any,
|
|
89
|
+
) -> Dict[Hashable, Any]:
|
|
90
|
+
"""Sets key to value in nested dictionary object.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
__d: Dictionary object.
|
|
94
|
+
keys: Either list of keys, or string of keys seperated by periods.
|
|
95
|
+
value: Value to set for key.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Updated dictionary object.
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
TypeError: when argument is not an instance of 'dict'.
|
|
102
|
+
|
|
103
|
+
"""
|
|
104
|
+
raise_for_instance(__d, (dict, ChainMap))
|
|
105
|
+
|
|
106
|
+
if isinstance(keys, str):
|
|
107
|
+
keys = keys.split(".")
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
key, remaining = keys[0], keys[1:]
|
|
111
|
+
__d[key] = (
|
|
112
|
+
deep_set(__d.get(key) or {}, remaining, value)
|
|
113
|
+
if len(remaining) >= 1
|
|
114
|
+
else value
|
|
115
|
+
)
|
|
116
|
+
except KeyError as error:
|
|
117
|
+
raise KeyError(f"{key}.{error.args[0]}") from error # type: ignore
|
|
118
|
+
|
|
119
|
+
except (IndexError, TypeError) as error:
|
|
120
|
+
raise KeyError(keys[0]) from error
|
|
121
|
+
|
|
122
|
+
return __d
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def iter_get(
|
|
126
|
+
__iter: List[Dict[Hashable, Any]],
|
|
127
|
+
/,
|
|
128
|
+
key: str,
|
|
129
|
+
) -> List[object]:
|
|
130
|
+
"""Get value for key from each dictionary in an iterable object.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
__iter: Iterable object containing dictionaries.
|
|
134
|
+
key: Key for which to retrieve value.
|
|
135
|
+
|
|
136
|
+
Raises:
|
|
137
|
+
TypeError: when argument is not an iterable object.
|
|
138
|
+
ValueError: when not all items are dictionaries.
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
raise_for_instance(__iter, list)
|
|
142
|
+
|
|
143
|
+
if not allinstance(__iter, dict):
|
|
144
|
+
raise ValueError("iterable object must contain dictionaries")
|
|
145
|
+
|
|
146
|
+
results = [deep_get(item, key) for item in __iter]
|
|
147
|
+
return results
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def iter_set(
|
|
151
|
+
__iter: List[Dict[Hashable, Any]],
|
|
152
|
+
/,
|
|
153
|
+
key: str,
|
|
154
|
+
value: Any,
|
|
155
|
+
) -> List[Dict[Hashable, Any]]:
|
|
156
|
+
"""Set value of key on each dictionary in an iterable object.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
__iter: Iterable object containing dictionaries.
|
|
160
|
+
key: Key for which to set value.
|
|
161
|
+
value: Value to set.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
List of updated dictionary objects.
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
TypeError: when argument is not an iterable object.
|
|
168
|
+
ValueError: when not all items are dictionaries.
|
|
169
|
+
|
|
170
|
+
"""
|
|
171
|
+
raise_for_instance(__iter, list)
|
|
172
|
+
|
|
173
|
+
if not allinstance(__iter, dict):
|
|
174
|
+
raise ValueError("iterable object must contain dictionaries")
|
|
175
|
+
|
|
176
|
+
results = [deep_set(item, key, value) for item in __iter]
|
|
177
|
+
return results
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def merge(
|
|
181
|
+
*args: Optional[Dict[K, V]],
|
|
182
|
+
overwrite: bool = True,
|
|
183
|
+
) -> Optional[Dict[K, V]]:
|
|
184
|
+
"""Combines dictionary objects into a single dictionary.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
*args: Dictionary objects to merge.
|
|
188
|
+
overwrite (optional): Overwrite existing keys. Default ``True``.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Merged dictionary.
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
TypeError: when arguments are not all dictionaries.
|
|
195
|
+
ValueError: when keys conflict and overwrite is ``False``.
|
|
196
|
+
|
|
197
|
+
"""
|
|
198
|
+
if not allinstance(args, (dict, type(None))):
|
|
199
|
+
raise TypeError("all arguments must be instances of 'dict'")
|
|
200
|
+
|
|
201
|
+
def _merge_dictionaries(
|
|
202
|
+
first: Dict[Any, Any],
|
|
203
|
+
second: Dict[Any, Any],
|
|
204
|
+
/,
|
|
205
|
+
path: Optional[List[str]] = None,
|
|
206
|
+
overwrite: bool = False,
|
|
207
|
+
) -> Dict[Any, Any]:
|
|
208
|
+
"""Merge two dictionaries.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
first: First dictionary.
|
|
212
|
+
second: Second dictionary.
|
|
213
|
+
path: Path of keys in nested dictionary.
|
|
214
|
+
overwrite (optional): Overwrite existing keys. Default ``False``.
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
ValueError: when keys conflict and overwrite is ``False``.
|
|
218
|
+
|
|
219
|
+
.. _Based On:
|
|
220
|
+
https://stackoverflow.com/questions/7204805/how-to-merge-dictionaries-of-dictionaries.
|
|
221
|
+
|
|
222
|
+
"""
|
|
223
|
+
__path: List[str] = [] if path is None else path
|
|
224
|
+
|
|
225
|
+
for key in second:
|
|
226
|
+
if key in first:
|
|
227
|
+
if allinstance((first[key], second[key]), dict):
|
|
228
|
+
first[key] = _merge_dictionaries(
|
|
229
|
+
first[key],
|
|
230
|
+
second[key],
|
|
231
|
+
path=[*__path, str(key)],
|
|
232
|
+
overwrite=overwrite,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
elif allinstance((first[key], second[key]), list):
|
|
236
|
+
first[key] = _merge_lists(first[key], second[key])
|
|
237
|
+
|
|
238
|
+
elif allinstance((first[key], second[key]), set):
|
|
239
|
+
first[key] = _merge_sets(first[key], second[key])
|
|
240
|
+
|
|
241
|
+
elif overwrite is True:
|
|
242
|
+
first[key] = second[key]
|
|
243
|
+
|
|
244
|
+
else:
|
|
245
|
+
location = ".".join(k for k in [*__path, str(key)] if k)
|
|
246
|
+
message = f"Conflict at {location!s}"
|
|
247
|
+
raise ValueError(message)
|
|
248
|
+
|
|
249
|
+
else:
|
|
250
|
+
first[key] = second[key]
|
|
251
|
+
|
|
252
|
+
return first
|
|
253
|
+
|
|
254
|
+
def _merge_lists(first: List[Any], second: List[Any]) -> List[Any]:
|
|
255
|
+
return [*first, *second]
|
|
256
|
+
|
|
257
|
+
def _merge_sets(first: Set[Any], second: Set[Any]) -> Set[Any]:
|
|
258
|
+
return first.union(second)
|
|
259
|
+
|
|
260
|
+
func = functools.partial(_merge_dictionaries, overwrite=overwrite)
|
|
261
|
+
result: Dict[Any, Any] = functools.reduce(
|
|
262
|
+
lambda acc, cur: func(acc, cur) if cur else acc, args, {}
|
|
263
|
+
)
|
|
264
|
+
return result if result else None
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def omit(
|
|
268
|
+
__d: Dict[Hashable, Any],
|
|
269
|
+
/,
|
|
270
|
+
keys: Collection[str],
|
|
271
|
+
) -> Dict[Hashable, Any]:
|
|
272
|
+
"""Omit multiple key-value pairs from dictionary.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
__d: Dictionary object.
|
|
276
|
+
keys: Collection of keys.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Dictionary without the selected key-value pairs.
|
|
280
|
+
|
|
281
|
+
Raises:
|
|
282
|
+
TypeError: when argument is not an instance of 'dict'.
|
|
283
|
+
|
|
284
|
+
"""
|
|
285
|
+
raise_for_instance(__d, dict)
|
|
286
|
+
|
|
287
|
+
# TODO: Add support for omitting key-value pairs from nested dictionaries.
|
|
288
|
+
results: Dict[Hashable, Any] = {k: __d[k] for k in __d if k not in keys}
|
|
289
|
+
return results
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def pick(
|
|
293
|
+
__d: Dict[Hashable, Any],
|
|
294
|
+
/,
|
|
295
|
+
keys: Collection[str],
|
|
296
|
+
) -> Dict[Hashable, Any]:
|
|
297
|
+
"""Pick multiple values from a dictionary.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
__d: Dictionary object.
|
|
301
|
+
keys: Collection of keys.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Dictionary containing the selected key-value pairs.
|
|
305
|
+
|
|
306
|
+
Raises:
|
|
307
|
+
TypeError: when argument is not an instance of 'dict'.
|
|
308
|
+
|
|
309
|
+
"""
|
|
310
|
+
raise_for_instance(__d, (dict, ChainMap))
|
|
311
|
+
|
|
312
|
+
def _last(key: str) -> Hashable:
|
|
313
|
+
return key.split(".")[-1]
|
|
314
|
+
|
|
315
|
+
results = {_last(key): deep_get(__d, key) for key in keys}
|
|
316
|
+
return results
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def remap_keys(
|
|
320
|
+
__d: Dict[Hashable, Any],
|
|
321
|
+
/,
|
|
322
|
+
key_map: Dict[Hashable, str],
|
|
323
|
+
remove: bool = False,
|
|
324
|
+
) -> Dict[Hashable, Any]:
|
|
325
|
+
"""Remap dictionary object to new keys.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
__d: Dictionary object for which keys will be remapped.
|
|
329
|
+
key_map: Dictionary mapping old keys to new ones.
|
|
330
|
+
remove (optional): Whether to drop key-value pairs if key is not found
|
|
331
|
+
in key map. Default ``False``.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Remapped dictionary.
|
|
335
|
+
|
|
336
|
+
Raises:
|
|
337
|
+
TypeError: when argument is not an instance of 'dict'.
|
|
338
|
+
|
|
339
|
+
"""
|
|
340
|
+
raise_for_instance(__d, dict)
|
|
341
|
+
|
|
342
|
+
result: Dict[Hashable, Any] = {
|
|
343
|
+
key_map.get(key, key): value
|
|
344
|
+
for key, value in __d.items()
|
|
345
|
+
if key in key_map or remove is False
|
|
346
|
+
}
|
|
347
|
+
return result
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def remove_nulls(
|
|
351
|
+
__d: Dict[Hashable, Any],
|
|
352
|
+
/,
|
|
353
|
+
null_values: Optional[Collection[Any]] = None,
|
|
354
|
+
) -> Dict[Hashable, Any]:
|
|
355
|
+
"""Remove all null values from dictionary.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
__d: Dictionary from which to remove null values.
|
|
359
|
+
null_values (optional): Additional values to recognize as null.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
Dictionary without null values.
|
|
363
|
+
|
|
364
|
+
Raises:
|
|
365
|
+
TypeError: when argument is not an instance of 'dict'.
|
|
366
|
+
|
|
367
|
+
"""
|
|
368
|
+
raise_for_instance(__d, dict)
|
|
369
|
+
|
|
370
|
+
nulls = null_values or []
|
|
371
|
+
result: Dict[Hashable, Any] = {
|
|
372
|
+
key: value
|
|
373
|
+
for key, value in __d.items()
|
|
374
|
+
if value is not None and value not in nulls
|
|
375
|
+
}
|
|
376
|
+
return result
|
apytizer/utils/errors.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/utils/errors.py
|
|
3
|
+
|
|
4
|
+
# Standard Library Imports
|
|
5
|
+
from typing import Any
|
|
6
|
+
from typing import Tuple
|
|
7
|
+
from typing import Union
|
|
8
|
+
|
|
9
|
+
# Local Imports
|
|
10
|
+
from .. import utils
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"raise_for_attribute",
|
|
14
|
+
"raise_for_instance",
|
|
15
|
+
"raise_for_none",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def raise_for_attribute(__obj: object, __attr: str, /) -> None:
|
|
20
|
+
"""Raise error if object does not contain expected attribute.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
__obj: Object to check for attribute.
|
|
24
|
+
__attr: Attribute for which to check.
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
if not hasattr(__obj, __attr):
|
|
28
|
+
cls = __obj.__class__.__name__
|
|
29
|
+
message = f"type object '{cls!s}' has no attribute '{__attr!s}'"
|
|
30
|
+
raise AttributeError(message)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def raise_for_instance(
|
|
34
|
+
__value: object,
|
|
35
|
+
__expected: Union[type, Tuple[Union[type, Tuple[Any, ...]], ...]],
|
|
36
|
+
/,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Raise error if value is not an instance of expected type.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
__value: Object to check for type.
|
|
42
|
+
__expected: Expected type(s).
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
correct_type = isinstance(__value, __expected)
|
|
46
|
+
|
|
47
|
+
if not correct_type and isinstance(__expected, tuple):
|
|
48
|
+
_raise_for_multiple_types(__value, __expected)
|
|
49
|
+
|
|
50
|
+
if not correct_type and not isinstance(__expected, tuple):
|
|
51
|
+
_raise_for_single_type(__value, __expected)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _raise_for_multiple_types(
|
|
55
|
+
__value: object, __types: Tuple[Union[type, Tuple[Any, ...]], ...], /
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Raise error if value is not among expected types.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
__value: Object to check for type.
|
|
61
|
+
__types: Expected types.
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
type_names = utils.iter_getattr(__types, "__name__")
|
|
65
|
+
formatted_names = utils.iter_format(type_names, "'{}'")
|
|
66
|
+
expected = utils.syntactic_list(formatted_names, "or")
|
|
67
|
+
actual = type(__value).__name__
|
|
68
|
+
|
|
69
|
+
message = f"expected types {expected!s}, got {actual!s} instead"
|
|
70
|
+
raise TypeError(message)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _raise_for_single_type(__value: object, __type: type, /) -> None:
|
|
74
|
+
"""Raise error if value is not an instance of expected type.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
__value: Object to check for type.
|
|
78
|
+
__type: Expected type.
|
|
79
|
+
|
|
80
|
+
"""
|
|
81
|
+
expected, actual = f"'{__type.__name__}'", type(__value).__name__
|
|
82
|
+
message = f"expected type {expected!s}, got {actual!s} instead"
|
|
83
|
+
raise TypeError(message)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def raise_for_none(*args: Any, **kwargs: Any) -> None:
|
|
87
|
+
"""Raise error if value is None.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
*args: Positional arguments.
|
|
91
|
+
**kwargs: Keyword arguments.
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
ValueError: when any argument is ``None``.
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
if any(arg is None for arg in args):
|
|
98
|
+
message = "argument cannot be 'None'"
|
|
99
|
+
raise ValueError(message)
|
|
100
|
+
|
|
101
|
+
for name, value in kwargs.items():
|
|
102
|
+
if value is None:
|
|
103
|
+
message = f"{name} cannot be 'None'"
|
|
104
|
+
raise ValueError(message)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/utils/iterables.py
|
|
3
|
+
|
|
4
|
+
# Standard Library Imports
|
|
5
|
+
from typing import Any
|
|
6
|
+
from typing import List
|
|
7
|
+
from typing import SupportsIndex
|
|
8
|
+
|
|
9
|
+
# Local Imports
|
|
10
|
+
from .typing import allinstance
|
|
11
|
+
|
|
12
|
+
__all__ = ["deep_append", "deep_extend", "split_list"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def deep_append(
|
|
16
|
+
__obj: List[List[Any]], __index: SupportsIndex, __item: Any, /
|
|
17
|
+
) -> List[List[Any]]:
|
|
18
|
+
"""Appends value to a list within an iterable object.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
__obj: Iterable object.
|
|
22
|
+
__index: Index at which to append item.
|
|
23
|
+
__item: Item to append to nested list.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Updated iterable object.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
TypeError: when argument does not support indexing.
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
if not isinstance(__obj, list): # type: ignore
|
|
33
|
+
message = f"expected type 'list', got {type(__obj)} instead"
|
|
34
|
+
raise TypeError(message)
|
|
35
|
+
|
|
36
|
+
if not allinstance(__obj, list):
|
|
37
|
+
raise ValueError("must contain only lists")
|
|
38
|
+
|
|
39
|
+
target: List[Any] = __obj[__index]
|
|
40
|
+
target.append(__item)
|
|
41
|
+
return __obj
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def deep_extend(
|
|
45
|
+
__obj: List[List[Any]],
|
|
46
|
+
__index: SupportsIndex,
|
|
47
|
+
__items: List[Any],
|
|
48
|
+
/,
|
|
49
|
+
) -> List[List[Any]]:
|
|
50
|
+
"""Extends list within nested iterable object with provided values.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
__obj: Iterable object.
|
|
54
|
+
__index: Index at which to extend list.
|
|
55
|
+
__items: Items with which to extend nested list.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Updated iterable object.
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
TypeError: when argument does not support indexing.
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
if not isinstance(__obj, list): # type: ignore
|
|
65
|
+
message = f"expected type 'list', got {type(__obj)} instead"
|
|
66
|
+
raise TypeError(message)
|
|
67
|
+
|
|
68
|
+
if not allinstance(__obj, list):
|
|
69
|
+
raise ValueError("iterable object must contain only lists")
|
|
70
|
+
|
|
71
|
+
target: List[Any] = __obj[__index]
|
|
72
|
+
target.extend(__items)
|
|
73
|
+
return __obj
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def split_list(__lst: List[Any], /, size: int) -> List[List[Any]]:
|
|
77
|
+
"""Split list into multiple groups of the same size.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
__lst: List to split into groups.
|
|
81
|
+
size: Maximum size of each group.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Groups.
|
|
85
|
+
|
|
86
|
+
.. _Based On:
|
|
87
|
+
https://stackoverflow.com/questions/2231663/slicing-a-list-into-a-list-of-sub-lists
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
results = [__lst[i : i + size] for i in range(0, len(__lst), size)]
|
|
91
|
+
return results
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# src/apytizer/utils/objects.py
|
|
3
|
+
|
|
4
|
+
# Standard Library Imports
|
|
5
|
+
import collections
|
|
6
|
+
import functools
|
|
7
|
+
from typing import Any
|
|
8
|
+
from typing import Iterable
|
|
9
|
+
from typing import List
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
# Local Imports
|
|
13
|
+
from .typing import allinstance
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"deep_getattr",
|
|
17
|
+
"deep_setattr",
|
|
18
|
+
"getattrs",
|
|
19
|
+
"setattrs",
|
|
20
|
+
"iter_getattr",
|
|
21
|
+
"iter_setattr",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def deep_getattr(__o: object, __name: Iterable[str], /) -> Any:
|
|
26
|
+
"""Get value of attribute in nested object.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
__o: Object on which to get attribute.
|
|
30
|
+
__name: Name of attribute from which to get value.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Value of attribute in nested object.
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
if not isinstance(__name, Iterable): # type: ignore
|
|
37
|
+
message = f"'{type(__name)}' object is not iterable"
|
|
38
|
+
raise TypeError(message)
|
|
39
|
+
|
|
40
|
+
attrs = __name.split(".") if isinstance(__name, str) else __name
|
|
41
|
+
result = functools.reduce(lambda acc, cur: getattr(acc, cur), attrs, __o)
|
|
42
|
+
return result
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def deep_setattr(__o: object, __name: str, __value: Any, /) -> None:
|
|
46
|
+
"""Sets attribute to value in nested object.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
__o: Object on which to set attributes.
|
|
50
|
+
__name: Name of attribute to set on object.
|
|
51
|
+
__value: Value to set attribute on object.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Updated object.
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
attrs = collections.deque(__name.split("."))
|
|
58
|
+
target = attrs.pop()
|
|
59
|
+
|
|
60
|
+
obj = functools.reduce(lambda acc, cur: getattr(acc, cur), attrs, __o)
|
|
61
|
+
setattr(obj, target, __value)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def getattrs(
|
|
65
|
+
__o: object, __names: List[str], __default: Optional[Any] = None, /
|
|
66
|
+
) -> List[Any]:
|
|
67
|
+
"""Get named attributes from an object.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
__o: Object from which to get attributes.
|
|
71
|
+
__names: Names of attributes to get from object.
|
|
72
|
+
__default (optional): Default value when attribute doesn't exist.
|
|
73
|
+
Default ``None``.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Attributes.
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
results = [getattr(__o, __name, __default) for __name in __names]
|
|
80
|
+
return results
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def setattrs(__o: object, __names: List[str], __values: List[Any], /) -> None:
|
|
84
|
+
"""Set named attributes on an object.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
__o: Object on which to set attributes.
|
|
88
|
+
__names: Names of attributes to set on object.
|
|
89
|
+
__values: Values to set for keys on object.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Updated object.
|
|
93
|
+
|
|
94
|
+
"""
|
|
95
|
+
for __name, __value in zip(__names, __values):
|
|
96
|
+
setattr(__o, __name, __value)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def iter_getattr(__iter: Iterable[object], __name: str, /) -> List[Any]:
|
|
100
|
+
"""Get a named attribute from each object.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
__iter: Iterable object containing objects.
|
|
104
|
+
__name: Attribute for which to retrieve value.
|
|
105
|
+
|
|
106
|
+
Raises:
|
|
107
|
+
TypeError: when argument is not an iterable object.
|
|
108
|
+
ValueError: when not all items are mappings.
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
if not isinstance(__iter, Iterable): # type: ignore
|
|
112
|
+
raise TypeError("must be an iterable object")
|
|
113
|
+
|
|
114
|
+
if not allinstance(__iter, object):
|
|
115
|
+
raise ValueError("all items within iterator must be objects")
|
|
116
|
+
|
|
117
|
+
results = [getattr(item, __name) for item in __iter]
|
|
118
|
+
return results
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def iter_setattr(
|
|
122
|
+
__iter: Iterable[Any], __name: str, __value: Any, /
|
|
123
|
+
) -> Iterable[Any]:
|
|
124
|
+
"""Sets the named attribute to the specified value on each object.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
__iter: Iterable object containing objects.
|
|
128
|
+
__name: Attribute to set to value.
|
|
129
|
+
value: Value to which to set attribute.
|
|
130
|
+
|
|
131
|
+
Raises:
|
|
132
|
+
TypeError: when argument is not an iterable object.
|
|
133
|
+
ValueError: when not all items are objects.
|
|
134
|
+
|
|
135
|
+
"""
|
|
136
|
+
if not isinstance(__iter, Iterable): # type: ignore
|
|
137
|
+
raise TypeError("must be an iterable object")
|
|
138
|
+
|
|
139
|
+
if not allinstance(__iter, object):
|
|
140
|
+
raise ValueError("all items within iterator must be objects")
|
|
141
|
+
|
|
142
|
+
for item in __iter:
|
|
143
|
+
setattr(item, __name, __value)
|
|
144
|
+
|
|
145
|
+
return __iter
|