ndsdk-cli 1.0.0__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.
Files changed (51) hide show
  1. ndsdk/__init__.py +3 -0
  2. ndsdk/config_normalizer.py +306 -0
  3. ndsdk/generators/__init__.py +8 -0
  4. ndsdk/generators/base.py +93 -0
  5. ndsdk/generators/engine.py +715 -0
  6. ndsdk/main.py +536 -0
  7. ndsdk/naming.py +112 -0
  8. ndsdk/packages/client_provider/Program.fragment.cs.j2 +1 -0
  9. ndsdk/packages/client_provider/appsettings.fragment.json.j2 +14 -0
  10. ndsdk/packages/client_provider/package.yaml +13 -0
  11. ndsdk/packages/db_provider/Program.fragment.cs.j2 +3 -0
  12. ndsdk/packages/db_provider/appsettings.fragment.json.j2 +30 -0
  13. ndsdk/packages/db_provider/package.yaml +123 -0
  14. ndsdk/packages/exception_handling/Program.app.fragment.cs.j2 +1 -0
  15. ndsdk/packages/exception_handling/Program.fragment.cs.j2 +1 -0
  16. ndsdk/packages/exception_handling/package.yaml +7 -0
  17. ndsdk/packages/file_storage_azure/Program.fragment.cs.j2 +1 -0
  18. ndsdk/packages/file_storage_azure/appsettings.fragment.json.j2 +6 -0
  19. ndsdk/packages/file_storage_azure/package.yaml +9 -0
  20. ndsdk/packages/observability/Program.fragment.cs.j2 +2 -0
  21. ndsdk/packages/observability/appsettings.fragment.json.j2 +39 -0
  22. ndsdk/packages/observability/package.yaml +16 -0
  23. ndsdk/registry.py +192 -0
  24. ndsdk/templates/_common/BO.cs.j2 +10 -0
  25. ndsdk/templates/_common/Controller.cs.j2 +131 -0
  26. ndsdk/templates/_common/DbProviderBase.cs.j2 +26 -0
  27. ndsdk/templates/_common/Dockerfile.j2 +15 -0
  28. ndsdk/templates/_common/Dto.cs.j2 +16 -0
  29. ndsdk/templates/_common/Entity.cs.j2 +16 -0
  30. ndsdk/templates/_common/IService.cs.j2 +25 -0
  31. ndsdk/templates/_common/Model.cs.j2 +16 -0
  32. ndsdk/templates/_common/Provider.cs.j2 +151 -0
  33. ndsdk/templates/_common/ProviderBase.cs.j2 +10 -0
  34. ndsdk/templates/_common/Service.cs.j2 +49 -0
  35. ndsdk/templates/_common/ServiceManager.cs.j2 +28 -0
  36. ndsdk/templates/_common/Solution.sln.j2 +22 -0
  37. ndsdk/templates/_common/_csproj_refs.inc.j2 +14 -0
  38. ndsdk/templates/_common/launchSettings.json.j2 +17 -0
  39. ndsdk/templates/microservice/clean/Api.csproj.j2 +12 -0
  40. ndsdk/templates/microservice/clean/ClassLib.csproj.j2 +11 -0
  41. ndsdk/templates/microservice/clean/Program.cs.j2 +35 -0
  42. ndsdk/templates/microservice/clean/layout.yaml +64 -0
  43. ndsdk/templates/microservice/layered/Program.cs.j2 +61 -0
  44. ndsdk/templates/microservice/layered/Project.csproj.j2 +12 -0
  45. ndsdk/templates/microservice/layered/layout.yaml +41 -0
  46. ndsdk/validators.py +213 -0
  47. ndsdk_cli-1.0.0.dist-info/METADATA +11 -0
  48. ndsdk_cli-1.0.0.dist-info/RECORD +51 -0
  49. ndsdk_cli-1.0.0.dist-info/WHEEL +5 -0
  50. ndsdk_cli-1.0.0.dist-info/entry_points.txt +2 -0
  51. ndsdk_cli-1.0.0.dist-info/top_level.txt +1 -0
