nsj-rest-lib2 0.0.36__py3-none-any.whl → 0.0.38__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.
@@ -398,15 +398,13 @@ class EDLCompiler:
398
398
  post_expected, post_properties = (
399
399
  response_dto_compiler.handler_result_details(post_handler)
400
400
  )
401
- put_expected, put_properties = (
402
- response_dto_compiler.handler_result_details(put_handler)
401
+ put_expected, put_properties = response_dto_compiler.handler_result_details(
402
+ put_handler
403
403
  )
404
404
  patch_expected, patch_properties = (
405
405
  response_dto_compiler.handler_result_details(patch_handler)
406
406
  )
407
- get_expected, _ = response_dto_compiler.handler_result_details(
408
- get_handler
409
- )
407
+ get_expected, _ = response_dto_compiler.handler_result_details(get_handler)
410
408
  list_expected, _ = response_dto_compiler.handler_result_details(
411
409
  list_handler
412
410
  )
@@ -471,32 +469,16 @@ class EDLCompiler:
471
469
  )
472
470
  compiler_result.retrieve_after_insert = post_expected == "entity_row"
473
471
  compiler_result.retrieve_after_update = put_expected == "entity_row"
474
- compiler_result.retrieve_after_partial_update = (
475
- patch_expected == "entity_row"
476
- )
472
+ compiler_result.retrieve_after_partial_update = patch_expected == "entity_row"
477
473
  compiler_result.post_response_dto_class_name = compiler_result_post_class
478
474
  compiler_result.put_response_dto_class_name = compiler_result_put_class
479
- compiler_result.patch_response_dto_class_name = (
480
- compiler_result_patch_class
481
- )
482
- compiler_result.custom_json_post_response = (
483
- post_expected == "custom_json"
484
- )
485
- compiler_result.custom_json_put_response = (
486
- put_expected == "custom_json"
487
- )
488
- compiler_result.custom_json_patch_response = (
489
- patch_expected == "custom_json"
490
- )
491
- compiler_result.custom_json_get_response = (
492
- get_expected == "custom_json"
493
- )
494
- compiler_result.custom_json_list_response = (
495
- list_expected == "custom_json"
496
- )
497
- compiler_result.custom_json_delete_response = (
498
- delete_expected == "custom_json"
499
- )
475
+ compiler_result.patch_response_dto_class_name = compiler_result_patch_class
476
+ compiler_result.custom_json_post_response = post_expected == "custom_json"
477
+ compiler_result.custom_json_put_response = put_expected == "custom_json"
478
+ compiler_result.custom_json_patch_response = patch_expected == "custom_json"
479
+ compiler_result.custom_json_get_response = get_expected == "custom_json"
480
+ compiler_result.custom_json_list_response = list_expected == "custom_json"
481
+ compiler_result.custom_json_delete_response = delete_expected == "custom_json"
500
482
 
501
483
  insert_code_compiled = insert_function_code.strip()
502
484
  update_code_compiled = update_function_code.strip()
@@ -1,6 +1,8 @@
1
+ import hashlib
1
2
  import uuid
2
- from typing import Iterable
3
+ from typing import Iterable, Iterator, Optional
3
4
 
5
+ from nsj_rest_lib2.compiler.model import CompilerResult
4
6
  from nsj_rest_lib2.compiler.util.str_util import CompilerStrUtil
5
7
 
6
8
 
