pyoq-sql 1.0.2__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.
- pyoq/__init__.py +10 -0
- pyoq/__main__.py +5 -0
- pyoq/_native.pyi +5 -0
- pyoq/cli/__init__.py +5 -0
- pyoq/cli/commands.py +270 -0
- pyoq/cli/defaults.py +98 -0
- pyoq/cli/services.py +97 -0
- pyoq/config/__init__.py +31 -0
- pyoq/config/connection.py +161 -0
- pyoq/config/loader.py +289 -0
- pyoq/config/models.py +245 -0
- pyoq/config/values.py +142 -0
- pyoq/descriptors.py +165 -0
- pyoq/diagnostics/__init__.py +68 -0
- pyoq/diagnostics/budget.py +136 -0
- pyoq/diagnostics/events.py +137 -0
- pyoq/diagnostics/fingerprint.py +267 -0
- pyoq/diagnostics/instrumented.py +237 -0
- pyoq/diagnostics/metrics.py +61 -0
- pyoq/diagnostics/observation.py +227 -0
- pyoq/diagnostics/scoped.py +103 -0
- pyoq/django/__init__.py +15 -0
- pyoq/django/apps.py +17 -0
- pyoq/django/execution.py +317 -0
- pyoq/django/generation.py +59 -0
- pyoq/django/management/__init__.py +0 -0
- pyoq/django/management/commands/__init__.py +0 -0
- pyoq/django/management/commands/makemigrations.py +53 -0
- pyoq/django/management/commands/pyoq_codegen.py +75 -0
- pyoq/django/parameters.py +101 -0
- pyoq/django/schema.py +379 -0
- pyoq/django/settings.py +87 -0
- pyoq/django/timeouts.py +105 -0
- pyoq/dsl/__init__.py +64 -0
- pyoq/dsl/aio/__init__.py +31 -0
- pyoq/dsl/aio/context.py +295 -0
- pyoq/dsl/aio/queries.py +335 -0
- pyoq/dsl/aio/writes.py +368 -0
- pyoq/dsl/context.py +326 -0
- pyoq/dsl/entry.py +37 -0
- pyoq/dsl/labels.py +36 -0
- pyoq/dsl/queries.py +339 -0
- pyoq/dsl/result.py +164 -0
- pyoq/dsl/writes.py +360 -0
- pyoq/errors.py +317 -0
- pyoq/fastapi/__init__.py +32 -0
- pyoq/fastapi/dependencies.py +167 -0
- pyoq/fastapi/lifespan.py +119 -0
- pyoq/fetching/__init__.py +55 -0
- pyoq/fetching/collections.py +136 -0
- pyoq/fetching/execution.py +587 -0
- pyoq/fetching/joined.py +79 -0
- pyoq/fetching/nesting.py +183 -0
- pyoq/fetching/plans.py +541 -0
- pyoq/fetching/select_in.py +149 -0
- pyoq/fetching/tables.py +110 -0
- pyoq/generation/__init__.py +54 -0
- pyoq/generation/cleanup.py +44 -0
- pyoq/generation/contracts.py +248 -0
- pyoq/generation/drift.py +169 -0
- pyoq/generation/lock.py +33 -0
- pyoq/generation/manifest.py +114 -0
- pyoq/generation/model.py +1001 -0
- pyoq/generation/pipeline.py +119 -0
- pyoq/generation/rendering/__init__.py +5 -0
- pyoq/generation/rendering/domains.py +51 -0
- pyoq/generation/rendering/enums.py +29 -0
- pyoq/generation/rendering/exports.py +70 -0
- pyoq/generation/rendering/imports.py +63 -0
- pyoq/generation/rendering/package.py +56 -0
- pyoq/generation/rendering/relations.py +133 -0
- pyoq/generation/rendering/routines.py +396 -0
- pyoq/generation/rendering/rows.py +79 -0
- pyoq/generation/rendering/source.py +121 -0
- pyoq/generation/rendering/tables.py +300 -0
- pyoq/generation/rendering/writes.py +514 -0
- pyoq/generation/validation.py +27 -0
- pyoq/generation/writer.py +184 -0
- pyoq/hydration/__init__.py +24 -0
- pyoq/hydration/engine.py +155 -0
- pyoq/hydration/identity.py +194 -0
- pyoq/hydration/plan.py +116 -0
- pyoq/migrations/__init__.py +9 -0
- pyoq/migrations/alembic.py +106 -0
- pyoq/migrations/hooks.py +75 -0
- pyoq/naming.py +261 -0
- pyoq/policies/__init__.py +47 -0
- pyoq/policies/bypass.py +122 -0
- pyoq/policies/governed.py +430 -0
- pyoq/policies/model.py +242 -0
- pyoq/policies/rewriting.py +263 -0
- pyoq/py.typed +1 -0
- pyoq/query/__init__.py +312 -0
- pyoq/query/aggregates.py +172 -0
- pyoq/query/arrays.py +65 -0
- pyoq/query/binding.py +52 -0
- pyoq/query/capabilities.py +317 -0
- pyoq/query/casts.py +73 -0
- pyoq/query/choices.py +185 -0
- pyoq/query/decoding.py +360 -0
- pyoq/query/documents.py +56 -0
- pyoq/query/execution/__init__.py +63 -0
- pyoq/query/execution/aio/__init__.py +31 -0
- pyoq/query/execution/aio/operations.py +228 -0
- pyoq/query/execution/aio/pooling.py +233 -0
- pyoq/query/execution/aio/streaming.py +161 -0
- pyoq/query/execution/aio/transactions.py +105 -0
- pyoq/query/execution/batch.py +96 -0
- pyoq/query/execution/binding_style.py +30 -0
- pyoq/query/execution/compilation.py +48 -0
- pyoq/query/execution/context.py +61 -0
- pyoq/query/execution/control.py +50 -0
- pyoq/query/execution/operations.py +224 -0
- pyoq/query/execution/planning.py +107 -0
- pyoq/query/execution/pooling.py +279 -0
- pyoq/query/execution/results.py +36 -0
- pyoq/query/execution/streaming.py +178 -0
- pyoq/query/execution/transactions.py +95 -0
- pyoq/query/expressions.py +1200 -0
- pyoq/query/fields.py +60 -0
- pyoq/query/mysql/__init__.py +59 -0
- pyoq/query/mysql/aio/__init__.py +38 -0
- pyoq/query/mysql/aio/commands.py +389 -0
- pyoq/query/mysql/aio/driver.py +196 -0
- pyoq/query/mysql/aio/executor.py +123 -0
- pyoq/query/mysql/aio/factory.py +26 -0
- pyoq/query/mysql/aio/operations.py +38 -0
- pyoq/query/mysql/aio/pool.py +53 -0
- pyoq/query/mysql/aio/transactions.py +313 -0
- pyoq/query/mysql/commands.py +354 -0
- pyoq/query/mysql/compiler.py +134 -0
- pyoq/query/mysql/context.py +20 -0
- pyoq/query/mysql/executor.py +126 -0
- pyoq/query/mysql/expressions.py +244 -0
- pyoq/query/mysql/factory.py +46 -0
- pyoq/query/mysql/health.py +66 -0
- pyoq/query/mysql/identifiers.py +9 -0
- pyoq/query/mysql/model.py +79 -0
- pyoq/query/mysql/operations.py +43 -0
- pyoq/query/mysql/parameters.py +69 -0
- pyoq/query/mysql/planning.py +20 -0
- pyoq/query/mysql/pool.py +67 -0
- pyoq/query/mysql/transactions.py +331 -0
- pyoq/query/mysql/writes.py +73 -0
- pyoq/query/nodes.py +750 -0
- pyoq/query/postgres/__init__.py +48 -0
- pyoq/query/postgres/aio/__init__.py +25 -0
- pyoq/query/postgres/aio/bulk.py +56 -0
- pyoq/query/postgres/aio/commands.py +264 -0
- pyoq/query/postgres/aio/executor.py +152 -0
- pyoq/query/postgres/aio/factory.py +26 -0
- pyoq/query/postgres/aio/operations.py +26 -0
- pyoq/query/postgres/aio/pool.py +40 -0
- pyoq/query/postgres/aio/transactions.py +295 -0
- pyoq/query/postgres/bulk.py +62 -0
- pyoq/query/postgres/commands.py +238 -0
- pyoq/query/postgres/compiler.py +114 -0
- pyoq/query/postgres/context.py +20 -0
- pyoq/query/postgres/executor.py +147 -0
- pyoq/query/postgres/expressions.py +311 -0
- pyoq/query/postgres/factory.py +24 -0
- pyoq/query/postgres/health.py +24 -0
- pyoq/query/postgres/identifiers.py +9 -0
- pyoq/query/postgres/model.py +81 -0
- pyoq/query/postgres/operations.py +25 -0
- pyoq/query/postgres/parameters.py +71 -0
- pyoq/query/postgres/planning.py +20 -0
- pyoq/query/postgres/pool.py +52 -0
- pyoq/query/postgres/transactions.py +295 -0
- pyoq/query/postgres/writes.py +37 -0
- pyoq/query/projections.py +105 -0
- pyoq/query/raw.py +90 -0
- pyoq/query/recursion.py +265 -0
- pyoq/query/rendering/__init__.py +1 -0
- pyoq/query/rendering/expressions.py +913 -0
- pyoq/query/rendering/identifiers.py +40 -0
- pyoq/query/rendering/projections.py +63 -0
- pyoq/query/rendering/queries.py +334 -0
- pyoq/query/rendering/sources.py +66 -0
- pyoq/query/rendering/writes.py +176 -0
- pyoq/query/results.py +459 -0
- pyoq/query/routines.py +196 -0
- pyoq/query/rows.py +156 -0
- pyoq/query/select.py +793 -0
- pyoq/query/select_nodes.py +277 -0
- pyoq/query/sources.py +236 -0
- pyoq/query/sqlite/__init__.py +43 -0
- pyoq/query/sqlite/commands.py +201 -0
- pyoq/query/sqlite/compiler.py +139 -0
- pyoq/query/sqlite/context.py +20 -0
- pyoq/query/sqlite/executor.py +119 -0
- pyoq/query/sqlite/expressions.py +224 -0
- pyoq/query/sqlite/factory.py +32 -0
- pyoq/query/sqlite/health.py +28 -0
- pyoq/query/sqlite/identifiers.py +9 -0
- pyoq/query/sqlite/model.py +73 -0
- pyoq/query/sqlite/operations.py +36 -0
- pyoq/query/sqlite/parameters.py +50 -0
- pyoq/query/sqlite/planning.py +20 -0
- pyoq/query/sqlite/pool.py +50 -0
- pyoq/query/sqlite/streaming.py +13 -0
- pyoq/query/sqlite/transactions.py +274 -0
- pyoq/query/sqlite/writes.py +35 -0
- pyoq/query/statements.py +27 -0
- pyoq/query/values.py +23 -0
- pyoq/query/vendor.py +162 -0
- pyoq/query/windows.py +424 -0
- pyoq/query/write_nodes.py +174 -0
- pyoq/query/writes.py +628 -0
- pyoq/relations/__init__.py +66 -0
- pyoq/relations/batching.py +219 -0
- pyoq/relations/derivation.py +111 -0
- pyoq/relations/fetching.py +355 -0
- pyoq/relations/graph.py +245 -0
- pyoq/relations/loading.py +74 -0
- pyoq/relations/model.py +75 -0
- pyoq/relations/planning.py +206 -0
- pyoq/runtime/__init__.py +9 -0
- pyoq/runtime/kernels.py +25 -0
- pyoq/runtime/python.py +43 -0
- pyoq/runtime/selection.py +73 -0
- pyoq/sanic/__init__.py +32 -0
- pyoq/sanic/scope.py +197 -0
- pyoq/sanic/workers.py +129 -0
- pyoq/schema/__init__.py +108 -0
- pyoq/schema/codec.py +711 -0
- pyoq/schema/models.py +604 -0
- pyoq/schema/mysql/__init__.py +16 -0
- pyoq/schema/mysql/connection.py +73 -0
- pyoq/schema/mysql/dsn.py +72 -0
- pyoq/schema/mysql/records.py +354 -0
- pyoq/schema/mysql/reflection.py +309 -0
- pyoq/schema/mysql/source.py +30 -0
- pyoq/schema/mysql/sql.py +128 -0
- pyoq/schema/mysql/types.py +105 -0
- pyoq/schema/postgres/__init__.py +13 -0
- pyoq/schema/postgres/connection.py +63 -0
- pyoq/schema/postgres/records.py +384 -0
- pyoq/schema/postgres/reflection.py +466 -0
- pyoq/schema/postgres/source.py +30 -0
- pyoq/schema/postgres/sql.py +246 -0
- pyoq/schema/postgres/types.py +98 -0
- pyoq/schema/registry.py +45 -0
- pyoq/schema/source.py +15 -0
- pyoq/schema/sqlite/__init__.py +6 -0
- pyoq/schema/sqlite/connection.py +54 -0
- pyoq/schema/sqlite/records.py +167 -0
- pyoq/schema/sqlite/reflection.py +393 -0
- pyoq/schema/sqlite/source.py +30 -0
- pyoq/schema/sqlite/sql.py +254 -0
- pyoq/schema/sqlite/types.py +74 -0
- pyoq/serving/__init__.py +23 -0
- pyoq/serving/databases.py +107 -0
- pyoq/serving/opening.py +331 -0
- pyoq/snapshots/__init__.py +20 -0
- pyoq/snapshots/drift.py +312 -0
- pyoq/snapshots/files.py +96 -0
- pyoq/snapshots/routing.py +40 -0
- pyoq/snapshots/source.py +33 -0
- pyoq/tracing/__init__.py +5 -0
- pyoq/tracing/spans.py +89 -0
- pyoq/unset.py +14 -0
- pyoq_sql-1.0.2.dist-info/METADATA +3050 -0
- pyoq_sql-1.0.2.dist-info/RECORD +267 -0
- pyoq_sql-1.0.2.dist-info/WHEEL +4 -0
- pyoq_sql-1.0.2.dist-info/entry_points.txt +3 -0
- pyoq_sql-1.0.2.dist-info/licenses/LICENSE +373 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Generation planning and execution orchestration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import PurePosixPath
|
|
7
|
+
|
|
8
|
+
from pyoq.config import Configuration
|
|
9
|
+
from pyoq.errors import (
|
|
10
|
+
GenerationDriftError,
|
|
11
|
+
GenerationOwnershipError,
|
|
12
|
+
GenerationValidationError,
|
|
13
|
+
)
|
|
14
|
+
from pyoq.generation.contracts import (
|
|
15
|
+
ConcernRenderer,
|
|
16
|
+
DriftChecker,
|
|
17
|
+
DriftReport,
|
|
18
|
+
GenerationMode,
|
|
19
|
+
GenerationPlan,
|
|
20
|
+
ManifestStore,
|
|
21
|
+
OwnershipManifest,
|
|
22
|
+
PlanValidator,
|
|
23
|
+
PlanWriter,
|
|
24
|
+
ProjectLock,
|
|
25
|
+
RenderedFile,
|
|
26
|
+
)
|
|
27
|
+
from pyoq.schema import SchemaSnapshot, SchemaSource
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True, slots=True)
|
|
31
|
+
class GenerationPlanner:
|
|
32
|
+
renderers: tuple[ConcernRenderer, ...]
|
|
33
|
+
|
|
34
|
+
def __post_init__(self) -> None:
|
|
35
|
+
if any(not renderer.concern for renderer in self.renderers):
|
|
36
|
+
message = "rendering concern must be a non-empty string"
|
|
37
|
+
raise GenerationValidationError(message)
|
|
38
|
+
duplicate_concerns = _duplicate_concerns(self.renderers)
|
|
39
|
+
if duplicate_concerns:
|
|
40
|
+
message = f"duplicate rendering concerns: {duplicate_concerns!r}"
|
|
41
|
+
raise GenerationValidationError(message)
|
|
42
|
+
|
|
43
|
+
def plan(
|
|
44
|
+
self,
|
|
45
|
+
snapshot: SchemaSnapshot,
|
|
46
|
+
configuration: Configuration,
|
|
47
|
+
) -> GenerationPlan:
|
|
48
|
+
rendered_files: list[RenderedFile] = []
|
|
49
|
+
for renderer in sorted(self.renderers, key=lambda candidate: candidate.concern):
|
|
50
|
+
rendered_files.extend(renderer.render(snapshot, configuration))
|
|
51
|
+
return GenerationPlan(tuple(rendered_files))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _duplicate_concerns(
|
|
55
|
+
renderers: tuple[ConcernRenderer, ...],
|
|
56
|
+
) -> tuple[str, ...]:
|
|
57
|
+
seen: set[str] = set()
|
|
58
|
+
duplicates: set[str] = set()
|
|
59
|
+
for renderer in renderers:
|
|
60
|
+
if renderer.concern in seen:
|
|
61
|
+
duplicates.add(renderer.concern)
|
|
62
|
+
seen.add(renderer.concern)
|
|
63
|
+
return tuple(sorted(duplicates))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True, slots=True)
|
|
67
|
+
class GenerationPipeline:
|
|
68
|
+
schema_source: SchemaSource
|
|
69
|
+
planner: GenerationPlanner
|
|
70
|
+
validator: PlanValidator
|
|
71
|
+
manifest_store: ManifestStore
|
|
72
|
+
drift_checker: DriftChecker
|
|
73
|
+
writer: PlanWriter
|
|
74
|
+
project_lock: ProjectLock
|
|
75
|
+
|
|
76
|
+
def execute(self, configuration: Configuration, mode: GenerationMode) -> str:
|
|
77
|
+
snapshot = self.schema_source.load(configuration)
|
|
78
|
+
plan = self.planner.plan(snapshot, configuration)
|
|
79
|
+
self.validator.validate(plan)
|
|
80
|
+
desired_manifest = OwnershipManifest.from_plan(
|
|
81
|
+
configuration.codegen_package,
|
|
82
|
+
plan,
|
|
83
|
+
)
|
|
84
|
+
with self.project_lock.acquire(configuration.project_root):
|
|
85
|
+
current_manifest = self.manifest_store.load(configuration.generated_path)
|
|
86
|
+
report = self.drift_checker.compare(
|
|
87
|
+
configuration.generated_path,
|
|
88
|
+
plan,
|
|
89
|
+
current_manifest,
|
|
90
|
+
desired_manifest,
|
|
91
|
+
)
|
|
92
|
+
if mode is GenerationMode.DRY_RUN:
|
|
93
|
+
return f"generation dry run: {report.summary()}"
|
|
94
|
+
if mode is GenerationMode.CHECK:
|
|
95
|
+
return _checked_message(report)
|
|
96
|
+
if report.is_current:
|
|
97
|
+
return "generated package is current"
|
|
98
|
+
_require_no_conflicts(report.conflicts)
|
|
99
|
+
self.writer.write(
|
|
100
|
+
configuration.generated_path,
|
|
101
|
+
plan,
|
|
102
|
+
current_manifest,
|
|
103
|
+
desired_manifest,
|
|
104
|
+
)
|
|
105
|
+
return f"generated {len(plan.files)} files"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _checked_message(report: DriftReport) -> str:
|
|
109
|
+
if report.is_current:
|
|
110
|
+
return "generated package is current"
|
|
111
|
+
message = f"generated package has drift: {report.summary()}"
|
|
112
|
+
raise GenerationDriftError(message)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _require_no_conflicts(conflicts: tuple[PurePosixPath, ...]) -> None:
|
|
116
|
+
if conflicts:
|
|
117
|
+
joined_paths = ", ".join(str(path) for path in conflicts)
|
|
118
|
+
message = f"generated package contains ownership conflicts: {joined_paths}"
|
|
119
|
+
raise GenerationOwnershipError(message)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Generated database domain type rendering."""
|
|
2
|
+
|
|
3
|
+
from pathlib import PurePosixPath
|
|
4
|
+
|
|
5
|
+
from pyoq.generation.contracts import RenderedFile
|
|
6
|
+
from pyoq.generation.model import GeneratedPackage
|
|
7
|
+
from pyoq.generation.rendering.imports import plan_annotation_imports
|
|
8
|
+
from pyoq.generation.rendering.source import (
|
|
9
|
+
PythonSource,
|
|
10
|
+
literal,
|
|
11
|
+
render_all,
|
|
12
|
+
render_from_import,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def render_domains(package: GeneratedPackage) -> RenderedFile:
|
|
17
|
+
"""A domain becomes a name of its own over the type it is written over.
|
|
18
|
+
|
|
19
|
+
Two domains over one base are two types, and a distinct name is what lets
|
|
20
|
+
a checker tell them apart. The value is still the base value, so nothing
|
|
21
|
+
is converted on the way in or out.
|
|
22
|
+
"""
|
|
23
|
+
source = PythonSource()
|
|
24
|
+
source.line('"""Generated database domain types."""')
|
|
25
|
+
annotations = tuple(domain.base_annotation for domain in package.domains)
|
|
26
|
+
imports = plan_annotation_imports(annotations, package)
|
|
27
|
+
if package.domains:
|
|
28
|
+
source.line()
|
|
29
|
+
source.lines(tuple(sorted((*imports.standard, "from typing import NewType"))))
|
|
30
|
+
if imports.descriptor_names:
|
|
31
|
+
source.line()
|
|
32
|
+
source.lines(
|
|
33
|
+
render_from_import("pyoq.descriptors", imports.descriptor_names)
|
|
34
|
+
)
|
|
35
|
+
if imports.enum_names:
|
|
36
|
+
source.line()
|
|
37
|
+
source.lines(render_from_import(".enums", imports.enum_names))
|
|
38
|
+
for domain in package.domains:
|
|
39
|
+
source.line()
|
|
40
|
+
source.line(
|
|
41
|
+
f"{domain.type_name} = NewType("
|
|
42
|
+
f"{literal(domain.type_name)}, {domain.base_annotation})"
|
|
43
|
+
)
|
|
44
|
+
source.line()
|
|
45
|
+
if package.domains:
|
|
46
|
+
source.line()
|
|
47
|
+
source.lines(render_all(tuple(domain.type_name for domain in package.domains)))
|
|
48
|
+
return RenderedFile(PurePosixPath("domains.py"), source.render())
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
__all__ = ("render_domains",)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Generated enum source rendering."""
|
|
2
|
+
|
|
3
|
+
from pathlib import PurePosixPath
|
|
4
|
+
|
|
5
|
+
from pyoq.generation.contracts import RenderedFile
|
|
6
|
+
from pyoq.generation.model import GeneratedPackage
|
|
7
|
+
from pyoq.generation.rendering.source import PythonSource, literal, render_all
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def render_enums(package: GeneratedPackage) -> RenderedFile:
|
|
11
|
+
source = PythonSource()
|
|
12
|
+
source.line('"""Generated database enum types."""')
|
|
13
|
+
if package.enums:
|
|
14
|
+
source.line()
|
|
15
|
+
source.line("from enum import StrEnum")
|
|
16
|
+
for enum in package.enums:
|
|
17
|
+
source.line()
|
|
18
|
+
source.line()
|
|
19
|
+
source.line(f"class {enum.class_name}(StrEnum):")
|
|
20
|
+
for member_name, member_value in enum.members:
|
|
21
|
+
source.line(f" {member_name} = {literal(member_value)}")
|
|
22
|
+
source.line()
|
|
23
|
+
if package.enums:
|
|
24
|
+
source.line()
|
|
25
|
+
source.lines(render_all(tuple(enum.class_name for enum in package.enums)))
|
|
26
|
+
return RenderedFile(PurePosixPath("enums.py"), source.render())
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
__all__ = ("render_enums",)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Generated package facade source rendering."""
|
|
2
|
+
|
|
3
|
+
from pathlib import PurePosixPath
|
|
4
|
+
|
|
5
|
+
from pyoq.generation.contracts import RenderedFile
|
|
6
|
+
from pyoq.generation.model import GeneratedPackage
|
|
7
|
+
from pyoq.generation.rendering.source import (
|
|
8
|
+
PythonSource,
|
|
9
|
+
render_all,
|
|
10
|
+
render_from_import,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def render_exports(package: GeneratedPackage) -> RenderedFile:
|
|
15
|
+
source = PythonSource()
|
|
16
|
+
source.line('"""Generated database package."""')
|
|
17
|
+
module_exports = _module_exports(package)
|
|
18
|
+
if any(names for _, names in module_exports):
|
|
19
|
+
source.line()
|
|
20
|
+
for module_name, names in module_exports:
|
|
21
|
+
if names:
|
|
22
|
+
source.lines(render_from_import(f".{module_name}", names))
|
|
23
|
+
source.line()
|
|
24
|
+
source.lines(
|
|
25
|
+
render_all(tuple(name for _, names in module_exports for name in names))
|
|
26
|
+
)
|
|
27
|
+
return RenderedFile(PurePosixPath("__init__.py"), source.render())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _module_exports(
|
|
31
|
+
package: GeneratedPackage,
|
|
32
|
+
) -> tuple[tuple[str, tuple[str, ...]], ...]:
|
|
33
|
+
enum_names = tuple(enum.class_name for enum in package.enums)
|
|
34
|
+
row_names = tuple(
|
|
35
|
+
name
|
|
36
|
+
for table in package.tables
|
|
37
|
+
for name in (table.row_name, *(key.type_name for key in table.keys))
|
|
38
|
+
)
|
|
39
|
+
write_names = tuple(
|
|
40
|
+
name
|
|
41
|
+
for table in package.tables
|
|
42
|
+
for name in (
|
|
43
|
+
table.insert_name,
|
|
44
|
+
f"{table.insert_name}Values",
|
|
45
|
+
table.update_name,
|
|
46
|
+
f"{table.update_name}Values",
|
|
47
|
+
table.builder_name,
|
|
48
|
+
table.update_builder_name,
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
table_names = tuple(
|
|
52
|
+
name
|
|
53
|
+
for table in package.tables
|
|
54
|
+
for name in (table.class_name, table.constant_name)
|
|
55
|
+
)
|
|
56
|
+
relation_names = tuple(
|
|
57
|
+
relation.constant_name
|
|
58
|
+
for table in package.tables
|
|
59
|
+
for relation in table.relations
|
|
60
|
+
)
|
|
61
|
+
return (
|
|
62
|
+
("enums", enum_names),
|
|
63
|
+
("relations", relation_names),
|
|
64
|
+
("rows", row_names),
|
|
65
|
+
("tables", table_names),
|
|
66
|
+
("writes", write_names),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
__all__ = ("render_exports",)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Import planning for generated type annotations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from pyoq.generation.model import GeneratedPackage
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class AnnotationImports:
|
|
12
|
+
standard: tuple[str, ...]
|
|
13
|
+
descriptor_names: tuple[str, ...]
|
|
14
|
+
enum_names: tuple[str, ...]
|
|
15
|
+
domain_names: tuple[str, ...] = ()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def plan_annotation_imports(
|
|
19
|
+
annotations: tuple[str, ...],
|
|
20
|
+
package: GeneratedPackage,
|
|
21
|
+
) -> AnnotationImports:
|
|
22
|
+
standard: list[str] = []
|
|
23
|
+
datetime_names = tuple(
|
|
24
|
+
name
|
|
25
|
+
for name in ("date", "datetime", "time", "timedelta")
|
|
26
|
+
if any(_contains_type(annotation, name) for annotation in annotations)
|
|
27
|
+
)
|
|
28
|
+
if datetime_names:
|
|
29
|
+
standard.append(f"from datetime import {', '.join(datetime_names)}")
|
|
30
|
+
if any(_contains_type(annotation, "Decimal") for annotation in annotations):
|
|
31
|
+
standard.append("from decimal import Decimal")
|
|
32
|
+
if any(_contains_type(annotation, "UUID") for annotation in annotations):
|
|
33
|
+
standard.append("from uuid import UUID")
|
|
34
|
+
descriptor_names = (
|
|
35
|
+
("JsonValue",)
|
|
36
|
+
if any(_contains_type(annotation, "JsonValue") for annotation in annotations)
|
|
37
|
+
else ()
|
|
38
|
+
)
|
|
39
|
+
enum_names = _named(tuple(enum.class_name for enum in package.enums), annotations)
|
|
40
|
+
domain_names = _named(
|
|
41
|
+
tuple(domain.type_name for domain in package.domains), annotations
|
|
42
|
+
)
|
|
43
|
+
return AnnotationImports(
|
|
44
|
+
tuple(sorted(standard)), descriptor_names, enum_names, domain_names
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _named(generated: tuple[str, ...], annotations: tuple[str, ...]) -> tuple[str, ...]:
|
|
49
|
+
"""The generated names an annotation writes, which are the ones to import."""
|
|
50
|
+
return tuple(
|
|
51
|
+
name
|
|
52
|
+
for name in generated
|
|
53
|
+
if any(_contains_type(annotation, name) for annotation in annotations)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _contains_type(annotation: str, type_name: str) -> bool:
|
|
58
|
+
normalized = annotation.replace("[", " ").replace("]", " ").replace(",", " ")
|
|
59
|
+
tokens = normalized.replace("|", " ").replace(".", " ").split()
|
|
60
|
+
return type_name in tokens
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
__all__ = ("AnnotationImports", "plan_annotation_imports")
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Generated database package rendering orchestration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
from pyoq.config import Configuration
|
|
9
|
+
from pyoq.generation.contracts import RenderedFile
|
|
10
|
+
from pyoq.generation.model import GeneratedPackage, build_codegen_package
|
|
11
|
+
from pyoq.generation.rendering.domains import render_domains
|
|
12
|
+
from pyoq.generation.rendering.enums import render_enums
|
|
13
|
+
from pyoq.generation.rendering.exports import render_exports
|
|
14
|
+
from pyoq.generation.rendering.relations import render_relations
|
|
15
|
+
from pyoq.generation.rendering.routines import render_routines
|
|
16
|
+
from pyoq.generation.rendering.rows import render_rows
|
|
17
|
+
from pyoq.generation.rendering.tables import render_tables
|
|
18
|
+
from pyoq.generation.rendering.writes import render_writes
|
|
19
|
+
from pyoq.naming import NamingPolicy
|
|
20
|
+
from pyoq.schema import SchemaSnapshot
|
|
21
|
+
|
|
22
|
+
PackageFileRenderer = Callable[[GeneratedPackage], RenderedFile]
|
|
23
|
+
DEFAULT_FILE_RENDERERS: tuple[PackageFileRenderer, ...] = (
|
|
24
|
+
render_domains,
|
|
25
|
+
render_enums,
|
|
26
|
+
render_exports,
|
|
27
|
+
render_relations,
|
|
28
|
+
render_routines,
|
|
29
|
+
render_rows,
|
|
30
|
+
render_tables,
|
|
31
|
+
render_writes,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True, slots=True)
|
|
36
|
+
class GeneratedTypesRenderer:
|
|
37
|
+
naming_policy: NamingPolicy = field(default_factory=NamingPolicy)
|
|
38
|
+
file_renderers: tuple[PackageFileRenderer, ...] = DEFAULT_FILE_RENDERERS
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def concern(self) -> str:
|
|
42
|
+
return "generated-types"
|
|
43
|
+
|
|
44
|
+
def render(
|
|
45
|
+
self,
|
|
46
|
+
snapshot: SchemaSnapshot,
|
|
47
|
+
configuration: Configuration,
|
|
48
|
+
) -> tuple[RenderedFile, ...]:
|
|
49
|
+
codegen_package = build_codegen_package(snapshot, self.naming_policy)
|
|
50
|
+
rendered_files = tuple(
|
|
51
|
+
renderer(codegen_package) for renderer in self.file_renderers
|
|
52
|
+
)
|
|
53
|
+
return tuple(sorted(rendered_files, key=lambda generated: str(generated.path)))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
__all__ = ("DEFAULT_FILE_RENDERERS", "GeneratedTypesRenderer", "PackageFileRenderer")
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Generated relationship descriptor source rendering."""
|
|
2
|
+
|
|
3
|
+
from pathlib import PurePosixPath
|
|
4
|
+
|
|
5
|
+
from pyoq.generation.contracts import RenderedFile
|
|
6
|
+
from pyoq.generation.model import (
|
|
7
|
+
GeneratedPackage,
|
|
8
|
+
GeneratedRelation,
|
|
9
|
+
GeneratedTable,
|
|
10
|
+
)
|
|
11
|
+
from pyoq.generation.rendering.source import (
|
|
12
|
+
PythonSource,
|
|
13
|
+
literal,
|
|
14
|
+
render_all,
|
|
15
|
+
render_from_import,
|
|
16
|
+
render_string_tuple,
|
|
17
|
+
render_subscript,
|
|
18
|
+
)
|
|
19
|
+
from pyoq.schema import Identifier
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def render_relations(package: GeneratedPackage) -> RenderedFile:
|
|
23
|
+
source = PythonSource()
|
|
24
|
+
source.line('"""Generated typed relationship descriptors."""')
|
|
25
|
+
has_relations = any(table.relations for table in package.tables)
|
|
26
|
+
if has_relations:
|
|
27
|
+
source.line()
|
|
28
|
+
source.line("from __future__ import annotations")
|
|
29
|
+
source.line()
|
|
30
|
+
source.line("from pyoq.descriptors import RelationshipDescriptor")
|
|
31
|
+
source.line("from pyoq.relations import RelationCardinality, RelationDirection")
|
|
32
|
+
source.line("from pyoq.schema import ReferentialAction")
|
|
33
|
+
source.line()
|
|
34
|
+
source.lines(
|
|
35
|
+
render_from_import(
|
|
36
|
+
".rows",
|
|
37
|
+
_imported_row_names(package),
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
_render_all_relations(source, package)
|
|
41
|
+
source.line()
|
|
42
|
+
if has_relations:
|
|
43
|
+
source.line()
|
|
44
|
+
source.lines(render_all(_exported_names(package)))
|
|
45
|
+
return RenderedFile(PurePosixPath("relations.py"), source.render())
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _render_all_relations(source: PythonSource, package: GeneratedPackage) -> None:
|
|
49
|
+
"""One blank line separates the imports from the first constant.
|
|
50
|
+
|
|
51
|
+
Two is what a definition gets. These are assignments, and the import
|
|
52
|
+
ordering check reads the difference.
|
|
53
|
+
"""
|
|
54
|
+
written = False
|
|
55
|
+
for table in package.tables:
|
|
56
|
+
for relation in table.relations:
|
|
57
|
+
source.line()
|
|
58
|
+
if written:
|
|
59
|
+
source.line()
|
|
60
|
+
_render_relation(source, table, relation)
|
|
61
|
+
written = True
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _render_relation(
|
|
65
|
+
source: PythonSource,
|
|
66
|
+
table: GeneratedTable,
|
|
67
|
+
relation: GeneratedRelation,
|
|
68
|
+
) -> None:
|
|
69
|
+
fact = relation.source
|
|
70
|
+
source.lines(
|
|
71
|
+
render_subscript(
|
|
72
|
+
f"{relation.constant_name}: ",
|
|
73
|
+
"RelationshipDescriptor",
|
|
74
|
+
(table.row_name, relation.target_row_name),
|
|
75
|
+
argument_indent=" ",
|
|
76
|
+
closing_indent="",
|
|
77
|
+
suffix=" = RelationshipDescriptor(",
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
source.line(f" source_table={literal(fact.source.table.name.value)},")
|
|
81
|
+
source.line(f" source_columns={_columns(fact.source.columns)},")
|
|
82
|
+
source.line(f" target_table={literal(fact.target.table.name.value)},")
|
|
83
|
+
source.line(f" target_columns={_columns(fact.target.columns)},")
|
|
84
|
+
source.line(f" source_schema={literal(_part(fact.source.table.schema))},")
|
|
85
|
+
source.line(f" source_catalog={literal(_part(fact.source.table.catalog))},")
|
|
86
|
+
source.line(f" target_schema={literal(_part(fact.target.table.schema))},")
|
|
87
|
+
source.line(f" target_catalog={literal(_part(fact.target.table.catalog))},")
|
|
88
|
+
database_name = fact.constraint.value if fact.constraint is not None else None
|
|
89
|
+
source.line(f" database_name={literal(database_name)},")
|
|
90
|
+
source.line(f" on_update=ReferentialAction.{fact.on_update.name},")
|
|
91
|
+
source.line(f" on_delete=ReferentialAction.{fact.on_delete.name},")
|
|
92
|
+
source.line(f" direction=RelationDirection.{fact.direction.name},")
|
|
93
|
+
source.line(f" cardinality=RelationCardinality.{fact.cardinality.name},")
|
|
94
|
+
source.line(f" optional={fact.optional!r},")
|
|
95
|
+
source.line(f" target_row_type={relation.target_row_name},")
|
|
96
|
+
source.line(")")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _part(value: Identifier | None) -> str | None:
|
|
100
|
+
return None if value is None else value.value
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _imported_row_names(package: GeneratedPackage) -> tuple[str, ...]:
|
|
104
|
+
"""Only the rows this file names, because an unused import is a defect.
|
|
105
|
+
|
|
106
|
+
A table without relations is never mentioned here, so importing its row
|
|
107
|
+
would leave the generated package failing a check the project runs.
|
|
108
|
+
"""
|
|
109
|
+
return tuple(
|
|
110
|
+
row_name
|
|
111
|
+
for table in package.tables
|
|
112
|
+
if table.relations
|
|
113
|
+
for row_name in (
|
|
114
|
+
table.row_name,
|
|
115
|
+
*(relation.target_row_name for relation in table.relations),
|
|
116
|
+
)
|
|
117
|
+
if row_name != "object"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _columns(columns: tuple[Identifier, ...]) -> str:
|
|
122
|
+
return render_string_tuple(tuple(column.value for column in columns))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _exported_names(package: GeneratedPackage) -> tuple[str, ...]:
|
|
126
|
+
return tuple(
|
|
127
|
+
relation.constant_name
|
|
128
|
+
for table in package.tables
|
|
129
|
+
for relation in table.relations
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
__all__ = ("render_relations",)
|