nsj-rest-lib2 0.0.19__tar.gz → 0.0.21__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/PKG-INFO +5 -1
  2. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/README.md +4 -0
  3. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/compiler.py +213 -19
  4. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/dto_compiler.py +34 -6
  5. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/edl_model/entity_model_base.py +4 -0
  6. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/edl_model/repository_model.py +23 -0
  7. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/entity_compiler.py +87 -30
  8. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/property_compiler.py +5 -1
  9. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2.egg-info/PKG-INFO +5 -1
  10. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/setup.cfg +1 -1
  11. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/__init__.py +0 -0
  12. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/__init__.py +0 -0
  13. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/ai_compiler.py +0 -0
  14. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/compiler_structures.py +0 -0
  15. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/edl_model/__init__.py +0 -0
  16. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/edl_model/ai_entity_edl.py +0 -0
  17. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/edl_model/api_model.py +0 -0
  18. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/edl_model/column_meta_model.py +0 -0
  19. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/edl_model/entity_model.py +0 -0
  20. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/edl_model/entity_model_root.py +0 -0
  21. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/edl_model/index_model.py +0 -0
  22. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/edl_model/primitives.py +0 -0
  23. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/edl_model/property_meta_model.py +0 -0
  24. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/edl_model/trait_property_meta_model.py +0 -0
  25. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/model.py +0 -0
  26. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/util/__init__.py +0 -0
  27. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/util/str_util.py +0 -0
  28. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/util/type_naming_util.py +0 -0
  29. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/compiler/util/type_util.py +0 -0
  30. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/controller/__init__.py +0 -0
  31. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/controller/dynamic_controller.py +0 -0
  32. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/exception.py +0 -0
  33. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/redis_config.py +0 -0
  34. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/service/__init__.py +0 -0
  35. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/service/entity_loader.py +0 -0
  36. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2/settings.py +0 -0
  37. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2.egg-info/SOURCES.txt +0 -0
  38. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2.egg-info/dependency_links.txt +0 -0
  39. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2.egg-info/requires.txt +0 -0
  40. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/nsj_rest_lib2.egg-info/top_level.txt +0 -0
  41. {nsj_rest_lib2-0.0.19 → nsj_rest_lib2-0.0.21}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nsj_rest_lib2
3
- Version: 0.0.19
3
+ Version: 0.0.21
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
@@ -26,6 +26,10 @@ Biblioteca para permitir a distribuição de rotas dinâmicas numa API, configur
26
26
  [ESPECIFICAÇÃO DO MODELO DE ENTIDADES](docs/especificacao.md)
27
27
 
28
28
  ## TODO
29
+ * Valores default
30
+ * Usar trait_properties como valor default
31
+ * Não está carregando a lista de dependências de execução, para os components
32
+ * Implementar gravação de entidades com extensão parcial
29
33
  * Unificar o arquivo redis_config.py
30
34
  * Usar pydantic, ou similar, para transformar a configuração das entidades, no redis, num objeto
31
35
  * Rever modo de usar o InjectFactory (talvez dando ciência, ao RestLib, do padrão multibanco)
@@ -5,6 +5,10 @@ Biblioteca para permitir a distribuição de rotas dinâmicas numa API, configur
5
5
  [ESPECIFICAÇÃO DO MODELO DE ENTIDADES](docs/especificacao.md)
6
6
 
7
7
  ## TODO
8
+ * Valores default
9
+ * Usar trait_properties como valor default
10
+ * Não está carregando a lista de dependências de execução, para os components
11
+ * Implementar gravação de entidades com extensão parcial
8
12
  * Unificar o arquivo redis_config.py
9
13
  * Usar pydantic, ou similar, para transformar a configuração das entidades, no redis, num objeto
10
14
  * Rever modo de usar o InjectFactory (talvez dando ciência, ao RestLib, do padrão multibanco)
@@ -12,9 +12,15 @@ from nsj_rest_lib2.compiler.edl_model.entity_model_base import EntityModelBase
12
12
  from nsj_rest_lib2.compiler.edl_model.entity_model_root import EntityModelRoot
13
13
  from nsj_rest_lib2.compiler.edl_model.primitives import REGEX_EXTERNAL_REF
14
14
  from nsj_rest_lib2.compiler.entity_compiler import EntityCompiler
