jararaca 0.2.20__tar.gz → 0.2.22__tar.gz

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.

Files changed (77) hide show
  1. {jararaca-0.2.20 → jararaca-0.2.22}/PKG-INFO +1 -1
  2. {jararaca-0.2.20 → jararaca-0.2.22}/pyproject.toml +1 -1
  3. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/__init__.py +14 -1
  4. jararaca-0.2.22/src/jararaca/persistence/sort_filter.py +186 -0
  5. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/tools/typescript/interface_parser.py +5 -5
  6. {jararaca-0.2.20 → jararaca-0.2.22}/LICENSE +0 -0
  7. {jararaca-0.2.20 → jararaca-0.2.22}/README.md +0 -0
  8. {jararaca-0.2.20 → jararaca-0.2.22}/docs/Architecture/Message Bus/Decorators.md +0 -0
  9. {jararaca-0.2.20 → jararaca-0.2.22}/docs/Architecture/Message Bus/Interceptors.md +0 -0
  10. {jararaca-0.2.20 → jararaca-0.2.22}/docs/Architecture/Observability/Decorators.md +0 -0
  11. {jararaca-0.2.20 → jararaca-0.2.22}/docs/Architecture/Observability/Interceptors.md +0 -0
  12. {jararaca-0.2.20 → jararaca-0.2.22}/docs/Architecture/Presentation/Decorators.md +0 -0
  13. {jararaca-0.2.20 → jararaca-0.2.22}/docs/Architecture/RPC/RestClients/Decorators.md +0 -0
  14. {jararaca-0.2.20 → jararaca-0.2.22}/docs/Architecture/RPC/RestClients/Middlewared.md +0 -0
  15. {jararaca-0.2.20 → jararaca-0.2.22}/docs/Architecture/Schedule/Decorators.md +0 -0
  16. {jararaca-0.2.20 → jararaca-0.2.22}/docs/Architecture/Websocket/Decorators.md +0 -0
  17. {jararaca-0.2.20 → jararaca-0.2.22}/docs/Architecture/Websocket/Interceptors.md +0 -0
  18. {jararaca-0.2.20 → jararaca-0.2.22}/docs/Concept/Adapters.md +0 -0
  19. {jararaca-0.2.20 → jararaca-0.2.22}/docs/Concept/Hexagonal Architecture.md +0 -0
  20. {jararaca-0.2.20 → jararaca-0.2.22}/docs/Concept/Ports.md +0 -0
  21. {jararaca-0.2.20 → jararaca-0.2.22}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.jpeg +0 -0
  22. {jararaca-0.2.20 → jararaca-0.2.22}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.webp +0 -0
  23. {jararaca-0.2.20 → jararaca-0.2.22}/docs/assets/tracing_example.png +0 -0
  24. {jararaca-0.2.20 → jararaca-0.2.22}/docs/index.md +0 -0
  25. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/__main__.py +0 -0
  26. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/cli.py +0 -0
  27. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/common/__init__.py +0 -0
  28. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/core/__init__.py +0 -0
  29. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/core/providers.py +0 -0
  30. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/core/uow.py +0 -0
  31. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/di.py +0 -0
  32. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/files/entity.py.mako +0 -0
  33. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/lifecycle.py +0 -0
  34. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/messagebus/__init__.py +0 -0
  35. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/messagebus/decorators.py +0 -0
  36. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/messagebus/interceptors/__init__.py +0 -0
  37. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/messagebus/interceptors/publisher_interceptor.py +0 -0
  38. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/messagebus/publisher.py +0 -0
  39. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/messagebus/types.py +0 -0
  40. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/messagebus/worker.py +0 -0
  41. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/microservice.py +0 -0
  42. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/observability/decorators.py +0 -0
  43. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/observability/interceptor.py +0 -0
  44. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/observability/providers/__init__.py +0 -0
  45. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/observability/providers/otel.py +0 -0
  46. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/persistence/base.py +0 -0
  47. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/persistence/exports.py +0 -0
  48. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/persistence/interceptors/__init__.py +0 -0
  49. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/persistence/interceptors/aiosqa_interceptor.py +0 -0
  50. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/persistence/session.py +0 -0
  51. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/presentation/__init__.py +0 -0
  52. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/presentation/decorators.py +0 -0
  53. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/presentation/hooks.py +0 -0
  54. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/presentation/http_microservice.py +0 -0
  55. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/presentation/server.py +0 -0
  56. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/presentation/websocket/__init__.py +0 -0
  57. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/presentation/websocket/base_types.py +0 -0
  58. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/presentation/websocket/context.py +0 -0
  59. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/presentation/websocket/decorators.py +0 -0
  60. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/presentation/websocket/redis.py +0 -0
  61. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/presentation/websocket/types.py +0 -0
  62. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/presentation/websocket/websocket_interceptor.py +0 -0
  63. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/py.typed +0 -0
  64. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/rpc/__init__.py +0 -0
  65. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/rpc/http/__init__.py +0 -0
  66. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/rpc/http/backends/__init__.py +0 -0
  67. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/rpc/http/backends/httpx.py +0 -0
  68. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/rpc/http/backends/otel.py +0 -0
  69. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/rpc/http/decorators.py +0 -0
  70. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/rpc/http/httpx.py +0 -0
  71. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/scheduler/__init__.py +0 -0
  72. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/scheduler/decorators.py +0 -0
  73. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/scheduler/scheduler.py +0 -0
  74. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/tools/app_config/__init__.py +0 -0
  75. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/tools/app_config/decorators.py +0 -0
  76. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/tools/app_config/interceptor.py +0 -0
  77. {jararaca-0.2.20 → jararaca-0.2.22}/src/jararaca/tools/metadata.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jararaca
