nsj-rest-lib2 0.0.7__py3-none-any.whl → 0.0.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nsj_rest_lib2/compiler/compiler.py +53 -16
- nsj_rest_lib2/compiler/dto_compiler.py +27 -1
- nsj_rest_lib2/compiler/edl_model/column_meta_model.py +12 -2
- nsj_rest_lib2/compiler/edl_model/entity_model.py +10 -2
- nsj_rest_lib2/compiler/edl_model/primitives.py +17 -5
- nsj_rest_lib2/compiler/edl_model/property_meta_model.py +15 -3
- nsj_rest_lib2/compiler/edl_model/trait_property_meta_model.py +2 -2
- nsj_rest_lib2/compiler/entity_compiler.py +2 -1
- nsj_rest_lib2/compiler/property_compiler.py +384 -221
- nsj_rest_lib2/compiler/util/type_naming_util.py +21 -0
- nsj_rest_lib2/controller/dynamic_controller.py +92 -44
- nsj_rest_lib2/service/entity_loader.py +125 -42
- {nsj_rest_lib2-0.0.7.dist-info → nsj_rest_lib2-0.0.9.dist-info}/METADATA +2 -1
- {nsj_rest_lib2-0.0.7.dist-info → nsj_rest_lib2-0.0.9.dist-info}/RECORD +16 -15
- {nsj_rest_lib2-0.0.7.dist-info → nsj_rest_lib2-0.0.9.dist-info}/WHEEL +0 -0
- {nsj_rest_lib2-0.0.7.dist-info → nsj_rest_lib2-0.0.9.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import re
|
|
1
2
|
from typing import Any
|
|
2
3
|
|
|
3
4
|
from nsj_rest_lib2.compiler.compiler_structures import (
|
|
@@ -5,6 +6,7 @@ from nsj_rest_lib2.compiler.compiler_structures import (
|
|
|
5
6
|
PropertiesCompilerStructure,
|
|
6
7
|
)
|
|
7
8
|
from nsj_rest_lib2.compiler.dto_compiler import DTOCompiler
|
|
9
|
+
from nsj_rest_lib2.compiler.edl_model.primitives import REGEX_EXTERNAL_REF
|
|
8
10
|
from nsj_rest_lib2.compiler.edl_model.repository_model import RepositoryModel
|
|
9
11
|
from nsj_rest_lib2.compiler.entity_compiler import EntityCompiler
|
|
10
12
|
from nsj_rest_lib2.compiler.property_compiler import EDLPropertyCompiler
|
|
@@ -13,14 +15,15 @@ from nsj_rest_lib2.compiler.edl_model.entity_model import EntityModel
|
|
|
13
15
|
|
|
14
16
|
from nsj_rest_lib2.settings import get_logger
|
|
15
17
|
|
|
16
|
-
# TODO
|
|
18
|
+
# TODO Autenticação nas rotas
|
|
19
|
+
# TODO Carregar, dinamicamente, em memória, o código compilado das dependÊncias por relacionamento
|
|
17
20
|
# TODO Atualizar o status da entidade pelo worker de compilação (e talvez parar uma compilação, quando se delete uma entidade)
|
|
18
21
|
# TODO Relacionamentos
|
|
19
22
|
# TODO Classes Abstratas
|
|
20
23
|
# TODO Partial Classes
|
|
21
24
|
# TODO Migrations
|
|
22
|
-
# TODO
|
|
23
|
-
# TODO
|
|
25
|
+
# TODO Adicionar autenticação aos endpoints dinâmicos
|
|
26
|
+
# TODO Criar imagem docker base para as aplicações, usando venv (para poderem atualizar o pydantic)
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
class CompilerResult:
|
|
@@ -29,7 +32,9 @@ class CompilerResult:
|
|
|
29
32
|
self.dto_code: str | None = None
|
|
30
33
|
self.entity_class_name: str | None = None
|
|
31
34
|
self.entity_code: str | None = None
|
|
32
|
-
|
|
35
|
+
self.api_expose: bool | None = None
|
|
36
|
+
self.api_resource: str | None = None
|
|
37
|
+
self.api_verbs: list[str] | None = None
|
|
33
38
|
|
|
34
39
|
|
|
35
40
|
class EDLCompiler:
|
|
@@ -103,17 +108,22 @@ class EDLCompiler:
|
|
|
103
108
|
)
|
|
104
109
|
|
|
105
110
|
# Criando a lista de atributos do DTO e da Entity; e recuperando as chaves primarias
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
(
|
|
112
|
+
ast_dto_attributes,
|
|
113
|
+
ast_entity_attributes,
|
|
114
|
+
props_pk,
|
|
115
|
+
enum_classes,
|
|
116
|
+
related_imports,
|
|
117
|
+
) = self._properties_compiler.compile(
|
|
118
|
+
properties_structure,
|
|
119
|
+
map_unique_by_property,
|
|
120
|
+
entity_model,
|
|
121
|
+
entity_models,
|
|
112
122
|
)
|
|
113
123
|
|
|
114
124
|
# Gerando o código do DTO
|
|
115
125
|
dto_class_name, code_dto = self._dto_compiler.compile(
|
|
116
|
-
entity_model, ast_dto_attributes, enum_classes
|
|
126
|
+
entity_model, ast_dto_attributes, enum_classes, related_imports
|
|
117
127
|
)
|
|
118
128
|
|
|
119
129
|
# Gerando o código da Entity
|
|
@@ -121,13 +131,18 @@ class EDLCompiler:
|
|
|
121
131
|
entity_model, ast_entity_attributes, props_pk
|
|
122
132
|
)
|
|
123
133
|
|
|
124
|
-
#
|
|
134
|
+
# Construindo o resultado
|
|
125
135
|
compiler_result = CompilerResult()
|
|
126
136
|
compiler_result.entity_class_name = entity_class_name
|
|
127
137
|
compiler_result.entity_code = code_entity
|
|
128
138
|
compiler_result.dto_class_name = dto_class_name
|
|
129
139
|
compiler_result.dto_code = code_dto
|
|
130
140
|
|
|
141
|
+
# Compilando questões das APIs
|
|
142
|
+
compiler_result.api_expose = entity_model.api.expose
|
|
143
|
+
compiler_result.api_resource = entity_model.api.resource
|
|
144
|
+
compiler_result.api_verbs = entity_model.api.verbs
|
|
145
|
+
|
|
131
146
|
return compiler_result
|
|
132
147
|
|
|
133
148
|
def _make_properties_structures(
|
|
@@ -228,16 +243,27 @@ class EDLCompiler:
|
|
|
228
243
|
|
|
229
244
|
def _list_dependencies(self, entity_model: EntityModel) -> list[str]:
|
|
230
245
|
entities: list[str] = []
|
|
246
|
+
|
|
231
247
|
if entity_model.trait_from:
|
|
232
248
|
entities.append(entity_model.trait_from)
|
|
233
249
|
|
|
250
|
+
# Populando com as dependências de propriedades de relacionamento 1_N
|
|
251
|
+
for pkey in entity_model.properties:
|
|
252
|
+
prop = entity_model.properties[pkey]
|
|
253
|
+
|
|
254
|
+
if isinstance(prop.type, str):
|
|
255
|
+
external_match = re.match(REGEX_EXTERNAL_REF, prop.type)
|
|
256
|
+
if external_match:
|
|
257
|
+
external_dependency = external_match.group(0)
|
|
258
|
+
entities.append(external_dependency)
|
|
259
|
+
|
|
234
260
|
return entities
|
|
235
261
|
|
|
236
262
|
|
|
237
263
|
def get_files_from_directory(directory):
|
|
238
264
|
files = []
|
|
239
265
|
for file in os.listdir(directory):
|
|
240
|
-
if file.endswith(".json"):
|
|
266
|
+
if file.endswith(".json") or file.endswith(".yml") or file.endswith(".yaml"):
|
|
241
267
|
files.append(os.path.join(directory, file))
|
|
242
268
|
return files
|
|
243
269
|
|
|
@@ -246,6 +272,7 @@ if __name__ == "__main__":
|
|
|
246
272
|
import argparse
|
|
247
273
|
import json
|
|
248
274
|
import os
|
|
275
|
+
import yaml
|
|
249
276
|
|
|
250
277
|
parser = argparse.ArgumentParser(
|
|
251
278
|
description="Compila arquivos EDL para classes Python"
|
|
@@ -263,13 +290,16 @@ if __name__ == "__main__":
|
|
|
263
290
|
|
|
264
291
|
entities = {}
|
|
265
292
|
for file in files:
|
|
266
|
-
with open(file) as f:
|
|
267
|
-
|
|
293
|
+
with open(file, "r") as f:
|
|
294
|
+
if file.endswith(".json"):
|
|
295
|
+
edl = json.load(f)
|
|
296
|
+
else:
|
|
297
|
+
edl = yaml.safe_load(f)
|
|
268
298
|
|
|
269
299
|
# Instanciando o objeto de modelo de entidade a partir do JSON,
|
|
270
300
|
# e já realizando as validações básicas de tipo e estrutura.
|
|
271
301
|
print(f"Validando arquivo: {file}")
|
|
272
|
-
entity_model = EntityModel(**
|
|
302
|
+
entity_model = EntityModel(**edl)
|
|
273
303
|
|
|
274
304
|
complete_entity_id = f"{entity_model.escopo}/{entity_model.id}"
|
|
275
305
|
entities[complete_entity_id] = entity_model
|
|
@@ -286,3 +316,10 @@ if __name__ == "__main__":
|
|
|
286
316
|
print(f"DTO: {compiler_result.dto_class_name}")
|
|
287
317
|
print(f"{compiler_result.dto_code}")
|
|
288
318
|
print("\n")
|
|
319
|
+
|
|
320
|
+
print("==========================================================")
|
|
321
|
+
print("API Expose: ", compiler_result.api_expose)
|
|
322
|
+
print("API Route Path: ", compiler_result.api_resource)
|
|
323
|
+
print("API Verbs: ", compiler_result.api_verbs)
|
|
324
|
+
print("==========================================================")
|
|
325
|
+
print("\n")
|
|
@@ -4,6 +4,7 @@ import black
|
|
|
4
4
|
|
|
5
5
|
from nsj_rest_lib2.compiler.edl_model.entity_model import EntityModel
|
|
6
6
|
from nsj_rest_lib2.compiler.util.str_util import CompilerStrUtil
|
|
7
|
+
from nsj_rest_lib2.compiler.util.type_naming_util import compile_dto_class_name
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class DTOCompiler:
|
|
@@ -15,6 +16,7 @@ class DTOCompiler:
|
|
|
15
16
|
entity_model: EntityModel,
|
|
16
17
|
ast_dto_attributes: list[ast.stmt],
|
|
17
18
|
enum_classes: list[ast.stmt],
|
|
19
|
+
related_imports: list[tuple[str, str, str]],
|
|
18
20
|
) -> tuple[str, str]:
|
|
19
21
|
"""
|
|
20
22
|
Compila o código do DTO a partir do AST e retorna o código compilado.
|
|
@@ -31,6 +33,7 @@ class DTOCompiler:
|
|
|
31
33
|
:return: Código compilado do DTO
|
|
32
34
|
:rtype: str
|
|
33
35
|
"""
|
|
36
|
+
# Criando o ast dos imports
|
|
34
37
|
imports = [
|
|
35
38
|
# import datetime
|
|
36
39
|
ast.Import(names=[ast.alias(name="datetime", asname=None)]),
|
|
@@ -50,6 +53,12 @@ class DTOCompiler:
|
|
|
50
53
|
names=[ast.alias(name="DTOField", asname=None)],
|
|
51
54
|
level=0,
|
|
52
55
|
),
|
|
56
|
+
# from nsj_rest_lib.descriptor.dto_list_field import DTOField
|
|
57
|
+
ast.ImportFrom(
|
|
58
|
+
module="nsj_rest_lib.descriptor.dto_list_field",
|
|
59
|
+
names=[ast.alias(name="DTOListField", asname=None)],
|
|
60
|
+
level=0,
|
|
61
|
+
),
|
|
53
62
|
# from nsj_rest_lib.descriptor.dto_field_validators import DTOFieldValidators
|
|
54
63
|
ast.ImportFrom(
|
|
55
64
|
module="nsj_rest_lib.descriptor.dto_field_validators",
|
|
@@ -64,7 +73,24 @@ class DTOCompiler:
|
|
|
64
73
|
),
|
|
65
74
|
]
|
|
66
75
|
|
|
67
|
-
|
|
76
|
+
for import_ in related_imports:
|
|
77
|
+
imports.append(
|
|
78
|
+
ast.ImportFrom(
|
|
79
|
+
module=f"dynamic.{import_[0]}",
|
|
80
|
+
names=[ast.alias(name=import_[1])],
|
|
81
|
+
level=0,
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
imports.append(
|
|
85
|
+
ast.ImportFrom(
|
|
86
|
+
module=f"dynamic.{import_[0]}",
|
|
87
|
+
names=[ast.alias(name=import_[2])],
|
|
88
|
+
level=0,
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Criando o ast da classe
|
|
93
|
+
class_name = compile_dto_class_name(entity_model.id)
|
|
68
94
|
ast_class = ast.ClassDef(
|
|
69
95
|
name=class_name,
|
|
70
96
|
bases=[ast.Name(id="DTOBase", ctx=ast.Load())],
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
from pydantic import BaseModel, Field
|
|
1
|
+
from pydantic import BaseModel, Field, model_validator
|
|
2
2
|
from typing import List, Optional, Literal
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class ColumnMetaModel(BaseModel):
|
|
6
|
-
column: str = Field(
|
|
6
|
+
column: Optional[str] = Field(None, description="Nome da coluna no banco de dados.")
|
|
7
|
+
relation_column: Optional[str] = Field(
|
|
8
|
+
None,
|
|
9
|
+
description="Nome da coluna usada para relacionamento com outra entidade (para um relacionamento 1_N, indica a coluna da entidade relacionada, que aponta para a PK da entidade corrente).",
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
@model_validator(mode="after")
|
|
13
|
+
def check_columns(self):
|
|
14
|
+
if self.column is None and self.relation_column is None:
|
|
15
|
+
raise ValueError("column or relation_column must be provided")
|
|
16
|
+
return self
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
|
|
1
3
|
from pydantic import BaseModel, Field
|
|
2
4
|
from typing import Dict, List, Optional
|
|
3
5
|
|
|
@@ -60,7 +62,13 @@ class EntityModel(BaseModel):
|
|
|
60
62
|
repository: RepositoryModel = Field(
|
|
61
63
|
..., description="Configurações de mapeamento para o banco de dados."
|
|
62
64
|
)
|
|
63
|
-
api:
|
|
64
|
-
|
|
65
|
+
api: APIModel = Field(
|
|
66
|
+
...,
|
|
65
67
|
description="Definição da API REST associada ao modelo, com todos os seus endpoints.",
|
|
66
68
|
)
|
|
69
|
+
|
|
70
|
+
# Propriedades de controle da compilação (não fazem parte do JSON de representação das entidades)
|
|
71
|
+
tenant: int = Field(default=0, exclude=True)
|
|
72
|
+
grupo_empresarial: uuid.UUID = Field(
|
|
73
|
+
default=uuid.UUID("00000000-0000-0000-0000-000000000000"), exclude=True
|
|
74
|
+
)
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import enum
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
from typing import Annotated, List, Union
|
|
4
|
+
from pydantic import StringConstraints
|
|
5
|
+
|
|
6
|
+
REGEX_EXTERNAL_REF = r"^(\w+)\/(\w+)$"
|
|
7
|
+
REGEX_INTERNAL_REF = r"^#\/(\w+)\/(\w+)$"
|
|
8
|
+
|
|
9
|
+
ExternalRefType = Annotated[str, StringConstraints(pattern=REGEX_EXTERNAL_REF)]
|
|
10
|
+
InternalRefType = Annotated[str, StringConstraints(pattern=REGEX_INTERNAL_REF)]
|
|
3
11
|
|
|
4
12
|
|
|
5
13
|
class PrimitiveTypes(enum.Enum):
|
|
@@ -8,8 +16,6 @@ class PrimitiveTypes(enum.Enum):
|
|
|
8
16
|
NUMBER = "number"
|
|
9
17
|
INTEGER = "integer"
|
|
10
18
|
BOOLEAN = "boolean"
|
|
11
|
-
ARRAY = "array"
|
|
12
|
-
OBJECT = "object"
|
|
13
19
|
UUID = "uuid"
|
|
14
20
|
CURRENCY = "currency"
|
|
15
21
|
QUANTITY = "quantity"
|
|
@@ -21,13 +27,13 @@ class PrimitiveTypes(enum.Enum):
|
|
|
21
27
|
DATETIME = "datetime"
|
|
22
28
|
|
|
23
29
|
|
|
30
|
+
PropertyType = Union[PrimitiveTypes, ExternalRefType, InternalRefType]
|
|
31
|
+
|
|
24
32
|
MAPPING_PRIMITIVE_TYPES_TO_PYTHON = {
|
|
25
33
|
PrimitiveTypes.STRING: "str",
|
|
26
34
|
PrimitiveTypes.NUMBER: "float",
|
|
27
35
|
PrimitiveTypes.INTEGER: "int",
|
|
28
36
|
PrimitiveTypes.BOOLEAN: "bool",
|
|
29
|
-
PrimitiveTypes.ARRAY: "List",
|
|
30
|
-
PrimitiveTypes.OBJECT: "dict",
|
|
31
37
|
PrimitiveTypes.UUID: "uuid.UUID",
|
|
32
38
|
PrimitiveTypes.CURRENCY: "float",
|
|
33
39
|
PrimitiveTypes.QUANTITY: "float",
|
|
@@ -41,3 +47,9 @@ MAPPING_PRIMITIVE_TYPES_TO_PYTHON = {
|
|
|
41
47
|
|
|
42
48
|
BasicTypes = int | bool | float | str
|
|
43
49
|
DefaultTypes = BasicTypes | List[BasicTypes]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CardinalityTypes(enum.Enum):
|
|
53
|
+
C1_1 = "1_1"
|
|
54
|
+
C1_N = "1_N"
|
|
55
|
+
CN_N = "N_N"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
from pydantic import BaseModel, Field
|
|
1
|
+
from pydantic import BaseModel, Field, model_validator
|
|
2
2
|
from typing import List, Optional
|
|
3
3
|
|
|
4
4
|
from nsj_rest_lib2.compiler.edl_model.primitives import BasicTypes, DefaultTypes
|
|
5
|
-
from nsj_rest_lib2.compiler.edl_model.primitives import
|
|
5
|
+
from nsj_rest_lib2.compiler.edl_model.primitives import CardinalityTypes, PropertyType
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class DomainConfigModel(BaseModel):
|
|
@@ -13,7 +13,7 @@ class DomainConfigModel(BaseModel):
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class PropertyMetaModel(BaseModel):
|
|
16
|
-
type:
|
|
16
|
+
type: PropertyType = Field(..., description="Tipo da propriedade.")
|
|
17
17
|
label: Optional[str] = Field(None, description="Rótulo da propriedade.")
|
|
18
18
|
description: Optional[str] = Field(None, description="Descrição da propriedade.")
|
|
19
19
|
default: Optional[DefaultTypes] = Field(
|
|
@@ -28,6 +28,10 @@ class PropertyMetaModel(BaseModel):
|
|
|
28
28
|
description="Indica se a propriedade é parte de uma chave alternativa (chave natural).",
|
|
29
29
|
alias="keyAlternative",
|
|
30
30
|
)
|
|
31
|
+
cardinality: Optional[CardinalityTypes] = Field(
|
|
32
|
+
None,
|
|
33
|
+
description="Cardinalidade do relacionamento (válido para propriedades do tipo 'array' ou 'object').",
|
|
34
|
+
)
|
|
31
35
|
# Validação
|
|
32
36
|
max_length: Optional[int] = Field(
|
|
33
37
|
None, description="Comprimento máximo (para strings)."
|
|
@@ -80,3 +84,11 @@ class PropertyMetaModel(BaseModel):
|
|
|
80
84
|
None,
|
|
81
85
|
description="Função de conversão do tipo da entidade para o tipo da API (ex.: converter uuid para string).",
|
|
82
86
|
)
|
|
87
|
+
|
|
88
|
+
@model_validator(mode="after")
|
|
89
|
+
def check_cardinality_required(self):
|
|
90
|
+
if isinstance(self.type, str) and self.cardinality is None:
|
|
91
|
+
raise ValueError(
|
|
92
|
+
"The property 'cardinality' is required when type point to other entity."
|
|
93
|
+
)
|
|
94
|
+
return self
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from pydantic import BaseModel, Field
|
|
2
2
|
|
|
3
|
-
from nsj_rest_lib2.compiler.edl_model.primitives import BasicTypes,
|
|
3
|
+
from nsj_rest_lib2.compiler.edl_model.primitives import BasicTypes, PropertyType
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class TraitPropertyMetaModel(BaseModel):
|
|
7
|
-
type:
|
|
7
|
+
type: PropertyType = Field(..., description="Tipo da propriedade.")
|
|
8
8
|
value: BasicTypes = Field(
|
|
9
9
|
..., description="Valor fixo da propriedade de condicionamento do trait."
|
|
10
10
|
)
|
|
@@ -5,6 +5,7 @@ import black
|
|
|
5
5
|
from nsj_rest_lib2.compiler.edl_model.entity_model import EntityModel
|
|
6
6
|
from nsj_rest_lib2.compiler.edl_model.repository_model import RepositoryModel
|
|
7
7
|
from nsj_rest_lib2.compiler.util.str_util import CompilerStrUtil
|
|
8
|
+
from nsj_rest_lib2.compiler.util.type_naming_util import compile_entity_class_name
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class EntityCompiler:
|
|
@@ -66,7 +67,7 @@ class EntityCompiler:
|
|
|
66
67
|
if CompilerStrUtil.to_snake_case(props_pk[0]) not in default_order_fields:
|
|
67
68
|
default_order_fields.append(CompilerStrUtil.to_snake_case(props_pk[0]))
|
|
68
69
|
|
|
69
|
-
class_name =
|
|
70
|
+
class_name = compile_entity_class_name(entity_model.id)
|
|
70
71
|
ast_class = ast.ClassDef(
|
|
71
72
|
name=class_name,
|
|
72
73
|
bases=[ast.Name(id="EntityBase", ctx=ast.Load())],
|