jararaca 0.2.21__py3-none-any.whl → 0.2.23__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.
Potentially problematic release.
This version of jararaca might be problematic. Click here for more details.
- jararaca/__init__.py +14 -1
- jararaca/persistence/sort_filter.py +189 -0
- {jararaca-0.2.21.dist-info → jararaca-0.2.23.dist-info}/METADATA +1 -1
- {jararaca-0.2.21.dist-info → jararaca-0.2.23.dist-info}/RECORD +8 -7
- pyproject.toml +1 -1
- {jararaca-0.2.21.dist-info → jararaca-0.2.23.dist-info}/LICENSE +0 -0
- {jararaca-0.2.21.dist-info → jararaca-0.2.23.dist-info}/WHEEL +0 -0
- {jararaca-0.2.21.dist-info → jararaca-0.2.23.dist-info}/entry_points.txt +0 -0
jararaca/__init__.py
CHANGED
|
@@ -2,10 +2,15 @@ from importlib import import_module
|
|
|
2
2
|
from typing import TYPE_CHECKING
|
|
3
3
|
|
|
4
4
|
if TYPE_CHECKING:
|
|
5
|
-
|
|
6
5
|
from jararaca.microservice import AppContext, AppInterceptor
|
|
7
6
|
from jararaca.observability.interceptor import ObservabilityInterceptor
|
|
8
7
|
from jararaca.observability.providers.otel import OtelObservabilityProvider
|
|
8
|
+
from jararaca.persistence.sort_filter import (
|
|
9
|
+
FILTER_SORT_ENTITY_ATTR_MAP,
|
|
10
|
+
FilterModel,
|
|
11
|
+
SortFilterRunner,
|
|
12
|
+
SortModel,
|
|
13
|
+
)
|
|
9
14
|
from jararaca.presentation.hooks import (
|
|
10
15
|
raises_200_on,
|
|
11
16
|
raises_400_on,
|
|
@@ -99,6 +104,10 @@ if TYPE_CHECKING:
|
|
|
99
104
|
from .tools.app_config.interceptor import AppConfigurationInterceptor
|
|
100
105
|
|
|
101
106
|
__all__ = [
|
|
107
|
+
"FILTER_SORT_ENTITY_ATTR_MAP",
|
|
108
|
+
"FilterModel",
|
|
109
|
+
"SortFilterRunner",
|
|
110
|
+
"SortModel",
|
|
102
111
|
"RegisterWebSocketMessage",
|
|
103
112
|
"TracedRequestMiddleware",
|
|
104
113
|
"raises_http_exception_on",
|
|
@@ -186,6 +195,10 @@ if TYPE_CHECKING:
|
|
|
186
195
|
__SPEC_PARENT__: str = __spec__.parent # type: ignore
|
|
187
196
|
# A mapping of {<member name>: (package, <module name>)} defining dynamic imports
|
|
188
197
|
_dynamic_imports: "dict[str, tuple[str, str, str | None]]" = {
|
|
198
|
+
"FILTER_SORT_ENTITY_ATTR_MAP": (__SPEC_PARENT__, "persistence.sort_filter", None),
|
|
199
|
+
"FilterModel": (__SPEC_PARENT__, "persistence.sort_filter", None),
|
|
200
|
+
"SortFilterRunner": (__SPEC_PARENT__, "persistence.sort_filter", None),
|
|
201
|
+
"SortModel": (__SPEC_PARENT__, "persistence.sort_filter", None),
|
|
189
202
|
"RegisterWebSocketMessage": (
|
|
190
203
|
__SPEC_PARENT__,
|
|
191
204
|
"presentation.websocket.decorators",
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
from datetime import date, datetime
|
|
2
|
+
from functools import reduce
|
|
3
|
+
from typing import Literal, Tuple, TypeVar
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from sqlalchemy import Select
|
|
8
|
+
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
|
9
|
+
|
|
10
|
+
from jararaca import BaseEntity
|
|
11
|
+
|
|
12
|
+
FILTER_SORT_ENTITY_ATTR_MAP = dict[
|
|
13
|
+
str, InstrumentedAttribute[str | int | datetime | date | UUID]
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
STRING_OPERATORS = Literal[
|
|
18
|
+
"contains",
|
|
19
|
+
"doesNotContain",
|
|
20
|
+
"equals",
|
|
21
|
+
"doesNotEqual",
|
|
22
|
+
"startsWith",
|
|
23
|
+
"endsWith",
|
|
24
|
+
"isEmpty",
|
|
25
|
+
"isNotEmpty",
|
|
26
|
+
"isAnyOf",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
DATE_DATETIME_OPERATORS = Literal[
|
|
31
|
+
"is", "not", "after", "onOrAfter", "before", "onOrBefore", "isEmpty", "isNotEmpty"
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
BOOLEAN_OPERATORS = Literal["is"]
|
|
35
|
+
|
|
36
|
+
NUMBER_OPERATORS = Literal[
|
|
37
|
+
"=", "!=", ">", "<", ">=", "<=", "isEmpty", "isNotEmpty", "isAnyOf"
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SortModel(BaseModel):
|
|
42
|
+
field: str
|
|
43
|
+
direction: Literal["asc", "desc"]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class FilterModel(BaseModel):
|
|
47
|
+
field: str
|
|
48
|
+
operator: Literal[
|
|
49
|
+
STRING_OPERATORS, DATE_DATETIME_OPERATORS, BOOLEAN_OPERATORS, NUMBER_OPERATORS
|
|
50
|
+
]
|
|
51
|
+
value: str | list[str] = ""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
INHERITS_BASE_ENTITY = TypeVar("INHERITS_BASE_ENTITY", bound=BaseEntity)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class SortFilterRunner:
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
allowed_filters: FILTER_SORT_ENTITY_ATTR_MAP,
|
|
61
|
+
allowed_sorts: FILTER_SORT_ENTITY_ATTR_MAP,
|
|
62
|
+
):
|
|
63
|
+
self.allowed_filters = allowed_filters
|
|
64
|
+
self.allowed_sorts = allowed_sorts
|
|
65
|
+
|
|
66
|
+
def create_query_for_filter(
|
|
67
|
+
self, query: Select[Tuple[INHERITS_BASE_ENTITY]], filter: FilterModel
|
|
68
|
+
) -> Select[Tuple[INHERITS_BASE_ENTITY]]:
|
|
69
|
+
field = self.allowed_filters.get(filter.field)
|
|
70
|
+
if field is None:
|
|
71
|
+
raise ValueError(f"Unsupported field: {filter.field}")
|
|
72
|
+
field_type = field.property.columns[0].type.python_type
|
|
73
|
+
|
|
74
|
+
if field_type is str:
|
|
75
|
+
match filter.operator:
|
|
76
|
+
case "contains":
|
|
77
|
+
return query.filter(field.contains(filter.value))
|
|
78
|
+
case "doesNotContain":
|
|
79
|
+
return query.filter(~field.contains(filter.value))
|
|
80
|
+
case "equals":
|
|
81
|
+
return query.filter(field == filter.value)
|
|
82
|
+
case "doesNotEqual":
|
|
83
|
+
return query.filter(field != filter.value)
|
|
84
|
+
case "startsWith":
|
|
85
|
+
return query.filter(field.startswith(filter.value))
|
|
86
|
+
case "endsWith":
|
|
87
|
+
return query.filter(field.endswith(filter.value))
|
|
88
|
+
case "isEmpty":
|
|
89
|
+
return query.filter(field == "")
|
|
90
|
+
case "isNotEmpty":
|
|
91
|
+
return query.filter(field != "")
|
|
92
|
+
case "isAnyOf":
|
|
93
|
+
return query.filter(field.in_(filter.value))
|
|
94
|
+
case _:
|
|
95
|
+
raise ValueError(f"Unsupported string operator: {filter.operator}")
|
|
96
|
+
elif field_type in [date, datetime]:
|
|
97
|
+
match filter.operator:
|
|
98
|
+
case "isEmpty":
|
|
99
|
+
return query.filter(field == None) # noqa
|
|
100
|
+
case "isNotEmpty":
|
|
101
|
+
return query.filter(field != None) # noqa
|
|
102
|
+
|
|
103
|
+
__value = (
|
|
104
|
+
filter.value[0] if isinstance(filter.value, list) else filter.value
|
|
105
|
+
)
|
|
106
|
+
if field_type is date:
|
|
107
|
+
value = datetime.strptime(__value, "%Y-%m-%d").date()
|
|
108
|
+
else:
|
|
109
|
+
value = datetime.strptime(__value, "%Y-%m-%dT%H:%M:%S.%fZ")
|
|
110
|
+
match filter.operator:
|
|
111
|
+
case "is":
|
|
112
|
+
return query.filter(field == value)
|
|
113
|
+
case "not":
|
|
114
|
+
return query.filter(field != value)
|
|
115
|
+
case "after":
|
|
116
|
+
return query.filter(field > value)
|
|
117
|
+
case "onOrAfter":
|
|
118
|
+
return query.filter(field >= value)
|
|
119
|
+
case "before":
|
|
120
|
+
return query.filter(field < value)
|
|
121
|
+
case "onOrBefore":
|
|
122
|
+
return query.filter(field <= value)
|
|
123
|
+
|
|
124
|
+
case _:
|
|
125
|
+
raise ValueError(
|
|
126
|
+
f"Unsupported data/datetime operator: {filter.operator}"
|
|
127
|
+
)
|
|
128
|
+
elif field_type is bool:
|
|
129
|
+
__value = (
|
|
130
|
+
filter.value[0] if isinstance(filter.value, list) else filter.value
|
|
131
|
+
)
|
|
132
|
+
match filter.operator:
|
|
133
|
+
case "is":
|
|
134
|
+
if __value == "":
|
|
135
|
+
return query.filter(field.is_not(None))
|
|
136
|
+
return query.filter(field == (__value == "true"))
|
|
137
|
+
case _:
|
|
138
|
+
raise ValueError(f"Unsupported bool operator: {filter.operator}")
|
|
139
|
+
elif field_type is int:
|
|
140
|
+
match filter.operator:
|
|
141
|
+
case "=":
|
|
142
|
+
return query.filter(field == filter.value)
|
|
143
|
+
case "!=":
|
|
144
|
+
return query.filter(field != filter.value)
|
|
145
|
+
case ">":
|
|
146
|
+
return query.filter(field > filter.value)
|
|
147
|
+
case "<":
|
|
148
|
+
return query.filter(field < filter.value)
|
|
149
|
+
case ">=":
|
|
150
|
+
return query.filter(field >= filter.value)
|
|
151
|
+
case "<=":
|
|
152
|
+
return query.filter(field <= filter.value)
|
|
153
|
+
case "isEmpty":
|
|
154
|
+
return query.filter(field == None) # noqa
|
|
155
|
+
case "isNotEmpty":
|
|
156
|
+
return query.filter(field != None) # noqa
|
|
157
|
+
case "isAnyOf":
|
|
158
|
+
return query.filter(field.in_(filter.value))
|
|
159
|
+
case _:
|
|
160
|
+
raise ValueError(f"Unsupported int operator: {filter.operator}")
|
|
161
|
+
|
|
162
|
+
raise ValueError(f"Unsupported field type: {field_type}")
|
|
163
|
+
|
|
164
|
+
def create_query_for_filter_list(
|
|
165
|
+
self, query: Select[Tuple[INHERITS_BASE_ENTITY]], filters: list[FilterModel]
|
|
166
|
+
) -> Select[Tuple[INHERITS_BASE_ENTITY]]:
|
|
167
|
+
return reduce(lambda q, f: self.create_query_for_filter(q, f), filters, query)
|
|
168
|
+
|
|
169
|
+
def create_query_for_sorting(
|
|
170
|
+
self, query: Select[Tuple[INHERITS_BASE_ENTITY]], sort: SortModel
|
|
171
|
+
) -> Select[Tuple[INHERITS_BASE_ENTITY]]:
|
|
172
|
+
field = self.allowed_sorts.get(sort.field)
|
|
173
|
+
if field is None:
|
|
174
|
+
raise ValueError(f"Unsupported field: {sort.field}")
|
|
175
|
+
return query.order_by(field.asc() if sort.direction == "asc" else field.desc())
|
|
176
|
+
|
|
177
|
+
def create_query_for_sorting_list(
|
|
178
|
+
self, query: Select[Tuple[INHERITS_BASE_ENTITY]], sorts: list[SortModel]
|
|
179
|
+
) -> Select[Tuple[INHERITS_BASE_ENTITY]]:
|
|
180
|
+
return reduce(lambda q, s: self.create_query_for_sorting(q, s), sorts, query)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
__all__ = [
|
|
184
|
+
"SortFilterRunner",
|
|
185
|
+
"FilterModel",
|
|
186
|
+
"SortModel",
|
|
187
|
+
"FILTER_SORT_ENTITY_ATTR_MAP",
|
|
188
|
+
"INHERITS_BASE_ENTITY",
|
|
189
|
+
]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
2
2
|
README.md,sha256=mte30I-ZEJJp-Oax-OganNgl6G9GaCZPL6JVFAvZGz4,7034
|
|
3
|
-
pyproject.toml,sha256=
|
|
4
|
-
jararaca/__init__.py,sha256=
|
|
3
|
+
pyproject.toml,sha256=DwBGMHMjD9OcQxeANd4lZh4-u8dMCPL0E273cqbQym4,1837
|
|
4
|
+
jararaca/__init__.py,sha256=fBLWY_hpImDrzD-81avNasZxAbZ8IImAkS3kl83_3Kc,13757
|
|
5
5
|
jararaca/__main__.py,sha256=-O3vsB5lHdqNFjUtoELDF81IYFtR-DSiiFMzRaiSsv4,67
|
|
6
6
|
jararaca/cli.py,sha256=fh7lp7rf5xbV5VaoSYWWehktel6BPcOXMjW7cw4wKms,5693
|
|
7
7
|
jararaca/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -28,6 +28,7 @@ jararaca/persistence/exports.py,sha256=Ghx4yoFaB4QVTb9WxrFYgmcSATXMNvrOvT8ybPNKX
|
|
|
28
28
|
jararaca/persistence/interceptors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
29
|
jararaca/persistence/interceptors/aiosqa_interceptor.py,sha256=H6ZjOdosYGCZUzKjugiXQwJkAbnsL4HnkZLOEQhULEc,1986
|
|
30
30
|
jararaca/persistence/session.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
+
jararaca/persistence/sort_filter.py,sha256=bUxUkFZ_W1Vuc0TyDzhA41P7Zjb-USWzcTD9kAimRq4,6806
|
|
31
32
|
jararaca/presentation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
33
|
jararaca/presentation/decorators.py,sha256=eL2YCgMSr19m4YCri5PQU46NRxf0QxsqDnz6MqKu0YQ,8389
|
|
33
34
|
jararaca/presentation/hooks.py,sha256=WBbU5DG3-MAm2Ro2YraQyYG_HENfizYfyShL2ktHi6k,1980
|
|
@@ -56,8 +57,8 @@ jararaca/tools/app_config/decorators.py,sha256=-ckkMZ1dswOmECdo1rFrZ15UAku--txaN
|
|
|
56
57
|
jararaca/tools/app_config/interceptor.py,sha256=nfFZiS80hrbnL7-XEYrwmp2rwaVYBqxvqu3Y-6o_ov4,2575
|
|
57
58
|
jararaca/tools/metadata.py,sha256=7nlCDYgItNybentPSSCc2MLqN7IpBd0VyQzfjfQycVI,1402
|
|
58
59
|
jararaca/tools/typescript/interface_parser.py,sha256=rvTlSGDffyxSwqoHDLxdXApwXDw0v8Tq6nOWPO033nQ,28382
|
|
59
|
-
jararaca-0.2.
|
|
60
|
-
jararaca-0.2.
|
|
61
|
-
jararaca-0.2.
|
|
62
|
-
jararaca-0.2.
|
|
63
|
-
jararaca-0.2.
|
|
60
|
+
jararaca-0.2.23.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
61
|
+
jararaca-0.2.23.dist-info/METADATA,sha256=eSuPU4Xv26Ri6bpmkGeiuK932KFPnjJp10z6bNSW8O4,8552
|
|
62
|
+
jararaca-0.2.23.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
63
|
+
jararaca-0.2.23.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
|
|
64
|
+
jararaca-0.2.23.dist-info/RECORD,,
|
pyproject.toml
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|