3
- Version: 0.2.20
3
+ Version: 0.2.22
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,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "jararaca"
3
- version = "0.2.20"
3
+ version = "0.2.22"
4
4
  description = "A simple and fast API framework for Python"
5
5
  authors = ["Lucas S <me@luscasleo.dev>"]
6
6
  readme = "README.md"
@@ -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,186 @@
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
+ __value = (
98
+ filter.value[0] if isinstance(filter.value, list) else filter.value
99
+ )
100
+ if field_type is date:
101
+ value = datetime.strptime(__value, "%Y-%m-%d").date()
102
+ else:
103
+ value = datetime.strptime(__value, "%Y-%m-%dT%H:%M:%S.%fZ")
104
+ match filter.operator:
105
+ case "is":
106
+ return query.filter(field == value)
107
+ case "not":
108
+ return query.filter(field != value)
109
+ case "after":
110
+ return query.filter(field > value)
111
+ case "onOrAfter":
112
+ return query.filter(field >= value)
113
+ case "before":
114
+ return query.filter(field < value)
115
+ case "onOrBefore":
116
+ return query.filter(field <= value)
117
+ case "isEmpty":
118
+ return query.filter(field == None) # noqa
119
+ case "isNotEmpty":
120
+ return query.filter(field != None) # noqa
121
+ case _:
122
+ raise ValueError(
123
+ f"Unsupported data/datetime operator: {filter.operator}"
124
+ )
125
+ elif field_type is bool:
126
+ __value = (
127
+ filter.value[0] if isinstance(filter.value, list) else filter.value
128
+ )
129
+ match filter.operator:
130
+ case "is":
131
+ if __value == "":
132
+ return query.filter(field.is_not(None))
133
+ return query.filter(field == (__value == "true"))
134
+ case _:
135
+ raise ValueError(f"Unsupported bool operator: {filter.operator}")
136
+ elif field_type is int:
137
+ match filter.operator:
138
+ case "=":
139
+ return query.filter(field == filter.value)
140
+ case "!=":
141
+ return query.filter(field != filter.value)
142
+ case ">":
143
+ return query.filter(field > filter.value)
144
+ case "<":
145
+ return query.filter(field < filter.value)
146
+ case ">=":
147
+ return query.filter(field >= filter.value)
148
+ case "<=":
149
+ return query.filter(field <= filter.value)
150
+ case "isEmpty":
151
+ return query.filter(field == None) # noqa
152
+ case "isNotEmpty":
153
+ return query.filter(field != None) # noqa
154
+ case "isAnyOf":
155
+ return query.filter(field.in_(filter.value))
156
+ case _:
157
+ raise ValueError(f"Unsupported int operator: {filter.operator}")
158
+
159
+ raise ValueError(f"Unsupported field type: {field_type}")
160
+
161
+ def create_query_for_filter_list(
162
+ self, query: Select[Tuple[INHERITS_BASE_ENTITY]], filters: list[FilterModel]
163
+ ) -> Select[Tuple[INHERITS_BASE_ENTITY]]:
164
+ return reduce(lambda q, f: self.create_query_for_filter(q, f), filters, query)
165
+
166
+ def create_query_for_sorting(
167
+ self, query: Select[Tuple[INHERITS_BASE_ENTITY]], sort: SortModel
168
+ ) -> Select[Tuple[INHERITS_BASE_ENTITY]]:
169
+ field = self.allowed_sorts.get(sort.field)
170
+ if field is None:
171
+ raise ValueError(f"Unsupported field: {sort.field}")
172
+ return query.order_by(field.asc() if sort.direction == "asc" else field.desc())
173
+
174
+ def create_query_for_sorting_list(
175
+ self, query: Select[Tuple[INHERITS_BASE_ENTITY]], sorts: list[SortModel]
176
+ ) -> Select[Tuple[INHERITS_BASE_ENTITY]]:
177
+ return reduce(lambda q, s: self.create_query_for_sorting(q, s), sorts, query)
178
+
179
+
180
+ __all__ = [
181
+ "SortFilterRunner",
182
+ "FilterModel",
183
+ "SortModel",
184
+ "FILTER_SORT_ENTITY_ATTR_MAP",
185
+ "INHERITS_BASE_ENTITY",
186
+ ]
@@ -251,11 +251,11 @@ def parse_type_to_typescript_interface(
251
251
  )
252
252
 
253
253
  if hasattr(basemodel_type, "__annotations__"):
254
- for field_name in (f for f in dir(basemodel_type) if is_constant(f)):
255
- field = getattr(basemodel_type, field_name)
256
- if field is None:
257
- continue
258
- string_builder.write(f" {field_name}: {parse_literal_value(field)};\n")
254
+ # for field_name in (f for f in dir(basemodel_type) if is_constant(f)):
255
+ # field = getattr(basemodel_type, field_name)
256
+ # if field is None:
257
+ # continue
258
+ # string_builder.write(f" {field_name}: {parse_literal_value(field)};\n")
259
259
  for field_name, field in basemodel_type.__annotations__.items():
260
260
  if field_name in cls_consts:
261
261
  continue
File without changes
File without changes
File without changes
File without changes
File without changes