nsj-rest-lib2 0.0.21__tar.gz → 0.0.23__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 (42) hide show
  1. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/PKG-INFO +1 -14
  2. nsj_rest_lib2-0.0.23/README.md +6 -0
  3. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/compiler.py +3 -13
  4. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/edl_model/primitives.py +8 -0
  5. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/edl_model/property_meta_model.py +0 -1
  6. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/property_compiler.py +313 -247
  7. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/util/str_util.py +10 -0
  8. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/service/entity_loader.py +0 -1
  9. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2.egg-info/PKG-INFO +1 -14
  10. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/setup.cfg +1 -1
  11. nsj_rest_lib2-0.0.21/README.md +0 -19
  12. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/__init__.py +0 -0
  13. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/__init__.py +0 -0
  14. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/ai_compiler.py +0 -0
  15. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/compiler_structures.py +0 -0
  16. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/dto_compiler.py +0 -0
  17. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/edl_model/__init__.py +0 -0
  18. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/edl_model/ai_entity_edl.py +0 -0
  19. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/edl_model/api_model.py +0 -0
  20. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/edl_model/column_meta_model.py +0 -0
  21. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/edl_model/entity_model.py +0 -0
  22. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/edl_model/entity_model_base.py +0 -0
  23. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/edl_model/entity_model_root.py +0 -0
  24. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/edl_model/index_model.py +0 -0
  25. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/edl_model/repository_model.py +0 -0
  26. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/edl_model/trait_property_meta_model.py +0 -0
  27. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/entity_compiler.py +0 -0
  28. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/model.py +0 -0
  29. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/util/__init__.py +0 -0
  30. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/util/type_naming_util.py +0 -0
  31. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/compiler/util/type_util.py +0 -0
  32. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/controller/__init__.py +0 -0
  33. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/controller/dynamic_controller.py +0 -0
  34. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/exception.py +0 -0
  35. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/redis_config.py +0 -0
  36. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/service/__init__.py +0 -0
  37. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2/settings.py +0 -0
  38. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2.egg-info/SOURCES.txt +0 -0
  39. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2.egg-info/dependency_links.txt +0 -0
  40. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2.egg-info/requires.txt +0 -0
  41. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/nsj_rest_lib2.egg-info/top_level.txt +0 -0
  42. {nsj_rest_lib2-0.0.21 → nsj_rest_lib2-0.0.23}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nsj_rest_lib2
3
- Version: 0.0.21
3
+ Version: 0.0.23
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
@@ -25,16 +25,3 @@ Biblioteca para permitir a distribuição de rotas dinâmicas numa API, configur
25
25
 
26
26
  [ESPECIFICAÇÃO DO MODELO DE ENTIDADES](docs/especificacao.md)
27
27
 
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
33
- * Unificar o arquivo redis_config.py
34
- * Usar pydantic, ou similar, para transformar a configuração das entidades, no redis, num objeto
35
- * Rever modo de usar o InjectFactory (talvez dando ciência, ao RestLib, do padrão multibanco)
36
- * Implementar e documentar campos join
37
- * Implementar e documentar conjuntos
38
- * Dar erro ao haver conflito de nomes das propriedades (por qualquer tipo de herança)
39
- * Rotas para o inline
40
- * inline será para composição (arquivo externo é agregação)
@@ -0,0 +1,6 @@
1
+ # nsj_rest_lib2
2
+
3
+ Biblioteca para permitir a distribuição de rotas dinâmicas numa API, configuradas por meio de EDLs declarativos (em formato JSON).
4
+
5
+ [ESPECIFICAÇÃO DO MODELO DE ENTIDADES](docs/especificacao.md)
6
+
@@ -25,18 +25,6 @@ from nsj_rest_lib2.compiler.edl_model.entity_model import EntityModel
25
25
 
26
26
  from nsj_rest_lib2.settings import get_logger
27
27
 
28
- # TODO GET e POST de relacionamentos 1X1
29
- # TODO Revisar compilação de valores default (sensível a tipos)
30
- # TODO Implementar suporte a conjuntos
31
- # TODO Autenticação nas rotas
32
- # TODO Atualizar o status da entidade pelo worker de compilação (e talvez parar uma compilação, quando se delete uma entidade)
33
- # TODO Classes Abstratas
34
- # TODO Partial Classes
35
- # TODO Migrations
36
- # TODO Criar imagem docker base para as aplicações, usando venv (para poderem atualizar o pydantic)
37
-
38
- # TODO Suporte ao "min_items" dos relacionamentos no RestLib1
39
-
40
28
 
