alpha-python 0.1.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.
Files changed (62) hide show
  1. alpha/__init__.py +0 -0
  2. alpha/adapters/__init__.py +0 -0
  3. alpha/adapters/sqla_unit_of_work.py +120 -0
  4. alpha/domain/__init__.py +0 -0
  5. alpha/domain/models/__init__.py +0 -0
  6. alpha/domain/models/base_model.py +25 -0
  7. alpha/encoder.py +62 -0
  8. alpha/exceptions.py +99 -0
  9. alpha/factories/__init__.py +0 -0
  10. alpha/factories/_type_conversion_matrix.py +233 -0
  11. alpha/factories/_type_mapping.py +29 -0
  12. alpha/factories/class_factories.py +496 -0
  13. alpha/factories/default_field_factory.py +50 -0
  14. alpha/factories/field_iterator.py +188 -0
  15. alpha/factories/logging_handler_factory.py +86 -0
  16. alpha/factories/model_class_factory.py +176 -0
  17. alpha/factories/models/__init__.py +0 -0
  18. alpha/factories/models/factory_classes.py +20 -0
  19. alpha/factories/request_factory.py +211 -0
  20. alpha/factories/response_factory.py +186 -0
  21. alpha/factories/type_factories.py +204 -0
  22. alpha/infra/__init__.py +0 -0
  23. alpha/infra/database/__init__.py +0 -0
  24. alpha/infra/database/sql_alchemy_database.py +159 -0
  25. alpha/infra/database/sql_alchemy_view.py +48 -0
  26. alpha/infra/models/__init__.py +0 -0
  27. alpha/infra/models/filter_operators.py +98 -0
  28. alpha/infra/models/json_patch.py +21 -0
  29. alpha/infra/models/order_by.py +69 -0
  30. alpha/infra/models/query_clause.py +45 -0
  31. alpha/infra/models/search_filter.py +586 -0
  32. alpha/interfaces/__init__.py +0 -0
  33. alpha/interfaces/attrs_instance.py +10 -0
  34. alpha/interfaces/dataclass_instance.py +11 -0
  35. alpha/interfaces/factories.py +102 -0
  36. alpha/interfaces/openapi_model.py +21 -0
  37. alpha/interfaces/patchable.py +8 -0
  38. alpha/interfaces/sql_database.py +36 -0
  39. alpha/interfaces/sql_mapper.py +23 -0
  40. alpha/interfaces/sql_repository.py +380 -0
  41. alpha/interfaces/token_factory.py +56 -0
  42. alpha/interfaces/unit_of_work.py +53 -0
  43. alpha/interfaces/updateable.py +7 -0
  44. alpha/py.typed +0 -0
  45. alpha/repositories/__init__.py +0 -0
  46. alpha/repositories/default_sql_repository.py +679 -0
  47. alpha/repositories/models/__init__.py +0 -0
  48. alpha/repositories/models/repository_model.py +16 -0
  49. alpha/services/__init__.py +0 -0
  50. alpha/services/authentication_service.py +71 -0
  51. alpha/utils/__init__.py +0 -0
  52. alpha/utils/_http_codes.py +148 -0
  53. alpha/utils/is_attrs.py +18 -0
  54. alpha/utils/logging_configurator.py +133 -0
  55. alpha/utils/logging_level_checker.py +26 -0
  56. alpha/utils/response_object.py +26 -0
  57. alpha/utils/version_check.py +17 -0
  58. alpha_python-0.1.0.dist-info/METADATA +22 -0
  59. alpha_python-0.1.0.dist-info/RECORD +62 -0
  60. alpha_python-0.1.0.dist-info/WHEEL +5 -0
  61. alpha_python-0.1.0.dist-info/licenses/LICENSE +21 -0
  62. alpha_python-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,496 @@
