overture-schema-codegen 0.1.1.dev0__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 (61) hide show
  1. overture/schema/codegen/__init__.py +1 -0
  2. overture/schema/codegen/cli.py +228 -0
  3. overture/schema/codegen/extraction/__init__.py +0 -0
  4. overture/schema/codegen/extraction/docstring.py +46 -0
  5. overture/schema/codegen/extraction/enum_extraction.py +40 -0
  6. overture/schema/codegen/extraction/examples.py +367 -0
  7. overture/schema/codegen/extraction/field.py +172 -0
  8. overture/schema/codegen/extraction/field_constraints.py +185 -0
  9. overture/schema/codegen/extraction/field_walk.py +275 -0
  10. overture/schema/codegen/extraction/length_constraints.py +49 -0
  11. overture/schema/codegen/extraction/literal_alternatives.py +26 -0
  12. overture/schema/codegen/extraction/model_constraints.py +252 -0
  13. overture/schema/codegen/extraction/model_extraction.py +240 -0
  14. overture/schema/codegen/extraction/newtype_extraction.py +73 -0
  15. overture/schema/codegen/extraction/numeric_extraction.py +74 -0
  16. overture/schema/codegen/extraction/pydantic_extraction.py +33 -0
  17. overture/schema/codegen/extraction/specs.py +295 -0
  18. overture/schema/codegen/extraction/type_analyzer.py +693 -0
  19. overture/schema/codegen/extraction/type_registry.py +137 -0
  20. overture/schema/codegen/extraction/union_extraction.py +270 -0
  21. overture/schema/codegen/layout/__init__.py +0 -0
  22. overture/schema/codegen/layout/module_layout.py +139 -0
  23. overture/schema/codegen/layout/type_collection.py +122 -0
  24. overture/schema/codegen/markdown/__init__.py +0 -0
  25. overture/schema/codegen/markdown/link_computation.py +70 -0
  26. overture/schema/codegen/markdown/path_assignment.py +114 -0
  27. overture/schema/codegen/markdown/pipeline.py +198 -0
  28. overture/schema/codegen/markdown/renderer.py +641 -0
  29. overture/schema/codegen/markdown/reverse_references.py +169 -0
  30. overture/schema/codegen/markdown/templates/_used_by.md.jinja2 +10 -0
  31. overture/schema/codegen/markdown/templates/enum.md.jinja2 +13 -0
  32. overture/schema/codegen/markdown/templates/feature.md.jinja2 +45 -0
  33. overture/schema/codegen/markdown/templates/geometric.md.jinja2 +11 -0
  34. overture/schema/codegen/markdown/templates/newtype.md.jinja2 +17 -0
  35. overture/schema/codegen/markdown/templates/numeric.md.jinja2 +27 -0
  36. overture/schema/codegen/markdown/templates/pydantic_type.md.jinja2 +8 -0
  37. overture/schema/codegen/markdown/type_format.py +383 -0
  38. overture/schema/codegen/py.typed +0 -0
  39. overture/schema/codegen/pyspark/__init__.py +1 -0
  40. overture/schema/codegen/pyspark/_primitive_fill.py +23 -0
  41. overture/schema/codegen/pyspark/_render_common.py +477 -0
  42. overture/schema/codegen/pyspark/check_builder.py +961 -0
  43. overture/schema/codegen/pyspark/check_ir.py +223 -0
  44. overture/schema/codegen/pyspark/constraint_dispatch.py +753 -0
  45. overture/schema/codegen/pyspark/pipeline.py +220 -0
  46. overture/schema/codegen/pyspark/renderer.py +816 -0
  47. overture/schema/codegen/pyspark/schema_builder.py +187 -0
  48. overture/schema/codegen/pyspark/templates/_check_function.py.jinja2 +10 -0
  49. overture/schema/codegen/pyspark/templates/model_module.py.jinja2 +83 -0
  50. overture/schema/codegen/pyspark/templates/test_module.py.jinja2 +129 -0
  51. overture/schema/codegen/pyspark/test_data/__init__.py +9 -0
  52. overture/schema/codegen/pyspark/test_data/base_row.py +835 -0
  53. overture/schema/codegen/pyspark/test_data/constraint_values.py +203 -0
  54. overture/schema/codegen/pyspark/test_data/invalid_value.py +105 -0
  55. overture/schema/codegen/pyspark/test_data/scaffold.py +390 -0
  56. overture/schema/codegen/pyspark/test_renderer.py +708 -0
  57. overture/schema/codegen/spec_discovery.py +66 -0
  58. overture_schema_codegen-0.1.1.dev0.dist-info/METADATA +13 -0
  59. overture_schema_codegen-0.1.1.dev0.dist-info/RECORD +61 -0
  60. overture_schema_codegen-0.1.1.dev0.dist-info/WHEEL +4 -0
  61. overture_schema_codegen-0.1.1.dev0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,220 @@
