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.
@@ -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}/<entity_id>"
59
- ONE_DYNAMIC_ROUTE = f"/{APP_NAME}/{dynamic_root_path}/<entity_id>/<id>"
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 "entity_id" not in kwargs:
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
- entity_id = kwargs.pop("entity_id")
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
- dto_class_name, entity_class_name, etities_dict = (
77
- entity_loader.load_entity_source(
78
- entity_id, tenant, grupo_empresarial
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 {entity_id} not found."
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 "entity_id" not in kwargs:
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
- entity_id = kwargs.pop("entity_id")
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
- dto_class_name, entity_class_name, etities_dict = (
114
- entity_loader.load_entity_source(
115
- entity_id, tenant, grupo_empresarial
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 {entity_id} not found."
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 "entity_id" not in kwargs:
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
- entity_id = kwargs.pop("entity_id")
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
- dto_class_name, entity_class_name, etities_dict = (
150
- entity_loader.load_entity_source(
151
- entity_id, tenant, grupo_empresarial
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 {entity_id} not found."
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 "entity_id" not in kwargs:
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
- entity_id = kwargs.pop("entity_id")
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
- dto_class_name, entity_class_name, etities_dict = (
186
- entity_loader.load_entity_source(
187
- entity_id, tenant, grupo_empresarial
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 {entity_id} not found."
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 "entity_id" not in kwargs:
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
- entity_id = kwargs.pop("entity_id")
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
- dto_class_name, entity_class_name, etities_dict = (
222
- entity_loader.load_entity_source(
223
- entity_id, tenant, grupo_empresarial
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 {entity_id} not found."
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 "entity_id" not in kwargs:
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
- entity_id = kwargs.pop("entity_id")
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
- dto_class_name, entity_class_name, etities_dict = (
258
- entity_loader.load_entity_source(
259
- entity_id, tenant, grupo_empresarial
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 {entity_id} not found."
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
- entity_id: str,
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 = f"tenant_{tenant}.ge_{grupo_empresarial}"
46
- tenant_key = f"tenant_{tenant}"
47
- default_key = "default"
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
- entity_id, grupo_key, tenant_key, default_key
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[entity_id]
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
- entity_id, grupo_key, tenant_key, default_key, entity_config_key
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 (dto_class_name, entity_class_name, entities_dict)
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
- entity_id,
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 (dto_class_name, entity_class_name, entities_dict)
111
+ return (
112
+ dto_class_name,
113
+ entity_class_name,
114
+ entities_dict,
115
+ api_expose,
116
+ api_verbs,
117
+ )
94
118
  else:
95
- dto_class_name, entity_class_name, namespace = result_execute
96
- return (dto_class_name, entity_class_name, namespace.entities_dict)
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 (dto_class_name, entity_class_name, entities_dict)
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
- entity_id, grupo_key, tenant_key, default_key, None
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, entity_id
157
+ entity_config_str, entity_config_key, entity_resource
116
158
  )
117
159
 
118
160
  if result_execute is None:
119
- raise RuntimeError(f"Erro desconhecido carregando entidade: {entity_id}")
120
- dto_class_name, entity_class_name, namespace = result_execute
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 (dto_class_name, entity_class_name, namespace.entities_dict)
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
- entity_id: str,
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 {entity_id}; na chave {entity_config_key}: {e}"
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 {entity_id}; na chave {entity_config_key}: {e}"
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(entity_id)
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 {entity_id} no namespace {entity_config_key}."
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 = types.ModuleType(entity_config_key)
189
- sys.modules[entity_config_key] = module
190
- module.__dict__["__builtins__"] = __builtins__
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
- namespace.module = module
193
- namespace.entities_dict = module.__dict__
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[entity_id] = loaded_entity
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
- entity_id: str,
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 {entity_id} no redis"
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, entity_id
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, entity_id
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, entity_id
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, entity_id
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
- entity_id: str,
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 entity_id in grupo_namespace.loaded_entities:
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 entity_id in tenant_namespace.loaded_entities:
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 entity_id in default_namespace.loaded_entities:
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.7
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