nsj-rest-lib2 0.0.29__py3-none-any.whl → 0.0.31__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,118 @@
1
+ import re
2
+ from dataclasses import dataclass
3
+ from typing import Literal, Sequence
4
+
5
+ from nsj_rest_lib2.compiler.edl_model.entity_model_base import EntityModelBase
6
+ from nsj_rest_lib2.compiler.edl_model.primitives import (
7
+ REGEX_EXTERNAL_COMPONENT_REF,
8
+ REGEX_EXTERNAL_REF,
9
+ REGEX_INTERNAL_REF,
10
+ )
11
+
12
+
13
+ RelationRefType = Literal["internal", "external", "external_component"]
14
+
15
+
16
+ @dataclass
17
+ class RelationRef:
18
+ ref_type: RelationRefType
19
+ scope: str | None
20
+ entity: str
21
+ components: list[str]
22
+
23
+ @property
24
+ def entity_key(self) -> str | None:
25
+ if not self.scope:
26
+ return None
27
+
28
+ return f"{self.scope}/{self.entity}"
29
+
30
+ @property
31
+ def target_id(self) -> str:
32
+ return self.components[-1] if self.components else self.entity
33
+
34
+ @property
35
+ def prefx_class_name(self) -> str:
36
+ if not self.components:
37
+ return ""
38
+
39
+ prefix_parts: list[str] = [self.entity, *self.components[:-1]]
40
+ return f"_{'_'.join(prefix_parts)}"
41
+
42
+ @property
43
+ def is_external(self) -> bool:
44
+ return self.ref_type in {"external", "external_component"}
45
+
46
+ def resolve_model(
47
+ self,
48
+ entity_models: dict[str, EntityModelBase],
49
+ base_model: EntityModelBase | None = None,
50
+ ) -> EntityModelBase | None:
51
+ entity_key = self.entity_key
52
+ resolved_base: EntityModelBase | None = None
53
+
54
+ if entity_key:
55
+ resolved_base = entity_models.get(entity_key)
56
+ elif base_model:
57
+ resolved_base = base_model
58
+
59
+ if not resolved_base:
60
+ return None
61
+
62
+ return RelationRefParser.follow_components(resolved_base, self.components)
63
+
64
+
65
+ class RelationRefParser:
66
+ @staticmethod
67
+ def parse(ref: str) -> RelationRef | None:
68
+ internal_match = re.match(REGEX_INTERNAL_REF, ref)
69
+ if internal_match:
70
+ components = RelationRefParser._split_components(internal_match.group(1))
71
+ if not components:
72
+ return None
73
+ entity = components[0]
74
+ extra_components = components[1:] if len(components) > 1 else []
75
+ return RelationRef("internal", None, entity, extra_components)
76
+
77
+ external_component_match = re.match(REGEX_EXTERNAL_COMPONENT_REF, ref)
78
+ if external_component_match:
79
+ components = RelationRefParser._split_components(
80
+ external_component_match.group(3)
81
+ )
82
+ return RelationRef(
83
+ "external_component",
84
+ external_component_match.group(1),
85
+ external_component_match.group(2),
86
+ components,
87
+ )
88
+
89
+ external_match = re.match(REGEX_EXTERNAL_REF, ref)
90
+ if external_match:
91
+ return RelationRef(
92
+ "external",
93
+ external_match.group(1),
94
+ external_match.group(2),
95
+ [],
96
+ )
97
+
98
+ return None
99
+
100
+ @staticmethod
101
+ def follow_components(
102
+ base_model: EntityModelBase, components: Sequence[str]
103
+ ) -> EntityModelBase | None:
104
+ target_model: EntityModelBase | None = base_model
105
+ for component in components:
106
+ if not target_model or not target_model.components:
107
+ return None
108
+ target_model = target_model.components.get(component)
109
+ if target_model is None:
110
+ return None
111
+
112
+ return target_model
113
+
114
+ @staticmethod
115
+ def _split_components(components_path: str) -> list[str]:
116
+ # Remove marcadores opcionais '#/components/' intermediários
117
+ cleaned = components_path.replace("#/components/", "/").replace("#", "")
118
+ return [part for part in cleaned.split("/") if part]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nsj_rest_lib2
3
- Version: 0.0.29
3
+ Version: 0.0.31
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
@@ -3,15 +3,18 @@ nsj_rest_lib2/exception.py,sha256=E9uMUdoCCQOVQfc8f6gD9b5Pxurf3Q4SytDCcqSlkZ8,56
3
3
  nsj_rest_lib2/redis_config.py,sha256=4KLcvYS3nJO7PMQgF6F9_j6r-TyqcS7TBbd3LEQuKDU,629