41
29
  class EDLCompiler:
42
30
  def __init__(self) -> None:
@@ -117,7 +105,9 @@ class EDLCompiler:
117
105
  f"Entidade base '{entity_model.partial_of}' da extensão parcial '{entity_model.id}' é inválida."
118
106
  )
119
107
 
120
- self._validate_partial_model(entity_model, base_model_candidate, entity_models)
108
+ self._validate_partial_model(
109
+ entity_model, base_model_candidate, entity_models
110
+ )
121
111
  partial_metadata = self._build_partial_metadata(
122
112
  entity_model, base_model_candidate
123
113
  )
@@ -48,6 +48,14 @@ MAPPING_PRIMITIVE_TYPES_TO_PYTHON = {
48
48
  BasicTypes = int | bool | float | str
49
49
  DefaultTypes = BasicTypes | List[BasicTypes]
50
50
 
51
+ STR_BASED_TYPES = {
52
+ PrimitiveTypes.STRING,
53
+ PrimitiveTypes.EMAIL,
54
+ PrimitiveTypes.CPF,
55
+ PrimitiveTypes.CNPJ,
56
+ PrimitiveTypes.CPF_CNPJ,
57
+ }
58
+
51
59
 
52
60
  class CardinalityTypes(enum.Enum):
53
61
  C1_1 = "1_1"
@@ -36,7 +36,6 @@ class PropertyMetaModel(BaseModel):
36
36
  None, description="Comprimento máximo (para strings)."
37
37
  )
38
38
 
39
- ## TODO Sugestões de validação
40
39
  min_length: Optional[int] = Field(
41
40
  None, description="Comprimento mínimo (para strings)."
42
41
  )
@@ -1,5 +1,7 @@
1
1
  import ast
2
+ import datetime
2
3
  import re
4
+ import uuid
3
5
 
4
6
  from nsj_rest_lib2.compiler.compiler_structures import (
5
7
  IndexCompilerStructure,
@@ -13,6 +15,7 @@ from nsj_rest_lib2.compiler.edl_model.primitives import (
13
15
  PrimitiveTypes,
14
16
  REGEX_EXTERNAL_REF,
15
17
  REGEX_INTERNAL_REF,
18
+ STR_BASED_TYPES,
16
19
  )
17
20
  from nsj_rest_lib2.compiler.edl_model.property_meta_model import PropertyMetaModel
18
21
  from nsj_rest_lib2.compiler.edl_model.trait_property_meta_model import (
@@ -27,10 +30,6 @@ from nsj_rest_lib2.compiler.util.type_naming_util import (
27
30
  )
28
31
  from nsj_rest_lib2.compiler.util.type_util import TypeUtil
29
32
 
30
- # TODO pattern
31
- # TODO lowercase
32
- # TODO uppercase
33
-
34
33
 
35
34
  class EDLPropertyCompiler:
36
35
 
@@ -51,9 +50,6 @@ class EDLPropertyCompiler:
51
50
  list[RelationDependency],
52
51
  list[tuple[str, BasicTypes]],
53
52
  ]:
54
-
55
- # TODO Criar opção de campo calculado?
56
-
57
53
  # Descobrindo os atributos marcados como PK (e recuperando a chave primária)
58
54
  # TODO Verificar se devemos manter essa verificação
59
55
  pk_keys = []
@@ -64,8 +60,8 @@ class EDLPropertyCompiler:
64
60
  if prop.pk:
65
61
  pk_keys.append(pkey)
66
62
 
67
- is_partial_extension = (
68
- isinstance(entity_model, EntityModel) and bool(entity_model.partial_of)
63
+ is_partial_extension = isinstance(entity_model, EntityModel) and bool(
64
+ entity_model.partial_of
69
65
  )
70
66
 
71
67
  if not entity_model.mixin:
@@ -92,6 +88,10 @@ class EDLPropertyCompiler:
92
88
  if properties_structure.properties is None:
93
89
  return (ast_dto_attributes, ast_entity_attributes, props_pk, aux_classes)
94
90
 
91
+ trait_fixed_filters = self._merge_trait_extends_properties(
92
+ properties_structure
93
+ )
94
+
95
95
  composed_properties = properties_structure.composed_properties or {}
96
96
 
97
97
  aggregator_class_names: dict[str, str] = {}
@@ -155,6 +155,9 @@ class EDLPropertyCompiler:
155
155
  prefx_class_name,
156
156
  )
157
157
 
158
+ if pkey in trait_fixed_filters:
159
+ fixed_filters.append((pkey, trait_fixed_filters[pkey]))
160
+
158
161
  elif isinstance(prop.type, str):
159
162
  # Tratando propriedade de relacionamento
160
163
  external_match = re.match(REGEX_EXTERNAL_REF, prop.type)
@@ -197,40 +200,6 @@ class EDLPropertyCompiler:
197
200
  f"Tipo da propriedade '{pkey}' não suportado: {prop.type}"
198
201
  )