15
- from nsj_rest_lib2.compiler.model import CompilerResult
15
+ from nsj_rest_lib2.compiler.model import CompilerResult, RelationDependency
16
16
  from nsj_rest_lib2.compiler.property_compiler import EDLPropertyCompiler
17
17
 
18
+ from nsj_rest_lib2.compiler.util.type_naming_util import (
19
+ compile_dto_class_name,
20
+ compile_entity_class_name,
21
+ compile_namespace_keys,
22
+ )
23
+
18
24
  from nsj_rest_lib2.compiler.edl_model.entity_model import EntityModel
19
25
 
20
26
  from nsj_rest_lib2.settings import get_logger
@@ -96,6 +102,27 @@ class EDLCompiler:
96
102
  if not escopo:
97
103
  raise Exception(f"Escopo não definido para a entidade: {entity_model.id}.")
98
104
 
105
+ # Tratando dos dados base para a extensão parcial
106
+ partial_metadata: dict[str, str] | None = None
107
+ partial_base_model: EntityModel | None = None
108
+ if isinstance(entity_model, EntityModel) and entity_model.partial_of:
109
+ if entity_model.partial_of not in entity_models:
110
+ raise Exception(
111
+ f"Entidade base '{entity_model.partial_of}' não encontrada para a extensão parcial '{entity_model.id}'."
112
+ )
113
+
114
+ base_model_candidate = entity_models[entity_model.partial_of]
115
+ if not isinstance(base_model_candidate, EntityModel):
116
+ raise Exception(
117
+ f"Entidade base '{entity_model.partial_of}' da extensão parcial '{entity_model.id}' é inválida."
118
+ )
119
+
120
+ self._validate_partial_model(entity_model, base_model_candidate, entity_models)
121
+ partial_metadata = self._build_partial_metadata(
122
+ entity_model, base_model_candidate
123
+ )
124
+ partial_base_model = base_model_candidate
125
+
99
126
  # Criando um mapa de índices por nome de property
100
127
  # TODO Implementar tratamento dos índices de apoio às query (não de unicidade)
101
128
  map_indexes_by_property: dict[str, list[IndexCompilerStructure]] = {}
@@ -131,6 +158,18 @@ class EDLCompiler:
131
158
  prefx_class_name,
132
159
  )
133
160
 
161
+ # Adicionando os imports da extensão parcial
162
+ if partial_metadata:
163
+ related_imports.append(
164
+ (
165
+ partial_metadata["module"],
166
+ partial_metadata["dto_class"],
167
+ partial_metadata["entity_class"],
168
+ )
169
+ )
170
+
171
+ related_imports = self._deduplicate_related_imports(related_imports)
172
+
134
173
  # Gerando o buffer para os códigos de DTO e Entity
135
174
  dto_code = ""
136
175
  entity_code = ""
@@ -175,6 +214,7 @@ class EDLCompiler:
175
214
  related_imports,
176
215
  fixed_filters,
177
216
  prefx_class_name,
217
+ partial_metadata,
178
218
  )
179
219
 
180
220
  # Gerando o código da Entity
@@ -183,26 +223,42 @@ class EDLCompiler:
183
223
  ast_entity_attributes,
184
224
  props_pk,
185
225
  prefx_class_name,
226
+ partial_metadata,
186
227
  )
187
228
 
188
229
  # Extendendo os buffers com os códigos gerados
189
230
  dto_code += code_dto
190
231
  entity_code += code_entity
232
+
233
+ # Adicionando as dependências das relações
191
234
  relations_dependencies_complete.extend(relations_dependencies)
192
235
 
236
+ # Adicionando as dependências da extensão parcial
237
+ if partial_metadata and partial_base_model:
238
+ relation_dependency = RelationDependency()
239
+ if not partial_base_model.api or not partial_base_model.api.resource:
240
+ raise Exception(
241
+ f"Entidade base '{partial_base_model.id}' não possui configuração de API necessária para extensão parcial."
242
+ )
243
+ relation_dependency.entity_resource = partial_base_model.api.resource
244
+ relation_dependency.entity_scope = partial_base_model.escopo
245
+ relation_dependency.tenant = partial_base_model.tenant
246
+ relation_dependency.grupo_empresarial = partial_base_model.grupo_empresarial
247
+ relations_dependencies_complete.append(relation_dependency)
248
+
193
249
  # Construindo o resultado
