nsj-rest-lib2 0.0.7__py3-none-any.whl → 0.0.9__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/compiler/compiler.py +53 -16
- nsj_rest_lib2/compiler/dto_compiler.py +27 -1
- nsj_rest_lib2/compiler/edl_model/column_meta_model.py +12 -2
- nsj_rest_lib2/compiler/edl_model/entity_model.py +10 -2
- nsj_rest_lib2/compiler/edl_model/primitives.py +17 -5
- nsj_rest_lib2/compiler/edl_model/property_meta_model.py +15 -3
- nsj_rest_lib2/compiler/edl_model/trait_property_meta_model.py +2 -2
- nsj_rest_lib2/compiler/entity_compiler.py +2 -1
- nsj_rest_lib2/compiler/property_compiler.py +384 -221
- nsj_rest_lib2/compiler/util/type_naming_util.py +21 -0
- nsj_rest_lib2/controller/dynamic_controller.py +92 -44
- nsj_rest_lib2/service/entity_loader.py +125 -42
- {nsj_rest_lib2-0.0.7.dist-info → nsj_rest_lib2-0.0.9.dist-info}/METADATA +2 -1
- {nsj_rest_lib2-0.0.7.dist-info → nsj_rest_lib2-0.0.9.dist-info}/RECORD +16 -15
- {nsj_rest_lib2-0.0.7.dist-info → nsj_rest_lib2-0.0.9.dist-info}/WHEEL +0 -0
- {nsj_rest_lib2-0.0.7.dist-info → nsj_rest_lib2-0.0.9.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
|
|
3
|
+
from nsj_rest_lib2.compiler.util.str_util import CompilerStrUtil
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def compile_namespace_keys(
|
|
7
|
+
tenant: str | int | None, grupo_empresarial: str | uuid.UUID | None
|
|
8
|
+
) -> tuple[str, str, str]:
|
|
9
|
+
grupo_key = f"tenant_{tenant}.ge_{grupo_empresarial}"
|
|
10
|
+
tenant_key = f"tenant_{tenant}"
|
|
11
|
+
default_key = "default"
|
|
12
|
+
|
|
13
|
+
return (grupo_key, tenant_key, default_key)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def compile_dto_class_name(entity_id: str) -> str:
|
|
17
|
+
return f"{CompilerStrUtil.to_pascal_case(entity_id)}DTO"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def compile_entity_class_name(entity_id: str) -> str:
|
|
21
|
+
return f"{CompilerStrUtil.to_pascal_case(entity_id)}Entity"
|
|
@@ -55,17 +55,17 @@ def setup_dynamic_routes(
|
|
|
55
55
|
injector_factory: Any = None,
|
|
56
56
|
) -> None:
|
|
57
57
|
|
|
58
|
-
COLLECTION_DYNAMIC_ROUTE = f"/{APP_NAME}/{dynamic_root_path}/<
|
|
59
|
-
ONE_DYNAMIC_ROUTE = f"/{APP_NAME}/{dynamic_root_path}/<
|
|
58
|
+
COLLECTION_DYNAMIC_ROUTE = f"/{APP_NAME}/{dynamic_root_path}/<entity_resource>"
|
|
59
|
+
ONE_DYNAMIC_ROUTE = f"/{APP_NAME}/{dynamic_root_path}/<entity_resource>/<id>"
|
|
60
60
|
|
|
61
61
|
def list_dynamic_wrapper(injector_factory: Any, *args: Any, **kwargs: Any) -> Any:
|
|
62
62
|
|
|
63
63
|
def list_dynamic(*args: Any, **kwargs: Any):
|
|
64
64
|
# Recuperando o identificador da entidade
|
|
65
|
-
if "
|
|
65
|
+
if "entity_resource" not in kwargs:
|
|
66
66
|
msg = "Faltando parâmetro identificador da entidade na URL."
|
|
67
67
|
return (format_json_error(msg), 400, {**DEFAULT_RESP_HEADERS})
|
|
68
|
-
|
|
68
|
+
entity_resource = kwargs.pop("entity_resource")
|
|
69
69
|
|
|
70
70
|
# Lendo tenant e grupo_empresarial
|
|
71
71
|
tenant, grupo_empresarial = _get_tenant_grupo()
|
|
@@ -73,12 +73,20 @@ def setup_dynamic_routes(
|
|
|
73
73
|
try:
|
|
74
74
|
# Recuperando o código do DTO e Entity correspondente
|
|
75
75
|
entity_loader = EntityLoader()
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
(
|
|
77
|
+
dto_class_name,
|
|
78
|
+
entity_class_name,
|
|
79
|
+
etities_dict,
|
|
80
|
+
api_expose,
|
|
81
|
+
api_verbs,
|
|
82
|
+
) = entity_loader.load_entity_source(
|
|
83
|
+
entity_resource, tenant, grupo_empresarial
|
|
80
84
|
)
|
|
81
85
|
|
|
86
|
+
# Verificando se essa API deve ser exposta
|
|
87
|
+
if not api_expose or "GET" not in api_verbs:
|
|
88
|
+
return ("", 405, {})
|
|
89
|
+
|
|
82
90
|
# Executando o list pelo RestLib
|
|
83
91
|
route = ListRoute(
|
|
84
92
|
url=COLLECTION_DYNAMIC_ROUTE,
|
|
@@ -90,7 +98,7 @@ def setup_dynamic_routes(
|
|
|
90
98
|
|
|
91
99
|
return route.handle_request(*args, **kwargs)
|
|
92
100
|
except MissingEntityConfigException:
|
|
93
|
-
msg = f"Entity configuration for {
|
|
101
|
+
msg = f"Entity configuration for {entity_resource} not found."
|
|
94
102
|
return (format_json_error(msg), 412, {**DEFAULT_RESP_HEADERS})
|
|
95
103
|
|
|
96
104
|
return list_dynamic
|
|
@@ -99,10 +107,10 @@ def setup_dynamic_routes(
|
|
|
99
107
|
|
|
100
108
|
def get_dynamic(*args: Any, **kwargs: Any):
|
|
101
109
|
# Recuperando o identificador da entidade
|
|
102
|
-
if "
|
|
110
|
+
if "entity_resource" not in kwargs:
|
|
103
111
|
msg = "Faltando parâmetro identificador da entidade na URL."
|
|
104
112
|
return (format_json_error(msg), 400, {**DEFAULT_RESP_HEADERS})
|
|
105
|
-
|
|
113
|
+
entity_resource = kwargs.pop("entity_resource")
|
|
106
114
|
|
|
107
115
|
# Lendo tenant e grupo_empresarial
|
|
108
116
|
tenant, grupo_empresarial = _get_tenant_grupo()
|
|
@@ -110,12 +118,20 @@ def setup_dynamic_routes(
|
|
|
110
118
|
try:
|
|
111
119
|
# Recuperando o código do DTO e Entity correspondente
|
|
112
120
|
entity_loader = EntityLoader()
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
(
|
|
122
|
+
dto_class_name,
|
|
123
|
+
entity_class_name,
|
|
124
|
+
etities_dict,
|
|
125
|
+
api_expose,
|
|
126
|
+
api_verbs,
|
|
127
|
+
) = entity_loader.load_entity_source(
|
|
128
|
+
entity_resource, tenant, grupo_empresarial
|
|
117
129
|
)
|
|
118
130
|
|
|
131
|
+
# Verificando se essa API deve ser exposta
|
|
132
|
+
if not api_expose or "GET" not in api_verbs:
|
|
133
|
+
return ("", 405, {})
|
|
134
|
+
|
|
119
135
|
# Executando o list pelo RestLib
|
|
120
136
|
route = GetRoute(
|
|
121
137
|
url=ONE_DYNAMIC_ROUTE,
|
|
@@ -127,7 +143,7 @@ def setup_dynamic_routes(
|
|
|
127
143
|
|
|
128
144
|
return route.handle_request(*args, **kwargs)
|
|
129
145
|
except MissingEntityConfigException:
|
|
130
|
-
msg = f"Entity configuration for {
|
|
146
|
+
msg = f"Entity configuration for {entity_resource} not found."
|
|
131
147
|
return (format_json_error(msg), 412, {**DEFAULT_RESP_HEADERS})
|
|
132
148
|
|
|
133
149
|
return get_dynamic
|
|
@@ -135,10 +151,10 @@ def setup_dynamic_routes(
|
|
|
135
151
|
def post_dynamic_wrapper(injector_factory: Any, *args: Any, **kwargs: Any) -> Any:
|
|
136
152
|
def post_dynamic(*args: Any, **kwargs: Any):
|
|
137
153
|
# Recuperando o identificador da entidade
|
|
138
|
-
if "
|
|
154
|
+
if "entity_resource" not in kwargs:
|
|
139
155
|
msg = "Faltando parâmetro identificador da entidade na URL."
|
|
140
156
|
return (format_json_error(msg), 400, {**DEFAULT_RESP_HEADERS})
|
|
141
|
-
|
|
157
|
+
entity_resource = kwargs.pop("entity_resource")
|
|
142
158
|
|
|
143
159
|
# Lendo tenant e grupo_empresarial
|
|
144
160
|
tenant, grupo_empresarial = _get_tenant_grupo()
|
|
@@ -146,12 +162,20 @@ def setup_dynamic_routes(
|
|
|
146
162
|
try:
|
|
147
163
|
# Recuperando o código do DTO e Entity correspondente
|
|
148
164
|
entity_loader = EntityLoader()
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
165
|
+
(
|
|
166
|
+
dto_class_name,
|
|
167
|
+
entity_class_name,
|
|
168
|
+
etities_dict,
|
|
169
|
+
api_expose,
|
|
170
|
+
api_verbs,
|
|
171
|
+
) = entity_loader.load_entity_source(
|
|
172
|
+
entity_resource, tenant, grupo_empresarial
|
|
153
173
|
)
|
|
154
174
|
|
|
175
|
+
# Verificando se essa API deve ser exposta
|
|
176
|
+
if not api_expose or "POST" not in api_verbs:
|
|
177
|
+
return ("", 405, {})
|
|
178
|
+
|
|
155
179
|
# Executando o list pelo RestLib
|
|
156
180
|
route = PostRoute(
|
|
157
181
|
url=COLLECTION_DYNAMIC_ROUTE,
|
|
@@ -163,7 +187,7 @@ def setup_dynamic_routes(
|
|
|
163
187
|
|
|
164
188
|
return route.handle_request(*args, **kwargs)
|
|
165
189
|
except MissingEntityConfigException:
|
|
166
|
-
msg = f"Entity configuration for {
|
|
190
|
+
msg = f"Entity configuration for {entity_resource} not found."
|
|
167
191
|
return (format_json_error(msg), 412, {**DEFAULT_RESP_HEADERS})
|
|
168
192
|
|
|
169
193
|
return post_dynamic
|
|
@@ -171,10 +195,10 @@ def setup_dynamic_routes(
|
|
|
171
195
|
def put_dynamic_wrapper(injector_factory: Any, *args: Any, **kwargs: Any) -> Any:
|
|
172
196
|
def put_dynamic(*args: Any, **kwargs: Any):
|
|
173
197
|
# Recuperando o identificador da entidade
|
|
174
|
-
if "
|
|
198
|
+
if "entity_resource" not in kwargs:
|
|
175
199
|
msg = "Faltando parâmetro identificador da entidade na URL."
|
|
176
200
|
return (format_json_error(msg), 400, {**DEFAULT_RESP_HEADERS})
|
|
177
|
-
|
|
201
|
+
entity_resource = kwargs.pop("entity_resource")
|
|
178
202
|
|
|
179
203
|
# Lendo tenant e grupo_empresarial
|
|
180
204
|
tenant, grupo_empresarial = _get_tenant_grupo()
|
|
@@ -182,12 +206,20 @@ def setup_dynamic_routes(
|
|
|
182
206
|
try:
|
|
183
207
|
# Recuperando o código do DTO e Entity correspondente
|
|
184
208
|
entity_loader = EntityLoader()
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
209
|
+
(
|
|
210
|
+
dto_class_name,
|
|
211
|
+
entity_class_name,
|
|
212
|
+
etities_dict,
|
|
213
|
+
api_expose,
|
|
214
|
+
api_verbs,
|
|
215
|
+
) = entity_loader.load_entity_source(
|
|
216
|
+
entity_resource, tenant, grupo_empresarial
|
|
189
217
|
)
|
|
190
218
|
|
|
219
|
+
# Verificando se essa API deve ser exposta
|
|
220
|
+
if not api_expose or "PUT" not in api_verbs:
|
|
221
|
+
return ("", 405, {})
|
|
222
|
+
|
|
191
223
|
# Executando o list pelo RestLib
|
|
192
224
|
route = PutRoute(
|
|
193
225
|
url=ONE_DYNAMIC_ROUTE,
|
|
@@ -199,7 +231,7 @@ def setup_dynamic_routes(
|
|
|
199
231
|
|
|
200
232
|
return route.handle_request(*args, **kwargs)
|
|
201
233
|
except MissingEntityConfigException:
|
|
202
|
-
msg = f"Entity configuration for {
|
|
234
|
+
msg = f"Entity configuration for {entity_resource} not found."
|
|
203
235
|
return (format_json_error(msg), 412, {**DEFAULT_RESP_HEADERS})
|
|
204
236
|
|
|
205
237
|
return put_dynamic
|
|
@@ -207,10 +239,10 @@ def setup_dynamic_routes(
|
|
|
207
239
|
def patch_dynamic_wrapper(injector_factory: Any, *args: Any, **kwargs: Any) -> Any:
|
|
208
240
|
def patch_dynamic(*args: Any, **kwargs: Any):
|
|
209
241
|
# Recuperando o identificador da entidade
|
|
210
|
-
if "
|
|
242
|
+
if "entity_resource" not in kwargs:
|
|
211
243
|
msg = "Faltando parâmetro identificador da entidade na URL."
|
|
212
244
|
return (format_json_error(msg), 400, {**DEFAULT_RESP_HEADERS})
|
|
213
|
-
|
|
245
|
+
entity_resource = kwargs.pop("entity_resource")
|
|
214
246
|
|
|
215
247
|
# Lendo tenant e grupo_empresarial
|
|
216
248
|
tenant, grupo_empresarial = _get_tenant_grupo()
|
|
@@ -218,12 +250,20 @@ def setup_dynamic_routes(
|
|
|
218
250
|
try:
|
|
219
251
|
# Recuperando o código do DTO e Entity correspondente
|
|
220
252
|
entity_loader = EntityLoader()
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
253
|
+
(
|
|
254
|
+
dto_class_name,
|
|
255
|
+
entity_class_name,
|
|
256
|
+
etities_dict,
|
|
257
|
+
api_expose,
|
|
258
|
+
api_verbs,
|
|
259
|
+
) = entity_loader.load_entity_source(
|
|
260
|
+
entity_resource, tenant, grupo_empresarial
|
|
225
261
|
)
|
|
226
262
|
|
|
263
|
+
# Verificando se essa API deve ser exposta
|
|
264
|
+
if not api_expose or "PATCH" not in api_verbs:
|
|
265
|
+
return ("", 405, {})
|
|
266
|
+
|
|
227
267
|
# Executando o list pelo RestLib
|
|
228
268
|
route = PatchRoute(
|
|
229
269
|
url=ONE_DYNAMIC_ROUTE,
|
|
@@ -235,7 +275,7 @@ def setup_dynamic_routes(
|
|
|
235
275
|
|
|
236
276
|
return route.handle_request(*args, **kwargs)
|
|
237
277
|
except MissingEntityConfigException:
|
|
238
|
-
msg = f"Entity configuration for {
|
|
278
|
+
msg = f"Entity configuration for {entity_resource} not found."
|
|
239
279
|
return (format_json_error(msg), 412, {**DEFAULT_RESP_HEADERS})
|
|
240
280
|
|
|
241
281
|
return patch_dynamic
|
|
@@ -243,10 +283,10 @@ def setup_dynamic_routes(
|
|
|
243
283
|
def delete_dynamic_wrapper(injector_factory: Any, *args: Any, **kwargs: Any) -> Any:
|
|
244
284
|
def delete_dynamic(*args: Any, **kwargs: Any):
|
|
245
285
|
# Recuperando o identificador da entidade
|
|
246
|
-
if "
|
|
286
|
+
if "entity_resource" not in kwargs:
|
|
247
287
|
msg = "Faltando parâmetro identificador da entidade na URL."
|
|
248
288
|
return (format_json_error(msg), 400, {**DEFAULT_RESP_HEADERS})
|
|
249
|
-
|
|
289
|
+
entity_resource = kwargs.pop("entity_resource")
|
|
250
290
|
|
|
251
291
|
# Lendo tenant e grupo_empresarial
|
|
252
292
|
tenant, grupo_empresarial = _get_tenant_grupo()
|
|
@@ -254,12 +294,20 @@ def setup_dynamic_routes(
|
|
|
254
294
|
try:
|
|
255
295
|
# Recuperando o código do DTO e Entity correspondente
|
|
256
296
|
entity_loader = EntityLoader()
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
297
|
+
(
|
|
298
|
+
dto_class_name,
|
|
299
|
+
entity_class_name,
|
|
300
|
+
etities_dict,
|
|
301
|
+
api_expose,
|
|
302
|
+
api_verbs,
|
|
303
|
+
) = entity_loader.load_entity_source(
|
|
304
|
+
entity_resource, tenant, grupo_empresarial
|
|
261
305
|
)
|
|
262
306
|
|
|
307
|
+
# Verificando se essa API deve ser exposta
|
|
308
|
+
if not api_expose or "DELETE" not in api_verbs:
|
|
309
|
+
return ("", 405, {})
|
|
310
|
+
|
|
263
311
|
# Executando o list pelo RestLib
|
|
264
312
|
route = DeleteRoute(
|
|
265
313
|
url=ONE_DYNAMIC_ROUTE,
|
|
@@ -271,7 +319,7 @@ def setup_dynamic_routes(
|
|
|
271
319
|
|
|
272
320
|
return route.handle_request(*args, **kwargs)
|
|
273
321
|
except MissingEntityConfigException:
|
|
274
|
-
msg = f"Entity configuration for {
|
|
322
|
+
msg = f"Entity configuration for {entity_resource} not found."
|
|
275
323
|
return (format_json_error(msg), 412, {**DEFAULT_RESP_HEADERS})
|
|
276
324
|
|
|
277
325
|
return delete_dynamic
|
|
@@ -6,6 +6,7 @@ import types
|
|
|
6
6
|
|
|
7
7
|
from nsj_rest_lib.settings import get_logger
|
|
8
8
|
|
|
9
|
+
from nsj_rest_lib2.compiler.util.type_naming_util import compile_namespace_keys
|
|
9
10
|
from nsj_rest_lib2.exception import MissingEntityConfigException
|
|
10
11
|
from nsj_rest_lib2.redis_config import get_redis
|
|
11
12
|
from nsj_rest_lib2.settings import ESCOPO_RESTLIB2, MIN_TIME_SOURCE_REFRESH
|
|
@@ -18,6 +19,8 @@ class LoadedEntity:
|
|
|
18
19
|
self.entity_class_name: str = ""
|
|
19
20
|
self.entity_hash: str = ""
|
|
20
21
|
self.loaded_at: datetime.datetime = datetime.datetime.now()
|
|
22
|
+
self.api_expose: bool = False
|
|
23
|
+
self.api_verbs: list[str] = []
|
|
21
24
|
|
|
22
25
|
|
|
23
26
|
class Namespace:
|
|
@@ -37,17 +40,17 @@ class EntityLoader:
|
|
|
37
40
|
|
|
38
41
|
def load_entity_source(
|
|
39
42
|
self,
|
|
40
|
-
|
|
43
|
+
entity_resource: str,
|
|
41
44
|
tenant: str | None,
|
|
42
45
|
grupo_empresarial: str | None,
|
|
43
|
-
) -> tuple[str, str, dict]:
|
|
46
|
+
) -> tuple[str, str, dict, bool, list[str]]:
|
|
44
47
|
# Montando as chaves dos namespaces
|
|
45
|
-
grupo_key =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
grupo_key, tenant_key, default_key = compile_namespace_keys(
|
|
49
|
+
tenant, grupo_empresarial
|
|
50
|
+
)
|
|
48
51
|
|
|
49
52
|
result = self._load_entity_source_from_memory(
|
|
50
|
-
|
|
53
|
+
entity_resource, grupo_key, tenant_key, default_key
|
|
51
54
|
)
|
|
52
55
|
|
|
53
56
|
# Se conseguiu carregar da memória, verifica se houve alteração no hash, em relação ao redis
|
|
@@ -58,24 +61,39 @@ class EntityLoader:
|
|
|
58
61
|
namespace,
|
|
59
62
|
) = result
|
|
60
63
|
|
|
61
|
-
loaded_entity = namespace.loaded_entities[
|
|
64
|
+
loaded_entity = namespace.loaded_entities[entity_resource]
|
|
62
65
|
dto_class_name = loaded_entity.dto_class_name
|
|
63
66
|
entity_class_name = loaded_entity.entity_class_name
|
|
64
67
|
entities_dict = namespace.entities_dict
|
|
68
|
+
api_expose = loaded_entity.api_expose
|
|
69
|
+
api_verbs = loaded_entity.api_verbs
|
|
65
70
|
|
|
66
71
|
# Se o tempo entre o carregamento e agora for maior do que MIN_TIME_SOURCE_REFRESH minutos,
|
|
67
72
|
# verifica se precisa de refresh
|
|
68
73
|
time_diff = datetime.datetime.now() - loaded_entity.loaded_at
|
|
69
74
|
|
|
70
75
|
if time_diff.total_seconds() >= MIN_TIME_SOURCE_REFRESH * 60:
|
|
76
|
+
# Renovando o tempo de refresh
|
|
77
|
+
loaded_entity.loaded_at = datetime.datetime.now()
|
|
78
|
+
|
|
71
79
|
# Recuperando do Redis direto pela key (faz uma só chamada ao redis)
|
|
72
80
|
loaded_config = self._load_entity_config_from_redis(
|
|
73
|
-
|
|
81
|
+
entity_resource,
|
|
82
|
+
grupo_key,
|
|
83
|
+
tenant_key,
|
|
84
|
+
default_key,
|
|
85
|
+
entity_config_key,
|
|
74
86
|
)
|
|
75
87
|
|
|
76
88
|
# Se não achar no redis, usa o que estava em memória
|
|
77
89
|
if not loaded_config:
|
|
78
|
-
return (
|
|
90
|
+
return (
|
|
91
|
+
dto_class_name,
|
|
92
|
+
entity_class_name,
|
|
93
|
+
entities_dict,
|
|
94
|
+
api_expose,
|
|
95
|
+
api_verbs,
|
|
96
|
+
)
|
|
79
97
|
|
|
80
98
|
# Desempacotando resultado
|
|
81
99
|
entity_config_key, entity_config_str = loaded_config
|
|
@@ -84,23 +102,47 @@ class EntityLoader:
|
|
|
84
102
|
result_execute = self._execute_entity_source(
|
|
85
103
|
entity_config_str,
|
|
86
104
|
entity_config_key,
|
|
87
|
-
|
|
105
|
+
entity_resource,
|
|
88
106
|
check_refresh=True,
|
|
89
107
|
)
|
|
90
108
|
|
|
91
109
|
# Se não carregou novo código, usa o que estava em memória
|
|
92
110
|
if result_execute is None:
|
|
93
|
-
return (
|
|
111
|
+
return (
|
|
112
|
+
dto_class_name,
|
|
113
|
+
entity_class_name,
|
|
114
|
+
entities_dict,
|
|
115
|
+
api_expose,
|
|
116
|
+
api_verbs,
|
|
117
|
+
)
|
|
94
118
|
else:
|
|
95
|
-
|
|
96
|
-
|
|
119
|
+
(
|
|
120
|
+
dto_class_name,
|
|
121
|
+
entity_class_name,
|
|
122
|
+
namespace,
|
|
123
|
+
api_expose,
|
|
124
|
+
api_verbs,
|
|
125
|
+
) = result_execute
|
|
126
|
+
return (
|
|
127
|
+
dto_class_name,
|
|
128
|
+
entity_class_name,
|
|
129
|
+
namespace.entities_dict,
|
|
130
|
+
api_expose,
|
|
131
|
+
api_verbs,
|
|
132
|
+
)
|
|
97
133
|
else:
|
|
98
134
|
# Se não deu o intervalo de verificação do refresh, retorna o que está em memória
|
|
99
|
-
return (
|
|
135
|
+
return (
|
|
136
|
+
dto_class_name,
|
|
137
|
+
entity_class_name,
|
|
138
|
+
entities_dict,
|
|
139
|
+
api_expose,
|
|
140
|
+
api_verbs,
|
|
141
|
+
)
|
|
100
142
|
|
|
101
143
|
# Se não conseguir recuperar a entidade, procura no redis:
|
|
102
144
|
loaded_config = self._load_entity_config_from_redis(
|
|
103
|
-
|
|
145
|
+
entity_resource, grupo_key, tenant_key, default_key, None
|
|
104
146
|
)
|
|
105
147
|
|
|
106
148
|
# Se também não achar no redis, lanca exceção
|
|
@@ -112,14 +154,24 @@ class EntityLoader:
|
|
|
112
154
|
|
|
113
155
|
# Executando o código da entidade
|
|
114
156
|
result_execute = self._execute_entity_source(
|
|
115
|
-
entity_config_str, entity_config_key,
|
|
157
|
+
entity_config_str, entity_config_key, entity_resource
|
|
116
158
|
)
|
|
117
159
|
|
|
118
160
|
if result_execute is None:
|
|
119
|
-
raise RuntimeError(
|
|
120
|
-
|
|
161
|
+
raise RuntimeError(
|
|
162
|
+
f"Erro desconhecido carregando entidade: {entity_resource}"
|
|
163
|
+
)
|
|
164
|
+
dto_class_name, entity_class_name, namespace, api_expose, api_verbs = (
|
|
165
|
+
result_execute
|
|
166
|
+
)
|
|
121
167
|
|
|
122
|
-
return (
|
|
168
|
+
return (
|
|
169
|
+
dto_class_name,
|
|
170
|
+
entity_class_name,
|
|
171
|
+
namespace.entities_dict,
|
|
172
|
+
api_expose,
|
|
173
|
+
api_verbs,
|
|
174
|
+
)
|
|
123
175
|
|
|
124
176
|
def clear_namespaces(self):
|
|
125
177
|
"""
|
|
@@ -131,13 +183,25 @@ class EntityLoader:
|
|
|
131
183
|
with self._lock:
|
|
132
184
|
namespaces_dict.clear()
|
|
133
185
|
|
|
186
|
+
def _ensure_dynamic_package(self):
|
|
187
|
+
"""
|
|
188
|
+
Garante que exista um pacote 'dynamic' em sys.modules.
|
|
189
|
+
"""
|
|
190
|
+
pkg = sys.modules.get("dynamic")
|
|
191
|
+
if pkg is None:
|
|
192
|
+
pkg = types.ModuleType("dynamic")
|
|
193
|
+
pkg.__path__ = [] # marca como pacote
|
|
194
|
+
pkg.__package__ = "dynamic"
|
|
195
|
+
sys.modules["dynamic"] = pkg
|
|
196
|
+
return pkg
|
|
197
|
+
|
|
134
198
|
def _execute_entity_source(
|
|
135
199
|
self,
|
|
136
200
|
entity_config_str: str,
|
|
137
201
|
entity_config_key: str,
|
|
138
|
-
|
|
202
|
+
entity_resource: str,
|
|
139
203
|
check_refresh: bool = False,
|
|
140
|
-
) -> tuple[str, str, Namespace] | None:
|
|
204
|
+
) -> tuple[str, str, Namespace, bool, list[str]] | None:
|
|
141
205
|
# Interpretando o json de configuração da entidade
|
|
142
206
|
try:
|
|
143
207
|
entity_config = json.loads(entity_config_str)
|
|
@@ -147,14 +211,18 @@ class EntityLoader:
|
|
|
147
211
|
source_dto = entity_config["source_dto"]
|
|
148
212
|
source_entity = entity_config["source_entity"]
|
|
149
213
|
entity_hash = entity_config["entity_hash"]
|
|
214
|
+
|
|
215
|
+
api_expose = entity_config["api_expose"]
|
|
216
|
+
# api_resource = entity_config["api_resource"]
|
|
217
|
+
api_verbs = entity_config["api_verbs"]
|
|
150
218
|
except json.JSONDecodeError as e:
|
|
151
219
|
if not check_refresh:
|
|
152
220
|
raise RuntimeError(
|
|
153
|
-
f"Erro ao decodificar JSON da entidade {
|
|
221
|
+
f"Erro ao decodificar JSON da entidade {entity_resource}; na chave {entity_config_key}: {e}"
|
|
154
222
|
)
|
|
155
223
|
else:
|
|
156
224
|
get_logger().error(
|
|
157
|
-
f"Erro ao decodificar JSON da entidade {
|
|
225
|
+
f"Erro ao decodificar JSON da entidade {entity_resource}; na chave {entity_config_key}: {e}"
|
|
158
226
|
)
|
|
159
227
|
return None
|
|
160
228
|
|
|
@@ -164,7 +232,7 @@ class EntityLoader:
|
|
|
164
232
|
if not loaded_namespace:
|
|
165
233
|
return None
|
|
166
234
|
|
|
167
|
-
loaded_entity = loaded_namespace.loaded_entities.get(
|
|
235
|
+
loaded_entity = loaded_namespace.loaded_entities.get(entity_resource)
|
|
168
236
|
if not loaded_entity:
|
|
169
237
|
return None
|
|
170
238
|
|
|
@@ -173,24 +241,37 @@ class EntityLoader:
|
|
|
173
241
|
|
|
174
242
|
# Imprimindo alerta de load no log
|
|
175
243
|
get_logger().debug(
|
|
176
|
-
f"Carregando entidade {
|
|
244
|
+
f"Carregando entidade {entity_resource} no namespace {entity_config_key}."
|
|
177
245
|
)
|
|
178
246
|
|
|
179
247
|
# Carregando a entidade no namespace
|
|
180
248
|
with self._lock:
|
|
249
|
+
self._ensure_dynamic_package()
|
|
250
|
+
|
|
181
251
|
namespace = namespaces_dict.get(entity_config_key)
|
|
182
252
|
if namespace is None:
|
|
183
253
|
namespace = Namespace()
|
|
184
254
|
namespace.key = entity_config_key
|
|
185
255
|
namespaces_dict[entity_config_key] = namespace
|
|
186
256
|
|
|
257
|
+
# Hot reload: removendo o módulo do sys.modules, se existir
|
|
258
|
+
full_name = f"dynamic.{entity_config_key}"
|
|
259
|
+
# if full_name in sys.modules:
|
|
260
|
+
# sys.modules.pop(full_name)
|
|
261
|
+
|
|
187
262
|
# Executando o código da entidade
|
|
188
|
-
module =
|
|
189
|
-
|
|
190
|
-
|
|
263
|
+
module = sys.modules.get(full_name)
|
|
264
|
+
if not module:
|
|
265
|
+
module = types.ModuleType(full_name)
|
|
266
|
+
module.__package__ = "dynamic"
|
|
267
|
+
module.__dict__["__builtins__"] = __builtins__
|
|
268
|
+
sys.modules[full_name] = module
|
|
269
|
+
|
|
270
|
+
parent = sys.modules["dynamic"]
|
|
271
|
+
setattr(parent, entity_config_key, module)
|
|
191
272
|
|
|
192
|
-
|
|
193
|
-
|
|
273
|
+
namespace.module = module
|
|
274
|
+
namespace.entities_dict = module.__dict__
|
|
194
275
|
|
|
195
276
|
self._safe_exec(source_dto, namespace.entities_dict, "DTO source")
|
|
196
277
|
self._safe_exec(source_entity, namespace.entities_dict, "Entity source")
|
|
@@ -200,10 +281,12 @@ class EntityLoader:
|
|
|
200
281
|
loaded_entity.dto_class_name = dto_class_name
|
|
201
282
|
loaded_entity.entity_class_name = entity_class_name
|
|
202
283
|
loaded_entity.entity_hash = entity_hash
|
|
284
|
+
loaded_entity.api_expose = api_expose
|
|
285
|
+
loaded_entity.api_verbs = api_verbs
|
|
203
286
|
|
|
204
|
-
namespace.loaded_entities[
|
|
287
|
+
namespace.loaded_entities[entity_resource] = loaded_entity
|
|
205
288
|
|
|
206
|
-
return (dto_class_name, entity_class_name, namespace)
|
|
289
|
+
return (dto_class_name, entity_class_name, namespace, api_expose, api_verbs)
|
|
207
290
|
|
|
208
291
|
def _safe_exec(self, source_code, context, description):
|
|
209
292
|
try:
|
|
@@ -214,35 +297,35 @@ class EntityLoader:
|
|
|
214
297
|
|
|
215
298
|
def _load_entity_config_from_redis(
|
|
216
299
|
self,
|
|
217
|
-
|
|
300
|
+
entity_resource: str,
|
|
218
301
|
grupo_key: str,
|
|
219
302
|
tenant_key: str,
|
|
220
303
|
default_key: str,
|
|
221
304
|
entity_config_key: str | None,
|
|
222
305
|
) -> tuple[str, str] | None:
|
|
223
306
|
get_logger().debug(
|
|
224
|
-
f"Procurando a configuração da entidade {
|
|
307
|
+
f"Procurando a configuração da entidade {entity_resource} no redis"
|
|
225
308
|
)
|
|
226
309
|
|
|
227
310
|
if entity_config_key is not None:
|
|
228
311
|
entity_config_str = get_redis(
|
|
229
|
-
"entity_config", ESCOPO_RESTLIB2, entity_config_key,
|
|
312
|
+
"entity_config", ESCOPO_RESTLIB2, entity_config_key, entity_resource
|
|
230
313
|
)
|
|
231
314
|
|
|
232
315
|
else:
|
|
233
316
|
entity_config_key = grupo_key
|
|
234
317
|
entity_config_str = get_redis(
|
|
235
|
-
"entity_config", ESCOPO_RESTLIB2, grupo_key,
|
|
318
|
+
"entity_config", ESCOPO_RESTLIB2, grupo_key, entity_resource
|
|
236
319
|
)
|
|
237
320
|
if entity_config_str is None:
|
|
238
321
|
entity_config_key = tenant_key
|
|
239
322
|
entity_config_str = get_redis(
|
|
240
|
-
"entity_config", ESCOPO_RESTLIB2, tenant_key,
|
|
323
|
+
"entity_config", ESCOPO_RESTLIB2, tenant_key, entity_resource
|
|
241
324
|
)
|
|
242
325
|
if entity_config_str is None:
|
|
243
326
|
entity_config_key = default_key
|
|
244
327
|
entity_config_str = get_redis(
|
|
245
|
-
"entity_config", ESCOPO_RESTLIB2, default_key,
|
|
328
|
+
"entity_config", ESCOPO_RESTLIB2, default_key, entity_resource
|
|
246
329
|
)
|
|
247
330
|
|
|
248
331
|
# Se não encontrar no redis, retorna None
|
|
@@ -253,7 +336,7 @@ class EntityLoader:
|
|
|
253
336
|
|
|
254
337
|
def _load_entity_source_from_memory(
|
|
255
338
|
self,
|
|
256
|
-
|
|
339
|
+
entity_resource: str,
|
|
257
340
|
grupo_key: str,
|
|
258
341
|
tenant_key: str,
|
|
259
342
|
default_key: str,
|
|
@@ -263,19 +346,19 @@ class EntityLoader:
|
|
|
263
346
|
|
|
264
347
|
# Pesquisando a entidade no namespace mais específico (grupo_empresarial)
|
|
265
348
|
grupo_namespace = namespaces_dict.get(grupo_key)
|
|
266
|
-
if grupo_namespace and
|
|
349
|
+
if grupo_namespace and entity_resource in grupo_namespace.loaded_entities:
|
|
267
350
|
entity_config_key = grupo_key
|
|
268
351
|
namespace = grupo_namespace
|
|
269
352
|
|
|
270
353
|
# Pesquisando a entidade no namespace intermediário (tenant)
|
|
271
354
|
tenant_namespace = namespaces_dict.get(tenant_key)
|
|
272
|
-
if tenant_namespace and
|
|
355
|
+
if tenant_namespace and entity_resource in tenant_namespace.loaded_entities:
|
|
273
356
|
entity_config_key = tenant_key
|
|
274
357
|
namespace = tenant_namespace
|
|
275
358
|
|
|
276
359
|
# Pesquisando a entidade no namespace padrão (default)
|
|
277
360
|
default_namespace = namespaces_dict.get(default_key)
|
|
278
|
-
if default_namespace and
|
|
361
|
+
if default_namespace and entity_resource in default_namespace.loaded_entities:
|
|
279
362
|
entity_config_key = default_key
|
|
280
363
|
namespace = default_namespace
|
|
281
364
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nsj_rest_lib2
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.9
|
|
4
4
|
Summary: Biblioteca para permitir a distribuição de rotas dinâmicas numa API, configuradas por meio de EDLs declarativos (em formato JSON).
|
|
5
5
|
Home-page: https://github.com/Nasajon/nsj_rest_lib2
|
|
6
6
|
Author: Nasajon Sistemas
|
|
@@ -17,6 +17,7 @@ Requires-Dist: redis<7.0.0,>=6.4.0
|
|
|
17
17
|
Requires-Dist: nsj-multi-database-lib<3.0.0,>=2.0.1
|
|
18
18
|
Requires-Dist: pydantic<3.0.0,>=2.11.9
|
|
19
19
|
Requires-Dist: black<26.0.0,>=25.1.0
|
|
20
|
+
Requires-Dist: pyyaml>7.0.0,>=6.0.3
|
|
20
21
|
|
|
21
22
|
# nsj_rest_lib2
|
|
22
23
|
|