@@ -14,6 +16,67 @@ def compile_namespace_keys(
14
16
  return (grupo_key, tenant_key, default_key)
15
17
 
16
18
 
19
+ def resolve_namespace_key(
20
+ tenant: str | int | None, grupo_empresarial: str | uuid.UUID | None
21
+ ) -> str:
22
+ grupo_key, tenant_key, default_key = compile_namespace_keys(
23
+ tenant,
24
+ grupo_empresarial,
25
+ )
26
+
27
+ if (
28
+ tenant
29
+ and tenant != 0
30
+ and grupo_empresarial
31
+ and str(grupo_empresarial) != "00000000-0000-0000-0000-000000000000"
32
+ ):
33
+ return grupo_key
34
+ if tenant and tenant != 0:
35
+ return tenant_key
36
+ return default_key
37
+
38
+
39
+ def build_entity_hash(compiler_result: CompilerResult) -> str:
40
+ hasher = hashlib.sha256()
41
+
42
+ for content in _iter_hash_chunks(
43
+ compiler_result.dto_code,
44
+ compiler_result.entity_code,
45
+ compiler_result.insert_function_name,
46
+ compiler_result.update_function_name,
47
+ compiler_result.get_function_name,
48
+ compiler_result.list_function_name,
49
+ compiler_result.delete_function_name,
50
+ compiler_result.source_insert_function,
51
+ compiler_result.source_update_function,
52
+ compiler_result.source_get_function_type,
53
+ compiler_result.source_list_function_type,
54
+ compiler_result.source_delete_function_type,
55
+ compiler_result.post_response_dto_class_name,
56
+ compiler_result.put_response_dto_class_name,
57
+ compiler_result.patch_response_dto_class_name,
58
+ str(compiler_result.retrieve_after_insert),
59
+ str(compiler_result.retrieve_after_update),
60
+ str(compiler_result.retrieve_after_partial_update),
61
+ str(compiler_result.custom_json_post_response),
62
+ str(compiler_result.custom_json_put_response),
63
+ str(compiler_result.custom_json_patch_response),
64
+ str(compiler_result.custom_json_get_response),
65
+ str(compiler_result.custom_json_list_response),
66
+ str(compiler_result.custom_json_delete_response),
67
+ compiler_result.service_account,
68
+ ):
69
+ hasher.update(content)
70
+
71
+ return hasher.hexdigest()
72
+
73
+
74
+ def _iter_hash_chunks(*chunks: Optional[str]) -> Iterator[bytes]:
75
+ for chunk in chunks:
76
+ if chunk:
77
+ yield chunk.encode("utf-8")
78
+
79
+
17
80
  def compile_dto_class_name(entity_id: str, prefx_class_name: str = "") -> str:
18
81
  return f"{CompilerStrUtil.to_pascal_case(prefx_class_name)}{CompilerStrUtil.to_pascal_case(entity_id)}DTO"
19
82
 
@@ -55,6 +55,7 @@ def setup_dynamic_routes(
55
55
  dynamic_root_path: str = "edl1",
56
56
  injector_factory: Any = None,
57
57
  escopo_in_url: bool = False,
58
+ edls_path: str | None = None,
58
59
  ) -> None:
59
60
 
60
61
  if not escopo_in_url:
@@ -68,6 +69,8 @@ def setup_dynamic_routes(
68
69
  f"/{APP_NAME}/{dynamic_root_path}/<entity_escopo>/<entity_resource>/<id>"
69
70
  )
70
71
 
72
+ entity_loader = EntityLoader(edls_path=edls_path)
73
+
71
74
  def _dynamic_route_wrapper(
72
75
  route_builder: Callable[[tuple[Any, ...]], Callable[..., Any]],
73
76
  endpoint_suffix: str | None = None,
@@ -83,7 +86,6 @@ def setup_dynamic_routes(
83
86
  tenant, grupo_empresarial, force_reload = _get_query_args()
84
87
 
85
88
  try:
86
- entity_loader = EntityLoader()
87
89
  entity_config = entity_loader.load_entity_source(
88
90
  entity_resource,
89
91
  tenant,
@@ -156,7 +158,7 @@ def setup_dynamic_routes(
156
158
  custom_json_response=bool(custom_json_list_response),
157
159
  )
158
160
 
159
- return route.handle_request(*args, **kwargs)
161
+ return route.internal_handle_request(*args, **kwargs)
160
162
 
161
163
  return list_dynamic
162
164
 
@@ -199,7 +201,7 @@ def setup_dynamic_routes(
199
201
  custom_json_response=bool(custom_json_get_response),
200
202
  )
201
203
 
202
- return route.handle_request(*args, **kwargs)
204
+ return route.internal_handle_request(*args, **kwargs)
203
205
 
204
206
  return get_dynamic
205
207
 
@@ -264,7 +266,7 @@ def setup_dynamic_routes(
264
266
  custom_json_response=bool(custom_json_post_response),
265
267
  )
266
268
 
267
- return route.handle_request(*args, **kwargs)
269
+ return route.internal_handle_request(*args, **kwargs)
268
270
 
269
271
  return post_dynamic
270
272
 
@@ -329,7 +331,7 @@ def setup_dynamic_routes(
329
331
  custom_json_response=bool(custom_json_put_response),
330
332
  )
331
333
 
332
- return route.handle_request(*args, **kwargs)
334
+ return route.internal_handle_request(*args, **kwargs)
333
335
 
334
336
  return put_dynamic
335
337
 
@@ -386,7 +388,7 @@ def setup_dynamic_routes(
386
388
  custom_json_response=bool(custom_json_patch_response),
387
389
  )
388
390
 
389
- return route.handle_request(*args, **kwargs)
391
+ return route.internal_handle_request(*args, **kwargs)
390
392
 
391
393
  return patch_dynamic
392
394
 
@@ -431,7 +433,7 @@ def setup_dynamic_routes(
431
433
  custom_json_response=bool(custom_json_delete_response),
432
434
  )
433
435
 
434
- return route.handle_request(*args, **kwargs)
436
+ return route.internal_handle_request(*args, **kwargs)
435
437
 
436
438
  return delete_dynamic
437
439
 
@@ -1,13 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
- import hashlib
4
3
  import json
5
- from typing import Any, Dict, Iterator, Optional
4
+ from typing import Any, Dict
6
5
 
7
6
  from nsj_rest_lib2.compiler.edl_model.entity_model import EntityModel
8
7
  from nsj_rest_lib2.compiler.edl_model.entity_model_base import EntityModelBase
9
8
  from nsj_rest_lib2.compiler.model import CompilerResult
10
- from nsj_rest_lib2.compiler.util.type_naming_util import compile_namespace_keys
9
+ from nsj_rest_lib2.compiler.util.type_naming_util import (
10
+ build_entity_hash,
11
+ resolve_namespace_key,
12
+ )
11
13
  from nsj_rest_lib2.redis_config import set_redis
12
14
  from nsj_rest_lib2.settings import ESCOPO_RESTLIB2
13
15
 
@@ -113,21 +115,7 @@ class EntityConfigWriter:
113
115
  tenant = getattr(entity_model, "tenant", None)
114
116
  grupo_empresarial = getattr(entity_model, "grupo_empresarial", None)
115
117
 
116
- grupo_key, tenant_key, default_key = compile_namespace_keys(
117
- tenant,
118
- grupo_empresarial,
119
- )
120
-
121
- if (
122
- tenant
123
- and tenant != 0
124
- and grupo_empresarial
125
- and str(grupo_empresarial) != "00000000-0000-0000-0000-000000000000"
126
- ):
127
- return grupo_key
128
- if tenant and tenant != 0:
129
- return tenant_key
130
- return default_key
118
+ return resolve_namespace_key(tenant, grupo_empresarial)
131
119
 
132
120
  def _resolve_resource(self, entity_model: EntityModelBase) -> str:
133
121
  if not isinstance(entity_model, EntityModel) or not entity_model.api:
@@ -139,41 +127,4 @@ class EntityConfigWriter:
139
127
  return entity_model.api.resource
140
128
 
141
129
  def _build_entity_hash(self, compiler_result: CompilerResult) -> str:
142
- hasher = hashlib.sha256()
143
-
144
- for content in self._iter_hash_chunks(
145
- compiler_result.dto_code,
146
- compiler_result.entity_code,
147
- compiler_result.insert_function_name,
148
- compiler_result.update_function_name,
149
- compiler_result.get_function_name,
150
- compiler_result.list_function_name,
151
- compiler_result.delete_function_name,
152
- compiler_result.source_insert_function,
153
- compiler_result.source_update_function,
154
- compiler_result.source_get_function_type,
155
- compiler_result.source_list_function_type,
156
- compiler_result.source_delete_function_type,
157
- compiler_result.post_response_dto_class_name,
158
- compiler_result.put_response_dto_class_name,
159
- compiler_result.patch_response_dto_class_name,
160
- str(compiler_result.retrieve_after_insert),
161
- str(compiler_result.retrieve_after_update),
162
- str(compiler_result.retrieve_after_partial_update),
163
- str(compiler_result.custom_json_post_response),
164
- str(compiler_result.custom_json_put_response),
165
- str(compiler_result.custom_json_patch_response),
166
- str(compiler_result.custom_json_get_response),
167
- str(compiler_result.custom_json_list_response),
168
- str(compiler_result.custom_json_delete_response),
169
- compiler_result.service_account,
170
- ):
171
- hasher.update(content)
172
-
173
- return hasher.hexdigest()
174
-
175
- @staticmethod
176
- def _iter_hash_chunks(*chunks: Optional[str]) -> Iterator[bytes]:
177
- for chunk in chunks:
178
- if chunk:
179
- yield chunk.encode("utf-8")
130
+ return build_entity_hash(compiler_result)
@@ -1,18 +1,34 @@
1
1
  import datetime
2
2
  import json
3
+ import os
3
4
  import re
4
5
  import sys
5
6
  import threading
6
7
  import types
8
+ from typing import Any
7
9
 
8
10
  from nsj_rest_lib.settings import get_logger
9
11
 
12
+ import yaml
13
+
14
+ from nsj_rest_lib2.compiler.compiler import EDLCompiler
15
+ from nsj_rest_lib2.compiler.edl_model.entity_model import EntityModel
16
+ from nsj_rest_lib2.compiler.edl_model.entity_model_base import EntityModelBase
17
+ from nsj_rest_lib2.compiler.edl_model.entity_model_root import EntityModelRoot
10
18
  from nsj_rest_lib2.compiler.edl_model.primitives import REGEX_EXTERNAL_REF
11
- from nsj_rest_lib2.compiler.model import RelationDependency
12
- from nsj_rest_lib2.compiler.util.type_naming_util import compile_namespace_keys
19
+ from nsj_rest_lib2.compiler.model import CompilerResult, RelationDependency
20
+ from nsj_rest_lib2.compiler.util.type_naming_util import (
21
+ build_entity_hash as build_entity_hash_util,
22
+ compile_namespace_keys,
23
+ resolve_namespace_key as resolve_namespace_key_util,
24
+ )
25
+ from nsj_rest_lib2.dto.escopo_dto import EscopoDTO
13
26
  from nsj_rest_lib2.exception import MissingEntityConfigException
14
- from nsj_rest_lib2.redis_config import get_redis
15
- from nsj_rest_lib2.settings import ESCOPO_RESTLIB2, MIN_TIME_SOURCE_REFRESH
27
+ from nsj_rest_lib2.settings import (
28
+ EDLS_FROM_REDIS,
29
+ ESCOPO_RESTLIB2,
30
+ MIN_TIME_SOURCE_REFRESH,
31
+ )
16
32
 
17
33
 
18
34
  class LoadedEntity:
@@ -24,6 +40,7 @@ class LoadedEntity:
24
40
  self.api_expose: bool = False
25
41
  self.api_verbs: list[str] = []
26
42
  self.relations_dependencies: list[RelationDependency] = []
43
+ self.never_expire: bool = False
27
44
  self.service_account: str | None = None
28
45
  self.insert_function_class_name: str | None = None
29
46
  self.update_function_class_name: str | None = None
@@ -61,8 +78,10 @@ namespaces_dict: dict[str, Namespace] = {}
61
78
 
62
79
 
63
80
  class EntityLoader:
64
- def __init__(self) -> types.NoneType:
81
+ def __init__(self, edls_path: str | None = None) -> types.NoneType:
65
82
  self._lock = threading.Lock()
83
+ if edls_path:
84
+ self._load_edls_from_disk(edls_path)
66
85
 
67
86
  def load_entity_source(
68
87
  self,
@@ -147,6 +166,37 @@ class EntityLoader:
147
166
  loaded_entity.delete_function_type_class_name
148
167
  )
149
168
 
169
+ cached_result = (
170
+ dto_class_name,
171
+ entity_class_name,
172
+ entities_dict,
173
+ api_expose,
174
+ api_verbs,
175
+ service_account,
176
+ insert_function_class_name,
177
+ update_function_class_name,
178
+ insert_function_name,
179
+ update_function_name,
180
+ get_function_name,
181
+ list_function_name,
182
+ delete_function_name,
183
+ get_function_type_class_name,
184
+ list_function_type_class_name,
185
+ delete_function_type_class_name,
186
+ loaded_entity.retrieve_after_insert,
187
+ loaded_entity.retrieve_after_update,
188
+ loaded_entity.retrieve_after_partial_update,
189
+ loaded_entity.post_response_dto_class_name,
190
+ loaded_entity.put_response_dto_class_name,
191
+ loaded_entity.patch_response_dto_class_name,
192
+ loaded_entity.custom_json_post_response,
193
+ loaded_entity.custom_json_put_response,
194
+ loaded_entity.custom_json_patch_response,
195
+ loaded_entity.custom_json_get_response,
196
+ loaded_entity.custom_json_list_response,
197
+ loaded_entity.custom_json_delete_response,
198
+ )
199
+
150
200
  # Verificando se alguma de suas dependências precisariam ser recarregadas
151
201
  for rd in relations_dependencies:
152
202
  if rd.entity_resource is None or rd.entity_scope is None:
@@ -162,6 +212,9 @@ class EntityLoader:
162
212
  force_reload=force_reload,
163
213
  )
164
214
 
215
+ if loaded_entity.never_expire or not EDLS_FROM_REDIS:
216
+ return cached_result
217
+
165
218
  # Se o tempo entre o carregamento e agora for maior do que MIN_TIME_SOURCE_REFRESH minutos,
166
219
  # verifica se precisa de refresh
167
220
  time_diff = datetime.datetime.now() - loaded_entity.loaded_at
@@ -185,36 +238,7 @@ class EntityLoader:
185
238
 
186
239
  # Se não achar no redis, usa o que estava em memória
187
240
  if not loaded_config:
188
- return (
189
- dto_class_name,
190
- entity_class_name,
191
- entities_dict,
192
- api_expose,
193
- api_verbs,
194
- service_account,
195
- insert_function_class_name,
196
- update_function_class_name,
197
- insert_function_name,
198
- update_function_name,
199
- get_function_name,
200
- list_function_name,
201
- delete_function_name,
202
- get_function_type_class_name,
203
- list_function_type_class_name,
204
- delete_function_type_class_name,
205
- loaded_entity.retrieve_after_insert,
206
- loaded_entity.retrieve_after_update,
207
- loaded_entity.retrieve_after_partial_update,
208
- loaded_entity.post_response_dto_class_name,
209
- loaded_entity.put_response_dto_class_name,
210
- loaded_entity.patch_response_dto_class_name,
211
- loaded_entity.custom_json_post_response,
212
- loaded_entity.custom_json_put_response,
213
- loaded_entity.custom_json_patch_response,
214
- loaded_entity.custom_json_get_response,
215
- loaded_entity.custom_json_list_response,
216
- loaded_entity.custom_json_delete_response,
217
- )
241
+ return cached_result
218
242
 
219
243
  # Desempacotando resultado
220
244
  entity_config_key, entity_config_str = loaded_config
@@ -229,36 +253,7 @@ class EntityLoader:
229
253
 
230
254
  # Se não carregou novo código, usa o que estava em memória
231
255
  if result_execute is None:
232
- return (
233
- dto_class_name,
234
- entity_class_name,
235
- entities_dict,
236
- api_expose,
237
- api_verbs,
238
- service_account,
239
- insert_function_class_name,
240
- update_function_class_name,
241
- insert_function_name,
242
- update_function_name,
243
- get_function_name,
244
- list_function_name,
245
- delete_function_name,
246
- get_function_type_class_name,
247
- list_function_type_class_name,
248
- delete_function_type_class_name,
249
- loaded_entity.retrieve_after_insert,
250
- loaded_entity.retrieve_after_update,
251
- loaded_entity.retrieve_after_partial_update,
252
- loaded_entity.post_response_dto_class_name,
253
- loaded_entity.put_response_dto_class_name,
254
- loaded_entity.patch_response_dto_class_name,
255
- loaded_entity.custom_json_post_response,
256
- loaded_entity.custom_json_put_response,
257
- loaded_entity.custom_json_patch_response,
258
- loaded_entity.custom_json_get_response,
259
- loaded_entity.custom_json_list_response,
260
- loaded_entity.custom_json_delete_response,
261
- )
256
+ return cached_result
262
257
  else:
263
258
  (
264
259
  dto_class_name,
@@ -322,36 +317,10 @@ class EntityLoader:
322
317
  )
323
318
  else:
324
319
  # Se não deu o intervalo de verificação do refresh, retorna o que está em memória
325
- return (
326
- dto_class_name,
327
- entity_class_name,
328
- entities_dict,
329
- api_expose,
330
- api_verbs,
331
- service_account,
332
- insert_function_class_name,
333
- update_function_class_name,
334
- insert_function_name,
335
- update_function_name,
336
- get_function_name,
337
- list_function_name,
338
- delete_function_name,
339
- get_function_type_class_name,
340
- list_function_type_class_name,
341
- delete_function_type_class_name,
342
- loaded_entity.retrieve_after_insert,
343
- loaded_entity.retrieve_after_update,
344
- loaded_entity.retrieve_after_partial_update,
345
- loaded_entity.post_response_dto_class_name,
346
- loaded_entity.put_response_dto_class_name,
347
- loaded_entity.patch_response_dto_class_name,
348
- loaded_entity.custom_json_post_response,
349
- loaded_entity.custom_json_put_response,
350
- loaded_entity.custom_json_patch_response,
351
- loaded_entity.custom_json_get_response,
352
- loaded_entity.custom_json_list_response,
353
- loaded_entity.custom_json_delete_response,
354
- )
320
+ return cached_result
321
+
322
+ if not EDLS_FROM_REDIS:
323
+ raise MissingEntityConfigException()
355
324
 
356
325
  # Se não conseguir recuperar a entidade, procura no redis:
357
326
  loaded_config = self._load_entity_config_from_redis(
@@ -451,6 +420,178 @@ class EntityLoader:
451
420
  with self._lock:
452
421
  namespaces_dict.clear()
453
422
 
423
+ def _load_edls_from_disk(self, edls_path: str) -> None:
424
+ if namespaces_dict:
425
+ return
426
+
427
+ resolved_path = self._resolve_edls_path(edls_path)
428
+ if not resolved_path:
429
+ return
430
+
431
+ if os.path.isdir(resolved_path):
432
+ edl_files = self._list_edl_files(resolved_path)
433
+ elif os.path.isfile(resolved_path) and self._is_edl_file(resolved_path):
434
+ edl_files = [resolved_path]
435
+ else:
436
+ return
437
+
438
+ if not edl_files:
439
+ return
440
+
441
+ entities: dict[str, EntityModelBase] = {}
442
+ for file_path in edl_files:
443
+ edl_json = self._read_edl_file(file_path)
444
+ if not isinstance(edl_json, dict):
445
+ continue
446
+
447
+ if edl_json.get("mixin", False):
448
+ entity_model = EntityModelRoot(**edl_json)
449
+ else:
450
+ entity_model = EntityModel(**edl_json)
451
+
452
+ complete_entity_id = f"{entity_model.escopo}/{entity_model.id}"
453
+ entities[complete_entity_id] = entity_model
454
+
455
+ if not entities:
456
+ return
457
+
458
+ escopos: dict[str, EscopoDTO] = {}
459
+ for entity_model in entities.values():
460
+ if entity_model.escopo not in escopos:
461
+ escopos[entity_model.escopo] = EscopoDTO(
462
+ codigo=entity_model.escopo,
463
+ service_account=None,
464
+ )
465
+
466
+ compiler = EDLCompiler()
467
+ dependencies = list(entities.items())
468
+
469
+ for entity_model in entities.values():
470
+ escopo_dto = escopos.get(entity_model.escopo)
471
+ if escopo_dto is None:
472
+ continue
473
+
474
+ compiler_result = compiler.compile_model(
475
+ entity_model,
476
+ dependencies,
477
+ escopo=escopo_dto,
478
+ )
479
+ if not compiler_result:
480
+ continue
481
+
482
+ entity_config = self._build_entity_payload(
483
+ entity_model,
484
+ compiler_result,
485
+ )
486
+ entity_resource = entity_config.get("api_resource")
487
+ if not entity_resource:
488
+ continue
489
+
490
+ entity_config_key = self._resolve_namespace_key(entity_model)
491
+ self._execute_entity_source(
492
+ json.dumps(entity_config, ensure_ascii=False),
493
+ entity_config_key,
494
+ entity_resource,
495
+ from_disk=True,
496
+ skip_dependency_load=not EDLS_FROM_REDIS,
497
+ )
498
+
499
+ def _resolve_edls_path(self, edls_path: str) -> str | None:
500
+ edls_path = edls_path.strip()
501
+ if not edls_path:
502
+ return None
503
+ if os.path.isabs(edls_path):
504
+ return edls_path
505
+ return os.path.join(os.getcwd(), edls_path)
506
+
507
+ def _is_edl_file(self, file_path: str) -> bool:
508
+ lower_name = file_path.lower()
509
+ return lower_name.endswith(".json") or lower_name.endswith(
510
+ ".yml"
511
+ ) or lower_name.endswith(".yaml")
512
+
513
+ def _list_edl_files(self, root_path: str) -> list[str]:
514
+ edl_files: list[str] = []
515
+ for current_root, _, files in os.walk(root_path):
516
+ for file_name in files:
517
+ file_path = os.path.join(current_root, file_name)
518
+ if self._is_edl_file(file_path):
519
+ edl_files.append(file_path)
520
+ edl_files.sort()
521
+ return edl_files
522
+
523
+ def _read_edl_file(self, file_path: str) -> dict[str, Any] | None:
524
+ with open(file_path, "r", encoding="utf-8") as file_handler:
525
+ if file_path.lower().endswith(".json"):
526
+ return json.load(file_handler)
527
+ return yaml.safe_load(file_handler)
528
+
529
+ def _resolve_namespace_key(self, entity_model: EntityModelBase) -> str:
530
+ tenant = getattr(entity_model, "tenant", None)
531
+ grupo_empresarial = getattr(entity_model, "grupo_empresarial", None)
532
+
533
+ return resolve_namespace_key_util(tenant, grupo_empresarial)
534
+
535
+ def _build_entity_payload(
536
+ self,
537
+ entity_model: EntityModelBase,
538
+ compiler_result: CompilerResult,
539
+ ) -> dict[str, Any]:
540
+ api_resource = compiler_result.api_resource
541
+ if not api_resource:
542
+ if isinstance(entity_model, EntityModel) and entity_model.api:
543
+ api_resource = entity_model.api.resource
544
+ if not api_resource:
545
+ raise ValueError("api.resource é obrigatório para carregar EDLs.")
546
+
547
+ relations = [
548
+ rd.to_dict()
549
+ for rd in (compiler_result.relations_dependencies or [])
550
+ ]
551
+
552
+ return {
553
+ "dto_class_name": compiler_result.dto_class_name,
554
+ "entity_class_name": compiler_result.entity_class_name,
555
+ "service_account": compiler_result.service_account,
556
+ "insert_function_class_name": compiler_result.insert_function_class_name,
557
+ "insert_function_name": compiler_result.insert_function_name,
558
+ "source_insert_function": compiler_result.source_insert_function,
559
+ "update_function_class_name": compiler_result.update_function_class_name,
560
+ "update_function_name": compiler_result.update_function_name,
561
+ "source_update_function": compiler_result.source_update_function,
562
+ "get_function_name": compiler_result.get_function_name,
563
+ "list_function_name": compiler_result.list_function_name,
564
+ "delete_function_name": compiler_result.delete_function_name,
565
+ "get_function_type_class_name": compiler_result.get_function_type_class_name,
566
+ "list_function_type_class_name": compiler_result.list_function_type_class_name,
567
+ "delete_function_type_class_name": compiler_result.delete_function_type_class_name,
568
+ "source_get_function_type": compiler_result.source_get_function_type,
569
+ "source_list_function_type": compiler_result.source_list_function_type,
570
+ "source_delete_function_type": compiler_result.source_delete_function_type,
571
+ "retrieve_after_insert": compiler_result.retrieve_after_insert,
572
+ "retrieve_after_update": compiler_result.retrieve_after_update,
573
+ "retrieve_after_partial_update": compiler_result.retrieve_after_partial_update,
574
+ "post_response_dto_class_name": compiler_result.post_response_dto_class_name,
575
+ "put_response_dto_class_name": compiler_result.put_response_dto_class_name,
576
+ "patch_response_dto_class_name": compiler_result.patch_response_dto_class_name,
577
+ "custom_json_post_response": compiler_result.custom_json_post_response,
578
+ "custom_json_put_response": compiler_result.custom_json_put_response,
579
+ "custom_json_patch_response": compiler_result.custom_json_patch_response,
580
+ "custom_json_get_response": compiler_result.custom_json_get_response,
581
+ "custom_json_list_response": compiler_result.custom_json_list_response,
582
+ "custom_json_delete_response": compiler_result.custom_json_delete_response,
583
+ "source_dto": compiler_result.dto_code,
584
+ "source_entity": compiler_result.entity_code,
585
+ "entity_hash": self._build_entity_hash(compiler_result),
586
+ "api_expose": compiler_result.api_expose,
587
+ "api_resource": api_resource,
588
+ "api_verbs": compiler_result.api_verbs or [],
589
+ "relations_dependencies": relations,
590
+ }
591
+
592
+ def _build_entity_hash(self, compiler_result: CompilerResult) -> str:
593
+ return build_entity_hash_util(compiler_result)
594
+
454
595
  def _ensure_dynamic_package(self):
455
596
  """
456
597
  Garante que exista um pacote 'dynamic' em sys.modules.
@@ -469,6 +610,8 @@ class EntityLoader:
469
610
  entity_config_key: str,
470
611
  entity_resource: str,
471
612
  check_refresh: bool = False,
613
+ from_disk: bool = False,
614
+ skip_dependency_load: bool = False,
472
615
  ) -> tuple[
473
616
  str,
474
617
  str,
@@ -590,18 +733,19 @@ class EntityLoader:
590
733
  return None
591
734
 
592
735
  # Verificando se alguma de suas dependências precisariam ser carregadas (ou recarregadas)
593
- for rd in relations_dependencies:
594
- if rd.entity_resource is None or rd.entity_scope is None:
595
- raise RuntimeError(
596
- f"Erro: Dependência de entidade mal formada na entidade {entity_resource}."
597
- )
736
+ if not skip_dependency_load:
737
+ for rd in relations_dependencies:
738
+ if rd.entity_resource is None or rd.entity_scope is None:
739
+ raise RuntimeError(
740
+ f"Erro: Dependência de entidade mal formada na entidade {entity_resource}."
741
+ )
598
742
 
599
- self.load_entity_source(
600
- rd.entity_resource,
601
- str(rd.tenant),
602
- str(rd.grupo_empresarial),
603
- rd.entity_scope,
604
- )
743
+ self.load_entity_source(
744
+ rd.entity_resource,
745
+ str(rd.tenant),
746
+ str(rd.grupo_empresarial),
747
+ rd.entity_scope,
748
+ )
605
749
 
606
750
  # Verificando se a entidade precisa ou não de refresh
607
751
  if check_refresh:
@@ -703,6 +847,7 @@ class EntityLoader:
703
847
  loaded_entity.api_verbs = api_verbs
704
848
  loaded_entity.service_account = service_account
705
849
  loaded_entity.relations_dependencies = relations_dependencies
850
+ loaded_entity.never_expire = from_disk
706
851
  loaded_entity.insert_function_class_name = insert_function_class_name
707
852
  loaded_entity.update_function_class_name = update_function_class_name
708
853
  loaded_entity.insert_function_name = insert_function_name
@@ -801,6 +946,11 @@ class EntityLoader:
801
946
  entity_config_key: str | None,
802
947
  escopo: str,
803
948
  ) -> tuple[str, str] | None:
949
+ if not EDLS_FROM_REDIS:
950
+ return None
951
+
952
+ from nsj_rest_lib2.redis_config import get_redis
953
+
804
954
  get_logger().debug(
805
955
  f"Procurando a configuração da entidade {entity_resource} no redis. Tenant key: {tenant_key} e Grupo key: {grupo_key}"
806
956
  )
nsj_rest_lib2/settings.py CHANGED
@@ -12,5 +12,15 @@ else:
12
12
  MIN_TIME_SOURCE_REFRESH = int(os.getenv("MIN_TIME_SOURCE_REFRESH", "15"))
13
13
 
14
14
 
15
+ def _get_env_bool(name: str, default: bool) -> bool:
16
+ value = os.getenv(name)
17
+ if value is None:
18
+ return default
19
+ return value.strip().lower() in ("1", "true", "t", "yes", "y", "on")
20
+
21
+
22
+ EDLS_FROM_REDIS = _get_env_bool("EDLS_FROM_REDIS", True)
23
+
24
+
15
25
  def get_logger():
16
26
  return logging.getLogger(APP_NAME)
@@ -0,0 +1,203 @@
1
+ Metadata-Version: 2.4
2
+ Name: nsj_rest_lib2
3
+ Version: 0.0.38
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<7.0.0,>=5.1.3
16
+ Requires-Dist: redis<7.0.0,>=6.4.0
17
+ Requires-Dist: nsj-multi-database-lib<3.0.0,>=2.0.1
18
+ Requires-Dist: pydantic<3.0.0,>=2.11.9
19
+ Requires-Dist: black<26.0.0,>=25.1.0
20
+ Requires-Dist: pyyaml<7.0.0,>=6.0.3
21
+
22
+ # RestLib2
23
+
24
+ O RestLib2 é uma plataforma que tem por objetivo o fornecimento de APIs para os clientes, a partir da descrição da entidades envolvidas, e sem a necessidade de programação imperativa tradicional.
25
+
26
+ Em resumo, a partir da declaração em JSON das entidades que compõe um negócio, bem como dos relacionamentos entre estas, as APIs já estarão disponíveis em produção.
27
+
28
+ O objetivo final é permitir que o desenvolvimento de APIs se torne rápido e simples, acessível àqueles que tratam diretamente com o negócio (incluindo implantadores, e, no limite, o próprio cliente).
29
+
30
+ ## Links
31
+
32
+ [Guia do EDL](docs/README.md)
33
+ [ESPECIFICAÇÃO DO MODELO DE ENTIDADES](docs/especificacao.md)
34
+
35
+ ## Rota das APIs geradas
36
+
37
+ A rota para acesso a uma API gerada por variar de acordo com o modo de configuração escolhido pela aplicação que usar o RestLib2. Mas, via de regra, sugere-se o padrão aplicado na API de DadosMestre:
38
+
39
+ ```http
40
+ #############################
41
+ # List Prod
42
+ #############################
43
+ GET https://api.nasajon.app/dados-mestre/edl1/clientes?tenant=47&grupo_empresarial=NASAJON HTTP/1.1
44
+ Authorization: Bearer *****
45
+ Accept: application/json
46
+ ```
47
+
48
+ * Note que, na API de Dados Mestre, todas as APIs do RestLib2 ficarão debaixo da rota: ```https://api.nasajon.app/dados-mestre/edl1/```
49
+ * O endpoint em si, varia a cada JSON, e fica configurado no nó ```api.resource``` do JSON de EDL em questão.
50
+
51
+ ## Fluxo básico de uso
52
+
53
+ Há dois modos básicos de publicar novas APIs a partir de um JSON gerado, para uma aplicação previamente configurada para uso da plataforma:
54
+
55
+ ### Fluxo por meio de código versionado
56
+ 1. Criar o JSON de descrição da entidade desejada, gravando-o no diretório "@schemas/entities" da aplicação em questão (tome [como exemplo o "dados-mestre"](https://github.com/Nasajon/dados-mestre-api/tree/production/%40schemas)).
57
+ 2. Fazer push das alterações.
58
+ 3. Aguardar o build da aplicação (pode ser útil conferir os logs do job de atualização dos JSON, na aplicação em questão, e também os logs do [worker de compilação](https://ci.nasajon.in/applications/restlib2?view=pods&conditions=false), no Argo).
59
+ 4. Testar as APIs compiladas.
60
+
61
+ ### Fluxo de deploy direto pela API
62
+ 1. Criar o JSON de descrição da entidade desejada.
63
+ 2. Registrá-lo diretamente, por meio da API de controle do RestLib2. segue exemplo de chamada abaixo:
64
+
65
+ ```http
66
+ ###############################
67
+ # Post dinâmico
68
+ ###############################
69
+ POST https://api.nasajon.app/restlib2/entities HTTP/1.1
70
+ Authorization: Bearer ******
71
+ Content-Type: application/json
72
+
73
+ {
74
+ "id": "{UUID}",
75
+ "escopo": "{escopo}",
76
+ "tenant": 0,
77
+ "grupo_empresarial": "00000000-0000-0000-0000-000000000000",
78
+ "codigo": "{codigo da entidade}",
79
+ "descricao": "{descrição da entidade}",
80
+ "json_schema": {
81
+ "edl_version": "1.0",
82
+ "escopo": "{escopo}",
83
+ "description": "{descrição da entidade}",
84
+ "id": "cliente",
85
+ "version": "0.0.1",
86
+ ...
87
+ }
88
+ }
89
+ ```
90
+
91
+ Observações:
92
+ * Note que, acima há placeholders a alterar.
93
+ * Note também que o JSON não está completo, e, o correto é deguir a documentação do EDL.
94
+ * Os valores tenant=0 e grupo_empresarial=00000000-0000-0000-0000-000000000000, indicam que a entidade é padrão, e serve para todos os tenants e grupos.
95
+
96
+ 3. Essa rota irá retornar algo semelhante a:
97
+
98
+ ```http
99
+ HTTP/1.1 202 ACCEPTED
100
+ Location: https://api.nasajon.app/restlib2/entity-compilations/status/{UUID do processo de compilação}
101
+ ```
102
+
103
+ E, se você fizer uma chamada à rota retornada, poderá acompanhar o status da compilação, incluindo eventuais erros que venham a ocorrer.
104
+
105
+ 4. Testar a API compilada.
106
+
107
+ ## Como rodar a compilação de EDLs localmente?
108
+
109
+ Você pode testar a compilação de seus EDLs localmente, incluindo a execução de diversas validações sobre os mesmos, desde que todos estejam dispostos num mesmo diertório.
110
+
111
+ Para isso, considere os passos a seguir:
112
+
113
+ 1. Crie um diretório, e coloque todos os seus EDLs lá (garantindo que EDLs relacionados estejam no mesmo).
114
+ 2. Instale, no seu ambiente pyhton de teste, a biblioteca `nsj-rest-lib2`:
115
+
116
+ ```sh
117
+ pip install nsj-rest-lib2
118
+ ```
119
+
120
+ 3. Rode o comando abaixo (adaptado para seu diretório):
121
+ ```sh
122
+ python3 -m nsj_rest_lib2.compiler.compiler -d $(shell pwd)/{diretorio_com_os_edls_json}
123
+ ```
124
+
125
+ O resultado da compilação será impresso no console (erros, ou código total gerado).
126
+
127
+ ## Como configurar uma aplicação para expôr rotas de acordo com o padrão do RestLib2?
128
+
129
+ Para que uma aplicação exponha as rotas no padrão do RestLib2, não são necessários muitos passos. Antes basta:
130
+
131
+ 1. Instalar, em sua aplicação, a dependência para o projeto nsj-rest-lib2
132
+
133
+ ```sh
134
+ pip install nsj-rest-lib2
135
+ ```
136
+
137
+ Não esqueça de fixar a versão usada (como sendo a última), no arquivo requirements.txt.
138
+
139
+ 2. Adicione a variável de ambiente abaixo em sua aplciação
140
+
141
+ ```env
142
+ ESCOPO_RESTLIB2: "{COLOQUE O IDENTIFICADOR DE ESCOPO QUE DESEJAR PARA SUA APLICAÇÃO (UMA SIMPLES STRING SEM ESPAÇO)}"
143
+ ```
144
+
145
+ * Esse identificador de escopo deve se único para sua aplicação.
146
+ * É importante nota que sua aplicação só irá expôr JSONs de EDL configurados para o mesmo escopo definido aqui.
147
+
148
+ 3. Instale, como dependência a biblioteca, nsj-rest-lib2:
149
+
150
+ ```sh
151
+ pip install nsj-rest-lib2
152
+ ```
153
+
154
+ Não esqueça de fixar a versão usada (como sendo a última), no arquivo requirements.txt.
155
+
156
+ 4. Adicione a linha abaixo no arquivo wsgi.py (de inicilização de sua aplicação):
157
+
158
+ ```python
159
+ from nsj_rest_lib2.controller.dynamic_controller import setup_dynamic_routes
160
+ from nasajon.injector_factory_multibanco import InjectorFactoryMultibanco
161
+
162
+ setup_dynamic_routes(application, injector_factory=InjectorFactoryMultibanco)
163
+ ```
164
+
165
+ * No exemplo acima, a rota é multibanco, mas, não é obrigatório.
166
+ * Os parâmetros de setup são:
167
+ * flask_app: Variável obrigatório, que aponte para sua aplicação Flask.
168
+ * multidb: Flag (padrão True)
169
+ * dynamic_root_path: URL padrão base de todos os endpoints do RestLib2 (padrão: "edl1")
170
+ * injector_factory: Classe de injeção de depndência usada (normalmente necessária para aplicações multibanco; o principal uso é justamente manipular a criação da conexão com o BD).
171
+
172
+ ## Carregando EDLs direto do disco
173
+
174
+ Também é possível carregar EDLs diretamente do disco, sem depender exclusivamente do Redis. Para isso, use o parâmetro opcional `edls_path` em `setup_dynamic_routes`, apontando para um caminho relativo ao workdir da aplicação (ou absoluto), contendo arquivos `.json`, `.yml` ou `.yaml` com os EDLs.
175
+
176
+ Exemplo:
177
+
178
+ ```python
179
+ from nsj_rest_lib2.controller.dynamic_controller import setup_dynamic_routes
180
+
181
+ setup_dynamic_routes(
182
+ application,
183
+ injector_factory=InjectorFactoryMultibanco,
184
+ edls_path="@schemas/entities",
185
+ )
186
+ ```
187
+
188
+ Observações:
189
+ * Os EDLs são carregados e compilados uma vez, ficando em cache em memória. Chamadas seguintes reutilizam o cache.
190
+ * A resolução das entidades ocorre primeiro pelo cache local; se não encontrar, o Redis é consultado (quando habilitado).
191
+
192
+ Para desabilitar o Redis e usar somente EDLs do disco, defina a variável de ambiente abaixo:
193
+
194
+ ```env
195
+ EDLS_FROM_REDIS=false
196
+ ```
197
+
198
+ O valor padrão de `EDLS_FROM_REDIS` é `true`.
199
+
200
+
201
+ **Pronto, isso deve bastar para expôr os EDLs configurados para o mesmo escopo da aplicação.**
202
+
203
+ **OBSERVAÇÃO GERAL: Isso só funciona para aplicações Flask.**
@@ -1,9 +1,9 @@
1
1
  nsj_rest_lib2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  nsj_rest_lib2/exception.py,sha256=E9uMUdoCCQOVQfc8f6gD9b5Pxurf3Q4SytDCcqSlkZ8,56
3
3
  nsj_rest_lib2/redis_config.py,sha256=4KLcvYS3nJO7PMQgF6F9_j6r-TyqcS7TBbd3LEQuKDU,629
4
- nsj_rest_lib2/settings.py,sha256=Hn_o1HZmievnYb8D1kNT2Nq-OEjxbyNjOiOpbnFsMwE,367
4
+ nsj_rest_lib2/settings.py,sha256=eK2aIFVhZK8kxEDHCW5agba5XP2aFPoh_PFnNLwCMH8,627
5
5
  nsj_rest_lib2/compiler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- nsj_rest_lib2/compiler/compiler.py,sha256=z-u9UeOhNKxH6bwaPWTxkN_SJjgZ6tGJD_iXc2D2Hr4,39433
6
+ nsj_rest_lib2/compiler/compiler.py,sha256=fiDDkLyc-E4b21j3LHZSiFRhBb4VY5cbxqFeZhjGVjw,39209
7
7
  nsj_rest_lib2/compiler/compiler_structures.py,sha256=stspjqJGXU7Vz3BqQ-ZF5ZmumFm3R4jpkWgVVsXW5d0,1488
8
8
  nsj_rest_lib2/compiler/dto_compiler.py,sha256=kT9v8z0MMHYGkVudkJA0VUY1tNCM7QzV6_RV-aWjSoE,7143
9
9
  nsj_rest_lib2/compiler/entity_compiler.py,sha256=LeGEBxsjAmZZog2gh4vjUX1aFp9JSVgHHOdTkY0aH-s,6733
@@ -32,18 +32,18 @@ nsj_rest_lib2/compiler/edl_model/trait_property_meta_model.py,sha256=NtMVZeOPu3L
32
32
  nsj_rest_lib2/compiler/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  nsj_rest_lib2/compiler/util/relation_ref.py,sha256=1M_e-NyeqozlIYLl1rB76KqkmtMvgtqH_nJS-NK0Pck,3695
34
34
  nsj_rest_lib2/compiler/util/str_util.py,sha256=0ReIQ2Vy4zAmVMvGv0FcUehRQw15hlz0e7yDsF89ghk,1178
35
- nsj_rest_lib2/compiler/util/type_naming_util.py,sha256=RAiYSM_tzRmfQZCCiH7_q6AAPB2_M24df6Ve_3LHQyc,2877
35
+ nsj_rest_lib2/compiler/util/type_naming_util.py,sha256=WKBqGZ0a-JO1IO5Q_rHVpLBESUmbc5D_lgF9DWBwkpk,5065
36
36
  nsj_rest_lib2/compiler/util/type_util.py,sha256=HTKOH4uRTOY0YgoM8oUv_6cEcReE_bgKYXFBsQCb-3A,357
37
37
  nsj_rest_lib2/controller/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- nsj_rest_lib2/controller/dynamic_controller.py,sha256=B9cVzJf61O0dJf7UymFz60wzjQMLoUVqEK4g_ccAi-U,17432
38
+ nsj_rest_lib2/controller/dynamic_controller.py,sha256=fgwUUiymg_ECA6kms5hf_-rFmnZTzkGhePv2gvKy_fw,17528
39
39
  nsj_rest_lib2/dto/__init__.py,sha256=MsSFjiLMLJZ7QhUPpVBWKiyDnCzryquRyr329NoCACI,2
40
40
  nsj_rest_lib2/dto/escopo_dto.py,sha256=R9gxRwYxOVYhGjR3q03_iXvHm14N905zbRKLsS3jI-A,1393
41
41
  nsj_rest_lib2/entity/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
42
  nsj_rest_lib2/entity/escopo_entity.py,sha256=T4bxFDzHJvIj-nZ_6d0Xh2oQg21HgoRjp19nt6clY18,338
43
43
  nsj_rest_lib2/service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
- nsj_rest_lib2/service/entity_config_writer.py,sha256=YBYo8a6H8zqt8CqKzPYCtrYjMnT2IOY8-KkJ8-6tPBs,7831
45
- nsj_rest_lib2/service/entity_loader.py,sha256=WNmnKcRYHPT44eiXXpiMglflJWx3BeqwXdenpXuNJsM,34237
46
- nsj_rest_lib2-0.0.36.dist-info/METADATA,sha256=lFFBWAgH8YMBDpaDjFpSHeBbT42L7JGXNigD1WkiO7g,1094
47
- nsj_rest_lib2-0.0.36.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
48
- nsj_rest_lib2-0.0.36.dist-info/top_level.txt,sha256=L6zh0EfH8_rur7OJ8_V-El-XEMf4qg3bkF8ADgqLVIA,14
49
- nsj_rest_lib2-0.0.36.dist-info/RECORD,,
44
+ nsj_rest_lib2/service/entity_config_writer.py,sha256=QVgEO3Tv5g3dK8MQgCrWD9BZyCKV-BxBLfYNFi53PX8,5806
45
+ nsj_rest_lib2/service/entity_loader.py,sha256=kCAt0uKgnrw4gr48_xjspLDQzl_5PxyUYnM8W1YzIxs,39775
46
+ nsj_rest_lib2-0.0.38.dist-info/METADATA,sha256=wLrQQsYtubI7njehcqfLtwFtRmzuJ750EmxeFAx6eN4,8260
47
+ nsj_rest_lib2-0.0.38.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
48
+ nsj_rest_lib2-0.0.38.dist-info/top_level.txt,sha256=L6zh0EfH8_rur7OJ8_V-El-XEMf4qg3bkF8ADgqLVIA,14
49
+ nsj_rest_lib2-0.0.38.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,27 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: nsj_rest_lib2
3
- Version: 0.0.36
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<7.0.0,>=5.1.3
16
- Requires-Dist: redis<7.0.0,>=6.4.0
17
- Requires-Dist: nsj-multi-database-lib<3.0.0,>=2.0.1
18
- Requires-Dist: pydantic<3.0.0,>=2.11.9
19
- Requires-Dist: black<26.0.0,>=25.1.0
20
- Requires-Dist: pyyaml<7.0.0,>=6.0.3
21
-
22
- # nsj_rest_lib2
23
-
24
- Biblioteca para permitir a distribuição de rotas dinâmicas numa API, configuradas por meio de EDLs declarativos (em formato JSON).
25
-
26
- [ESPECIFICAÇÃO DO MODELO DE ENTIDADES](docs/especificacao.md)
27
-