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,186 @@
|
|
|
1
|
+
"""Contains ResponseFactory class"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import MISSING, is_dataclass
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any, Iterable, get_args, get_origin
|
|
7
|
+
|
|
8
|
+
from alpha import exceptions
|
|
9
|
+
from alpha.encoder import JSONEncoder
|
|
10
|
+
from alpha.interfaces.dataclass_instance import DataclassInstance
|
|
11
|
+
from alpha.interfaces.openapi_model import OpenAPIModel
|
|
12
|
+
from alpha.utils.is_attrs import is_attrs
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ResponseFactory:
|
|
16
|
+
"""Mapping a dataclass instance to an OpenAPI model class"""
|
|
17
|
+
|
|
18
|
+
def process(
|
|
19
|
+
self,
|
|
20
|
+
response: DataclassInstance | Iterable[DataclassInstance],
|
|
21
|
+
cls: OpenAPIModel | Iterable[OpenAPIModel],
|
|
22
|
+
) -> object:
|
|
23
|
+
"""Mapping a dataclass instance or a collection of instances to an
|
|
24
|
+
OpenAPI model
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
response
|
|
29
|
+
DomainModel instance
|
|
30
|
+
cls
|
|
31
|
+
OpenAPI model class or an Iterable
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
OpenAPI model instance or a list of instances
|
|
36
|
+
|
|
37
|
+
Raises
|
|
38
|
+
------
|
|
39
|
+
exceptions.ClassMismatchException
|
|
40
|
+
The targeted object type is a list but the source object is not
|
|
41
|
+
iterable
|
|
42
|
+
Exception
|
|
43
|
+
Encountered unexpected exception
|
|
44
|
+
ValueError
|
|
45
|
+
Unable to instantiate a class of type OpenAPIModel without values.
|
|
46
|
+
Probably because there are required values which cannot be None
|
|
47
|
+
TypeError
|
|
48
|
+
cls value is not a valid OpenAPIModel type
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
cls_origin = get_origin(cls)
|
|
52
|
+
|
|
53
|
+
# When the source instance and target class are of an iterable type
|
|
54
|
+
if cls_origin in [list, tuple, set]:
|
|
55
|
+
if isinstance(response, Iterable):
|
|
56
|
+
arg = get_args(cls)[0]
|
|
57
|
+
return [
|
|
58
|
+
self.process(response=obj, cls=arg) for obj in response
|
|
59
|
+
]
|
|
60
|
+
if cls_origin != get_origin(response):
|
|
61
|
+
raise exceptions.ClassMismatchException(
|
|
62
|
+
"The targeted object type is a list but the source object "
|
|
63
|
+
"is not iterable"
|
|
64
|
+
)
|
|
65
|
+
raise Exception(
|
|
66
|
+
f"Encountered unexpected exception; cls_origin: {cls_origin}, "
|
|
67
|
+
+ f"response: {response}"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
attrs: dict[str, type]
|
|
71
|
+
# Check if the openapi_types variable is set as a class variable
|
|
72
|
+
if len(cls.openapi_types) > 0: # type: ignore
|
|
73
|
+
attrs = getattr(cls, "openapi_types")
|
|
74
|
+
# Else try to instantiate the class and read the openapi_types value
|
|
75
|
+
else:
|
|
76
|
+
try:
|
|
77
|
+
cls_instance: Any = cls() # type: ignore
|
|
78
|
+
except ValueError as e:
|
|
79
|
+
raise ValueError(
|
|
80
|
+
"Unable to instantiate a class of type OpenAPIModel "
|
|
81
|
+
"without values. Probably because there are required "
|
|
82
|
+
f"values which cannot be None: '{e}'. A solution to "
|
|
83
|
+
"this problem is to set the 'openapi_types' attribute "
|
|
84
|
+
"as a class variable instead of setting it in the "
|
|
85
|
+
"__init__ method"
|
|
86
|
+
)
|
|
87
|
+
error_msg = (
|
|
88
|
+
f"'{cls.__name__}' is not a " # type: ignore
|
|
89
|
+
"valid OpenAPIModel type"
|
|
90
|
+
)
|
|
91
|
+
if not hasattr(cls_instance, "openapi_types"):
|
|
92
|
+
raise TypeError(error_msg)
|
|
93
|
+
attrs = getattr(cls_instance, "openapi_types")
|
|
94
|
+
|
|
95
|
+
# Process attrs through _attr_factory
|
|
96
|
+
params = {
|
|
97
|
+
attr_name: self._attr_factory(
|
|
98
|
+
attr_name=attr_name, attr_type=attr_type, response=response
|
|
99
|
+
)
|
|
100
|
+
for attr_name, attr_type in attrs.items()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return cls(**params) # type: ignore
|
|
104
|
+
|
|
105
|
+
def _attr_factory(
|
|
106
|
+
self, attr_name: str, attr_type: type, response: object
|
|
107
|
+
) -> Any:
|
|
108
|
+
"""Handles the attributes of an object by returning the response value
|
|
109
|
+
in the correct form
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
attr_name
|
|
114
|
+
The name of the attribute
|
|
115
|
+
attr_type
|
|
116
|
+
The type of the attribute. This can be
|
|
117
|
+
response
|
|
118
|
+
A dataclass instance or an list of instances
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
Any
|
|
123
|
+
The value from the response object which corresponds to the
|
|
124
|
+
attr_name
|
|
125
|
+
"""
|
|
126
|
+
response_value = self._attr_lookup(obj=response, attr_name=attr_name)
|
|
127
|
+
|
|
128
|
+
if isinstance(response_value, Enum):
|
|
129
|
+
return response_value.name
|
|
130
|
+
|
|
131
|
+
# These if statements are needed to achieve a certain behavior
|
|
132
|
+
if isinstance(attr_type, OpenAPIModel) or (
|
|
133
|
+
get_origin(attr_type) is list
|
|
134
|
+
):
|
|
135
|
+
if get_origin(attr_type) is list:
|
|
136
|
+
first_item = get_args(attr_type)[0]
|
|
137
|
+
if not isinstance(first_item, OpenAPIModel):
|
|
138
|
+
return response_value
|
|
139
|
+
return self.process(response=response_value, cls=attr_type) # type: ignore
|
|
140
|
+
return response_value
|
|
141
|
+
|
|
142
|
+
def _attr_lookup(self, obj: object, attr_name: str) -> Any:
|
|
143
|
+
"""Lookup the attributes value from an object. The object can be of a
|
|
144
|
+
random type, including a dataclass or a dictionary.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
obj
|
|
149
|
+
The source object
|
|
150
|
+
attr_name
|
|
151
|
+
Attribute name
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
Any value which corresponds to the attributes name
|
|
156
|
+
|
|
157
|
+
Raises
|
|
158
|
+
------
|
|
159
|
+
exceptions.MissingAttributeError
|
|
160
|
+
Raises in case the attribute is not found
|
|
161
|
+
"""
|
|
162
|
+
if hasattr(obj, attr_name):
|
|
163
|
+
return getattr(obj, attr_name, None)
|
|
164
|
+
|
|
165
|
+
if is_dataclass(obj) or is_attrs(obj):
|
|
166
|
+
attrs = getattr(obj, "__match_args__", [])
|
|
167
|
+
|
|
168
|
+
for attr in attrs:
|
|
169
|
+
obj_attr = getattr(obj, attr, MISSING)
|
|
170
|
+
|
|
171
|
+
if attr == attr_name:
|
|
172
|
+
return obj_attr
|
|
173
|
+
|
|
174
|
+
# Lookup nested attribute
|
|
175
|
+
if hasattr(obj_attr, attr_name):
|
|
176
|
+
return getattr(obj_attr, attr_name)
|
|
177
|
+
|
|
178
|
+
if isinstance(obj, dict):
|
|
179
|
+
if attr_name in obj.keys():
|
|
180
|
+
value: Any = obj[attr_name]
|
|
181
|
+
return value
|
|
182
|
+
|
|
183
|
+
raise exceptions.MissingAttributeError(
|
|
184
|
+
f"'{attr_name}' can not be found in the response object:"
|
|
185
|
+
+ f"{json.dumps(obj, cls=JSONEncoder)}"
|
|
186
|
+
)
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Contains these TypeFactory classes:
|
|
2
|
+
- GenericTypeFactory
|
|
3
|
+
- DatetimeTypeFactory
|
|
4
|
+
- EnumTypeFactory
|
|
5
|
+
- JsonPatchTypeFactory
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import datetime
|
|
9
|
+
from typing import Any, Iterable
|
|
10
|
+
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
from alpha import exceptions
|
|
14
|
+
from alpha.factories._type_conversion_matrix import TYPE_CONVERSION_MATRIX
|
|
15
|
+
from alpha.interfaces.openapi_model import OpenAPIModel
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GenericTypeFactory:
|
|
19
|
+
"""An implementation of TypeFactory which is able if process objects of
|
|
20
|
+
type 'type'
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def process(
|
|
24
|
+
self, key: str, value: Any, cls: type, **kwargs: dict[str, Any]
|
|
25
|
+
) -> Any:
|
|
26
|
+
"""Processing generic object types
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
key
|
|
31
|
+
Keyword of the argument. Used for logging purposes
|
|
32
|
+
value
|
|
33
|
+
The value object
|
|
34
|
+
cls
|
|
35
|
+
The targeted class
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
An instance of the targeted class
|
|
40
|
+
|
|
41
|
+
Raises
|
|
42
|
+
------
|
|
43
|
+
exceptions.ObjectConversionNotSupported
|
|
44
|
+
When conversion of the value is not supported
|
|
45
|
+
exceptions.ObjectConversionError
|
|
46
|
+
When an error occured when trying to convert the value to a cls
|
|
47
|
+
instance
|
|
48
|
+
exceptions.ObjectConversionNotAllowed
|
|
49
|
+
When conversion of the value is not allowed
|
|
50
|
+
"""
|
|
51
|
+
from_type: Any = type(value)
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
allowed: bool = TYPE_CONVERSION_MATRIX[from_type][cls]
|
|
55
|
+
except KeyError as exc:
|
|
56
|
+
raise exceptions.ObjectConversionNotSupported(
|
|
57
|
+
"Unable to convert an object, because the source "
|
|
58
|
+
f" ({from_type.__name__}) or target ({cls.__name__}) type "
|
|
59
|
+
"is not supported"
|
|
60
|
+
) from exc
|
|
61
|
+
|
|
62
|
+
if allowed:
|
|
63
|
+
try:
|
|
64
|
+
return cls(value)
|
|
65
|
+
except ValueError as exc:
|
|
66
|
+
raise exceptions.ObjectConversionError(
|
|
67
|
+
f"Unable to convert a(n) {from_type.__name__} "
|
|
68
|
+
f"to a(n) {cls.__name__}" # type: ignore
|
|
69
|
+
) from exc
|
|
70
|
+
raise exceptions.ObjectConversionNotAllowed(
|
|
71
|
+
f"Converting '{key}' which is a(n) {from_type.__name__} class"
|
|
72
|
+
f"to a(n) {str(cls.__name__)} is not allowed" # type: ignore
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class DatetimeTypeFactory:
|
|
77
|
+
"""An implementation of TypeFactory which is able if process objects of
|
|
78
|
+
type 'datetime'
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def process(
|
|
82
|
+
self,
|
|
83
|
+
key: str,
|
|
84
|
+
value: datetime.datetime | datetime.date | str,
|
|
85
|
+
cls: type,
|
|
86
|
+
**kwargs: dict[str, Any],
|
|
87
|
+
) -> datetime.datetime | datetime.date:
|
|
88
|
+
"""Processing datetime object types
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
key
|
|
93
|
+
Keyword of the argument. Not used in this class
|
|
94
|
+
value
|
|
95
|
+
A datetime instance or datetime formatted string
|
|
96
|
+
cls
|
|
97
|
+
A datetime class
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
A datetime instance
|
|
102
|
+
"""
|
|
103
|
+
if isinstance(value, cls):
|
|
104
|
+
return value # type: ignore
|
|
105
|
+
|
|
106
|
+
day_first = True if kwargs.get("day_first", False) else False
|
|
107
|
+
|
|
108
|
+
date_time = pd.to_datetime( # type: ignore
|
|
109
|
+
value, dayfirst=day_first
|
|
110
|
+
).to_pydatetime()
|
|
111
|
+
if cls == datetime.date:
|
|
112
|
+
return date_time.date()
|
|
113
|
+
return date_time
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class EnumTypeFactory:
|
|
117
|
+
"""An implementation of TypeFactory which is able if process objects of
|
|
118
|
+
type 'Enum'
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def process(
|
|
122
|
+
self, key: str, value: Any, cls: Any, **kwargs: dict[str, Any]
|
|
123
|
+
) -> Any:
|
|
124
|
+
"""Creates Enum objects from either a Enum name or value
|
|
125
|
+
|
|
126
|
+
Parameters
|
|
127
|
+
----------
|
|
128
|
+
key
|
|
129
|
+
Keyword of the argument
|
|
130
|
+
value
|
|
131
|
+
The argument
|
|
132
|
+
cls
|
|
133
|
+
The class of the corresponding parameter
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
Enum
|
|
138
|
+
An Enum object
|
|
139
|
+
|
|
140
|
+
Raises
|
|
141
|
+
------
|
|
142
|
+
AttributeError
|
|
143
|
+
Not a valid Enum name or value
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
if not value:
|
|
147
|
+
try:
|
|
148
|
+
return getattr(cls, "NONE")
|
|
149
|
+
except AttributeError:
|
|
150
|
+
return None
|
|
151
|
+
if isinstance(value, str):
|
|
152
|
+
return getattr(cls, str(value).upper())
|
|
153
|
+
return cls(value) # type: ignore
|
|
154
|
+
except AttributeError as exc:
|
|
155
|
+
raise AttributeError(
|
|
156
|
+
f"{value} is not a valid Enum name for the {key} attribute"
|
|
157
|
+
) from exc
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class JsonPatchTypeFactory:
|
|
161
|
+
"""An implementation of TypeFactory which is able if process objects of
|
|
162
|
+
type 'JsonPatch'
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
def process(
|
|
166
|
+
self, key: str, value: Any, cls: Any, **kwargs: dict[str, Any]
|
|
167
|
+
) -> Any:
|
|
168
|
+
"""Processing JsonPatch object types
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
key
|
|
173
|
+
Keyword of the argument. Used for logging purposes
|
|
174
|
+
value
|
|
175
|
+
An iterable containing OpenAPIModel with 'op', 'path' and 'value'
|
|
176
|
+
attributes
|
|
177
|
+
cls
|
|
178
|
+
The targeted class, which should always be JsonPatch
|
|
179
|
+
|
|
180
|
+
Returns
|
|
181
|
+
-------
|
|
182
|
+
An instance of the targeted class
|
|
183
|
+
|
|
184
|
+
Raises
|
|
185
|
+
------
|
|
186
|
+
AttributeError
|
|
187
|
+
- When value is not an iterable
|
|
188
|
+
- When value is an empty iterable
|
|
189
|
+
- When value does not contain OpenAPIModel instances
|
|
190
|
+
"""
|
|
191
|
+
if not isinstance(value, Iterable):
|
|
192
|
+
raise AttributeError(f"The {key} attribute has to be an iterable")
|
|
193
|
+
if len(value) == 0: # type: ignore
|
|
194
|
+
raise AttributeError(
|
|
195
|
+
f"The {key} attribute cannot be an empty iterable"
|
|
196
|
+
)
|
|
197
|
+
if isinstance(value[0], OpenAPIModel): # type: ignore
|
|
198
|
+
patches: list[OpenAPIModel] = value # type: ignore
|
|
199
|
+
return cls([patch.to_dict() for patch in patches]) # type: ignore
|
|
200
|
+
else:
|
|
201
|
+
raise AttributeError(
|
|
202
|
+
f"The {key} attribute has to be an iterable containing "
|
|
203
|
+
"OpenAPIModel objects of a JsonPatch type"
|
|
204
|
+
)
|
alpha/infra/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""_summary_"""
|
|
2
|
+
|
|
3
|
+
import sqlalchemy as sa
|
|
4
|
+
from sqlalchemy.engine import Engine
|
|
5
|
+
from sqlalchemy.orm import (
|
|
6
|
+
scoped_session,
|
|
7
|
+
sessionmaker,
|
|
8
|
+
)
|
|
9
|
+
from sqlalchemy.orm.session import Session
|
|
10
|
+
|
|
11
|
+
from alpha.interfaces.sql_mapper import SqlMapper
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SqlAlchemyDatabase:
|
|
15
|
+
"""_summary_"""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
host: str = "",
|
|
20
|
+
port: int | None = None,
|
|
21
|
+
username: str = "",
|
|
22
|
+
password: str = "",
|
|
23
|
+
db_name: str = "",
|
|
24
|
+
db_type: str = "postgresql",
|
|
25
|
+
conn_str: str | None = None,
|
|
26
|
+
schema_name: str = "public",
|
|
27
|
+
create_schema: bool = True,
|
|
28
|
+
create_tables: bool = True,
|
|
29
|
+
pool_pre_ping: bool = False,
|
|
30
|
+
mapper: SqlMapper | None = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""_summary_
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
host : str, optional
|
|
37
|
+
_description_, by default ""
|
|
38
|
+
port : int | None, optional
|
|
39
|
+
_description_, by default None
|
|
40
|
+
username : str, optional
|
|
41
|
+
_description_, by default ""
|
|
42
|
+
password : str, optional
|
|
43
|
+
_description_, by default ""
|
|
44
|
+
db_name : str, optional
|
|
45
|
+
_description_, by default ""
|
|
46
|
+
db_type : str, optional
|
|
47
|
+
_description_, by default "postgresql"
|
|
48
|
+
conn_str : str | None, optional
|
|
49
|
+
_description_, by default None
|
|
50
|
+
schema_name : str, optional
|
|
51
|
+
_description_, by default "public"
|
|
52
|
+
create_schema : bool, optional
|
|
53
|
+
_description_, by default True
|
|
54
|
+
create_tables : bool, optional
|
|
55
|
+
_description_, by default True
|
|
56
|
+
mapper : interfaces.SqlMapper | None, optional
|
|
57
|
+
_description_, by default None
|
|
58
|
+
"""
|
|
59
|
+
self._host = host
|
|
60
|
+
self._port = port
|
|
61
|
+
self._username = username
|
|
62
|
+
self._password = password
|
|
63
|
+
self._db_name = db_name
|
|
64
|
+
self._db_type = db_type
|
|
65
|
+
self._schema_name = schema_name
|
|
66
|
+
self._mapper = mapper
|
|
67
|
+
|
|
68
|
+
if conn_str is None:
|
|
69
|
+
conn_str = (
|
|
70
|
+
f"{self._db_type}://{self._username}:"
|
|
71
|
+
+ f"{self._password}@{self._host}:{self._port}/{self._db_name}"
|
|
72
|
+
)
|
|
73
|
+
self._connection_string = conn_str
|
|
74
|
+
|
|
75
|
+
self._engine = sa.create_engine(
|
|
76
|
+
self._connection_string, pool_pre_ping=pool_pre_ping
|
|
77
|
+
)
|
|
78
|
+
self._session_factory = scoped_session(
|
|
79
|
+
sessionmaker(
|
|
80
|
+
bind=self._engine, autocommit=False, expire_on_commit=False
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if self._mapper:
|
|
85
|
+
if not self._mapper.started:
|
|
86
|
+
self._mapper.start_mapping()
|
|
87
|
+
if create_tables:
|
|
88
|
+
self.create_tables(self._mapper.metadata)
|
|
89
|
+
|
|
90
|
+
if hasattr(self._engine.dialect, "has_schema") & create_schema:
|
|
91
|
+
self._create_schema(self._engine, self._schema_name)
|
|
92
|
+
|
|
93
|
+
def get_session(self) -> Session:
|
|
94
|
+
"""_summary_
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
Session
|
|
99
|
+
_description_
|
|
100
|
+
"""
|
|
101
|
+
return self._session_factory()
|
|
102
|
+
|
|
103
|
+
def engine(self) -> Engine:
|
|
104
|
+
"""_summary_
|
|
105
|
+
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
Engine
|
|
109
|
+
_description_
|
|
110
|
+
"""
|
|
111
|
+
return self._engine
|
|
112
|
+
|
|
113
|
+
def create_tables(
|
|
114
|
+
self, metadata: sa.MetaData, tables: list[sa.Table] | None = None
|
|
115
|
+
) -> None:
|
|
116
|
+
"""_summary_
|
|
117
|
+
|
|
118
|
+
Parameters
|
|
119
|
+
----------
|
|
120
|
+
metadata : sa.MetaData
|
|
121
|
+
_description_
|
|
122
|
+
tables : list[sa.Table] | None, optional
|
|
123
|
+
_description_, by default None
|
|
124
|
+
"""
|
|
125
|
+
metadata.create_all(self._engine, tables=tables)
|
|
126
|
+
|
|
127
|
+
def drop_tables(
|
|
128
|
+
self, metadata: sa.MetaData, tables: list[sa.Table] | None = None
|
|
129
|
+
) -> None:
|
|
130
|
+
"""_summary_
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
metadata : sa.MetaData
|
|
135
|
+
_description_
|
|
136
|
+
tables : list[sa.Table] | None, optional
|
|
137
|
+
_description_, by default None
|
|
138
|
+
"""
|
|
139
|
+
metadata.drop_all(self._engine, tables=tables)
|
|
140
|
+
|
|
141
|
+
def _create_schema(self, engine: Engine, schema_name: str) -> None:
|
|
142
|
+
"""_summary_
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
engine : Engine
|
|
147
|
+
_description_
|
|
148
|
+
schema_name : str
|
|
149
|
+
_description_
|
|
150
|
+
"""
|
|
151
|
+
major, *_ = sa.__version__.split(".")
|
|
152
|
+
|
|
153
|
+
if int(major) < 2:
|
|
154
|
+
if not engine.dialect.has_schema(engine, schema_name): # type: ignore
|
|
155
|
+
getattr(engine, "execute")(sa.schema.CreateSchema(schema_name))
|
|
156
|
+
else:
|
|
157
|
+
with engine.begin() as connection:
|
|
158
|
+
if not sa.inspect(engine).has_schema(schema_name):
|
|
159
|
+
connection.execute(sa.schema.CreateSchema(schema_name))
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from sqlalchemy.schema import DDLElement
|
|
2
|
+
from sqlalchemy import MetaData, event, inspect, table
|
|
3
|
+
from sqlalchemy.ext import compiler
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CreateView(DDLElement):
|
|
7
|
+
def __init__(self, name, selectable):
|
|
8
|
+
self.name = name
|
|
9
|
+
self.selectable = selectable
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DropView(DDLElement):
|
|
13
|
+
def __init__(self, name):
|
|
14
|
+
self.name = name
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@compiler.compiles(CreateView)
|
|
18
|
+
def _create_view(element, compiler, **kw):
|
|
19
|
+
return "CREATE OR REPLACE VIEW %s AS %s" % (
|
|
20
|
+
element.name,
|
|
21
|
+
compiler.sql_compiler.process(element.selectable, literal_binds=True),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@compiler.compiles(DropView)
|
|
26
|
+
def _drop_view(element, compiler, **kw):
|
|
27
|
+
return "DROP VIEW %s" % (element.name)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class View:
|
|
31
|
+
def __init__(self, name: str, metadata: MetaData, selectable):
|
|
32
|
+
t = table(name)
|
|
33
|
+
|
|
34
|
+
t._columns._populate_separate_keys(
|
|
35
|
+
col._make_proxy(t) for col in selectable.selected_columns
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
event.listen(metadata, "after_create", CreateView(name, selectable))
|
|
39
|
+
event.listen(
|
|
40
|
+
metadata, "before_drop", DropView(name).execute_if(callable_=self.view_exists) # type: ignore
|
|
41
|
+
)
|
|
42
|
+
return t
|
|
43
|
+
|
|
44
|
+
def view_exists(self, ddl, target, connection, **kw):
|
|
45
|
+
return ddl.name in inspect(connection).get_view_names()
|
|
46
|
+
|
|
47
|
+
def view_doesnt_exist(self, ddl, target, connection, **kw):
|
|
48
|
+
return not self.view_exists(ddl, target, connection, **kw)
|
|
File without changes
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Contains these FilterOperator classes:
|
|
2
|
+
- And
|
|
3
|
+
- Or
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Callable, Iterable
|
|
7
|
+
|
|
8
|
+
from sqlalchemy.orm import Query
|
|
9
|
+
from sqlalchemy.sql.expression import ColumnElement, and_, or_
|
|
10
|
+
|
|
11
|
+
from alpha.infra.models.search_filter import SearchFilter
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FilterOperator:
|
|
15
|
+
"""Base class for filter operators which can be used to specify the
|
|
16
|
+
search query
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, *search_filters: SearchFilter):
|
|
20
|
+
"""Instantiate the filter operator by storing
|
|
21
|
+
the search filter objects
|
|
22
|
+
"""
|
|
23
|
+
self.search_filters: Iterable[SearchFilter | FilterOperator] = (
|
|
24
|
+
search_filters
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def filter_operator(
|
|
29
|
+
self,
|
|
30
|
+
) -> Callable[[ColumnElement[bool]], ColumnElement[bool]]:
|
|
31
|
+
"""Returns a filter operator
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
filter operator
|
|
36
|
+
|
|
37
|
+
Raises
|
|
38
|
+
------
|
|
39
|
+
NotImplementedError
|
|
40
|
+
When called directly
|
|
41
|
+
"""
|
|
42
|
+
raise NotImplementedError(
|
|
43
|
+
"The FilterOperator class cannot be used directly. "
|
|
44
|
+
"Use the And or Or classes instead."
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def filter(self, query: Query[Any]) -> Query[Any]:
|
|
48
|
+
"""Applies the search filters on the query by using the filter operator
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
query
|
|
53
|
+
Query object
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
Query object
|
|
58
|
+
"""
|
|
59
|
+
filters = [f.filter_statement for f in self.search_filters] # type: ignore
|
|
60
|
+
return query.filter(self.filter_operator(*filters)) # type: ignore
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class And(FilterOperator):
|
|
64
|
+
"""FilterOperator which can be used to explicitly specify an 'and'
|
|
65
|
+
statement to apply behaviore of SearchFilter objects which is comparable
|
|
66
|
+
to AND in SQL.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def filter_operator(
|
|
71
|
+
self,
|
|
72
|
+
) -> Callable[[ColumnElement[bool]], ColumnElement[bool]]:
|
|
73
|
+
"""Returns the 'and' filter operator
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
'and' filter operator
|
|
78
|
+
"""
|
|
79
|
+
return and_
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Or(FilterOperator):
|
|
83
|
+
"""FilterOperator which can be used to explicitly specify an 'or'
|
|
84
|
+
statement to apply behaviore of SearchFilter objects which is comparable
|
|
85
|
+
to OR in SQL.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def filter_operator(
|
|
90
|
+
self,
|
|
91
|
+
) -> Callable[[ColumnElement[bool]], ColumnElement[bool]]:
|
|
92
|
+
"""Returns the 'or' filter operator
|
|
93
|
+
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
'or' filter operator
|
|
97
|
+
"""
|
|
98
|
+
return or_
|