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,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
+ )
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_