nsj-rest-lib2 0.0.1__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.
- nsj_rest_lib2/__init__.py +0 -0
- nsj_rest_lib2/controller/__init__.py +0 -0
- nsj_rest_lib2/controller/dynamic_controller.py +300 -0
- nsj_rest_lib2/exception.py +2 -0
- nsj_rest_lib2/redis_config.py +31 -0
- nsj_rest_lib2/service/__init__.py +0 -0
- nsj_rest_lib2/service/entity_loader.py +268 -0
- nsj_rest_lib2/settings.py +3 -0
- nsj_rest_lib2-0.0.1.dist-info/METADATA +26 -0
- nsj_rest_lib2-0.0.1.dist-info/RECORD +12 -0
- nsj_rest_lib2-0.0.1.dist-info/WHEEL +5 -0
- nsj_rest_lib2-0.0.1.dist-info/top_level.txt +1 -0
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from flask import Flask, request
|
|
6
|
+
|
|
7
|
+
from nsj_rest_lib.settings import APP_NAME
|
|
8
|
+
|
|
9
|
+
from nsj_gcf_utils.rest_error_util import format_json_error
|
|
10
|
+
|
|
11
|
+
from nsj_multi_database_lib.decorator.multi_database import multi_database
|
|
12
|
+
|
|
13
|
+
from nsj_rest_lib.controller.controller_util import DEFAULT_RESP_HEADERS
|
|
14
|
+
from nsj_rest_lib.controller.list_route import ListRoute
|
|
15
|
+
from nsj_rest_lib.controller.get_route import GetRoute
|
|
16
|
+
from nsj_rest_lib.controller.post_route import PostRoute
|
|
17
|
+
from nsj_rest_lib.controller.put_route import PutRoute
|
|
18
|
+
from nsj_rest_lib.controller.patch_route import PatchRoute
|
|
19
|
+
from nsj_rest_lib.controller.delete_route import DeleteRoute
|
|
20
|
+
|
|
21
|
+
from nsj_rest_lib2.exception import MissingEntityConfigException
|
|
22
|
+
from nsj_rest_lib2.service.entity_loader import EntityLoader
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get_tenant_grupo() -> tuple[str, str]:
|
|
26
|
+
# Tentando ler do query args
|
|
27
|
+
query_args = request.args
|
|
28
|
+
tenant = query_args.get("tenant")
|
|
29
|
+
grupo_empresarial = query_args.get("grupo_empresarial")
|
|
30
|
+
|
|
31
|
+
# Tentando ler do corpo da requisição
|
|
32
|
+
try:
|
|
33
|
+
body_str = request.data.decode("utf-8")
|
|
34
|
+
body_json = json.loads(body_str)
|
|
35
|
+
|
|
36
|
+
if not tenant:
|
|
37
|
+
tenant = body_json.get("tenant")
|
|
38
|
+
if not grupo_empresarial:
|
|
39
|
+
grupo_empresarial = body_json.get("grupo_empresarial")
|
|
40
|
+
except:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
return (str(tenant), str(grupo_empresarial))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _endpoint_name(func: Any, multidb: bool, root: str) -> str:
|
|
47
|
+
suffix = "_mb" if multidb else ""
|
|
48
|
+
return f"{root}_{func.__name__}{suffix}"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def setup_dynamic_routes(
|
|
52
|
+
flask_app: Flask,
|
|
53
|
+
multidb: bool = True,
|
|
54
|
+
dynamic_root_path: str = "edl1",
|
|
55
|
+
injector_factory: Any = None,
|
|
56
|
+
) -> None:
|
|
57
|
+
|
|
58
|
+
COLLECTION_DYNAMIC_ROUTE = f"/{APP_NAME}/{dynamic_root_path}/<entity_id>"
|
|
59
|
+
ONE_DYNAMIC_ROUTE = f"/{APP_NAME}/{dynamic_root_path}/<entity_id>/<id>"
|
|
60
|
+
|
|
61
|
+
def list_dynamic_wrapper(injector_factory: Any, *args: Any, **kwargs: Any) -> Any:
|
|
62
|
+
|
|
63
|
+
def list_dynamic(*args: Any, **kwargs: Any):
|
|
64
|
+
# Recuperando o identificador da entidade
|
|
65
|
+
if "entity_id" not in kwargs:
|
|
66
|
+
msg = "Faltando parâmetro identificador da entidade na URL."
|
|
67
|
+
return (format_json_error(msg), 400, {**DEFAULT_RESP_HEADERS})
|
|
68
|
+
entity_id = kwargs.pop("entity_id")
|
|
69
|
+
|
|
70
|
+
# Lendo tenant e grupo_empresarial
|
|
71
|
+
tenant, grupo_empresarial = _get_tenant_grupo()
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
# Recuperando o código do DTO e Entity correspondente
|
|
75
|
+
entity_loader = EntityLoader()
|
|
76
|
+
dto_class_name, entity_class_name, etities_dict = (
|
|
77
|
+
entity_loader.load_entity_source(
|
|
78
|
+
entity_id, tenant, grupo_empresarial
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Executando o list pelo RestLib
|
|
83
|
+
route = ListRoute(
|
|
84
|
+
url=COLLECTION_DYNAMIC_ROUTE,
|
|
85
|
+
http_method="GET",
|
|
86
|
+
dto_class=etities_dict[dto_class_name],
|
|
87
|
+
entity_class=etities_dict[entity_class_name],
|
|
88
|
+
injector_factory=injector_factory,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return route.handle_request(*args, **kwargs)
|
|
92
|
+
except MissingEntityConfigException:
|
|
93
|
+
msg = f"Entity configuration for {entity_id} not found."
|
|
94
|
+
return (format_json_error(msg), 412, {**DEFAULT_RESP_HEADERS})
|
|
95
|
+
|
|
96
|
+
return list_dynamic
|
|
97
|
+
|
|
98
|
+
def get_dynamic(*args: Any, **kwargs: Any):
|
|
99
|
+
# Recuperando o identificador da entidade
|
|
100
|
+
if "entity_id" not in kwargs:
|
|
101
|
+
msg = "Faltando parâmetro identificador da entidade na URL."
|
|
102
|
+
return (format_json_error(msg), 400, {**DEFAULT_RESP_HEADERS})
|
|
103
|
+
entity_id = kwargs.pop("entity_id")
|
|
104
|
+
|
|
105
|
+
# Lendo tenant e grupo_empresarial
|
|
106
|
+
tenant, grupo_empresarial = _get_tenant_grupo()
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
# Recuperando o código do DTO e Entity correspondente
|
|
110
|
+
entity_loader = EntityLoader()
|
|
111
|
+
dto_class_name, entity_class_name, etities_dict = (
|
|
112
|
+
entity_loader.load_entity_source(entity_id, tenant, grupo_empresarial)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Executando o list pelo RestLib
|
|
116
|
+
route = GetRoute(
|
|
117
|
+
url=ONE_DYNAMIC_ROUTE,
|
|
118
|
+
http_method="GET",
|
|
119
|
+
dto_class=etities_dict[dto_class_name],
|
|
120
|
+
entity_class=etities_dict[entity_class_name],
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return route.handle_request(*args, **kwargs)
|
|
124
|
+
except MissingEntityConfigException:
|
|
125
|
+
msg = f"Entity configuration for {entity_id} not found."
|
|
126
|
+
return (format_json_error(msg), 412, {**DEFAULT_RESP_HEADERS})
|
|
127
|
+
|
|
128
|
+
def post_dynamic(*args: Any, **kwargs: Any):
|
|
129
|
+
# Recuperando o identificador da entidade
|
|
130
|
+
if "entity_id" not in kwargs:
|
|
131
|
+
msg = "Faltando parâmetro identificador da entidade na URL."
|
|
132
|
+
return (format_json_error(msg), 400, {**DEFAULT_RESP_HEADERS})
|
|
133
|
+
entity_id = kwargs.pop("entity_id")
|
|
134
|
+
|
|
135
|
+
# Lendo tenant e grupo_empresarial
|
|
136
|
+
tenant, grupo_empresarial = _get_tenant_grupo()
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
# Recuperando o código do DTO e Entity correspondente
|
|
140
|
+
entity_loader = EntityLoader()
|
|
141
|
+
dto_class_name, entity_class_name, etities_dict = (
|
|
142
|
+
entity_loader.load_entity_source(entity_id, tenant, grupo_empresarial)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Executando o list pelo RestLib
|
|
146
|
+
route = PostRoute(
|
|
147
|
+
url=COLLECTION_DYNAMIC_ROUTE,
|
|
148
|
+
http_method="POST",
|
|
149
|
+
dto_class=etities_dict[dto_class_name],
|
|
150
|
+
entity_class=etities_dict[entity_class_name],
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return route.handle_request(*args, **kwargs)
|
|
154
|
+
except MissingEntityConfigException:
|
|
155
|
+
msg = f"Entity configuration for {entity_id} not found."
|
|
156
|
+
return (format_json_error(msg), 412, {**DEFAULT_RESP_HEADERS})
|
|
157
|
+
|
|
158
|
+
def put_dynamic(*args: Any, **kwargs: Any):
|
|
159
|
+
# Recuperando o identificador da entidade
|
|
160
|
+
if "entity_id" not in kwargs:
|
|
161
|
+
msg = "Faltando parâmetro identificador da entidade na URL."
|
|
162
|
+
return (format_json_error(msg), 400, {**DEFAULT_RESP_HEADERS})
|
|
163
|
+
entity_id = kwargs.pop("entity_id")
|
|
164
|
+
|
|
165
|
+
# Lendo tenant e grupo_empresarial
|
|
166
|
+
tenant, grupo_empresarial = _get_tenant_grupo()
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
# Recuperando o código do DTO e Entity correspondente
|
|
170
|
+
entity_loader = EntityLoader()
|
|
171
|
+
dto_class_name, entity_class_name, etities_dict = (
|
|
172
|
+
entity_loader.load_entity_source(entity_id, tenant, grupo_empresarial)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Executando o list pelo RestLib
|
|
176
|
+
route = PutRoute(
|
|
177
|
+
url=ONE_DYNAMIC_ROUTE,
|
|
178
|
+
http_method="PUT",
|
|
179
|
+
dto_class=etities_dict[dto_class_name],
|
|
180
|
+
entity_class=etities_dict[entity_class_name],
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return route.handle_request(*args, **kwargs)
|
|
184
|
+
except MissingEntityConfigException:
|
|
185
|
+
msg = f"Entity configuration for {entity_id} not found."
|
|
186
|
+
return (format_json_error(msg), 412, {**DEFAULT_RESP_HEADERS})
|
|
187
|
+
|
|
188
|
+
def patch_dynamic(*args: Any, **kwargs: Any):
|
|
189
|
+
# Recuperando o identificador da entidade
|
|
190
|
+
if "entity_id" not in kwargs:
|
|
191
|
+
msg = "Faltando parâmetro identificador da entidade na URL."
|
|
192
|
+
return (format_json_error(msg), 400, {**DEFAULT_RESP_HEADERS})
|
|
193
|
+
entity_id = kwargs.pop("entity_id")
|
|
194
|
+
|
|
195
|
+
# Lendo tenant e grupo_empresarial
|
|
196
|
+
tenant, grupo_empresarial = _get_tenant_grupo()
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
# Recuperando o código do DTO e Entity correspondente
|
|
200
|
+
entity_loader = EntityLoader()
|
|
201
|
+
dto_class_name, entity_class_name, etities_dict = (
|
|
202
|
+
entity_loader.load_entity_source(entity_id, tenant, grupo_empresarial)
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Executando o list pelo RestLib
|
|
206
|
+
route = PatchRoute(
|
|
207
|
+
url=ONE_DYNAMIC_ROUTE,
|
|
208
|
+
http_method="PATCH",
|
|
209
|
+
dto_class=etities_dict[dto_class_name],
|
|
210
|
+
entity_class=etities_dict[entity_class_name],
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return route.handle_request(*args, **kwargs)
|
|
214
|
+
except MissingEntityConfigException:
|
|
215
|
+
msg = f"Entity configuration for {entity_id} not found."
|
|
216
|
+
return (format_json_error(msg), 412, {**DEFAULT_RESP_HEADERS})
|
|
217
|
+
|
|
218
|
+
def delete_dynamic(*args: Any, **kwargs: Any):
|
|
219
|
+
# Recuperando o identificador da entidade
|
|
220
|
+
if "entity_id" not in kwargs:
|
|
221
|
+
msg = "Faltando parâmetro identificador da entidade na URL."
|
|
222
|
+
return (format_json_error(msg), 400, {**DEFAULT_RESP_HEADERS})
|
|
223
|
+
entity_id = kwargs.pop("entity_id")
|
|
224
|
+
|
|
225
|
+
# Lendo tenant e grupo_empresarial
|
|
226
|
+
tenant, grupo_empresarial = _get_tenant_grupo()
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
# Recuperando o código do DTO e Entity correspondente
|
|
230
|
+
entity_loader = EntityLoader()
|
|
231
|
+
dto_class_name, entity_class_name, etities_dict = (
|
|
232
|
+
entity_loader.load_entity_source(entity_id, tenant, grupo_empresarial)
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Executando o list pelo RestLib
|
|
236
|
+
route = DeleteRoute(
|
|
237
|
+
url=ONE_DYNAMIC_ROUTE,
|
|
238
|
+
http_method="DELETE",
|
|
239
|
+
dto_class=etities_dict[dto_class_name],
|
|
240
|
+
entity_class=etities_dict[entity_class_name],
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
return route.handle_request(*args, **kwargs)
|
|
244
|
+
except MissingEntityConfigException:
|
|
245
|
+
msg = f"Entity configuration for {entity_id} not found."
|
|
246
|
+
return (format_json_error(msg), 412, {**DEFAULT_RESP_HEADERS})
|
|
247
|
+
|
|
248
|
+
# Ajustando para o padrão com multi database (se necessário)
|
|
249
|
+
if multidb:
|
|
250
|
+
list_dynamic = multi_database()(list_dynamic_wrapper(injector_factory))
|
|
251
|
+
get_dynamic = multi_database()(get_dynamic)
|
|
252
|
+
post_dynamic = multi_database()(post_dynamic)
|
|
253
|
+
put_dynamic = multi_database()(put_dynamic)
|
|
254
|
+
patch_dynamic = multi_database()(patch_dynamic)
|
|
255
|
+
delete_dynamic = multi_database()(delete_dynamic)
|
|
256
|
+
else:
|
|
257
|
+
list_dynamic = list_dynamic_wrapper(injector_factory)
|
|
258
|
+
get_dynamic = get_dynamic
|
|
259
|
+
post_dynamic = post_dynamic
|
|
260
|
+
put_dynamic = put_dynamic
|
|
261
|
+
patch_dynamic = patch_dynamic
|
|
262
|
+
delete_dynamic = delete_dynamic
|
|
263
|
+
|
|
264
|
+
# Registrando as rotas no flask
|
|
265
|
+
flask_app.add_url_rule(
|
|
266
|
+
COLLECTION_DYNAMIC_ROUTE,
|
|
267
|
+
endpoint=_endpoint_name(list_dynamic, multidb, dynamic_root_path),
|
|
268
|
+
view_func=list_dynamic,
|
|
269
|
+
methods=["GET"],
|
|
270
|
+
)
|
|
271
|
+
flask_app.add_url_rule(
|
|
272
|
+
ONE_DYNAMIC_ROUTE,
|
|
273
|
+
endpoint=_endpoint_name(get_dynamic, multidb, dynamic_root_path),
|
|
274
|
+
view_func=get_dynamic,
|
|
275
|
+
methods=["GET"],
|
|
276
|
+
)
|
|
277
|
+
flask_app.add_url_rule(
|
|
278
|
+
COLLECTION_DYNAMIC_ROUTE,
|
|
279
|
+
endpoint=_endpoint_name(post_dynamic, multidb, dynamic_root_path),
|
|
280
|
+
view_func=post_dynamic,
|
|
281
|
+
methods=["POST"],
|
|
282
|
+
)
|
|
283
|
+
flask_app.add_url_rule(
|
|
284
|
+
ONE_DYNAMIC_ROUTE,
|
|
285
|
+
endpoint=_endpoint_name(put_dynamic, multidb, dynamic_root_path),
|
|
286
|
+
view_func=put_dynamic,
|
|
287
|
+
methods=["PUT"],
|
|
288
|
+
)
|
|
289
|
+
flask_app.add_url_rule(
|
|
290
|
+
ONE_DYNAMIC_ROUTE,
|
|
291
|
+
endpoint=_endpoint_name(patch_dynamic, multidb, dynamic_root_path),
|
|
292
|
+
view_func=patch_dynamic,
|
|
293
|
+
methods=["PATCH"],
|
|
294
|
+
)
|
|
295
|
+
flask_app.add_url_rule(
|
|
296
|
+
ONE_DYNAMIC_ROUTE,
|
|
297
|
+
endpoint=_endpoint_name(delete_dynamic, multidb, dynamic_root_path),
|
|
298
|
+
view_func=delete_dynamic,
|
|
299
|
+
methods=["DELETE"],
|
|
300
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import redis
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
REDIS_HOST = os.environ["REDIS_HOST"]
|
|
7
|
+
REDIS_PORT = int(os.environ["REDIS_PORT"])
|
|
8
|
+
REDIS_DB = int(os.getenv("REDIS_DB", 0))
|
|
9
|
+
|
|
10
|
+
redis_client = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def k(*parts: str) -> str:
|
|
14
|
+
return ":".join(parts)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_redis(*args: str) -> Any:
|
|
18
|
+
value = redis_client.get(k(*args))
|
|
19
|
+
if value:
|
|
20
|
+
return value.decode("utf-8")
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def set_redis(*args) -> None:
|
|
25
|
+
value = args[-1]
|
|
26
|
+
redis_client.set(k(*args[:-1]), value)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if __name__ == "__main__":
|
|
30
|
+
set_redis("ping", "pong")
|
|
31
|
+
print(get_redis(("ping")))
|
|
File without changes
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
import threading
|
|
4
|
+
import types
|
|
5
|
+
|
|
6
|
+
from nsj_rest_lib.settings import get_logger
|
|
7
|
+
|
|
8
|
+
from nsj_rest_lib2.exception import MissingEntityConfigException
|
|
9
|
+
from nsj_rest_lib2.redis_config import get_redis
|
|
10
|
+
from nsj_rest_lib2.settings import ESCOPO_RESTLIB2
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LoadedEntity:
|
|
14
|
+
dto_class_name: str = ""
|
|
15
|
+
entity_class_name: str = ""
|
|
16
|
+
entity_hash: str = ""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Namespace:
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.key: str = ""
|
|
22
|
+
self.loaded_entities: dict[str, LoadedEntity] = {}
|
|
23
|
+
self.entities_dict: dict = None
|
|
24
|
+
self.module: types.ModuleType = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
namespaces_dict = {}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class EntityLoader:
|
|
31
|
+
def __init__(self) -> types.NoneType:
|
|
32
|
+
self._lock = threading.Lock()
|
|
33
|
+
|
|
34
|
+
def load_entity_source(
|
|
35
|
+
self,
|
|
36
|
+
entity_id: str,
|
|
37
|
+
tenant: str | None,
|
|
38
|
+
grupo_empresarial: str | None,
|
|
39
|
+
) -> tuple[str, str, dict]:
|
|
40
|
+
# Montando as chaves dos namespaces
|
|
41
|
+
grupo_key = f"tenant_{tenant}.ge_{grupo_empresarial}"
|
|
42
|
+
tenant_key = f"tenant_{tenant}"
|
|
43
|
+
default_key = "default"
|
|
44
|
+
|
|
45
|
+
result = self._load_entity_source_from_memory(
|
|
46
|
+
entity_id, grupo_key, tenant_key, default_key
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Se conseguiu carregar da memória, verifica se houve alteração no hash, em relação ao redis
|
|
50
|
+
if result is not None:
|
|
51
|
+
# Desempacotando o result
|
|
52
|
+
dto_class_name, entity_class_name, entities_dict, entity_config_key = result
|
|
53
|
+
|
|
54
|
+
# Recuperando do Redis direto pela key (faz uma só chamada ao redis)
|
|
55
|
+
loaded_config = self._load_entity_config_from_redis(
|
|
56
|
+
entity_id, grupo_key, tenant_key, default_key, entity_config_key
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Se não achar no redis, usa o que estava em memória
|
|
60
|
+
if not loaded_config:
|
|
61
|
+
return (dto_class_name, entity_class_name, entities_dict)
|
|
62
|
+
|
|
63
|
+
# Desempacotando resultado
|
|
64
|
+
entity_config_key, entity_config_str = loaded_config
|
|
65
|
+
|
|
66
|
+
# Executando o código da entidade, só se houver mudança no hash
|
|
67
|
+
result_execute = self._execute_entity_source(
|
|
68
|
+
entity_config_str,
|
|
69
|
+
entity_config_key,
|
|
70
|
+
entity_id,
|
|
71
|
+
check_refresh=True,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if result_execute is None:
|
|
75
|
+
return (dto_class_name, entity_class_name, entities_dict)
|
|
76
|
+
else:
|
|
77
|
+
dto_class_name, entity_class_name, namespace = result_execute
|
|
78
|
+
return (dto_class_name, entity_class_name, namespace.entities_dict)
|
|
79
|
+
|
|
80
|
+
# Se não conseguir recuperar a entidade, procura no redis:
|
|
81
|
+
loaded_config = self._load_entity_config_from_redis(
|
|
82
|
+
entity_id, grupo_key, tenant_key, default_key, None
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Se também não achar no redis, lanca exceção
|
|
86
|
+
if not loaded_config:
|
|
87
|
+
raise MissingEntityConfigException()
|
|
88
|
+
|
|
89
|
+
# Desempacotando resultado
|
|
90
|
+
entity_config_key, entity_config_str = loaded_config
|
|
91
|
+
|
|
92
|
+
# Executando o código da entidade
|
|
93
|
+
result_execute = self._execute_entity_source(
|
|
94
|
+
entity_config_str, entity_config_key, entity_id
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if result_execute is None:
|
|
98
|
+
raise RuntimeError(f"Erro desconhecido carregando entidade: {entity_id}")
|
|
99
|
+
dto_class_name, entity_class_name, namespace = result_execute
|
|
100
|
+
|
|
101
|
+
return (dto_class_name, entity_class_name, namespace.entities_dict)
|
|
102
|
+
|
|
103
|
+
def clear_namespaces(self):
|
|
104
|
+
"""
|
|
105
|
+
Clears all loaded namespaces from memory.
|
|
106
|
+
|
|
107
|
+
This method removes all entries from the namespaces_dict, effectively resetting
|
|
108
|
+
the in-memory cache of loaded entities and their associated namespaces.
|
|
109
|
+
"""
|
|
110
|
+
with self._lock:
|
|
111
|
+
namespaces_dict.clear()
|
|
112
|
+
|
|
113
|
+
def _execute_entity_source(
|
|
114
|
+
self,
|
|
115
|
+
entity_config_str: str,
|
|
116
|
+
entity_config_key: str,
|
|
117
|
+
entity_id: str,
|
|
118
|
+
check_refresh: bool = False,
|
|
119
|
+
) -> tuple[str, str, Namespace] | None:
|
|
120
|
+
# Interpretando o json de configuração da entidade
|
|
121
|
+
try:
|
|
122
|
+
entity_config = json.loads(entity_config_str)
|
|
123
|
+
|
|
124
|
+
dto_class_name = entity_config["dto_class_name"]
|
|
125
|
+
entity_class_name = entity_config["entity_class_name"]
|
|
126
|
+
source_dto = entity_config["source_dto"]
|
|
127
|
+
source_entity = entity_config["source_entity"]
|
|
128
|
+
entity_hash = entity_config["entity_hash"]
|
|
129
|
+
except json.JSONDecodeError as e:
|
|
130
|
+
if not check_refresh:
|
|
131
|
+
raise RuntimeError(
|
|
132
|
+
f"Erro ao decodificar JSON da entidade {entity_id}; na chave {entity_config_key}: {e}"
|
|
133
|
+
)
|
|
134
|
+
else:
|
|
135
|
+
get_logger().error(
|
|
136
|
+
f"Erro ao decodificar JSON da entidade {entity_id}; na chave {entity_config_key}: {e}"
|
|
137
|
+
)
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
# Verificando se a entidade precisa ou não de refresh
|
|
141
|
+
if check_refresh:
|
|
142
|
+
loaded_namespace = namespaces_dict.get(entity_config_key)
|
|
143
|
+
if not loaded_namespace:
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
loaded_entity = loaded_namespace.loaded_entities.get(entity_id)
|
|
147
|
+
if not loaded_entity:
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
if loaded_entity.entity_hash == entity_hash:
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
# Imprimindo alerta de load no log
|
|
154
|
+
get_logger().debug(
|
|
155
|
+
f"Carregando entidade {entity_id} no namespace {entity_config_key}."
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Carregando a entidade no namespace
|
|
159
|
+
with self._lock:
|
|
160
|
+
namespace = namespaces_dict.get(entity_config_key)
|
|
161
|
+
if namespace is None:
|
|
162
|
+
namespace = Namespace()
|
|
163
|
+
namespace.key = entity_config_key
|
|
164
|
+
namespaces_dict[entity_config_key] = namespace
|
|
165
|
+
|
|
166
|
+
# Executando o código da entidade
|
|
167
|
+
module = types.ModuleType(entity_config_key)
|
|
168
|
+
sys.modules[entity_config_key] = module
|
|
169
|
+
module.__dict__["__builtins__"] = __builtins__
|
|
170
|
+
|
|
171
|
+
namespace.module = module
|
|
172
|
+
namespace.entities_dict = module.__dict__
|
|
173
|
+
|
|
174
|
+
self._safe_exec(source_dto, namespace.entities_dict, "DTO source")
|
|
175
|
+
self._safe_exec(source_entity, namespace.entities_dict, "Entity source")
|
|
176
|
+
|
|
177
|
+
# Gravando a entidade no dict de entidades carregadas
|
|
178
|
+
loaded_entity = LoadedEntity()
|
|
179
|
+
loaded_entity.dto_class_name = dto_class_name
|
|
180
|
+
loaded_entity.entity_class_name = entity_class_name
|
|
181
|
+
loaded_entity.entity_hash = entity_hash
|
|
182
|
+
|
|
183
|
+
namespace.loaded_entities[entity_id] = loaded_entity
|
|
184
|
+
|
|
185
|
+
return (dto_class_name, entity_class_name, namespace)
|
|
186
|
+
|
|
187
|
+
def _safe_exec(self, source_code, context, description):
|
|
188
|
+
try:
|
|
189
|
+
exec(source_code, context)
|
|
190
|
+
except Exception as e:
|
|
191
|
+
get_logger().error(f"Error executing {description}: {e}")
|
|
192
|
+
raise
|
|
193
|
+
|
|
194
|
+
def _load_entity_config_from_redis(
|
|
195
|
+
self,
|
|
196
|
+
entity_id: str,
|
|
197
|
+
grupo_key: str,
|
|
198
|
+
tenant_key: str,
|
|
199
|
+
default_key: str,
|
|
200
|
+
entity_config_key: str | None,
|
|
201
|
+
) -> tuple[str, str] | None:
|
|
202
|
+
get_logger().debug(
|
|
203
|
+
f"Procurando a configuração da entidade {entity_id} no redis"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
if entity_config_key is not None:
|
|
207
|
+
entity_config_str = get_redis(
|
|
208
|
+
"entity_config", ESCOPO_RESTLIB2, entity_config_key, entity_id
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
else:
|
|
212
|
+
entity_config_key = grupo_key
|
|
213
|
+
entity_config_str = get_redis(
|
|
214
|
+
"entity_config", ESCOPO_RESTLIB2, grupo_key, entity_id
|
|
215
|
+
)
|
|
216
|
+
if entity_config_str is None:
|
|
217
|
+
entity_config_key = tenant_key
|
|
218
|
+
entity_config_str = get_redis(
|
|
219
|
+
"entity_config", ESCOPO_RESTLIB2, tenant_key, entity_id
|
|
220
|
+
)
|
|
221
|
+
if entity_config_str is None:
|
|
222
|
+
entity_config_key = default_key
|
|
223
|
+
entity_config_str = get_redis(
|
|
224
|
+
"entity_config", ESCOPO_RESTLIB2, default_key, entity_id
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Se não encontrar no redis, retorna None
|
|
228
|
+
if entity_config_str is None:
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
return (entity_config_key, entity_config_str)
|
|
232
|
+
|
|
233
|
+
def _load_entity_source_from_memory(
|
|
234
|
+
self,
|
|
235
|
+
entity_id: str,
|
|
236
|
+
grupo_key: str,
|
|
237
|
+
tenant_key: str,
|
|
238
|
+
default_key: str,
|
|
239
|
+
) -> tuple[str, str, dict, str] | None:
|
|
240
|
+
namespace = None
|
|
241
|
+
entity_config_key = None
|
|
242
|
+
|
|
243
|
+
# Pesquisando a entidade no namespace mais específico (grupo_empresarial)
|
|
244
|
+
grupo_namespace = namespaces_dict.get(grupo_key)
|
|
245
|
+
if grupo_namespace and entity_id in grupo_namespace.loaded_entities:
|
|
246
|
+
entity_config_key = grupo_key
|
|
247
|
+
namespace = grupo_namespace
|
|
248
|
+
|
|
249
|
+
# Pesquisando a entidade no namespace intermediário (tenant)
|
|
250
|
+
tenant_namespace = namespaces_dict.get(tenant_key)
|
|
251
|
+
if tenant_namespace and entity_id in tenant_namespace.loaded_entities:
|
|
252
|
+
entity_config_key = tenant_key
|
|
253
|
+
namespace = tenant_namespace
|
|
254
|
+
|
|
255
|
+
# Pesquisando a entidade no namespace padrão (default)
|
|
256
|
+
default_namespace = namespaces_dict.get(default_key)
|
|
257
|
+
if default_namespace and entity_id in default_namespace.loaded_entities:
|
|
258
|
+
entity_config_key = default_key
|
|
259
|
+
namespace = default_namespace
|
|
260
|
+
|
|
261
|
+
if namespace:
|
|
262
|
+
loaded_entity = namespace.loaded_entities[entity_id]
|
|
263
|
+
dto_class_name = loaded_entity.dto_class_name
|
|
264
|
+
entity_class_name = loaded_entity.entity_class_name
|
|
265
|
+
entities_dict = namespace.entities_dict
|
|
266
|
+
return (dto_class_name, entity_class_name, entities_dict, entity_config_key)
|
|
267
|
+
else:
|
|
268
|
+
return None
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nsj_rest_lib2
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Biblioteca para permitir a distribuição de rotas dinâmicas numa API, configuradas por meio de EDLs declarativos (em formato JSON).
|
|
5
|
+
Home-page: https://github.com/Nasajon/nsj_rest_lib2
|
|
6
|
+
Author: Nasajon Sistemas
|
|
7
|
+
Author-email: contact.dev@nasajon.com.br
|
|
8
|
+
Project-URL: Source, https://github.com/Nasajon/nsj_rest_lib2
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Requires-Python: >=3.4
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: nsj-rest-lib<5.0.0,>=4.9.0
|
|
16
|
+
Requires-Dist: redis<7.0.0,>=6.4.0
|
|
17
|
+
|
|
18
|
+
# nsj_rest_lib2
|
|
19
|
+
|
|
20
|
+
Biblioteca para permitir a distribuição de rotas dinâmicas numa API, configuradas por meio de EDLs declarativos (em formato JSON).
|
|
21
|
+
|
|
22
|
+
## TODO
|
|
23
|
+
* Colocar tempo para recarregamento das entidades (para checar, no redis, se mudou o hash.)
|
|
24
|
+
* Unificar o arquivo redis_config.py
|
|
25
|
+
* Usar pydantic, ou similar, para transformar a configuração das entidades, no redis, num objeto
|
|
26
|
+
* Rever modo de usar o InjectFactory (talvez dando ciência, ao RestLib, do padrão multibanco)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
nsj_rest_lib2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
nsj_rest_lib2/exception.py,sha256=E9uMUdoCCQOVQfc8f6gD9b5Pxurf3Q4SytDCcqSlkZ8,56
|
|
3
|
+
nsj_rest_lib2/redis_config.py,sha256=4KLcvYS3nJO7PMQgF6F9_j6r-TyqcS7TBbd3LEQuKDU,629
|
|
4
|
+
nsj_rest_lib2/settings.py,sha256=F6y6lOAdLhGzZA6NE01x3t1vSAvrm1pC9ImjLhe61IU,59
|
|
5
|
+
nsj_rest_lib2/controller/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
nsj_rest_lib2/controller/dynamic_controller.py,sha256=3XjRm9QSvE5ORaQB4zQNTkCG0saFyFfjYtuQCiYTg14,11600
|
|
7
|
+
nsj_rest_lib2/service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
nsj_rest_lib2/service/entity_loader.py,sha256=i7ljsgDV4jlG-Flq5u-gmMqJp7SnpNfMRX1MYunln0E,9771
|
|
9
|
+
nsj_rest_lib2-0.0.1.dist-info/METADATA,sha256=EJwy1kt7HUZw4MjhbF4On9v2wwa5XyFs3llvU87ivCw,1196
|
|
10
|
+
nsj_rest_lib2-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11
|
+
nsj_rest_lib2-0.0.1.dist-info/top_level.txt,sha256=L6zh0EfH8_rur7OJ8_V-El-XEMf4qg3bkF8ADgqLVIA,14
|
|
12
|
+
nsj_rest_lib2-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nsj_rest_lib2
|