1
+ """PySpark generation pipeline: produce modules without I/O.
2
+
3
+ Orchestrates check building, schema building, and rendering into
4
+ GeneratedModule objects. The caller decides what to do with them (write
5
+ to disk, stream to stdout, etc.).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Sequence
11
+ from dataclasses import dataclass
12
+ from pathlib import PurePosixPath
13
+
14
+ from overture.schema.system.case import to_snake_case
15
+ from overture.schema.system.discovery import entry_point_to_path
16
+ from overture.schema.system.geometric import GeometryType
17
+
18
+ from ..extraction.specs import ModelSpec, UnionSpec
19
+ from .check_builder import build_checks
20
+ from .check_ir import Check, ModelCheck
21
+ from .renderer import render_model_module
22
+ from .schema_builder import build_schema
23
+ from .test_data.base_row import (
24
+ generate_arm_rows,
25
+ generate_base_row,
26
+ generate_populated_arm_rows,
27
+ generate_populated_row,
28
+ )
29
+ from .test_renderer import render_test_module
30
+
31
+ __all__ = [
32
+ "GeneratedModule",
33
+ "PipelineOutput",
34
+ "generate_pyspark_module",
35
+ "generate_pyspark_modules",
36
+ ]
37
+
38
+
39
+ @dataclass(frozen=True, slots=True)
40
+ class GeneratedModule:
41
+ """A generated Python module with its content and output path."""
42
+
43
+ content: str
44
+ path: PurePosixPath
45
+
46
+
47
+ @dataclass(frozen=True, slots=True)
48
+ class PipelineOutput:
49
+ """PySpark modules emitted by the pipeline, split by output tree.
50
+
51
+ Source and test modules write to separate directories
52
+ (`--output-dir` and `--test-output-dir`), so they travel as two
53
+ lists rather than one. Both trees mirror the same relative layout,
54
+ so a path is meaningful only relative to its own tree.
55
+ """
56
+
57
+ source: list[GeneratedModule]
58
+ test: list[GeneratedModule]
59
+
60
+
61
+ _OUTPUT_PACKAGE = "overture.schema.pyspark.expressions.generated"
62
+
63
+
64
+ def _require_entry_point(spec: ModelSpec) -> str:
65
+ """Return *spec*'s entry point or raise if it's missing."""
66
+ if spec.entry_point is None:
67
+ msg = f"ModelSpec {spec.name!r} has no entry_point."
68
+ raise ValueError(msg)
69
+ return spec.entry_point
70
+
71
+
72
+ def _directory_and_model_name(spec: ModelSpec) -> tuple[PurePosixPath, str]:
73
+ """Return the output directory and snake_case model name for a spec.
74
+
75
+ Both halves derive from the entry-point's class name so filenames
76
+ and symbol names stay in sync with what the runtime registry
77
+ discovers.
78
+ """
79
+ directory, cls_name = entry_point_to_path(_require_entry_point(spec))
80
+ return directory, to_snake_case(cls_name)
81
+
82
+
83
+ def _extract_geometry_types(
84
+ field_checks: list[Check],
85
+ ) -> tuple[GeometryType, ...]:
86
+ """Collect allowed geometry types from every `check_geometry_type` descriptor.
87
+
88
+ A model may carry multiple `check_geometry_type` descriptors -- e.g.
89
+ one per union arm with a distinct allowed-types set. The result is the
90
+ union of all of them, sorted by name for deterministic output.
91
+ """
92
+ seen: set[GeometryType] = set()
93
+ for check in field_checks:
94
+ for desc in check.descriptors:
95
+ if desc.function != "check_geometry_type":
96
+ continue
97
+ for arg in desc.args:
98
+ if isinstance(arg, GeometryType):
99
+ seen.add(arg)
100
+ return tuple(sorted(seen, key=lambda g: g.name))
101
+
102
+
103
+ def generate_pyspark_module(spec: ModelSpec) -> GeneratedModule:
104
+ """Generate a PySpark validation module from a model spec.
105
+
106
+ Parameters
107
+ ----------
108
+ spec
109
+ The extracted model spec to generate from.
110
+
111
+ Returns
112
+ -------
113
+ GeneratedModule
114
+ Module content and a relative output path mirroring the
115
+ model's entry-point package layout.
116
+ """
117
+ return _render_module(spec, build_checks(spec))
118
+
119
+
120
+ def generate_pyspark_modules(
121
+ model_specs: Sequence[ModelSpec],
122
+ ) -> PipelineOutput:
123
+ """Generate PySpark validation modules for all models.
124
+
125
+ Parameters
126
+ ----------
127
+ model_specs
128
+ Extracted model specs to generate from.
129
+
130
+ Returns
131
+ -------
132
+ PipelineOutput
133
+ Source-tree model modules and test-tree modules. The generated
134
+ tree is PEP 420, so no `__init__.py` files are emitted.
135
+ """
136
+ items = [(spec, build_checks(spec)) for spec in model_specs]
137
+ source = [_render_module(spec, checks) for spec, checks in items]
138
+ test: list[GeneratedModule] = []
139
+ for spec, checks in items:
140
+ test.extend(_render_test_modules(spec, checks))
141
+ return PipelineOutput(source=source, test=test)
142
+
143
+
144
+ def _render_module(
145
+ spec: ModelSpec,
146
+ checks: tuple[list[Check], list[ModelCheck]],
147
+ ) -> GeneratedModule:
148
+ """Build checks, schema, and render for a model spec."""
149
+ field_checks, model_checks = checks
150
+ schema_fields = build_schema(spec)
151
+ geometry_types = _extract_geometry_types(field_checks)
152
+ directory, model_name = _directory_and_model_name(spec)
153
+ content = render_model_module(
154
+ model_name,
155
+ field_checks,
156
+ model_checks,
157
+ schema_fields,
158
+ geometry_types,
159
+ entry_point=_require_entry_point(spec),
160
+ partitions=spec.partitions,
161
+ )
162
+ return GeneratedModule(
163
+ content=content,
164
+ path=directory / f"{model_name}.py",
165
+ )
166
+
167
+
168
+ def _select_arm_rows(
169
+ spec: ModelSpec,
170
+ ) -> dict[str | None, tuple[dict[str, object], dict[str, object]]]:
171
+ """Map each test module's arm key to its (sparse, populated) base rows.
172
+
173
+ Multi-arm unions key by discriminator value (one entry per arm); other
174
+ specs use a single `None` key. Either way the caller iterates the dict
175
+ to emit one test module per entry.
176
+ """
177
+ if isinstance(spec, UnionSpec) and spec.discriminator_field:
178
+ sparse_arm_rows = generate_arm_rows(spec)
179
+ populated_arm_rows = generate_populated_arm_rows(spec)
180
+ return {
181
+ arm: (sparse_arm_rows[arm], populated_arm_rows[arm])
182
+ for arm in sparse_arm_rows
183
+ }
184
+ return {None: (generate_base_row(spec), generate_populated_row(spec))}
185
+
186
+
187
+ def _render_test_modules(
188
+ spec: ModelSpec,
189
+ checks: tuple[list[Check], list[ModelCheck]],
190
+ ) -> list[GeneratedModule]:
191
+ """Render test modules for a model spec.
192
+
193
+ For union specs with multiple discriminator arms, produces one
194
+ test module per arm. Each arm's test includes the field and
195
+ model checks tagged with that arm (or untagged), filtered by
196
+ `render_test_module`.
197
+ """
198
+ field_checks, model_checks = checks
199
+ directory, model_name = _directory_and_model_name(spec)
200
+ expression_import = ".".join([_OUTPUT_PACKAGE, *directory.parts, model_name])
201
+
202
+ modules: list[GeneratedModule] = []
203
+ for arm, (base_row_sparse, base_row_populated) in _select_arm_rows(spec).items():
204
+ suffix = f"_{arm}" if arm is not None else ""
205
+ modules.append(
206
+ GeneratedModule(
207
+ content=render_test_module(
208
+ model_name,
209
+ field_checks,
210
+ model_checks,
211
+ base_row_sparse=base_row_sparse,
212
+ base_row_populated=base_row_populated,
213
+ arm=arm,
214
+ spec=spec,
215
+ expression_import=expression_import,
216
+ ),
217
+ path=directory / f"test_{model_name}{suffix}.py",
218
+ )
219
+ )
220
+ return modules