4
4
  nsj_rest_lib2/settings.py,sha256=Hn_o1HZmievnYb8D1kNT2Nq-OEjxbyNjOiOpbnFsMwE,367
5
5
  nsj_rest_lib2/compiler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- nsj_rest_lib2/compiler/ai_compiler.py,sha256=shAtN4T0ai_52qh15L3mK1QbeC6glHOR6C3J3gj14II,9029
7
- nsj_rest_lib2/compiler/compiler.py,sha256=8avORndGTPOlQO3-s8PkC3fsfCLsUzBW4Qfq-UaueEE,30403
8
- nsj_rest_lib2/compiler/compiler_structures.py,sha256=a_DF_nU4VgCt0sWJI03Ky6fPHvoIM0cjMkwQb_dYv6Q,1437
9
- nsj_rest_lib2/compiler/dto_compiler.py,sha256=ksZ2eYd_urrs6HBNK1m5_LhQhhq7i60GfSzD-AaF0fU,6743
10
- nsj_rest_lib2/compiler/entity_compiler.py,sha256=-mDA1-dHKYp0umQi1A4GbA0F1hlaC-gQRqj69vkADC4,6536
6
+ nsj_rest_lib2/compiler/compiler.py,sha256=dDH3WXEaWg6eg95Bix1ZMY_fuB3prgps8xsevw9wYE4,30915
7
+ nsj_rest_lib2/compiler/compiler_structures.py,sha256=stspjqJGXU7Vz3BqQ-ZF5ZmumFm3R4jpkWgVVsXW5d0,1488
8
+ nsj_rest_lib2/compiler/dto_compiler.py,sha256=Fs6VysuAjBgct7_Wc0sugvx7z9tfnlZcSrBu0l0Pyp0,7019
9
+ nsj_rest_lib2/compiler/entity_compiler.py,sha256=LeGEBxsjAmZZog2gh4vjUX1aFp9JSVgHHOdTkY0aH-s,6733
11
10
  nsj_rest_lib2/compiler/function_compiler.py,sha256=t5e_NoDB0IckiIEuYLsvk2lPSwrcrjL91s4LTQkpRqA,18085
12
11
  nsj_rest_lib2/compiler/function_model.py,sha256=iSKMlCSZDWlP_aTdlcbYQhsEAIMH-XbSJUU0-KNtLCs,2066
12
+ nsj_rest_lib2/compiler/migration_compiler.py,sha256=vlG54XmYRqzIo0Iey-4HbSRzPg3Ylp5kpWWCGFAVzx8,29023
13
+ nsj_rest_lib2/compiler/migration_compiler_alter_table.py,sha256=awtqVrKox86rmlQV7K17JzZJZqz9cF7McshLBlLx65s,7969
14
+ nsj_rest_lib2/compiler/migration_compiler_create_table.py,sha256=h22cU53EFnuB5t28fMZ_r7MM-14Vqqu3emebBUJN2LY,2606
15
+ nsj_rest_lib2/compiler/migration_compiler_util.py,sha256=WB_GRX78nTRKlZkgL-4yowwlQoAe81llZ-E2AYfIbh4,5168
13
16
  nsj_rest_lib2/compiler/model.py,sha256=8UxzTvcNgZQdLwC592o8ZMVPhG3AloBCg5q2V72Up8U,1688
