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.
Files changed (75) hide show
  1. apytizer/__init__.py +2 -12
  2. apytizer/adapters/__init__.py +2 -3
  3. apytizer/adapters/transport_adapter.py +91 -0
  4. apytizer/apis/__init__.py +6 -0
  5. apytizer/apis/abstract_api.py +36 -0
  6. apytizer/apis/web_api.py +460 -0
  7. apytizer/connections/__init__.py +6 -0
  8. apytizer/connections/abstract_connection.py +28 -0
  9. apytizer/connections/http_connection.py +431 -0
  10. apytizer/decorators/__init__.py +5 -5
  11. apytizer/decorators/caching.py +60 -9
  12. apytizer/decorators/chunking.py +105 -0
  13. apytizer/decorators/connection.py +55 -20
  14. apytizer/decorators/json.py +70 -12
  15. apytizer/decorators/pagination.py +50 -32
  16. apytizer/endpoints/__init__.py +6 -0
  17. apytizer/endpoints/abstract_endpoint.py +38 -0
  18. apytizer/endpoints/web_endpoint.py +519 -0
  19. apytizer/engines/__init__.py +6 -0
  20. apytizer/engines/abstract_engine.py +45 -0
  21. apytizer/engines/http_engine.py +171 -0
  22. apytizer/errors.py +34 -0
  23. apytizer/factories/__init__.py +5 -0
  24. apytizer/factories/abstract_factory.py +17 -0
  25. apytizer/http_methods.py +34 -0
  26. apytizer/managers/__init__.py +12 -0
  27. apytizer/managers/abstract_manager.py +80 -0
  28. apytizer/managers/base_manager.py +116 -0
  29. apytizer/mappers/__init__.py +6 -0
  30. apytizer/mappers/abstract_mapper.py +48 -0
  31. apytizer/mappers/base_mapper.py +78 -0
  32. apytizer/media_types.py +118 -0
  33. apytizer/models/__init__.py +6 -0
  34. apytizer/models/abstract_model.py +119 -0
  35. apytizer/models/base_model.py +85 -0
  36. apytizer/protocols.py +38 -0
  37. apytizer/repositories/__init__.py +6 -0
  38. apytizer/repositories/abstract_repository.py +81 -0
  39. apytizer/repositories/managed_repository.py +92 -0
  40. apytizer/routes/__init__.py +6 -0
  41. apytizer/routes/abstract_route.py +32 -0
  42. apytizer/routes/base_route.py +138 -0
  43. apytizer/sessions/__init__.py +33 -0
  44. apytizer/sessions/abstract_session.py +63 -0
  45. apytizer/sessions/requests_session.py +125 -0
  46. apytizer/states/__init__.py +6 -0
  47. apytizer/states/abstract_state.py +71 -0
  48. apytizer/states/local_state.py +99 -0
  49. apytizer/utils/__init__.py +9 -4
  50. apytizer/utils/caching.py +39 -0
  51. apytizer/utils/dictionaries.py +376 -0
  52. apytizer/utils/errors.py +104 -0
  53. apytizer/utils/iterables.py +91 -0
  54. apytizer/utils/objects.py +145 -0
  55. apytizer/utils/strings.py +69 -0
  56. apytizer/utils/typing.py +29 -0
  57. apytizer-0.0.1b2.dist-info/METADATA +41 -0
  58. apytizer-0.0.1b2.dist-info/RECORD +60 -0
  59. {apytizer-0.0.1a0.dist-info → apytizer-0.0.1b2.dist-info}/WHEEL +1 -2
  60. apytizer/abstracts/__init__.py +0 -8
  61. apytizer/abstracts/api.py +0 -147
  62. apytizer/abstracts/endpoint.py +0 -177
  63. apytizer/abstracts/model.py +0 -50
  64. apytizer/abstracts/session.py +0 -39
  65. apytizer/adapters/transport.py +0 -40
  66. apytizer/base/__init__.py +0 -8
  67. apytizer/base/api.py +0 -510
  68. apytizer/base/endpoint.py +0 -443
  69. apytizer/base/model.py +0 -119
  70. apytizer/utils/generate_key.py +0 -18
  71. apytizer/utils/merge.py +0 -19
  72. apytizer-0.0.1a0.dist-info/METADATA +0 -27
  73. apytizer-0.0.1a0.dist-info/RECORD +0 -25
  74. apytizer-0.0.1a0.dist-info/top_level.txt +0 -1
  75. {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
@@ -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