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.
- alpha/__init__.py +0 -0
- alpha/adapters/__init__.py +0 -0
- alpha/adapters/sqla_unit_of_work.py +120 -0
- alpha/domain/__init__.py +0 -0
- alpha/domain/models/__init__.py +0 -0
- alpha/domain/models/base_model.py +25 -0
- alpha/encoder.py +62 -0
- alpha/exceptions.py +99 -0
- alpha/factories/__init__.py +0 -0
- alpha/factories/_type_conversion_matrix.py +233 -0
- alpha/factories/_type_mapping.py +29 -0
- alpha/factories/class_factories.py +496 -0
- alpha/factories/default_field_factory.py +50 -0
- alpha/factories/field_iterator.py +188 -0
- alpha/factories/logging_handler_factory.py +86 -0
- alpha/factories/model_class_factory.py +176 -0
- alpha/factories/models/__init__.py +0 -0
- alpha/factories/models/factory_classes.py +20 -0
- alpha/factories/request_factory.py +211 -0
- alpha/factories/response_factory.py +186 -0
- alpha/factories/type_factories.py +204 -0
- alpha/infra/__init__.py +0 -0
- alpha/infra/database/__init__.py +0 -0
- alpha/infra/database/sql_alchemy_database.py +159 -0
- alpha/infra/database/sql_alchemy_view.py +48 -0
- alpha/infra/models/__init__.py +0 -0
- alpha/infra/models/filter_operators.py +98 -0
- alpha/infra/models/json_patch.py +21 -0
- alpha/infra/models/order_by.py +69 -0
- alpha/infra/models/query_clause.py +45 -0
- alpha/infra/models/search_filter.py +586 -0
- alpha/interfaces/__init__.py +0 -0
- alpha/interfaces/attrs_instance.py +10 -0
- alpha/interfaces/dataclass_instance.py +11 -0
- alpha/interfaces/factories.py +102 -0
- alpha/interfaces/openapi_model.py +21 -0
- alpha/interfaces/patchable.py +8 -0
- alpha/interfaces/sql_database.py +36 -0
- alpha/interfaces/sql_mapper.py +23 -0
- alpha/interfaces/sql_repository.py +380 -0
- alpha/interfaces/token_factory.py +56 -0
- alpha/interfaces/unit_of_work.py +53 -0
- alpha/interfaces/updateable.py +7 -0
- alpha/py.typed +0 -0
- alpha/repositories/__init__.py +0 -0
- alpha/repositories/default_sql_repository.py +679 -0
- alpha/repositories/models/__init__.py +0 -0
- alpha/repositories/models/repository_model.py +16 -0
- alpha/services/__init__.py +0 -0
- alpha/services/authentication_service.py +71 -0
- alpha/utils/__init__.py +0 -0
- alpha/utils/_http_codes.py +148 -0
- alpha/utils/is_attrs.py +18 -0
- alpha/utils/logging_configurator.py +133 -0
- alpha/utils/logging_level_checker.py +26 -0
- alpha/utils/response_object.py +26 -0
- alpha/utils/version_check.py +17 -0
- alpha_python-0.1.0.dist-info/METADATA +22 -0
- alpha_python-0.1.0.dist-info/RECORD +62 -0
- alpha_python-0.1.0.dist-info/WHEEL +5 -0
- alpha_python-0.1.0.dist-info/licenses/LICENSE +21 -0
- alpha_python-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Contains Field & FieldIterator class"""
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import attrs
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from alpha.interfaces.attrs_instance import AttrsInstance
|
|
8
|
+
from alpha.interfaces.dataclass_instance import DataclassInstance
|
|
9
|
+
from alpha.utils.is_attrs import is_attrs
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Field:
|
|
13
|
+
"""An object which is used in ModelClassFactory & ClassFactory instances
|
|
14
|
+
to share specific values about class attributes
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
init: bool,
|
|
20
|
+
name: str,
|
|
21
|
+
type_: Any,
|
|
22
|
+
default: Any,
|
|
23
|
+
default_factory: Any = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
"""Initialize a Field object
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
init
|
|
30
|
+
init value
|
|
31
|
+
name
|
|
32
|
+
name value
|
|
33
|
+
type_
|
|
34
|
+
type value. Can be an actual type or the string name of the type
|
|
35
|
+
in which case the value will be evaluated to extract the type
|
|
36
|
+
default
|
|
37
|
+
default value
|
|
38
|
+
default_factory, optional
|
|
39
|
+
default value if present, by default None
|
|
40
|
+
"""
|
|
41
|
+
self.init = init
|
|
42
|
+
self.name = name
|
|
43
|
+
self.default = default
|
|
44
|
+
self.default_factory = default_factory
|
|
45
|
+
|
|
46
|
+
if isinstance(type_, str):
|
|
47
|
+
try:
|
|
48
|
+
self.type = eval(type_)
|
|
49
|
+
except NameError as exc:
|
|
50
|
+
raise NameError(
|
|
51
|
+
f"Unable to evaluate '{type_}' as a type: {exc}.\n"
|
|
52
|
+
"A string value is found as type annotation of a class "
|
|
53
|
+
"attribute. This behavure is probably caused by importing "
|
|
54
|
+
"__future__.annotations in the module of a data class. "
|
|
55
|
+
"This behavure is also described in PEP563 "
|
|
56
|
+
"(https://peps.python.org/pep-0563/). Try not importing "
|
|
57
|
+
"__future__.annotations."
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
self.type = type_
|
|
61
|
+
|
|
62
|
+
def __repr__(self) -> str:
|
|
63
|
+
"""Creates a string representation of the object
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
string representation of the object
|
|
68
|
+
"""
|
|
69
|
+
return (
|
|
70
|
+
"Field("
|
|
71
|
+
f"name={self.name!r},"
|
|
72
|
+
f"type={self.type!r},"
|
|
73
|
+
f"default={self.default!r},"
|
|
74
|
+
f"default_factory={self.default_factory!r},"
|
|
75
|
+
f"init={self.init!r}"
|
|
76
|
+
")"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def from_dataclass(cls, obj: dataclasses.Field[Any]) -> "Field":
|
|
81
|
+
"""Create a Field object from a dataclass Field
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
obj
|
|
86
|
+
dataclass Field
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
Field object
|
|
91
|
+
"""
|
|
92
|
+
return cls(
|
|
93
|
+
init=obj.init,
|
|
94
|
+
name=obj.name,
|
|
95
|
+
type_=obj.type,
|
|
96
|
+
default=obj.default,
|
|
97
|
+
default_factory=obj.default_factory,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def from_attrs(cls, obj: attrs.Attribute) -> "Field": # type: ignore
|
|
102
|
+
"""Create a Field object from a attrs Attribute
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
obj
|
|
107
|
+
attrs Attribute
|
|
108
|
+
|
|
109
|
+
Returns
|
|
110
|
+
-------
|
|
111
|
+
Field object
|
|
112
|
+
"""
|
|
113
|
+
return cls(
|
|
114
|
+
init=obj.init,
|
|
115
|
+
name=obj.name,
|
|
116
|
+
type_=obj.type, # type: ignore
|
|
117
|
+
default=obj.default, # type: ignore
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class FieldIterator:
|
|
122
|
+
"""A collection of Field objects"""
|
|
123
|
+
|
|
124
|
+
def __init__(self, obj: DataclassInstance | AttrsInstance) -> None:
|
|
125
|
+
"""Initialize a FieldIterator by determining the class type of the
|
|
126
|
+
obj argument
|
|
127
|
+
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
obj
|
|
131
|
+
Class of the dataclass or attrs type
|
|
132
|
+
|
|
133
|
+
Raises
|
|
134
|
+
------
|
|
135
|
+
TypeError
|
|
136
|
+
When the obj argument is of an unsupported type
|
|
137
|
+
"""
|
|
138
|
+
self._index = 0
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
if dataclasses.is_dataclass(obj):
|
|
142
|
+
self._fields = [
|
|
143
|
+
Field.from_dataclass(field)
|
|
144
|
+
for field in dataclasses.fields(obj)
|
|
145
|
+
]
|
|
146
|
+
elif is_attrs(obj):
|
|
147
|
+
self._fields = [
|
|
148
|
+
Field.from_attrs(field) # type: ignore
|
|
149
|
+
for field in attrs.fields(obj) # type: ignore
|
|
150
|
+
]
|
|
151
|
+
else:
|
|
152
|
+
raise TypeError(
|
|
153
|
+
"Incorrect object type. Only a dataclass- or "
|
|
154
|
+
"attrs class is supported"
|
|
155
|
+
)
|
|
156
|
+
except NameError as exc:
|
|
157
|
+
raise NameError(
|
|
158
|
+
"An error occured while evaluating an attribute of the "
|
|
159
|
+
f"{obj.__name__} class. {exc}" # type: ignore
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def __iter__(self) -> "FieldIterator":
|
|
163
|
+
"""Iter method
|
|
164
|
+
|
|
165
|
+
Returns
|
|
166
|
+
-------
|
|
167
|
+
This object
|
|
168
|
+
"""
|
|
169
|
+
return self
|
|
170
|
+
|
|
171
|
+
def __next__(self) -> Field:
|
|
172
|
+
"""Next method
|
|
173
|
+
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
Next item in the collection
|
|
177
|
+
|
|
178
|
+
Raises
|
|
179
|
+
------
|
|
180
|
+
StopIteration
|
|
181
|
+
Collection out of range
|
|
182
|
+
"""
|
|
183
|
+
if self._index < len(self._fields):
|
|
184
|
+
item = self._fields[self._index]
|
|
185
|
+
self._index += 1
|
|
186
|
+
return item
|
|
187
|
+
else:
|
|
188
|
+
raise StopIteration
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Contains LoggerHandlerFactory class"""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from alpha import exceptions
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LoggingHandlerFactory:
|
|
9
|
+
"""For creating a valid logging handler from a dict object.
|
|
10
|
+
|
|
11
|
+
Supported handlers:
|
|
12
|
+
- logging.StreamHandler
|
|
13
|
+
- logging.FileHandler
|
|
14
|
+
- logging.handlers.RotatingFileHandler
|
|
15
|
+
- logging.handlers.TimedRotatingFileHandler
|
|
16
|
+
- logging.handlers.WatchedFileHandler
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def parse(cls, handler: dict[str, Any]) -> dict[str, Any]:
|
|
21
|
+
"""Parse a logging handler object.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
handler
|
|
26
|
+
A dictionary with at least the 'type' key. All other keys depend on
|
|
27
|
+
the handler type/class. Use the 'logging.handlers' section of the
|
|
28
|
+
python docs to determine which keys can be used for each handler
|
|
29
|
+
type/class.
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
A handler dictionary which can be used in the handlers section of a
|
|
34
|
+
logging.config.dictConfig compatible dictionary.
|
|
35
|
+
|
|
36
|
+
Raises
|
|
37
|
+
------
|
|
38
|
+
exceptions.LoggingHandlerException
|
|
39
|
+
- When the 'type' value of the handler is missing or None
|
|
40
|
+
- When a 'FileHandler' is missing the 'filename' value
|
|
41
|
+
"""
|
|
42
|
+
type_: str | None = handler.get("type", None)
|
|
43
|
+
if type_ is None:
|
|
44
|
+
raise exceptions.LoggingHandlerException(
|
|
45
|
+
"the logger handler is missing a type attribute"
|
|
46
|
+
)
|
|
47
|
+
*_, class_name = type_.split(".")
|
|
48
|
+
|
|
49
|
+
obj: dict[str, Any] = {
|
|
50
|
+
"class": type_,
|
|
51
|
+
"level": handler.get("level", "DEBUG").upper(),
|
|
52
|
+
"formatter": handler.get("formatter", "default"),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if class_name == "StreamHandler":
|
|
56
|
+
obj.update({"stream": handler.get("stream", "ext://sys.stderr")})
|
|
57
|
+
if "FileHandler" in class_name:
|
|
58
|
+
_filename = handler.get("filename")
|
|
59
|
+
if not _filename:
|
|
60
|
+
raise exceptions.LoggingHandlerException(
|
|
61
|
+
"the logger handler is missing a filename attribute"
|
|
62
|
+
)
|
|
63
|
+
obj.update(
|
|
64
|
+
{
|
|
65
|
+
"filename": _filename,
|
|
66
|
+
"encoding": handler.get("encoding"),
|
|
67
|
+
"delay": handler.get("delay", False),
|
|
68
|
+
"errors": handler.get("errors"),
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
if "RotatingFileHandler" in class_name:
|
|
72
|
+
obj.update({"backupCount": handler.get("backupCount", 0)})
|
|
73
|
+
if class_name == "RotatingFileHandler":
|
|
74
|
+
obj.update({"maxBytes": handler.get("maxBytes", 0)})
|
|
75
|
+
if class_name == "TimedRotatingFileHandler":
|
|
76
|
+
obj.update(
|
|
77
|
+
{
|
|
78
|
+
"when": handler.get("when", "h"),
|
|
79
|
+
"interval": handler.get("interval", 1),
|
|
80
|
+
"utc": handler.get("utc", False),
|
|
81
|
+
"atTime": handler.get("atTime"),
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
obj.update({"mode": handler.get("mode", "a")})
|
|
86
|
+
return obj
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Contains ModelClassFactory class"""
|
|
2
|
+
|
|
3
|
+
import enum
|
|
4
|
+
import types
|
|
5
|
+
import typing
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from alpha import exceptions
|
|
9
|
+
from alpha.factories.class_factories import (
|
|
10
|
+
AnyClassFactory,
|
|
11
|
+
DataclassClassFactory,
|
|
12
|
+
DictClassFactory,
|
|
13
|
+
EnumClassFactory,
|
|
14
|
+
GenericAliasClassFactory,
|
|
15
|
+
IterableClassFactory,
|
|
16
|
+
NativeClassFactory,
|
|
17
|
+
UnionClassFactory,
|
|
18
|
+
)
|
|
19
|
+
from alpha.factories.default_field_factory import DefaultFieldFactory
|
|
20
|
+
from alpha.factories.field_iterator import FieldIterator
|
|
21
|
+
from alpha.factories.models.factory_classes import FactoryClasses
|
|
22
|
+
from alpha.factories.type_factories import (
|
|
23
|
+
GenericTypeFactory,
|
|
24
|
+
DatetimeTypeFactory,
|
|
25
|
+
EnumTypeFactory,
|
|
26
|
+
JsonPatchTypeFactory,
|
|
27
|
+
)
|
|
28
|
+
from alpha.interfaces.attrs_instance import AttrsInstance
|
|
29
|
+
from alpha.interfaces.dataclass_instance import DataclassInstance
|
|
30
|
+
from alpha.interfaces.factories import (
|
|
31
|
+
ClassFactory,
|
|
32
|
+
TypeFactory,
|
|
33
|
+
)
|
|
34
|
+
from alpha.interfaces.openapi_model import OpenAPIModel
|
|
35
|
+
from alpha.utils.version_check import minor_version_gte
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
CLASS_FACTORIES: dict[str, ClassFactory] = {
|
|
39
|
+
"iterable": IterableClassFactory(),
|
|
40
|
+
"dict": DictClassFactory(),
|
|
41
|
+
"dataclass": DataclassClassFactory(),
|
|
42
|
+
"generic_alias": GenericAliasClassFactory(),
|
|
43
|
+
"union": UnionClassFactory(),
|
|
44
|
+
"native": NativeClassFactory(),
|
|
45
|
+
"enum": EnumClassFactory(),
|
|
46
|
+
"any": AnyClassFactory(),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
TYPE_FACTORIES: dict[str, TypeFactory] = {
|
|
51
|
+
"generic": GenericTypeFactory(),
|
|
52
|
+
"datetime": DatetimeTypeFactory(),
|
|
53
|
+
"enum": EnumTypeFactory(),
|
|
54
|
+
"json_patch": JsonPatchTypeFactory(),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
TYPING_CLASSES: dict[object, ClassFactory] = {
|
|
59
|
+
getattr(typing, "_GenericAlias"): CLASS_FACTORIES["generic_alias"],
|
|
60
|
+
getattr(typing, "_UnionGenericAlias"): CLASS_FACTORIES["union"],
|
|
61
|
+
getattr(typing, "_SpecialForm"): CLASS_FACTORIES["any"],
|
|
62
|
+
enum.EnumMeta: CLASS_FACTORIES["enum"],
|
|
63
|
+
type: CLASS_FACTORIES["native"],
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if minor_version_gte(10):
|
|
67
|
+
TYPING_CLASSES.update(
|
|
68
|
+
{
|
|
69
|
+
getattr(types, "UnionType"): CLASS_FACTORIES["union"],
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if minor_version_gte(11):
|
|
74
|
+
TYPING_CLASSES.update(
|
|
75
|
+
{
|
|
76
|
+
getattr(typing, "_AnyMeta"): CLASS_FACTORIES["any"],
|
|
77
|
+
getattr(types, "GenericAlias"): CLASS_FACTORIES["generic_alias"],
|
|
78
|
+
getattr(enum, "EnumType"): CLASS_FACTORIES["enum"],
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
FACTORY_CLASSES = FactoryClasses(
|
|
84
|
+
class_factories=CLASS_FACTORIES,
|
|
85
|
+
type_factories=TYPE_FACTORIES,
|
|
86
|
+
default_factory=DefaultFieldFactory(),
|
|
87
|
+
model_class_factory=None,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ModelClassFactory:
|
|
92
|
+
"""The ModelClassFactory can be used to cast OpenAPIModel objects to
|
|
93
|
+
instances of dataclass or attrs classes
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(
|
|
97
|
+
self,
|
|
98
|
+
typing_classes: dict[object, ClassFactory] = TYPING_CLASSES,
|
|
99
|
+
factory_classes: FactoryClasses = FACTORY_CLASSES,
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Initializing and setting the self.typing_classes class variable
|
|
102
|
+
which contains typing classes and references to the corresponding
|
|
103
|
+
factory classes. The set of typing classes depend on the python minor
|
|
104
|
+
version.
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
typing_classes, optional
|
|
109
|
+
A collection of class types, by default TYPING_CLASSES
|
|
110
|
+
factory_classes, optional
|
|
111
|
+
An instance of FactoryClasses which acts as a toolbox of Factory
|
|
112
|
+
classes, by default FACTORY_CLASSES
|
|
113
|
+
"""
|
|
114
|
+
self.typing_classes = typing_classes
|
|
115
|
+
self.factory_classes = factory_classes
|
|
116
|
+
|
|
117
|
+
if self.factory_classes.model_class_factory is None:
|
|
118
|
+
self.factory_classes.model_class_factory = self
|
|
119
|
+
|
|
120
|
+
def process(
|
|
121
|
+
self,
|
|
122
|
+
obj: OpenAPIModel,
|
|
123
|
+
cls: DataclassInstance | AttrsInstance | Any,
|
|
124
|
+
) -> DataclassInstance | AttrsInstance | None:
|
|
125
|
+
"""Creating a new cls instance from a OpenAPIModel object. This class
|
|
126
|
+
uses a compatibele ClassFactory, from the self.typing_classes
|
|
127
|
+
collection, per cls field to process each value.
|
|
128
|
+
|
|
129
|
+
Parameters
|
|
130
|
+
----------
|
|
131
|
+
obj
|
|
132
|
+
OpenAPIModel object
|
|
133
|
+
cls
|
|
134
|
+
A dataclass or attrs class to create a new instance
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
Dataclass or attrs instance
|
|
139
|
+
|
|
140
|
+
Raises
|
|
141
|
+
------
|
|
142
|
+
exceptions.ModelClassFactoryException
|
|
143
|
+
When cls is not a dataclass or attrs decorated class
|
|
144
|
+
KeyError
|
|
145
|
+
When the class type is not present in self.typing_classes
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
fields = FieldIterator(cls)
|
|
150
|
+
except TypeError:
|
|
151
|
+
|
|
152
|
+
raise exceptions.ModelClassFactoryException(
|
|
153
|
+
"cls argument has to be a dataclass or attrs decorated class"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
params: dict[str, Any] = {}
|
|
157
|
+
|
|
158
|
+
for field in [f for f in fields if f.init]:
|
|
159
|
+
type_class: type | str = "Unknown"
|
|
160
|
+
try:
|
|
161
|
+
type_class = field.type.__class__
|
|
162
|
+
class_factory = self.typing_classes[type_class]
|
|
163
|
+
value: Any = class_factory.process(
|
|
164
|
+
obj=obj,
|
|
165
|
+
field=field,
|
|
166
|
+
factory_classes=self.factory_classes,
|
|
167
|
+
)
|
|
168
|
+
except KeyError as exc:
|
|
169
|
+
raise exceptions.ModelClassFactoryException(
|
|
170
|
+
"The class of this dataclass field is not supported. "
|
|
171
|
+
f"{field.name=}; "
|
|
172
|
+
f"{field.type=}; "
|
|
173
|
+
f"{field.type.__class__=}; "
|
|
174
|
+
) from exc
|
|
175
|
+
params[field.name] = value
|
|
176
|
+
return cls(**params)
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Contains FactoryClasses dataclass"""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from alpha.interfaces.factories import (
|
|
6
|
+
ClassFactory,
|
|
7
|
+
DefaultFactory,
|
|
8
|
+
ModelClassFactoryInstance,
|
|
9
|
+
TypeFactory,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class FactoryClasses:
|
|
15
|
+
"""A FactoryClasses instance acts as a toolbox for Factory classes"""
|
|
16
|
+
|
|
17
|
+
class_factories: dict[str, ClassFactory]
|
|
18
|
+
type_factories: dict[str, TypeFactory]
|
|
19
|
+
default_factory: DefaultFactory
|
|
20
|
+
model_class_factory: ModelClassFactoryInstance | None
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Contains RequestFactory class"""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import types
|
|
5
|
+
import typing
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any, Callable, get_args, get_origin
|
|
8
|
+
|
|
9
|
+
from alpha import exceptions
|
|
10
|
+
from alpha.factories._type_conversion_matrix import TYPE_CONVERSION_MATRIX
|
|
11
|
+
from alpha.factories.model_class_factory import (
|
|
12
|
+
ModelClassFactory,
|
|
13
|
+
)
|
|
14
|
+
from alpha.factories.type_factories import (
|
|
15
|
+
EnumTypeFactory,
|
|
16
|
+
GenericTypeFactory,
|
|
17
|
+
JsonPatchTypeFactory,
|
|
18
|
+
)
|
|
19
|
+
from alpha.infra.models.json_patch import JsonPatch
|
|
20
|
+
from alpha.interfaces.dataclass_instance import DataclassInstance
|
|
21
|
+
from alpha.interfaces.factories import (
|
|
22
|
+
ModelClassFactoryInstance,
|
|
23
|
+
TypeFactory,
|
|
24
|
+
)
|
|
25
|
+
from alpha.interfaces.openapi_model import OpenAPIModel
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class RequestFactory:
|
|
29
|
+
"""This class handles API requests"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
func: Callable[[Any], Any],
|
|
34
|
+
cast_args: bool = True,
|
|
35
|
+
use_model_class_factory: bool = True,
|
|
36
|
+
model_class_factory: type[
|
|
37
|
+
ModelClassFactoryInstance
|
|
38
|
+
] = ModelClassFactory,
|
|
39
|
+
generic_type_factory: type[TypeFactory] = GenericTypeFactory,
|
|
40
|
+
enum_type_factory: type[TypeFactory] = EnumTypeFactory,
|
|
41
|
+
json_patch_type_factory: type[TypeFactory] = JsonPatchTypeFactory,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""Initializing the class with a service function
|
|
44
|
+
The service function will be called when calling
|
|
45
|
+
the cls.__call__ function.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
func
|
|
50
|
+
A callable service function
|
|
51
|
+
cast_args, optional
|
|
52
|
+
Make use of the GenericTypeFactory to cast arguments,
|
|
53
|
+
by default True
|
|
54
|
+
use_model_class_factory, optional
|
|
55
|
+
Make use of the ModelClassFactory to map objects to a dataclass,
|
|
56
|
+
by default True
|
|
57
|
+
model_class_factory, optional
|
|
58
|
+
A ModelClassFactory class
|
|
59
|
+
by default ModelClassFactory
|
|
60
|
+
generic_type_factory, optional
|
|
61
|
+
A TypeFactory that can handle generic types
|
|
62
|
+
by default GenericTypeFactory
|
|
63
|
+
enum_type_factory, optional
|
|
64
|
+
A TypeFactory that can handle enum types
|
|
65
|
+
by default EnumTypeFactory
|
|
66
|
+
json_patch_type_factory, optional
|
|
67
|
+
A TypeFactory that can handle a JsonPatch type
|
|
68
|
+
by default JsonPatchTypeFactory
|
|
69
|
+
"""
|
|
70
|
+
self.func = func
|
|
71
|
+
self.cast_args = cast_args
|
|
72
|
+
self.use_model_class_factory = use_model_class_factory
|
|
73
|
+
self.model_class_factory = model_class_factory
|
|
74
|
+
self.generic_type_factory = generic_type_factory
|
|
75
|
+
self.enum_type_factory = enum_type_factory
|
|
76
|
+
self.json_patch_type_factory = json_patch_type_factory
|
|
77
|
+
|
|
78
|
+
def __call__(self, **kwargs: dict[str, Any]) -> Any:
|
|
79
|
+
"""Calling the service function
|
|
80
|
+
Any keyword argument will be parsed by the self._parse_args function
|
|
81
|
+
Each argument will be mapped on the functions parameter type of the
|
|
82
|
+
corresponding keyword.
|
|
83
|
+
|
|
84
|
+
The keyword arguments need to match the functions parameters.
|
|
85
|
+
Therefore, *args or **kwargs are not allowed as the functions
|
|
86
|
+
parameters.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
kwargs
|
|
91
|
+
Any keyword argument that will be passed to the service function
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
Any
|
|
96
|
+
The returned object of the called service function
|
|
97
|
+
"""
|
|
98
|
+
annotations = self.func.__annotations__
|
|
99
|
+
params = {
|
|
100
|
+
k: self._parse_args(key=k, value=v, cls=annotations[k])
|
|
101
|
+
for k, v in kwargs.items()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return self.func(**params) # type: ignore
|
|
105
|
+
|
|
106
|
+
def _parse_args(
|
|
107
|
+
self,
|
|
108
|
+
key: str,
|
|
109
|
+
value: Any,
|
|
110
|
+
cls: Any | list[Any],
|
|
111
|
+
) -> Any:
|
|
112
|
+
"""Parsing each keyword argument
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
key
|
|
117
|
+
Keyword of the argument
|
|
118
|
+
value
|
|
119
|
+
The argument
|
|
120
|
+
cls
|
|
121
|
+
The class of the corresponding parameter
|
|
122
|
+
|
|
123
|
+
Returns
|
|
124
|
+
-------
|
|
125
|
+
Any
|
|
126
|
+
Mapped objects
|
|
127
|
+
|
|
128
|
+
Raises
|
|
129
|
+
------
|
|
130
|
+
exceptions.ClassMismatchException
|
|
131
|
+
When the source and destination types are not both of an iterable
|
|
132
|
+
type
|
|
133
|
+
"""
|
|
134
|
+
union_types = getattr(typing, "_UnionGenericAlias")
|
|
135
|
+
|
|
136
|
+
if sys.version_info.minor >= 10:
|
|
137
|
+
union_types = (
|
|
138
|
+
getattr(typing, "_UnionGenericAlias") | types.UnionType
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if isinstance(cls, union_types):
|
|
142
|
+
union_args = get_args(cls)
|
|
143
|
+
if value is None and type(None) in union_args:
|
|
144
|
+
return value
|
|
145
|
+
cls = union_args[0]
|
|
146
|
+
|
|
147
|
+
if get_origin(cls) in [list, set, tuple]:
|
|
148
|
+
if type(value) not in [list, set, tuple]:
|
|
149
|
+
raise exceptions.ClassMismatchException(
|
|
150
|
+
"The targeted object type is an iterable but the source "
|
|
151
|
+
"object is not"
|
|
152
|
+
)
|
|
153
|
+
arg = get_args(cls)[0]
|
|
154
|
+
return [
|
|
155
|
+
self._parse_args(key=key, value=item, cls=arg)
|
|
156
|
+
for item in value
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
if isinstance(cls, DataclassInstance):
|
|
160
|
+
return self._to_dataclass(value=value, cls=cls)
|
|
161
|
+
|
|
162
|
+
if isinstance(cls, type(Enum)):
|
|
163
|
+
return self.enum_type_factory().process(
|
|
164
|
+
key=key, value=value, cls=cls
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if cls == JsonPatch:
|
|
168
|
+
return self.json_patch_type_factory().process(
|
|
169
|
+
key=key, value=value, cls=cls
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if cls in TYPE_CONVERSION_MATRIX.keys() and self.cast_args:
|
|
173
|
+
return self.generic_type_factory().process(
|
|
174
|
+
key=key, value=value, cls=cls
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return value
|
|
178
|
+
|
|
179
|
+
def _to_dataclass(
|
|
180
|
+
self, value: OpenAPIModel | Any, cls: DataclassInstance
|
|
181
|
+
) -> Any:
|
|
182
|
+
"""Handling the mapping from an OpenAPI Model instance to a dataclass
|
|
183
|
+
The ModelClassFactory will be used if the cls does not have
|
|
184
|
+
a 'from_dict' method
|
|
185
|
+
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
value
|
|
189
|
+
The argument, which is an OpenAPI Model
|
|
190
|
+
cls
|
|
191
|
+
The class of the corresponding parameter
|
|
192
|
+
|
|
193
|
+
Returns
|
|
194
|
+
-------
|
|
195
|
+
dataclass
|
|
196
|
+
Dataclass object
|
|
197
|
+
|
|
198
|
+
Raises
|
|
199
|
+
------
|
|
200
|
+
TypeError
|
|
201
|
+
The value is not an instance of the OpenAPI (Base)Model
|
|
202
|
+
"""
|
|
203
|
+
if not isinstance(value, OpenAPIModel):
|
|
204
|
+
raise TypeError(f"Unable to map {type(value)} on dataclass model")
|
|
205
|
+
|
|
206
|
+
if hasattr(cls, "from_dict"):
|
|
207
|
+
return getattr(cls, "from_dict")(value.to_dict())
|
|
208
|
+
|
|
209
|
+
if self.use_model_class_factory:
|
|
210
|
+
return self.model_class_factory().process(obj=value, cls=cls)
|
|
211
|
+
return value
|