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 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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jararaca
3
- Version: 0.2.21
3
+ Version: 0.2.23
4
4
  Summary: A simple and fast API framework for Python
5
5
  Home-page: https://github.com/LuscasLeo/jararaca
6
6
  Author: Lucas S
@@ -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=FjHyojTsOg-6l0EqxPv_DAblh7-fQXgGfgOqSZ0hUoI,1837
4
- jararaca/__init__.py,sha256=Dt5phB59JV6w9UaOAO7HxIqINazMJega8hVhFaerjr8,13184
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.21.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
60
- jararaca-0.2.21.dist-info/METADATA,sha256=DJnA44AKrVVw9DMR9lxY5hlmP0PQYVQFB6lIuj4ii0U,8552
61
- jararaca-0.2.21.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
62
- jararaca-0.2.21.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
63
- jararaca-0.2.21.dist-info/RECORD,,
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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "jararaca"
3
- version = "0.2.21"
3
+ version = "0.2.23"
4
4
  description = "A simple and fast API framework for Python"
5
5
  authors = ["Lucas S <me@luscasleo.dev>"]
6
6
  readme = "README.md"