199
202
 
200
- for pkey in properties_structure.trait_properties:
201
- prop = properties_structure.trait_properties[pkey]
202
-
203
- self._compile_trait_extends_property(
204
- properties_structure,
205
- map_unique_by_property,
206
- pkey,
207
- prop,
208
- ast_dto_attributes,
209
- ast_entity_attributes,
210
- fixed_filters,
211
- escopo,
212
- entity_model,
213
- aux_classes,
214
- prefx_class_name,
215
- )
216
-
217
- for pkey in properties_structure.extends_properties:
218
- prop = properties_structure.extends_properties[pkey]
219
-
220
- self._compile_trait_extends_property(
221
- properties_structure,
222
- map_unique_by_property,
223
- pkey,
224
- prop,
225
- ast_dto_attributes,
226
- ast_entity_attributes,
227
- fixed_filters,
228
- escopo,
229
- entity_model,
230
- aux_classes,
231
- prefx_class_name,
232
- )
233
-
234
203
  for composed_key, class_name in aggregator_class_names.items():
235
204
  dto_attributes = aggregator_dto_attributes.get(composed_key, [])
236
205
 
@@ -258,199 +227,6 @@ class EDLPropertyCompiler:
258
227
  fixed_filters,
259
228
  )
260
229
 
261
- def _compile_trait_extends_property(
262
- self,
263
- properties_structure: PropertiesCompilerStructure,
264
- map_unique_by_property: dict[str, IndexCompilerStructure],
265
- pkey: str,
266
- prop: TraitPropertyMetaModel,
267
- ast_dto_attributes: list[ast.stmt],
268
- ast_entity_attributes: list[ast.stmt],
269
- fixed_filters: list[tuple[str, BasicTypes]],
270
- escopo: str,
271
- entity_model: EntityModelBase,
272
- aux_classes: list[ast.stmt],
273
- prefx_class_name: str,
274
- ):
275
- enum_class_name = None
276
- keywords = []
277
-
278
- if (
279
- properties_structure.main_properties
280
- and pkey in properties_structure.main_properties
281
- ):
282
- keywords.append(ast.keyword(arg="resume", value=ast.Constant(True)))
283
-
284
- if properties_structure.required and pkey in properties_structure.required:
285
- keywords.append(ast.keyword(arg="not_null", value=ast.Constant(True)))
286
-
287
- if (
288
- properties_structure.partition_data
289
- and pkey in properties_structure.partition_data
290
- ):
291
- keywords.append(ast.keyword(arg="partition_data", value=ast.Constant(True)))
292
-
293
- if pkey in map_unique_by_property:
294
- unique = map_unique_by_property[pkey].index_model
295
- keywords.append(
296
- ast.keyword(
297
- arg="unique",
298
- value=ast.Constant(unique.name),
299
- )
300
- )
301
-
302
- if (
303
- properties_structure.search_properties
304
- and pkey in properties_structure.search_properties
305
- ):
306
- keywords.append(ast.keyword(arg="search", value=ast.Constant(True)))
307
- else:
308
- keywords.append(ast.keyword(arg="search", value=ast.Constant(False)))
309
-
310
- if (
311
- properties_structure.metric_label
312
- and pkey in properties_structure.metric_label
313
- ):
314
- keywords.append(ast.keyword(arg="metric_label", value=ast.Constant(True)))
315
-
316
- if prop.type == PrimitiveTypes.CPF:
317
- keywords.append(
318
- ast.keyword(
319
- arg="validator",
320
- value=ast.Attribute(
321
- value=ast.Call(
322
- func=ast.Name(id="DTOFieldValidators", ctx=ast.Load()),
323
- args=[],
324
- keywords=[],
325
- ),
326
- attr="validate_cpf",
327
- ctx=ast.Load(),
328
- ),
329
- )
330
- )
331
- elif prop.type == PrimitiveTypes.CNPJ:
332
- keywords.append(
333
- ast.keyword(
334
- arg="validator",
335
- value=ast.Attribute(
336
- value=ast.Call(
337
- func=ast.Name(id="DTOFieldValidators", ctx=ast.Load()),
338
- args=[],
339
- keywords=[],
340
- ),
341
- attr="validate_cnpj",
342
- ctx=ast.Load(),
343
- ),
344
- )
345
- )
346
- elif prop.type == PrimitiveTypes.CPF_CNPJ:
347
- keywords.append(
348
- ast.keyword(
349
- arg="validator",
350
- value=ast.Attribute(
351
- value=ast.Call(
352
- func=ast.Name(id="DTOFieldValidators", ctx=ast.Load()),
353
- args=[],
354
- keywords=[],
355
- ),
356
- attr="validate_cpf_or_cnpj",
357
- ctx=ast.Load(),
358
- ),
359
- )
360
- )
361
- elif prop.type == PrimitiveTypes.EMAIL:
362
- keywords.append(
363
- ast.keyword(
364
- arg="validator",
365
- value=ast.Attribute(
366
- value=ast.Call(
367
- func=ast.Name(id="DTOFieldValidators", ctx=ast.Load()),
368
- args=[],
369
- keywords=[],
370
- ),
371
- attr="validate_email",
372
- ctx=ast.Load(),
373
- ),
374
- )
375
- )
376
-
377
- # Trtando de uma definição de enum
378
- if prop.domain_config:
379
- result = self._compile_domain_config(
380
- pkey, prop, escopo, entity_model, prefx_class_name
381
- )
382
- if not result:
383
- raise Exception(f"Erro desconhecido ao compilar a propriedade {pkey}")
384
-
385
- enum_class_name, ast_enum_class = result
386
- aux_classes.append(ast_enum_class)
387
-
388
- # Resolvendo o nome da propriedade no Entity
389
- if (
390
- properties_structure.entity_properties
391
- and pkey in properties_structure.entity_properties
392
- ):
393
- entity_field_name = properties_structure.entity_properties[pkey].column
394
- else:
395
- entity_field_name = pkey
396
-
397
- # Escrevendo, se necessário, o alias para o nome da entity
398
- if entity_field_name != pkey:
399
- keywords.append(
400
- ast.keyword(
401
- arg="entity_field",
402
- value=ast.Constant(value=entity_field_name),
403
- )
404
- )
405
-
406
- # Instanciando o atributo AST
407
- if not isinstance(prop.type, PrimitiveTypes):
408
- raise Exception(
409
- f"Tipo da trait_property '{pkey}' não suportado: {prop.type} (deveria ser um Tipo Primitivo)"
410
- )
411
-
412
- # Instanciando o atributo AST
413
- if enum_class_name:
414
- prop_type = enum_class_name
415
- else:
416
- prop_type = TypeUtil.property_type_to_python_type(prop.type)
417
-
418
- ast_attr = ast.AnnAssign(
419
- target=ast.Name(id=CompilerStrUtil.to_snake_case(pkey), ctx=ast.Store()),
420
- annotation=ast.Name(
421
- id=prop_type,
422
- ctx=ast.Load(),
423
- ),
424
- value=ast.Call(
425
- func=ast.Name(id="DTOField", ctx=ast.Load()),
426
- args=[],
427
- keywords=keywords,
428
- ),
429
- simple=1,
430
- )
431
-
432
- ast_dto_attributes.append(ast_attr)
433
-
434
- # Entity
435
- ast_entity_attr = ast.AnnAssign(
436
- target=ast.Name(
437
- id=CompilerStrUtil.to_snake_case(entity_field_name),
438
- ctx=ast.Store(),
439
- ),
440
- annotation=ast.Name(
441
- id=TypeUtil.property_type_to_python_type(prop.type),
442
- ctx=ast.Load(),
443
- ),
444
- value=ast.Constant(value=None),
445
- simple=1,
446
- )
447
-
448
- ast_entity_attributes.append(ast_entity_attr)
449
-
450
- # Guardando como um fixed_filter
451
- # TODO Pensar em validações para esse value (se está de acordo com o tipo ou enum)
452
- fixed_filters.append((pkey, prop.value))
453
-
454
230
  def _build_aggregator_class_ast(
455
231
  self,
456
232
  class_name: str,
@@ -944,16 +720,6 @@ class EDLPropertyCompiler:
944
720
  )