ndsdk/validators.py ADDED
@@ -0,0 +1,213 @@
1
+ """Validation for the ND-SDK C# config schema."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .config_normalizer import normalize_config
6
+ from .generators import available_architectures
7
+ from .registry import PackageRegistry
8
+
9
+ VALID_METHODS = {"GET", "POST", "PUT", "DELETE", "PATCH"}
10
+ VALID_DB_TYPES = {"relational", "non_relational"}
11
+ VALID_DIRECTIONS = {"in", "out", "cursor"}
12
+
13
+
14
+ class ConfigValidator:
15
+ def __init__(self):
16
+ self.packages = PackageRegistry()
17
+
18
+ def validate(self, config: dict) -> tuple[bool, list[str]]:
19
+ errors: list[str] = []
20
+ config = normalize_config(config)
21
+ if not isinstance(config, dict) or "project" not in config:
22
+ return False, ["Missing 'project' section"]
23
+
24
+ project = config["project"]
25
+ if not project.get("name"):
26
+ errors.append("Missing required field: project.name")
27
+
28
+ ptype = project.get("type", "microservice")
29
+ style = project.get("architecture", "layered")
30
+ variant = f"{ptype}/{style}"
31
+ if variant not in available_architectures():
32
+ errors.append(
33
+ f"Unknown architecture '{variant}'. "
34
+ f"Available: {', '.join(available_architectures())}"
35
+ )
36
+
37
+ errors += self._validate_packages(
38
+ config.get("defaults", {}).get("packages", {}),
39
+ "defaults.packages",
40
+ )
41
+
42
+ services = config.get("services")
43
+ if not services:
44
+ errors.append("At least one service must be defined under 'services'")
45
+ else:
46
+ names: set = set()
47
+ for i, svc in enumerate(services):
48
+ errors += self._validate_service(svc, i, names)
49
+ return len(errors) == 0, errors
50
+
51
+ def _validate_packages(self, pkg_cfg: dict, where: str) -> list[str]:
52
+ errors = []
53
+ available = set(self.packages.available())
54
+ for key in (pkg_cfg or {}):
55
+ if key not in available:
56
+ errors.append(
57
+ f"{where}: unknown package '{key}'. "
58
+ f"Available: {', '.join(sorted(available)) or '(none)'}"
59
+ )
60
+ return errors
61
+
62
+ def _validate_service(self, svc: dict, idx: int, names: set) -> list[str]:
63
+ errors = []
64
+ name = svc.get("name")
65
+ if not name:
66
+ return [f"services[{idx}]: missing 'name'"]
67
+ if name in names:
68
+ errors.append(f"services[{idx}]: duplicate service name '{name}'")
69
+ names.add(name)
70
+
71
+ errors += self._validate_packages(
72
+ svc.get("packages", {}), f"services.{name}.packages"
73
+ )
74
+
75
+ model_names = {m.get("name") for m in svc.get("models", [])}
76
+ dto_names = {d.get("name") for d in svc.get("dtos", [])}
77
+ entity_names = {e.get("name") for e in svc.get("entities", [])}
78
+ integrations = {
79
+ i.get("name"): i
80
+ for i in svc.get("integrations", []) or []
81
+ if isinstance(i, dict) and i.get("name")
82
+ }
83
+
84
+ errors += self._validate_integrations(svc, name)
85
+ errors += self._validate_endpoints(svc, name, model_names, dto_names)
86
+ errors += self._validate_providers(svc, name, integrations, entity_names)
87
+ return errors
88
+
89
+ def _validate_integrations(self, svc: dict, service_name: str) -> list[str]:
90
+ errors = []
91
+ seen: set[str] = set()
92
+ for ii, integration in enumerate(svc.get("integrations", []) or []):
93
+ label = f"services.{service_name}.integrations[{ii}]"
94
+ if not isinstance(integration, dict):
95
+ errors.append(f"{label}: integration must be an object")
96
+ continue
97
+ iname = integration.get("name")
98
+ if not iname:
99
+ errors.append(f"{label}: missing 'name'")
100
+ elif iname in seen:
101
+ errors.append(f"{label}: duplicate integration name '{iname}'")
102
+ else:
103
+ seen.add(iname)
104
+ if not integration.get("kind"):
105
+ errors.append(f"{label}: missing 'kind'")
106
+ if str(integration.get("kind", "")).lower() == "database":
107
+ settings = integration.get("settings") if isinstance(integration.get("settings"), dict) else {}
108
+ dbt = settings.get("database_type", "non_relational")
109
+ if dbt not in VALID_DB_TYPES:
110
+ errors.append(
111
+ f"{label}.settings.database_type/database.type '{dbt}' invalid "
112
+ f"(expected one of {', '.join(sorted(VALID_DB_TYPES))})"
113
+ )
114
+ return errors
115
+
116
+ def _validate_endpoints(self, svc: dict, service_name: str, model_names: set, dto_names: set) -> list[str]:
117
+ errors = []
118
+ ep_names: set = set()
119
+ for j, ep in enumerate(svc.get("endpoints", [])):
120
+ label = f"services.{service_name}.endpoints[{j}]"
121
+ if not ep.get("name"):
122
+ errors.append(f"{label}: missing 'name'")
123
+ elif ep["name"] in ep_names:
124
+ errors.append(f"{label}: duplicate endpoint name '{ep['name']}'")
125
+ else:
126
+ ep_names.add(ep["name"])
127
+ method = str(ep.get("http_method", "GET")).upper()
128
+ if method not in VALID_METHODS:
129
+ errors.append(f"{label}: invalid http_method '{method}'")
130
+ for ref_field in ("request_dto", "response_dto"):
131
+ ref = ep.get(ref_field)
132
+ if ref and ref not in dto_names:
133
+ errors.append(
134
+ f"{label}: {ref_field} '{ref}' not defined in services.{service_name}.dtos"
135
+ )
136
+ for ref_field in ("request_model", "response_model"):
137
+ ref = ep.get(ref_field)
138
+ if ref and ref not in model_names and ref not in dto_names:
139
+ errors.append(
140
+ f"{label}: {ref_field} '{ref}' not defined in services.{service_name}.models or services.{service_name}.dtos"
141
+ )
142
+ return errors
143
+
144
+ def _validate_providers(self, svc: dict, service_name: str, integrations: dict, entity_names: set) -> list[str]:
145
+ errors = []
146
+ for k, prov in enumerate(svc.get("providers", []) or []):
147
+ plabel = f"services.{service_name}.providers[{k}]"
148
+ if not prov.get("name"):
149
+ errors.append(f"{plabel}: missing 'name'")
150
+ integration = integrations.get(prov.get("integration"))
151
+ if prov.get("integration") and not integration:
152
+ errors.append(f"{plabel}: integration '{prov.get('integration')}' not defined in services.{service_name}.integrations")
153
+ provider_kind = str(prov.get("kind") or (integration or {}).get("kind") or "custom").lower()
154
+
155
+ for mi, m in enumerate(prov.get("methods", []) or []):
156
+ mlabel = f"{plabel}.methods[{mi}]"
157
+ if not m.get("name"):
158
+ errors.append(f"{mlabel}: missing 'name'")
159
+ if not m.get("operation"):
160
+ errors.append(f"{mlabel}: missing 'operation'")
161
+
162
+ input_entity = m.get("input_entity") or m.get("entity")
163
+ if input_entity and input_entity not in entity_names:
164
+ errors.append(
165
+ f"{mlabel}: input_entity '{input_entity}' not defined in services.{service_name}.entities"
166
+ )
167
+ output_entity = m.get("output_entity")
168
+ if output_entity and output_entity not in entity_names:
169
+ errors.append(
170
+ f"{mlabel}: output_entity '{output_entity}' not defined in services.{service_name}.entities"
171
+ )
172
+
173
+ if provider_kind == "database":
174
+ errors += self._validate_database_method(m, mlabel)
175
+ return errors
176
+
177
+ def _validate_database_method(self, method: dict, label: str) -> list[str]:
178
+ errors = []
179
+ settings = method.get("settings") if isinstance(method.get("settings"), dict) else {}
180
+ # Compatibility when validator receives already-normalized or old shapes.
181
+ db_block = method.get("database") if isinstance(method.get("database"), dict) else {}
182
+ settings = {**db_block, **settings}
183
+ for key in ("stored_procedure", "target", "table", "inputs", "parameters"):
184
+ if key in method and key not in settings:
185
+ settings[key] = method[key]
186
+
187
+ method_type = settings.get("method_type") or method.get("kind")
188
+ if method_type and method_type not in VALID_DB_TYPES:
189
+ errors.append(f"{label}: invalid method_type '{method_type}'")
190
+
191
+ operation = str(method.get("operation", "")).lower()
192
+ if operation in {"storedprocedure", "stored_proc", "stored_procedure", "sp"}:
193
+ if not settings.get("stored_procedure") and not settings.get("target"):
194
+ errors.append(f"{label}: stored_procedure operation needs settings.stored_procedure or settings.target")
195
+ elif operation in {"query", "getresults", "get_results"}:
196
+ if not settings.get("table") and not settings.get("inputs") and not settings.get("target"):
197
+ errors.append(f"{label}: query operation needs settings.table, settings.inputs, or settings.target")
198
+
199
+ for pi, param in enumerate(settings.get("parameters", []) or []):
200
+ plabel = f"{label}.settings.parameters[{pi}]"
201
+ if not param.get("name"):
202
+ errors.append(f"{plabel}: missing 'name'")
203
+ d = param.get("direction", "in")
204
+ if d not in VALID_DIRECTIONS:
205
+ errors.append(
206
+ f"{plabel}: invalid direction '{d}' "
207
+ f"(expected {', '.join(sorted(VALID_DIRECTIONS))})"
208
+ )
209
+ if "size" in param and not isinstance(param.get("size"), int):
210
+ errors.append(f"{plabel}: 'size' must be an integer")
211
+ if d == "cursor" and not (param.get("cursor_name") or param.get("name")):
212
+ errors.append(f"{plabel}: cursor parameter needs 'cursor_name' or 'name'")
213
+ return errors
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: ndsdk-cli
3
+ Version: 1.0.0
4
+ Summary: ND-SDK metadata-driven Template generator
5
+ Author-email: Hari Hara Sudhan P <harihara.sudhan@novacisinnovations.com>
6
+ License: Proprietary
7
+ Keywords: code-generation,csharp,microservice,scaffolding,yaml
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: click>=8.1
10
+ Requires-Dist: PyYAML>=6.0
11
+ Requires-Dist: Jinja2>=3.1
@@ -0,0 +1,51 @@
1
+ ndsdk/__init__.py,sha256=DaMgQOu4LMF7-EsicjJ2dSuLaCIvhPZo8nQ67XUEdiU,107
2
+ ndsdk/config_normalizer.py,sha256=xxO0oebjXu3_2qu2ztF3aY0pVM_AtpOB-4icojt4M7I,11532
3
+ ndsdk/main.py,sha256=-l82D6_FpGcVB7x-CAfzTKRMap7ZZimFIo9w5Lj5Wdc,21827
4
+ ndsdk/naming.py,sha256=YpGR3k0aF0-9Wiz9lxv__Zg26v33_mvXhq5q9JpwBp0,3332
5
+ ndsdk/registry.py,sha256=vcGpbF4KaTBGAfEumxwg-M42f2HMpUdifG2BRnKb-5U,7271
6
+ ndsdk/validators.py,sha256=Ti1h-_QlzWuTLHk2JdvLmU_46DOEFwwCLBSoapIV_0I,10136
7
+ ndsdk/generators/__init__.py,sha256=FYwM29LgK-CSkmf0XXsiBeZT2L1K4EN8UAmPuoabcEo,307
8
+ ndsdk/generators/base.py,sha256=iySl6iHh9C0K3S3S4ZR9mUTccw336fFArTeql_otxjU,3101
9
+ ndsdk/generators/engine.py,sha256=X_pE912sV1thbIgrtNVJGknP6_iyq5AOEpi7FquY0Xs,32183
10
+ ndsdk/packages/client_provider/Program.fragment.cs.j2,sha256=20odXlUkDsvRHStEQNe0Lhj5OYFlTs4FeXHzmKqA858,38
11
+ ndsdk/packages/client_provider/appsettings.fragment.json.j2,sha256=jUQrmS_Ry6T2RJqSjGH3hXEyoMGNl7fs6a7gWXRjOPc,491
12
+ ndsdk/packages/client_provider/package.yaml,sha256=jx1kkPicrzsN_Fi5Knflv_Ds23Ero3xv5Wddn8pJ-CY,418
13
+ ndsdk/packages/db_provider/Program.fragment.cs.j2,sha256=qINRo0EY1ewMjoWGlyQvT5PpE0hnQrOgafSFtIO867c,157
14
+ ndsdk/packages/db_provider/appsettings.fragment.json.j2,sha256=AaUS1zGWrRxMWWVsBj_KNNPy7WEgOOSI0pm9Tye6OqI,1195
15
+ ndsdk/packages/db_provider/package.yaml,sha256=mysmCg6rvP519Qv2ZCDr9O8qwdsKMGl5HB0fxdk5DBU,5816
16
+ ndsdk/packages/exception_handling/Program.app.fragment.cs.j2,sha256=TURXywQHee1jFpuFLv6cO8Fj5g7GPFSjNkZDnHvItfE,52
17
+ ndsdk/packages/exception_handling/Program.fragment.cs.j2,sha256=f3BmbNY-HbuldoKixiy3CBySDPoSQh_qgGH2-gKyOHw,41
18
+ ndsdk/packages/exception_handling/package.yaml,sha256=thjkfWgggkaalekhpi6umVks1yL9e6EmrvfASoVB79U,240
19
+ ndsdk/packages/file_storage_azure/Program.fragment.cs.j2,sha256=pqnV9gUKRDMfqoLaGjqUkF9GYxoM71xqwmkzc4xqlEw,41
20
+ ndsdk/packages/file_storage_azure/appsettings.fragment.json.j2,sha256=gz2VwOkL35SwiuM_UZe6AX1gQgprXbCVwHZ3SL5UhM8,136
21
+ ndsdk/packages/file_storage_azure/package.yaml,sha256=d8H1XhBIap8hNwbLe_WFxoq7QUTqDvCiNrA5T-EtRTI,459
22
+ ndsdk/packages/observability/Program.fragment.cs.j2,sha256=roghPhFKbJkajGNsX3WEyhYFZykbh7Xb7d2yhr0tvn8,107
23
+ ndsdk/packages/observability/appsettings.fragment.json.j2,sha256=_RXiBMxu_GxUbKQKFZim0lBO5eSZVuK3jnjLiSAgLCU,1257
24
+ ndsdk/packages/observability/package.yaml,sha256=CM3t6_3_7szLsEOr7ydoQh4Zyh1Wa9n-VmNDIXwkfYU,610
25
+ ndsdk/templates/_common/BO.cs.j2,sha256=zr-bvzaZyDZUa_PADCyrFaTwIREc93P4Tm8yeeqGB5o,297
26
+ ndsdk/templates/_common/Controller.cs.j2,sha256=xVs4a4Idz8zcgO1irTqFbCJEuK0eZp2rZk3vfSyWCVc,5030
27
+ ndsdk/templates/_common/DbProviderBase.cs.j2,sha256=2efb1FRpWHFB-ru5XbbyGpUk5XyBmIaiKjy_gcPi4S0,1222
28
+ ndsdk/templates/_common/Dockerfile.j2,sha256=zgSGbeuQBgBSpGzgbnje9N5Nx25eA0Clc2yry9909bY,468
29
+ ndsdk/templates/_common/Dto.cs.j2,sha256=kedLzSIwlGZIWcDZCXIxW5GMJklPUtLlkjKVkasPqak,508
30
+ ndsdk/templates/_common/Entity.cs.j2,sha256=TSt4im7mLvpK8zGEAaP0Q_bFbuS4AxCu8GgvJ3IjN28,539
31
+ ndsdk/templates/_common/IService.cs.j2,sha256=n4_KbozXPSaXBoGqjX1eFE84Z5DdaD9YABwQfhg6C08,988
32
+ ndsdk/templates/_common/Model.cs.j2,sha256=gvWF4vRgcqdxP860Cfrwv-OpNxophnY7GfX7y2AxLvs,519
33
+ ndsdk/templates/_common/Provider.cs.j2,sha256=UNWS0ClPFHLKlznMHEcT69IGPpw2_SnbYycUqmlHVmA,7186
34
+ ndsdk/templates/_common/ProviderBase.cs.j2,sha256=wSZNJ_u-1fUuDePnWSkw9BiE61-yxSZCnqEvgMHZiMU,326
35
+ ndsdk/templates/_common/Service.cs.j2,sha256=yCrZ5UeV9x4p1ZVEHdt5DngK6PO0tCJYAgFjAbAbXm0,1752
36
+ ndsdk/templates/_common/ServiceManager.cs.j2,sha256=_SFqTur_EefqKqy0Ziw6kpnpabHaEZP_9OKcfEWjTJ8,917
37
+ ndsdk/templates/_common/Solution.sln.j2,sha256=e1YELg9n0cSlRyKBjS6bO09eJtgQ-gYrB-Q0tCjXmSc,933
38
+ ndsdk/templates/_common/_csproj_refs.inc.j2,sha256=6FvtI5GyGBMuw22BVe2PB11MZb5X4y8gjuT6sSWhPqk,379
39
+ ndsdk/templates/_common/launchSettings.json.j2,sha256=KXYX4kb5GCikPYwYW2h4Jw5YNEesyU81bRq9aToWy-A,538
40
+ ndsdk/templates/microservice/clean/Api.csproj.j2,sha256=T7nqKw0qNfpsmC_s63CMmAYmoJt4Cqde9qXzuw4kXRs,363
41
+ ndsdk/templates/microservice/clean/ClassLib.csproj.j2,sha256=gvYJRGDfrOR6-uTLQ_hzR92eJmuuntbHZrTGtug1_1Q,307
42
+ ndsdk/templates/microservice/clean/Program.cs.j2,sha256=Uwra5ky6FV0WuqT8UMgdSjkgBsSFYYjvZ4rzENqFqV8,919
43
+ ndsdk/templates/microservice/clean/layout.yaml,sha256=Al4b3PDTIgifxeyv31ad8b2TzpMvImxITIzJgPIusKk,3266
44
+ ndsdk/templates/microservice/layered/Program.cs.j2,sha256=CfaLTmsHSaeSppWkR_lnsgOrDZ10EOoDk7oCoojMFpU,1767
45
+ ndsdk/templates/microservice/layered/Project.csproj.j2,sha256=T7nqKw0qNfpsmC_s63CMmAYmoJt4Cqde9qXzuw4kXRs,363
46
+ ndsdk/templates/microservice/layered/layout.yaml,sha256=Vhjk7PHPXazsmCgu9U0XOIBF_s43mwNGonUAvoslcMI,2520
47
+ ndsdk_cli-1.0.0.dist-info/METADATA,sha256=HL2Z4bcgQ2oQv9S9DA5OUe4z_aM31nfdDwqpFr_LvgQ,393
48
+ ndsdk_cli-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
49
+ ndsdk_cli-1.0.0.dist-info/entry_points.txt,sha256=0G9b-hgeriQ70sEriA_Q3VzqhzudekwLLT_0Fo1mINY,45
50
+ ndsdk_cli-1.0.0.dist-info/top_level.txt,sha256=Pq37okmtVsM9QGJhaflyPxyojtMhrXKP9DCSndA_IFE,6
51
+ ndsdk_cli-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ndsdk-cli = ndsdk.main:cli
@@ -0,0 +1 @@
1
+ ndsdk