1
+ """Contains these ClassFactory classes:
2
+ - IterableClassFactory
3
+ - DictClassFactory
4
+ - DataclassClassFactory
5
+ - GenericAliasClassFactory
6
+ - UnionClassFactory
7
+ - NativeClassFactory
8
+ - EnumClassFactory
9
+ - AnyClassFactory
10
+ """
11
+
12
+ import enum
13
+ import sys
14
+ import types
15
+ import typing
16
+ from dataclasses import is_dataclass
17
+ from typing import Any, Iterable, Optional, Union, get_args, get_origin
18
+
19
+ from alpha import exceptions
20
+ from alpha.factories._type_mapping import TYPES
21
+ from alpha.factories.field_iterator import Field
22
+ from alpha.infra.models.json_patch import JsonPatch
23
+ from alpha.interfaces.factories import (
24
+ FactoryClassesInstance,
25
+ )
26
+ from alpha.interfaces.openapi_model import OpenAPIModel
27
+ from alpha.utils.is_attrs import is_attrs
28
+
29
+
30
+ class IterableClassFactory:
31
+ def process(
32
+ self,
33
+ obj: OpenAPIModel,
34
+ field: Field,
35
+ factory_classes: FactoryClassesInstance,
36
+ ) -> Any:
37
+ """Processing iterable classes. Mapping the items of the corresponding
38
+ OpenAPIModel instance attribute and return them in an iterable of the
39
+ field.type
40
+
41
+ Parameters
42
+ ----------
43
+ obj
44
+ OpenAPIModel instance
45
+ field
46
+ Field object of dataclass attribute
47
+ factory_classes
48
+ FactoryClasses instance which acts as a toolbox of Factory classes
49
+
50
+ Returns
51
+ -------
52
+ The new iterable object
53
+
54
+ Raises
55
+ ------
56
+ exceptions.TypingFactoryException
57
+ Unable to convert an iterable to a certain iterable type
58
+ """
59
+ field_type_origin = get_origin(field.type)
60
+ field_args = get_args(field.type)
61
+
62
+ values: Union[Iterable[Any], Any, None] = getattr(
63
+ obj, field.name, None
64
+ )
65
+
66
+ if not isinstance(values, Iterable):
67
+ values = [values]
68
+
69
+ cls = field_args[0]
70
+
71
+ if is_dataclass(cls) or is_attrs(cls):
72
+ if factory_classes.model_class_factory is None:
73
+ raise ValueError(
74
+ "ModelClassFactory instance is not present in "
75
+ "FactoryClasses instance"
76
+ )
77
+ collection = [
78
+ factory_classes.model_class_factory.process(obj=obj, cls=cls)
79
+ for obj in values
80
+ ]
81
+ else:
82
+ if not getattr(obj, field.name, None):
83
+ return factory_classes.default_factory.process(field=field)
84
+
85
+ if isinstance(cls, type(enum.Enum)):
86
+ type_factory = factory_classes.type_factories["enum"]
87
+ else:
88
+ try:
89
+ type_factory = TYPES[cls]()
90
+ except KeyError as exc:
91
+ raise exceptions.ClassFactoryException(
92
+ "The class of this object type is not supported. "
93
+ f"{field.type=}; "
94
+ f"{field_type_origin=}; "
95
+ f"{field.type.__class__=}; "
96
+ ) from exc
97
+
98
+ collection = [
99
+ type_factory.process(
100
+ key=field.name,
101
+ value=val,
102
+ cls=cls,
103
+ )
104
+ for val in values
105
+ ]
106
+
107
+ try:
108
+ if field_type_origin:
109
+ result = field_type_origin(collection)
110
+ return result
111
+ except ValueError as exc:
112
+ raise exceptions.TypingFactoryException(
113
+ "Unable to convert a collection of "
114
+ f"{type(collection[0])} "
115
+ f"instances to a {field_type_origin}: {exc}"
116
+ ) from exc
117
+
118
+
119
+ class DictClassFactory:
120
+ def process(
121
+ self,
122
+ obj: OpenAPIModel,
123
+ field: Field,
124
+ factory_classes: FactoryClassesInstance,
125
+ ) -> Any:
126
+ """Processing dictionary classes. Considering multiple options to get a
127
+ dictionary from an object.
128
+
129
+ Parameters
130
+ ----------
131
+ obj
132
+ OpenAPIModel instance
133
+ field
134
+ Field object of dataclass attribute
135
+ factory_classes
136
+ Unused for this implementation of the ClassFactory interface
137
+
138
+ Returns
139
+ -------
140
+ Dictionary object
141
+
142
+ Raises
143
+ ------
144
+ exceptions.ObjectConversionError
145
+ Unable to return a dictionary
146
+ """
147
+ value: Union[dict[str, Any], Any] = getattr(obj, field.name, None)
148
+
149
+ if isinstance(value, dict):
150
+ return value
151
+ if hasattr(value, "to_dict"):
152
+ return getattr(value, "to_dict")()
153
+ if hasattr(value, "_asdict"):
154
+ return getattr(value, "_asdict")()
155
+ if hasattr(value, "__dict__"):
156
+ return value.__dict__
157
+ try:
158
+ return dict(value)
159
+ except (TypeError, ValueError) as exc:
160
+ raise exceptions.ObjectConversionError(
161
+ f'Unable to convert a(n) "{type(value)}" object to a dict'
162
+ ) from exc
163
+
164
+
165
+ class DataclassClassFactory:
166
+ def process(
167
+ self,
168
+ obj: OpenAPIModel,
169
+ field: Field,
170
+ factory_classes: FactoryClassesInstance,
171
+ ) -> Any:
172
+ """Processing dataclass classes by using the model_class_factory from
173
+ the factory_class instance
174
+
175
+ Parameters
176
+ ----------
177
+ obj
178
+ OpenAPIModel instance
179
+ field
180
+ Field object of dataclass attribute
181
+ factory_classes
182
+ FactoryClasses instance which acts as a toolbox of Factory classes
183
+
184
+ Returns
185
+ -------
186
+ DataclassInterface object
187
+
188
+ Raises
189
+ ------
190
+ ValueError
191
+ factory_classes.model_class_factory is None
192
+ """
193
+ if hasattr(obj, field.name):
194
+ obj = getattr(obj, field.name)
195
+ type_ = field.type
196
+ if factory_classes.model_class_factory is None:
197
+ raise ValueError(
198
+ "ModelClassFactory instance is not present in FactoryClasses "
199
+ "instance"
200
+ )
201
+ return factory_classes.model_class_factory.process(obj=obj, cls=type_)
202
+
203
+
204
+ class GenericAliasClassFactory:
205
+ def process(
206
+ self,
207
+ obj: OpenAPIModel,
208
+ field: Field,
209
+ factory_classes: FactoryClassesInstance,
210
+ ) -> Any:
211
+ """Processing generic alias types. Built-in type subclasses from both
212
+ typing._GenericAlias and types.GenericAlias are supported. It uses the
213
+ ClassFactory for iterables and dictionaries, from the class_factories
214
+ attribute of the FactoryClassesInstance, to process each type.
215
+
216
+ These examples are all supported:
217
+ - dict[str, Any]
218
+ - typing.Tuple[int]
219
+ - set[float]
220
+ - list[Object]
221
+
222
+ An exception will be raised when a union argument is used because only
223
+ one argument (two in case of a dict) is supported.
224
+
225
+ Parameters
226
+ ----------
227
+ obj
228
+ OpenAPIModel instance
229
+ field
230
+ Field object of dataclass attribute
231
+ factory_classes
232
+ FactoryClasses instance which acts as a toolbox of Factory classes
233
+
234
+ Returns
235
+ -------
236
+ An iterable or a dictionary
237
+
238
+ Raises
239
+ ------
240
+ exceptions.MixedArgumentTypesError
241
+ Unable to process a union argument type. eg: list[int | float]
242
+ exceptions.TypingFactoryException
243
+ Catching any future generic alias type
244
+ """
245
+ field_type = get_origin(field.type)
246
+ field_args = get_args(field.type)
247
+
248
+ arg_classes = [
249
+ getattr(typing, "_UnionGenericAlias"),
250
+ ]
251
+ if sys.version_info.minor >= 10: # Backwards compatible with <3.10
252
+ arg_classes.append(getattr(types, "UnionType"))
253
+
254
+ for arg in field_args:
255
+ if arg.__class__ in arg_classes:
256
+ raise exceptions.MixedArgumentTypesError(
257
+ f"Mixed object types in a '{field_type}' are not allowed"
258
+ )
259
+
260
+ if field_type in (list, tuple, set, frozenset):
261
+ return factory_classes.class_factories["iterable"].process(
262
+ obj=obj,
263
+ field=field,
264
+ factory_classes=factory_classes,
265
+ )
266
+ if field_type is dict:
267
+ return factory_classes.class_factories["dict"].process(
268
+ obj=obj,
269
+ field=field,
270
+ factory_classes=factory_classes,
271
+ )
272
+ raise exceptions.TypingFactoryException(
273
+ f"Mapping a '{field_type}' type is not supported"
274
+ )
275
+
276
+
277
+ class UnionClassFactory:
278
+ def process(
279
+ self,
280
+ obj: OpenAPIModel,
281
+ field: Field,
282
+ factory_classes: FactoryClassesInstance,
283
+ ) -> Any:
284
+ """Processing union types. Both typing._UnionGenericAlias and
285
+ types.UnionType are supported, eg. `typing.Union[int, float]` or
286
+ `int | float`.
287
+
288
+ These rules are being applied:
289
+ - If the first argument is a dataclass or attrs class all other
290
+ arguments will be ignored
291
+ - If the value is of one of the types in the union arguments the
292
+ value will be returned untouched
293
+ - If the value type is not in the union arguments, the value will
294
+ be processed by the GenericTypeClass, to try to map the
295
+ value to match the type of the first union argument
296
+
297
+ Parameters
298
+ ----------
299
+ obj
300
+ OpenAPIModel instance
301
+ field
302
+ Field object of dataclass attribute
303
+ factory_classes
304
+ FactoryClasses instance which acts as a toolbox of Factory classes
305
+
306
+ Returns
307
+ -------
308
+ Any value with a type of one of the union arguments
309
+
310
+ Raises
311
+ ------
312
+ ValueError
313
+ factory_classes.model_class_factory is None
314
+ exceptions.UnionArgumentError
315
+ The attributes value of the OpenAPIModel instance is not compatible
316
+ with one of the types in the union type arguments
317
+ """
318
+ value = getattr(obj, field.name, None)
319
+ if not value:
320
+ return factory_classes.default_factory.process(field=field)
321
+
322
+ args = get_args(field.type)
323
+ cls = args[0]
324
+
325
+ if is_dataclass(cls) or is_attrs(cls):
326
+ if factory_classes.model_class_factory is None:
327
+ raise ValueError(
328
+ "ModelClassFactory instance is not present in "
329
+ "FactoryClasses instance"
330
+ )
331
+ return factory_classes.model_class_factory.process(
332
+ obj=obj, cls=cls
333
+ )
334
+ if type(value) in args:
335
+ return value
336
+ try:
337
+ return factory_classes.type_factories["generic"].process(
338
+ key=field.name, value=value, cls=cls
339
+ )
340
+ except Exception as exc:
341
+ raise exceptions.UnionArgumentError(
342
+ f"{cls} is of a type that is not compatible with the type "
343
+ + "arguments in the union. Casting the value is not possible"
344
+ ) from exc
345
+
346
+
347
+ class NativeClassFactory:
348
+ def process(
349
+ self,
350
+ obj: OpenAPIModel,
351
+ field: Field,
352
+ factory_classes: FactoryClassesInstance,
353
+ ) -> Optional[Any]:
354
+ """Processing native types. Native types are all types that have a
355
+ `type` value for the `__class__` attribute.
356
+
357
+ The supported types are present in the keys of the TYPES constant and
358
+ are processed by their corresponding type factories. A dataclass or
359
+ attrs type class will be processed by the DataclassClassFactory.
360
+
361
+ Parameters
362
+ ----------
363
+ obj
364
+ OpenAPIModel instance
365
+ field
366
+ Field object of dataclass attribute
367
+ factory_classes
368
+ FactoryClasses instance which acts as a toolbox of Factory classes
369
+
370
+ Returns
371
+ -------
372
+ Any value which is returned by another Factory
373
+
374
+ Raises
375
+ ------
376
+ KeyError
377
+ In case the type is not present in TYPES
378
+ """
379
+ # Backwards compatibility for python 3.9/10
380
+ if get_args(field.type):
381
+ return factory_classes.class_factories["generic_alias"].process(
382
+ obj=obj, field=field, factory_classes=factory_classes
383
+ )
384
+
385
+ value = getattr(obj, field.name, None)
386
+ cls: type = field.type # type: ignore
387
+
388
+ # return the value untouched if it is an instance of cls
389
+ if isinstance(value, cls):
390
+ return value
391
+
392
+ if is_dataclass(cls) or is_attrs(cls):
393
+ return factory_classes.class_factories["dataclass"].process(
394
+ obj=obj, field=field, factory_classes=factory_classes
395
+ )
396
+ if cls == JsonPatch:
397
+ return factory_classes.class_factories["json_patch"].process(
398
+ obj=obj, field=field, factory_classes=factory_classes
399
+ )
400
+ try:
401
+ if value:
402
+ type_factory = TYPES[cls]
403
+ return type_factory().process(
404
+ key=field.name,
405
+ value=value,
406
+ cls=cls,
407
+ )
408
+ return factory_classes.default_factory.process(field=field)
409
+ except KeyError as exc:
410
+ raise KeyError(
411
+ f"This attributes type is not supported: {field.type}"
412
+ ) from exc
413
+
414
+
415
+ class EnumClassFactory:
416
+ def process(
417
+ self,
418
+ obj: OpenAPIModel,
419
+ field: Field,
420
+ factory_classes: FactoryClassesInstance,
421
+ ) -> Optional[enum.Enum]:
422
+ """Processing Enum types. This class acts as an adapter for the
423
+ EnumTypeFactory.
424
+
425
+ Parameters
426
+ ----------
427
+ obj
428
+ OpenAPIModel instance
429
+ field
430
+ Field object of dataclass attribute
431
+ factory_classes
432
+ FactoryClasses instance which acts as a toolbox of Factory classes
433
+
434
+ Returns
435
+ -------
436
+ An enum instance
437
+ """
438
+ return factory_classes.type_factories["enum"].process(
439
+ key=field.name, value=getattr(obj, field.name), cls=field.type
440
+ )
441
+
442
+
443
+ class JsonPatchClassFactory:
444
+ def process(
445
+ self,
446
+ obj: OpenAPIModel,
447
+ field: Field,
448
+ factory_classes: FactoryClassesInstance,
449
+ ) -> Optional[JsonPatch]:
450
+ """Processing JsonPatch types. This class acts as an adapter for the
451
+ JsonPatchTypeFactory.
452
+
453
+ Parameters
454
+ ----------
455
+ obj
456
+ OpenAPIModel instance
457
+ field
458
+ Field object of dataclass attribute
459
+ factory_classes
460
+ FactoryClasses instance which acts as a toolbox of Factory classes
461
+
462
+ Returns
463
+ -------
464
+ An JsonPatch instance
465
+ """
466
+ return factory_classes.type_factories["json_patch"].process(
467
+ key=field.name, value=getattr(obj, field.name), cls=field.type
468
+ )
469
+
470
+
471
+ class AnyClassFactory:
472
+ def process(
473
+ self,
474
+ obj: OpenAPIModel,
475
+ field: Field,
476
+ factory_classes: FactoryClassesInstance,
477
+ ) -> Any:
478
+ """Processing Any types.
479
+
480
+ Parameters
481
+ ----------
482
+ obj
483
+ OpenAPIModel instance
484
+ field
485
+ Field object of dataclass attribute
486
+ factory_classes
487
+ FactoryClasses instance which acts as a toolbox of Factory classes
488
+
489
+ Returns
490
+ -------
491
+ Any value
492
+ """
493
+ value = getattr(obj, field.name, None)
494
+ if not value:
495
+ return factory_classes.default_factory.process(field=field)
496
+ return value
@@ -0,0 +1,50 @@
1
+ """Contains DefaultFieldFactory class"""
2
+
3
+ from dataclasses import _MISSING_TYPE # type: ignore
4
+ from typing import Any, get_args
5
+
6
+ from alpha import exceptions
7
+ from alpha.factories.field_iterator import Field
8
+
9
+
10
+ class DefaultFieldFactory:
11
+ def process(self, field: Field) -> Any:
12
+ """Processes the default value from the field object of a dataclass
13
+ attribute
14
+
15
+ Parameters
16
+ ----------
17
+ field
18
+ field object of a dataclass attribute
19
+
20
+ Returns
21
+ -------
22
+ The default value found in the field object
23
+
24
+ Raises
25
+ ------
26
+ exceptions.DefaultFactoryException
27
+ - When None is allowed by typing but a default value is missing
28
+ - When a default value is missing and the attribute cannot be empty
29
+ - In case of an unknown error
30
+ """
31
+ if hasattr(field, "default_factory"):
32
+ if callable(field.default_factory):
33
+ return field.default_factory()
34
+ if callable(field.default):
35
+ return field.default()
36
+ if not isinstance(field.default, _MISSING_TYPE):
37
+ return field.default
38
+ if type(None) in get_args(field.type):
39
+ raise exceptions.DefaultFactoryException(
40
+ f'None is allowed by typing for the "{field.name}" '
41
+ "attribute, but a default value is missing"
42
+ )
43
+ if isinstance(field.default, _MISSING_TYPE): # type: ignore
44
+ raise exceptions.DefaultFactoryException(
45
+ f'The source object has no "{field.name}" attribute and no '
46
+ "default value is set"
47
+ )
48
+ raise exceptions.DefaultFactoryException(
49
+ "Unknown default_factory exception"
50
+ )