194
250
  compiler_result = CompilerResult()
195
251
  compiler_result.entity_class_name = entity_class_name
196
252
  compiler_result.entity_code = entity_code
197
253
  compiler_result.dto_class_name = dto_class_name
198
254
  compiler_result.dto_code = dto_code
255
+ compiler_result.relations_dependencies = relations_dependencies_complete
199
256
 
200
257
  # Compilando questões das APIs
201
258
  if isinstance(entity_model, EntityModel):
202
259
  compiler_result.api_expose = entity_model.api.expose
203
260
  compiler_result.api_resource = entity_model.api.resource
204
261
  compiler_result.api_verbs = entity_model.api.verbs
205
- compiler_result.relations_dependencies = relations_dependencies_complete
206
262
 
207
263
  get_logger().debug(f"código gerado para a entidade: {entity_model.id}")
208
264
  get_logger().debug("DTO Code:")
@@ -212,6 +268,138 @@ class EDLCompiler:
212
268
 
213
269
  return compiler_result
214
270
 
271
+ def _validate_partial_model(
272
+ self,
273
+ partial_model: EntityModel,
274
+ base_model: EntityModel,
275
+ entity_models: dict[str, EntityModel],
276
+ ) -> None:
277
+ base_properties_structure = PropertiesCompilerStructure()
278
+ self._make_properties_structures(
279
+ base_properties_structure,
280
+ base_model,
281
+ entity_models,
282
+ )
283
+ aggregated_base_properties = set(base_properties_structure.properties.keys())
284
+
285
+ duplicated_properties = aggregated_base_properties.intersection(
286
+ set(partial_model.properties.keys())
287
+ )
288
+ if duplicated_properties:
289
+ raise Exception(
290
+ f"Extensão parcial '{partial_model.id}' redefine propriedades da entidade base '{base_model.id}': {sorted(duplicated_properties)}."
291
+ )
292
+
293
+ lists_to_check = {
294
+ "required": partial_model.required,
295
+ "main_properties": partial_model.main_properties,
296
+ "partition_data": partial_model.partition_data,
297
+ "search_properties": partial_model.search_properties,
298
+ "metric_label": partial_model.metric_label,
299
+ }
300
+
301
+ for list_name, values in lists_to_check.items():
302
+ if not values:
303
+ continue
304
+
305
+ conflicts = [
306
+ value
307
+ for value in values
308
+ if isinstance(value, str) and value in aggregated_base_properties
309
+ ]
310
+ if conflicts:
311
+ raise Exception(
312
+ f"Extensão parcial '{partial_model.id}' utiliza propriedades da entidade base '{base_model.id}' em '{list_name}': {conflicts}."
313
+ )
314
+
315
+ link = partial_model.repository.link_to_base
316
+ if not link:
317
+ raise Exception(
318
+ f"Extensão parcial '{partial_model.id}' requer configuração 'link_to_base' no bloco 'repository'."
319
+ )
320
+
321
+ if not link.base_property:
322
+ raise Exception(
323
+ f"Extensão parcial '{partial_model.id}' requer 'base_property' definido em 'link_to_base'."
324
+ )
325
+
326
+ if not link.column:
327
+ raise Exception(
328
+ f"Extensão parcial '{partial_model.id}' requer 'column' definido em 'link_to_base'."
329
+ )
330
+
331
+ if link.base_property not in aggregated_base_properties:
332
+ raise Exception(
333
+ f"'base_property' '{link.base_property}' não corresponde a uma propriedade da entidade base '{base_model.id}'."
334
+ )
335
+
336
+ # if not base_model.api or not base_model.api.resource:
337
+ # raise Exception(
338
+ # f"Entidade base '{base_model.id}' não possui configuração de API compatível com extensões parciais."
339
+ # )
340
+
341
+ def _build_partial_metadata(
342
+ self, partial_model: EntityModel, base_model: EntityModel
343
+ ) -> dict[str, str]:
344
+ link = partial_model.repository.link_to_base
345
+ if link is None:
346
+ raise Exception(
347
+ f"Extensão parcial '{partial_model.id}' está sem configuração 'link_to_base'."
348
+ )
349
+
350
+ grupo_key, tenant_key, default_key = compile_namespace_keys(
351
+ base_model.tenant, base_model.grupo_empresarial
352
+ )
353
+ namespace = self._resolve_namespace_key(
354
+ base_model.tenant,
355
+ base_model.grupo_empresarial,
356
+ grupo_key,
357
+ tenant_key,
358
+ default_key,
359
+ )
360
+
361
+ return {
362
+ "module": namespace,
363
+ "dto_class": compile_dto_class_name(base_model.id),
364
+ "entity_class": compile_entity_class_name(base_model.id),
365
+ "relation_field": link.column,
366
+ "related_entity_field": link.base_property,
367
+ }
368
+
369
+ def _resolve_namespace_key(
370
+ self,
371
+ tenant: str | int | None,
372
+ grupo_empresarial,
373
+ grupo_key: str,
374
+ tenant_key: str,
375
+ default_key: str,
376
+ ) -> str:
377
+ has_tenant = tenant not in (None, 0, "0")
378
+ has_grupo = (
379
+ grupo_empresarial
380
+ and str(grupo_empresarial) != "00000000-0000-0000-0000-000000000000"
381
+ )
382
+
383
+ if has_tenant and has_grupo:
384
+ return grupo_key
385
+ if has_tenant:
386
+ return tenant_key
387
+ return default_key
388
+
389
+ def _deduplicate_related_imports(
390
+ self, related_imports: list[tuple[str, str, str]]
391
+ ) -> list[tuple[str, str, str]]:
392
+ deduped: list[tuple[str, str, str]] = []
393
+ seen: set[tuple[str, str, str]] = set()
394
+
395
+ for import_tuple in related_imports:
396
+ if import_tuple in seen:
397
+ continue
398
+ seen.add(import_tuple)
399
+ deduped.append(import_tuple)
400
+
401
+ return deduped
402
+
215
403
  def _make_components_structures(
216
404
  self,
217
405
  components_structure: ComponentsCompilerStructure,
@@ -295,7 +483,9 @@ class EDLCompiler:
295
483
  continue
296
484
 
297
485
  if "/" in main_property:
298
- path_parts = [part.strip() for part in main_property.split("/") if part]
486
+ path_parts = [
487
+ part.strip() for part in main_property.split("/") if part
488
+ ]
299
489
  if len(path_parts) < 2 or not path_parts[0]:
300
490
  raise Exception(
301
491
  f"Propriedade resumo inválida '{main_property}' na entidade '{entity_model.id}'."
@@ -430,6 +620,10 @@ class EDLCompiler:
430
620
  if entity_model.trait_from:
431
621
  entities.append(entity_model.trait_from)
432
622
 
623
+ # Adicionando dependências por classes parciais
624
+ if isinstance(entity_model, EntityModel) and entity_model.partial_of:
625
+ entities.append(entity_model.partial_of)
626
+
433
627
  # Populando com as dependências de propriedades de relacionamento
434
628
  relations = self._list_dependencies_relations(entity_model)
435
629
 
@@ -512,19 +706,19 @@ if __name__ == "__main__":
512
706
  compiler = EDLCompiler()
513
707
  compiler_results = compiler.compile_models(entities)
514
708
 
515
- for compiler_result in compiler_results:
516
- print("==========================================================")
517
- print(f"Entity: {compiler_result.entity_class_name}")
518
- print(f"{compiler_result.entity_code}")
519
- print("\n")
520
- print("==========================================================")
521
- print(f"DTO: {compiler_result.dto_class_name}")
522
- print(f"{compiler_result.dto_code}")
523
- print("\n")
524
-
525
- print("==========================================================")
526
- print("API Expose: ", compiler_result.api_expose)
527
- print("API Route Path: ", compiler_result.api_resource)
528
- print("API Verbs: ", compiler_result.api_verbs)
529
- print("==========================================================")
530
- print("\n")
709
+ with open("output_compilacao_local.py", "w") as f:
710
+ for compiler_result in compiler_results:
711
+ f.write("==========================================================\n")
712
+ f.write(f"Entity: {compiler_result.entity_class_name}\n")
713
+ f.write(f"{compiler_result.entity_code}\n")
714
+ f.write("\n")
715
+ f.write("==========================================================\n")
716
+ f.write(f"DTO: {compiler_result.dto_class_name}\n")
717
+ f.write(f"{compiler_result.dto_code}\n")
718
+ f.write("\n")
719
+ f.write("==========================================================\n")
720
+ f.write(f"API Expose: {compiler_result.api_expose}\n")
721
+ f.write(f"API Route Path: {compiler_result.api_resource}\n")
722
+ f.write(f"API Verbs: {compiler_result.api_verbs}\n")
723
+ f.write("==========================================================\n")
724
+ f.write("\n")
@@ -20,6 +20,7 @@ class DTOCompiler:
20
20
  related_imports: list[tuple[str, str, str]],
21
21
  fixed_filters: list[tuple[str, BasicTypes]],
22
22
  prefx_class_name: str,
23
+ partial_metadata: dict[str, str] | None,
23
24
  ) -> tuple[str, str]:
24
25
  """
25
26
  Compila o código do DTO a partir do AST e retorna o código compilado.
@@ -110,15 +111,42 @@ class DTOCompiler:
110
111
  )
111
112
  )
112
113
 
114
+ # Keywords para a extensão parcial
115
+ decorator_keywords: list[ast.keyword] = []
116
+
117
+ if partial_metadata:
118
+ partial_dict_keys = [
119
+ ast.Constant(value="dto"),
120
+ ast.Constant(value="relation_field"),
121
+ ]
122
+ partial_dict_values = [
123
+ ast.Name(id=partial_metadata["dto_class"], ctx=ast.Load()),
124
+ ast.Constant(value=partial_metadata["relation_field"]),
125
+ ]
126
+
127
+ related_field = partial_metadata.get("related_entity_field")
128
+ if related_field:
129
+ partial_dict_keys.append(ast.Constant(value="related_entity_field"))
130
+ partial_dict_values.append(ast.Constant(value=related_field))
131
+
132
+ decorator_keywords.append(
133
+ ast.keyword(
134
+ arg="partial_of",
135
+ value=ast.Dict(
136
+ keys=partial_dict_keys,
137
+ values=partial_dict_values,
138
+ ),
139
+ )
140
+ )
141
+
113
142
  # Keywords para tipos usados em fixed_filters
114
- keywords_decorator_dto = []
115
- for fixed_filter in fixed_filters:
116
- keywords_decorator_dto.append(
143
+ if fixed_filters:
144
+ decorator_keywords.append(
117
145
  ast.keyword(
118
146
  arg="fixed_filters",
119
147
  value=ast.Dict(
120
- keys=[ast.Constant(value=fixed_filter[0])],
121
- values=[ast.Constant(value=fixed_filter[1])],
148
+ keys=[ast.Constant(value=item[0]) for item in fixed_filters],
149
+ values=[ast.Constant(value=item[1]) for item in fixed_filters],
122
150
  ),
123
151
  )
124
152
  )
@@ -133,7 +161,7 @@ class DTOCompiler:
133
161
  ast.Call(
134
162
  func=ast.Name(id="DTO", ctx=ast.Load()),
135
163
  args=[],
136
- keywords=keywords_decorator_dto,
164
+ keywords=decorator_keywords,
137
165
  )
138
166
  ],
139
167
  body=ast_dto_attributes,
@@ -29,6 +29,10 @@ class EntityModelBase(BaseModel):
29
29
  trait_from: Optional[str] = Field(
30
30
  None, description="Identificador da entidade que a trait estende."
31
31
  )
32
+ partial_of: Optional[str] = Field(
33
+ None,
34
+ description="Identificador da entidade base estendida por esta extensão parcial (relacionamento 1x1 transparente).",
35
+ )
32
36
  mixins: Optional[List[str]] = Field(
33
37
  None,
34
38
  description="Lista de mixins (blocos reutilizáveis) aplicados ao modelo.",
@@ -5,6 +5,25 @@ from nsj_rest_lib2.compiler.edl_model.column_meta_model import ColumnMetaModel
5
5
  from nsj_rest_lib2.compiler.edl_model.index_model import IndexModel
6
6
 
7
7
 
8
+ class RepositoryLinkToBaseModel(BaseModel):
9
+ base_property: str = Field(
10
+ ...,
11
+ description="Nome da propriedade na entidade base utilizada para o relacionamento 1x1.",
12
+ )
13
+ column: str = Field(
14
+ ...,
15
+ description="Nome da coluna (ou propriedade) na entidade parcial responsável por referenciar a entidade base.",
16
+ )
17
+ on_delete: Optional[str] = Field(
18
+ None,
19
+ description="Política de exclusão configurada no relacionamento com a entidade base.",
20
+ )
21
+ nullable: Optional[bool] = Field(
22
+ False,
23
+ description="Indica se o relacionamento com a entidade base pode ser nulo.",
24
+ )
25
+
26
+
8
27
  class RepositoryModel(BaseModel):
9
28
  map: str = Field(
10
29
  ..., description="Nome da tabela, no BD, para a qual a entidade é mapeada."
@@ -22,3 +41,7 @@ class RepositoryModel(BaseModel):
22
41
  indexes: Optional[List[IndexModel]] = Field(
23
42
  None, description="Lista de índices de banco de dados, associados à entidade."
24
43
  )
44
+ link_to_base: Optional[RepositoryLinkToBaseModel] = Field(
45
+ None,
46
+ description="Configuração de relacionamento 1x1 com a entidade base (extensões parciais).",
47
+ )
@@ -18,6 +18,7 @@ class EntityCompiler:
18
18
  ast_entity_attributes: list[ast.stmt],
19
19
  props_pk: list[str],
20
20
  prefix_class_name: str,
21
+ partial_metadata: dict[str, str] | None,
21
22
  ) -> tuple[str, str]:
22
23
  # Imports
23
24
  imports = [
@@ -39,25 +40,53 @@ class EntityCompiler:
39
40
  ),
40
41
  ]
41
42
 
43
+ if partial_metadata:
44
+ imports.append(
45
+ ast.ImportFrom(
46
+ module=f"dynamic.{partial_metadata['module']}",
47
+ names=[
48
+ ast.alias(name=partial_metadata["entity_class"], asname=None)
49
+ ],
50
+ level=0,
51
+ )
52
+ )
53
+
42
54
  # Entity
43
55
  if len(props_pk) > 1:
44
56
  raise Exception(
45
57
  f"Entidade '{entity_model.id}' possui mais de uma chave primária (ainda não suportado): {props_pk}"
46
58
  )
47
- elif len(props_pk) <= 0:
59
+
60
+ if len(props_pk) == 0 and not partial_metadata:
48
61
  raise Exception(
49
62
  f"Entidade '{entity_model.id}' não possui nenhuma chave primária (ainda não suportado)."
50
63
  )
51
64
 
52
65
  default_order_props = []
53
66
 
54
- key_field = props_pk[0]
55
- if entity_model.repository.properties:
56
- if (
57
- key_field in entity_model.repository.properties
58
- and entity_model.repository.properties[key_field].column
59
- ):
60
- key_field = entity_model.repository.properties[props_pk[0]].column
67
+ # Resolvendo o nome da coluna da chave primária
68
+ if len(props_pk) == 1:
69
+ key_field_property = props_pk[0]
70
+ else:
71
+ key_field_property = (
72
+ partial_metadata["relation_field"] if partial_metadata else None
73
+ )
74
+
75
+ key_field = key_field_property
76
+ if (
77
+ len(props_pk) == 1
78
+ and entity_model.repository.properties
79
+ and key_field_property in entity_model.repository.properties
80
+ and entity_model.repository.properties[key_field_property].column
81
+ ):
82
+ key_field = entity_model.repository.properties[key_field_property].column
83
+ elif partial_metadata and partial_metadata.get("relation_field"):
84
+ key_field = partial_metadata["relation_field"]
85
+
86
+ if key_field is None:
87
+ raise Exception(
88
+ f"Não foi possível determinar a chave primária para a entidade '{entity_model.id}'."
89
+ )
61
90
 
62
91
  if (
63
92
  isinstance(entity_model, EntityModel)
@@ -90,28 +119,9 @@ class EntityCompiler:
90
119
  ast.Call(
91
120
  func=ast.Name(id="Entity", ctx=ast.Load()),
92
121
  args=[],
93
- keywords=[
94
- ast.keyword(
95
- arg="table_name",
96
- value=ast.Constant(value=entity_model.repository.map),
97
- ),
98
- ast.keyword(
99
- arg="pk_field",
100
- value=ast.Constant(
101
- value=CompilerStrUtil.to_snake_case(key_field)
102
- ),
103
- ),
104
- ast.keyword(
105
- arg="default_order_fields",
106
- value=ast.List(
107
- elts=[
108
- ast.Constant(value=field)
109
- for field in default_order_fields
110
- ],
111
- ctx=ast.Load(),
112
- ),
113
- ),
114
- ],
122
+ keywords=self._build_entity_decorator_keywords(
123
+ entity_model, key_field, default_order_fields, partial_metadata
124
+ ),
115
125
  )
116
126
  ],
117
127
  body=ast_entity_attributes,
@@ -131,3 +141,50 @@ class EntityCompiler:
131
141
  code = black.format_str(code, mode=black.FileMode())
132
142
 
133
143
  return (class_name, code)
144
+
145
+ def _build_entity_decorator_keywords(
146
+ self,
147
+ entity_model: EntityModelBase,
148
+ key_field: str,
149
+ default_order_fields: list[str],
150
+ partial_metadata: dict[str, str] | None,
151
+ ) -> list[ast.keyword]:
152
+ keywords = [
153
+ ast.keyword(
154
+ arg="table_name",
155
+ value=ast.Constant(value=entity_model.repository.map),
156
+ ),
157
+ ]
158
+
159
+ if not partial_metadata:
160
+ keywords.extend(
161
+ [
162
+ ast.keyword(
163
+ arg="pk_field",
164
+ value=ast.Constant(
165
+ value=CompilerStrUtil.to_snake_case(key_field)
166
+ ),
167
+ ),
168
+ ast.keyword(
169
+ arg="default_order_fields",
170
+ value=ast.List(
171
+ elts=[
172
+ ast.Constant(value=field)
173
+ for field in default_order_fields
174
+ ],
175
+ ctx=ast.Load(),
176
+ ),
177
+ ),
178
+ ]
179
+ )
180
+
181
+ if partial_metadata:
182
+ keywords.insert(
183
+ 0,
184
+ ast.keyword(
185
+ arg="partial_of",
186
+ value=ast.Name(id=partial_metadata["entity_class"], ctx=ast.Load()),
187
+ ),
188
+ )
189
+
190
+ return keywords
@@ -64,12 +64,16 @@ class EDLPropertyCompiler:
64
64
  if prop.pk:
65
65
  pk_keys.append(pkey)
66
66
 
67
+ is_partial_extension = (
68
+ isinstance(entity_model, EntityModel) and bool(entity_model.partial_of)
69
+ )
70
+
67
71
  if not entity_model.mixin:
68
72
  if len(pk_keys) > 1:
69
73
  raise Exception(
70
74
  f"Entidade '{entity_model.id}' possui mais de uma chave primária (ainda não suportado): {pk_keys}"
71
75
  )
72
- elif len(pk_keys) == 0:
76
+ elif len(pk_keys) == 0 and not is_partial_extension:
73
77
  raise Exception(
74
78
  f"Entidade '{entity_model.id}' não tem nenhuma chave primária (ainda não suportado)"
75
79
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nsj_rest_lib2
3
- Version: 0.0.19
3
+ Version: 0.0.21
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
@@ -26,6 +26,10 @@ Biblioteca para permitir a distribuição de rotas dinâmicas numa API, configur
26
26
  [ESPECIFICAÇÃO DO MODELO DE ENTIDADES](docs/especificacao.md)
27
27
 
28
28
  ## TODO
29
+ * Valores default
30
+ * Usar trait_properties como valor default
31
+ * Não está carregando a lista de dependências de execução, para os components
32
+ * Implementar gravação de entidades com extensão parcial
29
33
  * Unificar o arquivo redis_config.py
30
34
  * Usar pydantic, ou similar, para transformar a configuração das entidades, no redis, num objeto
31
35
  * Rever modo de usar o InjectFactory (talvez dando ciência, ao RestLib, do padrão multibanco)
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = nsj_rest_lib2
3
- version = 0.0.19
3
+ version = 0.0.21
4
4
  author = Nasajon Sistemas
5
5
  author_email = contact.dev@nasajon.com.br
6
6
  description = Biblioteca para permitir a distribuição de rotas dinâmicas numa API, configuradas por meio de EDLs declarativos (em formato JSON).