14
- nsj_rest_lib2/compiler/property_compiler.py,sha256=7cShFGtbZwSamyS9frSd_CebdmTmWLxpFDsq6vUqj80,47894
17
+ nsj_rest_lib2/compiler/property_compiler.py,sha256=hMQX5vrLqjOEM_wdzuerxuARMq5JhZQ_R8cUVoLHNNM,47631
15
18
  nsj_rest_lib2/compiler/edl_model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
19
  nsj_rest_lib2/compiler/edl_model/ai_entity_edl.py,sha256=664QBDcOgVnyfwtUOXO1W7AKaZhueBG335x5DuogruY,7644
17
20
  nsj_rest_lib2/compiler/edl_model/api_model.py,sha256=7RpZkUGROZmTBh2psgTG_b3e4xxjc3uBr1EYb-wiAkc,3810
@@ -20,11 +23,12 @@ nsj_rest_lib2/compiler/edl_model/entity_model.py,sha256=Yc6wvjsiwacmz796mZIU-i9h
20
23
  nsj_rest_lib2/compiler/edl_model/entity_model_base.py,sha256=eRn0pirIPHvniqGpT0xE-mmgqz5RIVtqghxcnfxKNdQ,4345
21
24
  nsj_rest_lib2/compiler/edl_model/entity_model_root.py,sha256=VinsxFlNyCaKKk37ZzcbmWaWgoUP2-dZBG61Ke7NvVs,231
22
25
  nsj_rest_lib2/compiler/edl_model/index_model.py,sha256=cXWlu0hxtro5vvYoirkDW4R3PCnBW5oCCWjRJ6AX5zE,508
23
- nsj_rest_lib2/compiler/edl_model/primitives.py,sha256=y6wvz76R995BHVAW9z2Kywd7sUIF4uwvk0-O2H_Zd1w,1613
26
+ nsj_rest_lib2/compiler/edl_model/primitives.py,sha256=FK2U3rRJIp2j_R_RuQtoRj_MSyua0-eH_QEoCGnutOE,1963
24
27
  nsj_rest_lib2/compiler/edl_model/property_meta_model.py,sha256=ciphkgdTtfPfLpeMQpcf3GXQ_qx7xl1tzgeUoJBjPBU,3871
25
28
  nsj_rest_lib2/compiler/edl_model/repository_model.py,sha256=OmtUzSq2LKmM76VXefGrKwg8xtVFCutt3JNH2nv42qU,1821
26
29
  nsj_rest_lib2/compiler/edl_model/trait_property_meta_model.py,sha256=NtMVZeOPu3LJwXI-8tCOLVuQiGua_0t7kL1h9gL7HuM,657
27
30
  nsj_rest_lib2/compiler/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
+ nsj_rest_lib2/compiler/util/relation_ref.py,sha256=1M_e-NyeqozlIYLl1rB76KqkmtMvgtqH_nJS-NK0Pck,3695
28
32
  nsj_rest_lib2/compiler/util/str_util.py,sha256=0ReIQ2Vy4zAmVMvGv0FcUehRQw15hlz0e7yDsF89ghk,1178
29
33
  nsj_rest_lib2/compiler/util/type_naming_util.py,sha256=fPyzsEsbG9sgzaUJghamijcmZ6Odh_rIv3zk-lcP5pQ,1419
30
34
  nsj_rest_lib2/compiler/util/type_util.py,sha256=HTKOH4uRTOY0YgoM8oUv_6cEcReE_bgKYXFBsQCb-3A,357
@@ -33,7 +37,7 @@ nsj_rest_lib2/controller/dynamic_controller.py,sha256=UJtlr0_A6bGJ1gD24oj3481tCb
33
37
  nsj_rest_lib2/service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
38
  nsj_rest_lib2/service/entity_config_writer.py,sha256=DC8M4BYKfaOkNty1YFmAmWiNvENjvmiUqDKgsAV3eNc,4688
35
39
  nsj_rest_lib2/service/entity_loader.py,sha256=I8Ua2CPCxxUovHJn8SSRz0bYfmdFqfWj-JQfNTBBypc,17842