945
721
  )
946
722
 
947
- if (
948
- prop.default
949
- ): # TODO Verificar esse modo de tratar valores default (principalmente expressões)
950
- keywords.append(
951
- ast.keyword(
952
- arg="default_value",
953
- value=ast.Name(str(prop.default), ctx=ast.Load()),
954
- )
955
- )
956
-
957
723
  if prop.trim:
958
724
  keywords.append(ast.keyword(arg="strip", value=ast.Constant(True)))
959
725
 
@@ -1086,6 +852,15 @@ class EDLPropertyCompiler:
1086
852
  enum_class_name, ast_enum_class = result
1087
853
  aux_classes.append(ast_enum_class)
1088
854
 
855
+ default_value_ast = self._build_default_value_ast(pkey, prop, enum_class_name)
856
+ if default_value_ast is not None:
857
+ keywords.append(
858
+ ast.keyword(
859
+ arg="default_value",
860
+ value=default_value_ast,
861
+ )
862
+ )
863
+
1089
864
  # Resolvendo o nome da propriedade no Entity
1090
865
  if (
1091
866
  properties_structure.entity_properties
@@ -1118,6 +893,297 @@ class EDLPropertyCompiler:
1118
893
 
1119
894
  ast_entity_attributes.append(ast_entity_attr)
1120
895
 
896
+ def _build_default_value_ast(
897
+ self,
898
+ pkey: str,
899
+ prop: PropertyMetaModel,
900
+ enum_class_name: str | None,
901
+ ) -> ast.expr | None:
902
+ default_value = prop.default
903
+ if default_value is None:
904
+ return None
905
+
906
+ if prop.domain_config and enum_class_name:
907
+ return self._build_enum_default_value_ast(
908
+ pkey, prop, enum_class_name, default_value
909
+ )
910
+
911
+ if not isinstance(prop.type, PrimitiveTypes):
912
+ raise ValueError(
913
+ f"Propriedade '{pkey}' não suporta valor default para relacionamentos."
914
+ )
915
+
916
+ return self._build_primitive_default_value_ast(pkey, prop, default_value)
917
+
918
+ def _build_enum_default_value_ast(
919
+ self,
920
+ pkey: str,
921
+ prop: PropertyMetaModel,
922
+ enum_class_name: str,
923
+ default_value: object,
924
+ ) -> ast.expr:
925
+ target_option = None
926
+ for option in prop.domain_config or []:
927
+ if option.value == default_value:
928
+ target_option = option
929
+ break
930
+
931
+ if option.mapped_value is not None and option.mapped_value == default_value:
932
+ target_option = option
933
+ break
934
+
935
+ if not target_option:
936
+ raise ValueError(
937
+ f"Propriedade '{pkey}' possui valor default '{default_value}' que não corresponde a nenhuma opção do enum."
938
+ )
939
+
940
+ enum_member_name = CompilerStrUtil.to_enum_member_name(target_option.value)
941
+
942
+ return ast.Attribute(
943
+ value=ast.Name(id=enum_class_name, ctx=ast.Load()),
944
+ attr=enum_member_name,
945
+ ctx=ast.Load(),
946
+ )
947
+
948
+ def _merge_trait_extends_properties(
949
+ self, properties_structure: PropertiesCompilerStructure
950
+ ) -> dict[str, BasicTypes]:
951
+ fixed_filters: dict[str, BasicTypes] = {}
952
+
953
+ trait_sources = (
954
+ properties_structure.trait_properties or {},
955
+ properties_structure.extends_properties or {},
956
+ )
957
+
958
+ for prop_dict in trait_sources:
959
+ for pkey, tprop in prop_dict.items():
960
+ if not isinstance(tprop.type, PrimitiveTypes):
961
+ raise ValueError(
962
+ f"Propriedade '{pkey}' definida em trait/extends precisa ser um tipo primitivo."
963
+ )
964
+
965
+ base_prop = properties_structure.properties.get(pkey)
966
+
967
+ if base_prop:
968
+ if base_prop.type != tprop.type:
969
+ raise ValueError(
970
+ f"Tipo da propriedade '{pkey}' em trait/extends não coincide com a definição existente."
971
+ )
972
+
973
+ base_prop.default = tprop.value
974
+
975
+ if tprop.domain_config and not base_prop.domain_config:
976
+ base_prop.domain_config = tprop.domain_config
977
+ else:
978
+ base_prop = PropertyMetaModel(
979
+ type=tprop.type,
980
+ default=tprop.value,
981
+ domain_config=tprop.domain_config,
982
+ )
983
+ properties_structure.properties[pkey] = base_prop
984
+
985
+ fixed_filters[pkey] = tprop.value
986
+
987
+ return fixed_filters
988
+
989
+ def _build_primitive_default_value_ast(
990
+ self,
991
+ pkey: str,
992
+ prop: PropertyMetaModel,
993
+ default_value: object,
994
+ ) -> ast.expr:
995
+ primitive_type = prop.type
996
+
997
+ if isinstance(default_value, str):
998
+ expression_ast = self._build_python_expression_from_string(default_value)
999
+ if expression_ast is not None:
1000
+ return expression_ast
1001
+
1002
+ if primitive_type in STR_BASED_TYPES:
1003
+ if not isinstance(default_value, str):
1004
+ raise ValueError(
1005
+ f"Propriedade '{pkey}' exige valor default textual, recebido '{default_value}'."
1006
+ )
1007
+ return ast.Constant(value=default_value)
1008
+
1009
+ if primitive_type == PrimitiveTypes.BOOLEAN:
1010
+ if isinstance(default_value, bool):
1011
+ return ast.Constant(value=default_value)
1012
+ raise ValueError(
1013
+ f"Propriedade '{pkey}' exige valor default booleano, recebido '{default_value}'."
1014
+ )
1015
+
1016
+ if primitive_type == PrimitiveTypes.INTEGER:
1017
+ return self._build_numeric_constant_default_ast(
1018
+ pkey,
1019
+ default_value,
1020
+ int,
1021
+ "inteiro",
1022
+ forbid_fraction=True,
1023
+ )
1024
+
1025
+ if primitive_type in (
1026
+ PrimitiveTypes.NUMBER,
1027
+ PrimitiveTypes.CURRENCY,
1028
+ PrimitiveTypes.QUANTITY,
1029
+ ):
1030
+ return self._build_numeric_constant_default_ast(
1031
+ pkey,
1032
+ default_value,
1033
+ float,
1034
+ "numérico",
1035
+ )
1036
+
1037
+ if primitive_type == PrimitiveTypes.UUID:
1038
+ return self._build_uuid_default_ast(pkey, default_value)
1039
+
1040
+ if primitive_type == PrimitiveTypes.DATE:
1041
+ return self._build_iso_datetime_default_ast(
1042
+ pkey,
1043
+ default_value,
1044
+ target="date",
1045
+ )
1046
+
1047
+ if primitive_type == PrimitiveTypes.DATETIME:
1048
+ return self._build_iso_datetime_default_ast(
1049
+ pkey,
1050
+ default_value,
1051
+ target="datetime",
1052
+ )
1053
+
1054
+ raise ValueError(
1055
+ f"Propriedade '{pkey}' não suporta valor default para o tipo '{primitive_type.value}'."
1056
+ )
1057
+
1058
+ def _build_numeric_constant_default_ast(
1059
+ self,
1060
+ pkey: str,
1061
+ default_value: object,
1062
+ cast_type: type,
1063
+ type_label: str,
1064
+ *,
1065
+ forbid_fraction: bool = False,
1066
+ ) -> ast.expr:
1067
+ if isinstance(default_value, bool):
1068
+ raise ValueError(
1069
+ f"Propriedade '{pkey}' exige valor default {type_label}, recebido '{default_value}'."
1070
+ )
1071
+
1072
+ value_to_convert = (
1073
+ default_value.strip() if isinstance(default_value, str) else default_value
1074
+ )
1075
+
1076
+ if forbid_fraction and isinstance(value_to_convert, float):
1077
+ if not value_to_convert.is_integer():
1078
+ raise ValueError(
1079
+ f"Propriedade '{pkey}' exige valor default inteiro, recebido '{default_value}'."
1080
+ )
1081
+
1082
+ try:
1083
+ converted_value = cast_type(value_to_convert)
1084
+ except (TypeError, ValueError) as exc:
1085
+ raise ValueError(
1086
+ f"Propriedade '{pkey}' exige valor default {type_label}, recebido '{default_value}'."
1087
+ ) from exc
1088
+
1089
+ if forbid_fraction and isinstance(value_to_convert, float):
1090
+ converted_value = int(converted_value)
1091
+
1092
+ return ast.Constant(value=converted_value)
1093
+
1094
+ def _build_uuid_default_ast(
1095
+ self,
1096
+ pkey: str,
1097
+ default_value: object,
1098
+ ) -> ast.expr:
1099
+ if isinstance(default_value, uuid.UUID):
1100
+ uuid_value = str(default_value)
1101
+ elif isinstance(default_value, str):
1102
+ try:
1103
+ uuid_value = str(uuid.UUID(default_value))
1104
+ except ValueError as exc:
1105
+ raise ValueError(
1106
+ f"Propriedade '{pkey}' exige UUID válido ou expressão Python, recebido '{default_value}'."
1107
+ ) from exc
1108
+ else:
1109
+ raise ValueError(
1110
+ f"Propriedade '{pkey}' exige UUID válido ou expressão Python, recebido '{default_value}'."
1111
+ )
1112
+
1113
+ return ast.Call(
1114
+ func=ast.Attribute(
1115
+ value=ast.Name(id="uuid", ctx=ast.Load()),
1116
+ attr="UUID",
1117
+ ctx=ast.Load(),
1118
+ ),
1119
+ args=[ast.Constant(value=uuid_value)],
1120
+ keywords=[],
1121
+ )
1122
+
1123
+ def _build_iso_datetime_default_ast(
1124
+ self,
1125
+ pkey: str,
1126
+ default_value: object,
1127
+ *,
1128
+ target: str,
1129
+ ) -> ast.expr:
1130
+ if target == "date":
1131
+ parser = datetime.date
1132
+ else:
1133
+ parser = datetime.datetime
1134
+
1135
+ if isinstance(default_value, parser):
1136
+ iso_value = default_value.isoformat()
1137
+ elif isinstance(default_value, str):
1138
+ iso_value = default_value.strip()
1139
+ else:
1140
+ raise ValueError(
1141
+ f"Propriedade '{pkey}' exige {target} em formato ISO ou expressão Python, recebido '{default_value}'."
1142
+ )
1143
+
1144
+ try:
1145
+ parser.fromisoformat(iso_value)
1146
+ except ValueError as exc:
1147
+ raise ValueError(
1148
+ f"Propriedade '{pkey}' exige {target} em formato ISO ou expressão Python, recebido '{default_value}'."
1149
+ ) from exc
1150
+
1151
+ return ast.Call(
1152
+ func=ast.Attribute(
1153
+ value=ast.Attribute(
1154
+ value=ast.Name(id="datetime", ctx=ast.Load()),
1155
+ attr=target,
1156
+ ctx=ast.Load(),
1157
+ ),
1158
+ attr="fromisoformat",
1159
+ ctx=ast.Load(),
1160
+ ),
1161
+ args=[ast.Constant(value=iso_value)],
1162
+ keywords=[],
1163
+ )
1164
+
1165
+ def _build_python_expression_from_string(self, expression: str) -> ast.expr | None:
1166
+ try:
1167
+ parsed_expr = ast.parse(expression, mode="eval").body
1168
+ except SyntaxError:
1169
+ return None
1170
+
1171
+ if self._is_safe_python_expression(parsed_expr):
1172
+ return parsed_expr
1173
+
1174
+ return None
1175
+
1176
+ def _is_safe_python_expression(self, node: ast.AST) -> bool:
1177
+ if isinstance(node, ast.Name):
1178
+ return True
1179
+ if isinstance(node, ast.Attribute):
1180
+ return self._is_safe_python_expression(node.value)
1181
+ if isinstance(node, ast.Call):
1182
+ if node.keywords or node.args:
1183
+ return False
1184
+ return self._is_safe_python_expression(node.func)
1185
+ return False
1186
+
1121
1187
  def _compile_domain_config(
1122
1188
  self,
1123
1189
  pkey: str,
@@ -1139,7 +1205,7 @@ class EDLPropertyCompiler:
1139
1205
  # Compilando as opções do enum
1140
1206
  ast_values = []
1141
1207
  for value in prop.domain_config:
1142
- value_name = CompilerStrUtil.to_snake_case(value.value).upper()
1208
+ value_name = CompilerStrUtil.to_enum_member_name(value.value)
1143
1209
 
1144
1210
  if use_mapped_value and value.mapped_value is None:
1145
1211
  raise Exception(
@@ -1,3 +1,6 @@
1
+ import re
2
+
3
+
1
4
  class CompilerStrUtil:
2
5
  @staticmethod
3
6
  def to_camel_case(snake_str: str) -> str:
@@ -20,3 +23,10 @@ class CompilerStrUtil:
20
23
  snake_str += "_"
21
24
  snake_str += char.lower()
22
25
  return snake_str
26
+
27
+ @staticmethod
28
+ def to_enum_member_name(raw_value: str) -> str:
29
+ """Converte um valor livre para o nome de membro enum padronizado."""
30
+ sanitized = re.sub(r"[^0-9A-Za-z]+", "_", raw_value.strip())
31
+ sanitized = sanitized.strip("_")
32
+ return CompilerStrUtil.to_snake_case(sanitized).upper()
@@ -15,7 +15,6 @@ from nsj_rest_lib2.redis_config import get_redis
15
15
  from nsj_rest_lib2.settings import ESCOPO_RESTLIB2, MIN_TIME_SOURCE_REFRESH
16
16
 
17
17
 
18
- # TODO Verificar se está atualizando o loaded_at, quando o hash não muda
19
18
  class LoadedEntity:
20
19
  def __init__(self):
21
20
  self.dto_class_name: str = ""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nsj_rest_lib2
3
- Version: 0.0.21
3
+ Version: 0.0.23
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
@@ -25,16 +25,3 @@ Biblioteca para permitir a distribuição de rotas dinâmicas numa API, configur
25
25
 
26
26
  [ESPECIFICAÇÃO DO MODELO DE ENTIDADES](docs/especificacao.md)
27
27
 
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
33
- * Unificar o arquivo redis_config.py
34
- * Usar pydantic, ou similar, para transformar a configuração das entidades, no redis, num objeto
35
- * Rever modo de usar o InjectFactory (talvez dando ciência, ao RestLib, do padrão multibanco)
36
- * Implementar e documentar campos join
37
- * Implementar e documentar conjuntos
38
- * Dar erro ao haver conflito de nomes das propriedades (por qualquer tipo de herança)
39
- * Rotas para o inline
40
- * inline será para composição (arquivo externo é agregação)
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = nsj_rest_lib2
3
- version = 0.0.21
3
+ version = 0.0.23
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).
@@ -1,19 +0,0 @@
1
- # nsj_rest_lib2
2
-
3
- Biblioteca para permitir a distribuição de rotas dinâmicas numa API, configuradas por meio de EDLs declarativos (em formato JSON).
4
-
5
- [ESPECIFICAÇÃO DO MODELO DE ENTIDADES](docs/especificacao.md)
6
-
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
12
- * Unificar o arquivo redis_config.py
13
- * Usar pydantic, ou similar, para transformar a configuração das entidades, no redis, num objeto
14
- * Rever modo de usar o InjectFactory (talvez dando ciência, ao RestLib, do padrão multibanco)
15
- * Implementar e documentar campos join
16
- * Implementar e documentar conjuntos
17
- * Dar erro ao haver conflito de nomes das propriedades (por qualquer tipo de herança)
18
- * Rotas para o inline
19
- * inline será para composição (arquivo externo é agregação)