36
- nsj_rest_lib2-0.0.29.dist-info/METADATA,sha256=n4ENC9nHnSZETVqLpUKWLDM8qpf6U_nzpWpG9z1Af9w,1094
37
- nsj_rest_lib2-0.0.29.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
- nsj_rest_lib2-0.0.29.dist-info/top_level.txt,sha256=L6zh0EfH8_rur7OJ8_V-El-XEMf4qg3bkF8ADgqLVIA,14
39
- nsj_rest_lib2-0.0.29.dist-info/RECORD,,
40
+ nsj_rest_lib2-0.0.31.dist-info/METADATA,sha256=I1o7yO3ACZoRB6Qnw1qRhegqWBv5_MoK_f-VgIppAcM,1094
41
+ nsj_rest_lib2-0.0.31.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
42
+ nsj_rest_lib2-0.0.31.dist-info/top_level.txt,sha256=L6zh0EfH8_rur7OJ8_V-El-XEMf4qg3bkF8ADgqLVIA,14
43
+ nsj_rest_lib2-0.0.31.dist-info/RECORD,,
@@ -1,285 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
-
4
- """
5
- Gerador de DTO e Entity a partir de um EDL JSON (v1.x) — AGORA usando 'repository'
6
- no lugar de 'storage', e propagando o de-para de colunas:
7
- - Entity usa os nomes físicos vindos de model.repository.columns[*].column (quando houver).
8
- - DTOField ganha entity_field="<nome_do_atributo_na_Entity>" para cada propriedade.
9
-
10
- Uso:
11
- python generator.py caminho/para/arquivo.edl.json
12
- """
13
-
14
- import json
15
- import sys
16
- import keyword
17
- from typing import Any, Dict, List, Tuple
18
-
19
- # -----------------------
20
- # Helpers de transformação
21
- # -----------------------
22
-
23
-
24
- def py_identifier(name: str) -> str:
25
- """Garante que o nome é um identificador Python válido."""
26
- n = (name or "").strip().replace("-", "_")
27
- if not n:
28
- n = "_"
29
- if not n.isidentifier() or keyword.iskeyword(n):
30
- n = f"{n}_"
31
- return n
32
-
33
-
34
- def detect_pk(model_props: Dict[str, Any]) -> str:
35
- for logical, meta in model_props.items():
36
- if isinstance(meta, dict) and meta.get("pk") is True:
37
- return logical
38
- return ""
39
-
40
-
41
- def to_python_type(prop: Dict[str, Any]) -> str:
42
- t = (prop.get("type") or "string").lower()
43
- fmt = (prop.get("format") or "").lower()
44
-
45
- if fmt in {"uuid"}:
46
- return "uuid.UUID"
47
- if t in {"datetime"}:
48
- return "datetime.datetime"
49
- if t in {"date"}:
50
- return "datetime.date"
51
- if t in {"integer"}:
52
- return "int"
53
- if t in {"number"}:
54
- return "float"
55
- if t in {"boolean"}:
56
- return "bool"
57
- return "str"
58
-
59
-
60
- def build_entity_field_name(logical: str, columns_map: Dict[str, Any]) -> str:
61
- """
62
- Retorna o nome do atributo na Entity para um dado campo lógico,
63
- priorizando o nome físico em repository.columns[logical].column (se existir).
64
- """
65
- col_meta = (columns_map or {}).get(logical) or {}
66
- entity_attr = col_meta.get("column") or logical
67
- return py_identifier(entity_attr)
68
-
69
-
70
- def dto_field_args(
71
- logical: str,
72
- prop: Dict[str, Any],
73
- required: List[str],
74
- columns_map: Dict[str, Any],
75
- ) -> Dict[str, Any]:
76
- args: Dict[str, Any] = {}
77
-
78
- # Política: sempre expor (resume=True) e nunca ocultar campos que existem na origem
79
- args["resume"] = True
80
-
81
- # PK / not_null
82
- if prop.get("pk") is True:
83
- args["pk"] = True
84
- args["not_null"] = True
85
- if logical in set(required):
86
- args["not_null"] = True
87
-
88
- # Strings: strip + limites
89
- t = (prop.get("type") or "string").lower()
90
- if t == "string":
91
- args["strip"] = True
92
- if "length" in prop:
93
- args["max"] = int(prop["length"])
94
- if "minimum" in prop:
95
- args["min"] = int(prop["minimum"])
96
-
97
- # Números
98
- if t in {"integer", "number"}:
99
- if "minimum" in prop:
100
- args["min"] = (
101
- int(prop["minimum"]) if t == "integer" else float(prop["minimum"])
102
- )
103
- if "maximum" in prop:
104
- args["max"] = (
105
- int(prop["maximum"]) if t == "integer" else float(prop["maximum"])
106
- )
107
-
108
- # Formatos especiais
109
- fmt = (prop.get("format") or "").lower()
110
- if fmt == "uuid":
111
- args["validator"] = "DTOFieldValidators().validate_uuid"
112
- args["min"] = 36
113
- args["max"] = 36
114
-
115
- # Default lógico (quando presente)
116
- if "default" in prop and prop["default"] is not None:
117
- default_val = prop["default"]
118
- if isinstance(default_val, (int, float, bool)):
119
- args["default_value"] = repr(default_val)
120
- elif isinstance(default_val, str) and default_val.endswith("()"):
121
- args["default_value"] = default_val
122
- else:
123
- args["default_value"] = repr(default_val)
124
-
125
- # Sempre informar o nome do atributo correspondente na Entity
126
- args["entity_field"] = build_entity_field_name(logical, columns_map)
127
-
128
- return args
129
-
130
-
131
- def render_dto(edl: Dict[str, Any]) -> Tuple[str, str]:
132
- model = edl.get("model", {}) or {}
133
- props: Dict[str, Any] = model.get("properties", {}) or {}
134
- required: List[str] = model.get("required", []) or []
135
- repository = model.get("repository", {}) or {}
136
- columns_map: Dict[str, Any] = repository.get("columns", {}) or {}
137
-
138
- entity_name_full = edl.get("id") or "Entity"
139
- class_base = entity_name_full.split(".")[-1]
140
- class_name = f"{class_base[0].upper()}{class_base[1:]}"
141
- dto_class = f"{class_name}DTO"
142
-
143
- # imports
144
- need_uuid = any(((p.get("format") or "").lower() == "uuid") for p in props.values())
145
- need_datetime = any(
146
- ((p.get("type") or "").lower() in {"datetime", "date"}) for p in props.values()
147
- )
148
-
149
- header_imports = [
150
- "from nsj_rest_lib.decorator.dto import DTO",
151
- "from nsj_rest_lib.descriptor.dto_field import DTOField",
152
- "from nsj_rest_lib.descriptor.dto_field_validators import DTOFieldValidators",
153
- "from nsj_rest_lib.dto.dto_base import DTOBase",
154
- ]
155
- if need_uuid:
156
- header_imports.insert(0, "import uuid")
157
- if need_datetime:
158
- header_imports.insert(0, "import datetime")
159
-
160
- lines: List[str] = []
161
- lines.extend(header_imports)
162
- lines.append("")
163
- lines.append("")
164
- lines.append("@DTO()")
165
- lines.append(f"class {dto_class}(DTOBase):")
166
- if not props:
167
- lines.append(" pass")
168
- return (dto_class, "\n".join(lines))
169
-
170
- for logical in props:
171
- meta = props[logical] or {}
172
- py_type = to_python_type(meta)
173
- field_args = dto_field_args(logical, meta, required, columns_map)
174
-
175
- # Monta chamada DTOField(...)
176
- arg_parts = []
177
- for k, v in field_args.items():
178
- if k == "validator":
179
- arg_parts.append(f"{k}={v}")
180
- else:
181
- arg_parts.append(f"{k}={repr(v)}")
182
- args_str = ", ".join(arg_parts) if arg_parts else ""
183
-
184
- lines.append("")
185
- lines.append(f" {py_identifier(logical)}: {py_type} = DTOField({args_str})")
186
-
187
- return (dto_class, "\n".join(lines))
188
-
189
-
190
- def render_entity(edl: Dict[str, Any]) -> Tuple[str, str]:
191
- model = edl.get("model", {}) or {}
192
- props: Dict[str, Any] = model.get("properties", {}) or {}
193
- repository = model.get("repository", {}) or {}
194
- api = model.get("api", {}) or {}
195
-
196
- entity_name_full = edl.get("id") or "Entity"
197
- class_base = entity_name_full.split(".")[-1]
198
- class_name = f"{class_base[0].upper()}{class_base[1:]}"
199
- entity_class = f"{class_name}Entity"
200
-
201
- table_name = repository.get("map") or "schema.tabela"
202
- pk_field = detect_pk(props) or "id"
203
-
204
- # default_order_fields = api.default_sort (removendo prefixos '+'|'-')
205
- default_sort: List[str] = []
206
- for item in api.get("default_sort", []) or []:
207
- fld = str(item).lstrip("+-")
208
- if fld in props:
209
- default_sort.append(fld)
210
- if not default_sort:
211
- default_sort = [pk_field] if pk_field else []
212
-
213
- # imports
214
- need_uuid = any(((p.get("format") or "").lower() == "uuid") for p in props.values())
215
- need_datetime = any(
216
- ((p.get("type") or "").lower() in {"datetime", "date"}) for p in props.values()
217
- )
218
-
219
- header_imports = [
220
- "from nsj_rest_lib.entity.entity_base import EntityBase",
221
- "from nsj_rest_lib.decorator.entity import Entity",
222
- ]
223
- if need_uuid:
224
- header_imports.insert(0, "import uuid")
225
- if need_datetime:
226
- header_imports.insert(0, "import datetime")
227
-
228
- columns_map: Dict[str, Any] = repository.get("columns", {}) or {}
229
-
230
- lines: List[str] = []
231
- lines.extend(header_imports)
232
- lines.append("")
233
- lines.append("")
234
- lines.append("@Entity(")
235
- lines.append(f" table_name={repr(table_name)},")
236
- lines.append(f" pk_field={repr(py_identifier(pk_field))},")
237
- if default_sort:
238
- lines.append(
239
- f" default_order_fields={[py_identifier(x) for x in default_sort]},"
240
- )
241
- lines.append(")")
242
- lines.append(f"class {entity_class}(EntityBase):")
243
-
244
- if not props:
245
- lines.append(" pass")
246
- return (entity_class, "\n".join(lines))
247
-
248
- for logical, meta in props.items():
249
- py_type = to_python_type(meta)
250
- entity_attr = build_entity_field_name(logical, columns_map)
251
- lines.append(f" {entity_attr}: {py_type} = None")
252
-
253
- return (entity_class, "\n".join(lines))
254
-
255
-
256
- def generate_from_edl(edl: Dict[str, Any]) -> Tuple[str, str, str, str]:
257
- dto_class_name, dto_code = render_dto(edl)
258
- entity_class_name, entity_code = render_entity(edl)
259
- return (dto_class_name, dto_code, entity_class_name, entity_code)
260
-
261
-
262
- # -----------------------
263
- # CLI
264
- # -----------------------
265
-
266
-
267
- def main():
268
- if len(sys.argv) < 2:
269
- print("Uso: python generator.py caminho/para/arquivo.edl.json", file=sys.stderr)
270
- sys.exit(2)
271
-
272
- with open(sys.argv[1], "r", encoding="utf-8") as f:
273
- edl = json.load(f)
274
-
275
- _, dto_code, _, entity_code = generate_from_edl(edl)
276
-
277
- sep = "\n" + ("#" * 80) + "\n"
278
- print(sep + "# DTO\n" + sep)
279
- print(dto_code)
280
- print(sep + "# ENTITY\n" + sep)
281
- print(entity_code)
282
-
283
-
284
- if __name__ == "__main__":
285
- main()