codepot-gen 0.1.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.
- app/__init__.py +9 -0
- app/app.py +77 -0
- app/io/__init__.py +5 -0
- app/io/inference_output.py +20 -0
- app/models/__init__.py +35 -0
- app/models/diagnostics.py +15 -0
- app/models/events.py +22 -0
- app/models/inputs.py +45 -0
- app/models/outputs.py +108 -0
- app/workflows/__init__.py +7 -0
- app/workflows/emit.py +143 -0
- app/workflows/infer.py +102 -0
- app/workflows/inspect.py +99 -0
- app/workflows/template_paths.py +26 -0
- app/workflows/validate.py +142 -0
- cli/__init__.py +7 -0
- cli/bootstrap.py +24 -0
- cli/commands/__init__.py +6 -0
- cli/commands/emit.py +138 -0
- cli/commands/infer.py +92 -0
- cli/commands/inspect.py +73 -0
- cli/commands/validate.py +73 -0
- cli/constants/__init__.py +6 -0
- cli/constants/constants.py +42 -0
- cli/constants/defaults.py +41 -0
- cli/main.py +97 -0
- cli/paths.py +24 -0
- cli/presentation/__init__.py +5 -0
- cli/presentation/core/__init__.py +5 -0
- cli/presentation/core/console.py +59 -0
- cli/presentation/core/diagnostics.py +47 -0
- cli/presentation/core/interactive.py +135 -0
- cli/presentation/core/tables.py +139 -0
- cli/presentation/emit/__init__.py +5 -0
- cli/presentation/emit/diagnostics.py +7 -0
- cli/presentation/emit/files.py +19 -0
- cli/presentation/emit/renderer.py +29 -0
- cli/presentation/emit/status.py +22 -0
- cli/presentation/emit/summary.py +24 -0
- cli/presentation/infer/__init__.py +5 -0
- cli/presentation/infer/diagnostics.py +7 -0
- cli/presentation/infer/files.py +19 -0
- cli/presentation/infer/renderer.py +21 -0
- cli/presentation/infer/schemas.py +52 -0
- cli/presentation/infer/status.py +11 -0
- cli/presentation/infer/summary.py +28 -0
- cli/presentation/inspect/__init__.py +5 -0
- cli/presentation/inspect/diagnostics.py +7 -0
- cli/presentation/inspect/renderer.py +18 -0
- cli/presentation/inspect/resources.py +26 -0
- cli/presentation/inspect/status.py +11 -0
- cli/presentation/inspect/summary.py +27 -0
- cli/presentation/validate/__init__.py +5 -0
- cli/presentation/validate/diagnostics.py +7 -0
- cli/presentation/validate/issues.py +29 -0
- cli/presentation/validate/renderer.py +21 -0
- cli/presentation/validate/summary.py +17 -0
- codepot_gen/__init__.py +9 -0
- codepot_gen/templates/dart/.gitignore +5 -0
- codepot_gen/templates/dart/.prettierrc +5 -0
- codepot_gen/templates/dart/CHANGELOG.md.j2 +5 -0
- codepot_gen/templates/dart/README.md.j2 +55 -0
- codepot_gen/templates/dart/analysis_options.yaml.j2 +12 -0
- codepot_gen/templates/dart/lib/[project.name.path].dart.j2 +12 -0
- codepot_gen/templates/dart/paths.yaml +69 -0
- codepot_gen/templates/dart/pubspec.yaml.j2 +23 -0
- codepot_gen/templates/dart/{dto}/index.dart.j2 +1 -0
- codepot_gen/templates/dart/{dto}/model.dart.j2 +37 -0
- codepot_gen/templates/dart/{enum}/enum.dart.j2 +8 -0
- codepot_gen/templates/dart/{enum}/index.dart.j2 +1 -0
- codepot_gen/templates/dart/{feature}/[resource.name.path]_feature.dart.j2 +104 -0
- codepot_gen/templates/dart/{model}/index.dart.j2 +1 -0
- codepot_gen/templates/dart/{model}/model.dart.j2 +37 -0
- codepot_gen/templates/dart/{route_group}/[resource.name.path]_endpoints.dart.j2 +15 -0
- codepot_gen/templates/dart/{route_root}/routes.dart.j2 +11 -0
- codepot_gen/templates/dart/{version}/client.dart.j2 +22 -0
- codepot_gen/templates/dart/{version}/v1.dart.j2 +18 -0
- codepot_gen/templates/debug/README.md.j2 +38 -0
- codepot_gen/templates/debug/_partials/fields-table.md.j2 +0 -0
- codepot_gen/templates/debug/_partials/metadata.md.j2 +0 -0
- codepot_gen/templates/debug/_partials/operation-summary.md.j2 +0 -0
- codepot_gen/templates/debug/_partials/parameters-table.md.j2 +0 -0
- codepot_gen/templates/debug/_partials/request-body.md.j2 +0 -0
- codepot_gen/templates/debug/_partials/responses-table.md.j2 +0 -0
- codepot_gen/templates/debug/_partials/schema-summary.md.j2 +0 -0
- codepot_gen/templates/debug/paths.yaml +54 -0
- codepot_gen/templates/debug/{dto}/[dto.name.path].md.j2 +88 -0
- codepot_gen/templates/debug/{enum}/[enum.name.path].md.j2 +82 -0
- codepot_gen/templates/debug/{model}/[model.name.path].dependencies.md.j2 +23 -0
- codepot_gen/templates/debug/{model}/[model.name.path].fields.md.j2 +37 -0
- codepot_gen/templates/debug/{model}/[model.name.path].md.j2 +110 -0
- codepot_gen/templates/debug/{operation}/[operation.api.method]_[operation.name.path].md.j2 +45 -0
- codepot_gen/templates/debug/{resource}/index.md.j2 +57 -0
- codepot_gen/templates/debug/{resource}/operations.md.j2 +100 -0
- codepot_gen/templates/debug/{resource}/schemas.md.j2 +25 -0
- codepot_gen/templates/next/paths.yaml +112 -0
- codepot_gen/templates/next/{dto}/[dto.name.path.o].ts.j2 +26 -0
- codepot_gen/templates/next/{enum}/[enum.name.path.o].ts.j2 +38 -0
- codepot_gen/templates/next/{model}/[model.name.path.o].ts.j2 +26 -0
- codepot_gen/templates/next/{resource_dto}/index.ts.j2 +16 -0
- codepot_gen/templates/next/{resource_enums}/index.ts.j2 +16 -0
- codepot_gen/templates/next/{resource_models}/index.ts.j2 +16 -0
- codepot_gen/templates/next/{resource_types}/index.ts.j2 +28 -0
- codepot_gen/templates/next/{resource}/[resource.name.path.o].actions.ts.j2 +208 -0
- codepot_gen/templates/next/{root}/[project.meta.api_version].ts.j2 +12 -0
- codepot_gen/templates/next/{root}/actions/helpers.ts.j2 +176 -0
- codepot_gen/templates/next/{root}/actions/index.ts.j2 +11 -0
- codepot_gen/templates/next/{root}/api.ts.j2 +17 -0
- codepot_gen/templates/next/{root}/index.ts.j2 +8 -0
- codepot_gen/templates/next/{root}/next-actions.md +213 -0
- codepot_gen/templates/next/{root}/routes.ts.j2 +49 -0
- codepot_gen/templates/next/{root}/types/index.ts.j2 +10 -0
- codepot_gen/templates/next/{ui_page}/page.tsx.j2 +127 -0
- codepot_gen/templates/next/{ui_resource}/[resource.name.path.o]-columns.tsx.j2 +180 -0
- codepot_gen/templates/next/{ui_resource}/[resource.name.path.o]-dialogs.tsx.j2 +382 -0
- codepot_gen/templates/next/{ui_resource}/[resource.name.path.o]-form.tsx.j2 +270 -0
- codepot_gen/templates/next/{ui_resource}/[resource.name.path.o]-table.tsx.j2 +87 -0
- codepot_gen/templates/next/{ui_resource}/index.ts.j2 +12 -0
- codepot_gen/templates/typescript/.gitignore +40 -0
- codepot_gen/templates/typescript/.prettierrc +5 -0
- codepot_gen/templates/typescript/README.md.j2 +21 -0
- codepot_gen/templates/typescript/package.json.j2 +16 -0
- codepot_gen/templates/typescript/paths.yaml +72 -0
- codepot_gen/templates/typescript/tsconfig.json.j2 +14 -0
- codepot_gen/templates/typescript/{dto}/dto.ts.j2 +9 -0
- codepot_gen/templates/typescript/{enum}/enum.ts.j2 +5 -0
- codepot_gen/templates/typescript/{model}/model.ts.j2 +9 -0
- codepot_gen/templates/typescript/{operation}/operation.ts.j2 +5 -0
- codepot_gen/templates/typescript/{version}/index.ts.j2 +12 -0
- codepot_gen-0.1.0.dist-info/METADATA +106 -0
- codepot_gen-0.1.0.dist-info/RECORD +264 -0
- codepot_gen-0.1.0.dist-info/WHEEL +5 -0
- codepot_gen-0.1.0.dist-info/entry_points.txt +4 -0
- codepot_gen-0.1.0.dist-info/top_level.txt +11 -0
- constants/__init__.py +7 -0
- constants/app.py +6 -0
- constants/codegen.py +39 -0
- constants/emission.py +38 -0
- constants/files.py +17 -0
- constants/http.py +25 -0
- constants/openapi.py +116 -0
- contracts/__init__.py +124 -0
- contracts/api.py +318 -0
- contracts/emission.py +55 -0
- contracts/events.py +33 -0
- contracts/language.py +61 -0
- contracts/names.py +15 -0
- contracts/path_yaml.py +217 -0
- contracts/paths.py +127 -0
- contracts/template.py +601 -0
- core/__init__.py +7 -0
- core/config.py +47 -0
- core/errors.py +21 -0
- core/paths.py +79 -0
- emission/__init__.py +5 -0
- emission/dependencies/output_index.py +80 -0
- emission/dependencies/resolver.py +70 -0
- emission/engine.py +497 -0
- emission/imports/base.py +29 -0
- emission/imports/markdown.py +48 -0
- emission/imports/paths.py +18 -0
- emission/paths/__init__.py +5 -0
- emission/paths/config_loader.py +23 -0
- emission/paths/selection.py +59 -0
- emission/templates/__init__.py +5 -0
- emission/templates/descriptor.py +59 -0
- emission/templates/path_expander.py +103 -0
- emission/templates/path_safety.py +64 -0
- emission/templates/path_tokens.py +143 -0
- emission/templates/renderer.py +90 -0
- emission/templates/resolver.py +67 -0
- emission/templates/scanner.py +33 -0
- emission/writer/__init__.py +0 -0
- emission/writer/file_writer.py +100 -0
- inference/__init__.py +10 -0
- inference/classifiers.py +96 -0
- inference/contract.py +448 -0
- inference/engine.py +116 -0
- inference/graph.py +51 -0
- inference/metadata/__init__.py +5 -0
- inference/metadata/parameters.py +71 -0
- inference/metadata/query.py +43 -0
- inference/metadata/targets.py +62 -0
- inference/metadata/ui.py +61 -0
- inference/models/__init__.py +35 -0
- inference/models/base.py +13 -0
- inference/models/dependencies.py +10 -0
- inference/models/graph.py +24 -0
- inference/models/operations.py +90 -0
- inference/models/resources.py +11 -0
- inference/models/schemas.py +87 -0
- inference/operations/__init__.py +7 -0
- inference/operations/dependencies.py +32 -0
- inference/operations/engine.py +198 -0
- inference/operations/parameter_targets.py +49 -0
- inference/operations/parameters.py +59 -0
- inference/operations/request_bodies.py +70 -0
- inference/operations/resources.py +17 -0
- inference/operations/responses.py +85 -0
- inference/ref_metadata.py +81 -0
- inference/resources.py +48 -0
- inference/schemas/__init__.py +251 -0
- inference/schemas/composition.py +193 -0
- inference/schemas/enums.py +53 -0
- inference/schemas/field_types.py +143 -0
- inference/schemas/fields.py +166 -0
- inference/schemas/primitives.py +52 -0
- inference/schemas/resolution.py +115 -0
- inference/serialization.py +10 -0
- languages/__init__.py +5 -0
- languages/dart/__init__.py +6 -0
- languages/dart/adapter.py +185 -0
- languages/dart/constants.py +16 -0
- languages/dart/context.py +67 -0
- languages/dart/dependencies.py +229 -0
- languages/dart/fields.py +87 -0
- languages/dart/imports.py +193 -0
- languages/dart/names.py +218 -0
- languages/dart/operations.py +500 -0
- languages/dart/paths.py +39 -0
- languages/dart/resources.py +108 -0
- languages/dart/schemas.py +276 -0
- languages/dart/types.py +275 -0
- languages/dart/urls.py +32 -0
- languages/debug/__init__.py +5 -0
- languages/debug/adapter.py +896 -0
- languages/debug/context/__init__.py +2 -0
- languages/debug/context/path_values.py +23 -0
- languages/decorators.py +88 -0
- languages/discovery.py +51 -0
- languages/typescript/__init__.py +6 -0
- languages/typescript/adapter.py +178 -0
- languages/typescript/constants.py +18 -0
- languages/typescript/context.py +61 -0
- languages/typescript/dependencies.py +228 -0
- languages/typescript/fields.py +170 -0
- languages/typescript/imports.py +210 -0
- languages/typescript/names.py +259 -0
- languages/typescript/operations.py +700 -0
- languages/typescript/paths.py +39 -0
- languages/typescript/resources.py +113 -0
- languages/typescript/schemas.py +243 -0
- languages/typescript/types.py +269 -0
- languages/typescript/urls.py +20 -0
- openapi/__init__.py +9 -0
- openapi/document.py +88 -0
- openapi/inspector.py +126 -0
- openapi/loader.py +71 -0
- openapi/ref_metadata.py +67 -0
- openapi/refs.py +88 -0
- openapi/resolver/__init__.py +28 -0
- openapi/resolver/components.py +104 -0
- openapi/resolver/content.py +81 -0
- openapi/resolver/parameters.py +110 -0
- openapi/resolver/pointers.py +69 -0
- openapi/resolver/request_bodies.py +66 -0
- openapi/resolver/responses.py +73 -0
- utils/__init__.py +1 -0
- utils/naming/__init__.py +17 -0
- utils/naming/aliases.py +18 -0
- utils/naming/cases.py +80 -0
- utils/naming/number.py +25 -0
- utils/naming/plurality.py +126 -0
- utils/naming/provider.py +188 -0
app/__init__.py
ADDED
app/app.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Public runtime application API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from app.models import (
|
|
8
|
+
EmitInput,
|
|
9
|
+
EmitOutput,
|
|
10
|
+
InferInput,
|
|
11
|
+
InferOutput,
|
|
12
|
+
InspectInput,
|
|
13
|
+
InspectOutput,
|
|
14
|
+
ValidateInput,
|
|
15
|
+
ValidateOutput,
|
|
16
|
+
)
|
|
17
|
+
from app.workflows.emit import run_emit
|
|
18
|
+
from app.workflows.infer import run_infer
|
|
19
|
+
from app.workflows.inspect import run_inspect
|
|
20
|
+
from app.workflows.validate import run_validate
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class GeneratorApp:
|
|
24
|
+
"""Public runtime API for the generator.
|
|
25
|
+
|
|
26
|
+
Interfaces such as CLI, UI, tests, or HTTP handlers should call this class.
|
|
27
|
+
Runtime methods return structured results and do not render terminal output.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def inspect(self, input_path: Path) -> InspectOutput:
|
|
31
|
+
"""Inspect an OpenAPI document."""
|
|
32
|
+
return run_inspect(
|
|
33
|
+
InspectInput(
|
|
34
|
+
input_path=input_path,
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def infer(
|
|
39
|
+
self,
|
|
40
|
+
input_path: Path,
|
|
41
|
+
output_path: Path | None = None,
|
|
42
|
+
) -> InferOutput:
|
|
43
|
+
"""Run OpenAPI inference."""
|
|
44
|
+
return run_infer(
|
|
45
|
+
InferInput(
|
|
46
|
+
input_path=input_path,
|
|
47
|
+
output_path=output_path,
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def emit(
|
|
52
|
+
self,
|
|
53
|
+
input_path: Path,
|
|
54
|
+
language: str,
|
|
55
|
+
output_path: Path,
|
|
56
|
+
*,
|
|
57
|
+
dry_run: bool = False,
|
|
58
|
+
templates_path: Path | None = None,
|
|
59
|
+
) -> EmitOutput:
|
|
60
|
+
"""Emit generated output for a language."""
|
|
61
|
+
return run_emit(
|
|
62
|
+
EmitInput(
|
|
63
|
+
input_path=input_path,
|
|
64
|
+
language=language,
|
|
65
|
+
output_path=output_path,
|
|
66
|
+
dry_run=dry_run,
|
|
67
|
+
templates_path=templates_path,
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def validate(self, input_path: Path) -> ValidateOutput:
|
|
72
|
+
"""Validate an OpenAPI document."""
|
|
73
|
+
return run_validate(
|
|
74
|
+
ValidateInput(
|
|
75
|
+
input_path=input_path,
|
|
76
|
+
)
|
|
77
|
+
)
|
app/io/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Inference graph output serialization."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from constants.files import ENCODING_UTF8
|
|
9
|
+
from inference.models import InferenceGraph
|
|
10
|
+
from inference.serialization import inference_graph_to_dict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def write_inference_graph(graph: InferenceGraph, output_path: Path) -> Path:
|
|
14
|
+
"""Write an inference graph as JSON and return the written path."""
|
|
15
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
16
|
+
output_path.write_text(
|
|
17
|
+
json.dumps(inference_graph_to_dict(graph), indent=2, default=str),
|
|
18
|
+
encoding=ENCODING_UTF8,
|
|
19
|
+
)
|
|
20
|
+
return output_path
|
app/models/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Runtime input and output contracts.
|
|
2
|
+
|
|
3
|
+
These models define the public runtime interface used by CLI, tests, UI, API
|
|
4
|
+
servers, and other adapters.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from app.models.diagnostics import RuntimeDiagnostic
|
|
8
|
+
from app.models.events import ProgressSink, RuntimeEvent
|
|
9
|
+
from app.models.inputs import EmitInput, InferInput, InspectInput, ValidateInput
|
|
10
|
+
from app.models.outputs import (
|
|
11
|
+
AliasSchemaSummary,
|
|
12
|
+
EmitOutput,
|
|
13
|
+
InferOutput,
|
|
14
|
+
InspectOutput,
|
|
15
|
+
ResourceSummary,
|
|
16
|
+
UnknownSchemaSummary,
|
|
17
|
+
ValidateOutput,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"EmitInput",
|
|
22
|
+
"InferInput",
|
|
23
|
+
"InspectInput",
|
|
24
|
+
"ValidateInput",
|
|
25
|
+
"EmitOutput",
|
|
26
|
+
"InferOutput",
|
|
27
|
+
"InspectOutput",
|
|
28
|
+
"ValidateOutput",
|
|
29
|
+
"ResourceSummary",
|
|
30
|
+
"UnknownSchemaSummary",
|
|
31
|
+
"AliasSchemaSummary",
|
|
32
|
+
"RuntimeDiagnostic",
|
|
33
|
+
"RuntimeEvent",
|
|
34
|
+
"ProgressSink",
|
|
35
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Runtime diagnostic models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class RuntimeDiagnostic:
|
|
11
|
+
"""A structured diagnostic emitted by runtime workflows."""
|
|
12
|
+
|
|
13
|
+
level: str
|
|
14
|
+
message: str
|
|
15
|
+
details: dict[str, Any] = field(default_factory=dict)
|
app/models/events.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Runtime progress event models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class RuntimeEvent:
|
|
12
|
+
"""A structured runtime progress event."""
|
|
13
|
+
|
|
14
|
+
stage: str
|
|
15
|
+
message: str
|
|
16
|
+
level: str = "info"
|
|
17
|
+
current: int | None = None
|
|
18
|
+
total: int | None = None
|
|
19
|
+
details: dict[str, Any] = field(default_factory=dict)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
ProgressSink = Callable[[RuntimeEvent], None]
|
app/models/inputs.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Runtime input contracts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from app.models.events import ProgressSink
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class InspectInput:
|
|
13
|
+
"""Input for inspecting an OpenAPI document."""
|
|
14
|
+
|
|
15
|
+
input_path: Path
|
|
16
|
+
progress: ProgressSink | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class ValidateInput:
|
|
21
|
+
"""Input for validating an OpenAPI document."""
|
|
22
|
+
|
|
23
|
+
input_path: Path
|
|
24
|
+
progress: ProgressSink | None = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class InferInput:
|
|
29
|
+
"""Input for OpenAPI inference."""
|
|
30
|
+
|
|
31
|
+
input_path: Path
|
|
32
|
+
output_path: Path | None = None
|
|
33
|
+
progress: ProgressSink | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class EmitInput:
|
|
38
|
+
"""Input for emitting generated output."""
|
|
39
|
+
|
|
40
|
+
input_path: Path
|
|
41
|
+
language: str
|
|
42
|
+
output_path: Path
|
|
43
|
+
dry_run: bool = False
|
|
44
|
+
templates_path: Path | None = None
|
|
45
|
+
progress: ProgressSink | None = None
|
app/models/outputs.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Runtime output contracts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from app.models.diagnostics import RuntimeDiagnostic
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class ResourceSummary:
|
|
14
|
+
"""Detected resource summary."""
|
|
15
|
+
|
|
16
|
+
name: str
|
|
17
|
+
path: str = "-"
|
|
18
|
+
operations_count: int = 0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class UnknownSchemaSummary:
|
|
23
|
+
"""Unknown schema summary for inference diagnostics."""
|
|
24
|
+
|
|
25
|
+
name: str
|
|
26
|
+
ref: str
|
|
27
|
+
x_codegen_kind: str = "-"
|
|
28
|
+
keys: list[str] = field(default_factory=list)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class AliasSchemaSummary:
|
|
33
|
+
"""Alias schema summary for inference diagnostics."""
|
|
34
|
+
|
|
35
|
+
name: str
|
|
36
|
+
kind: str
|
|
37
|
+
alias_of: str = "-"
|
|
38
|
+
resource: str = "-"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class InspectOutput:
|
|
43
|
+
"""Output from OpenAPI inspection."""
|
|
44
|
+
|
|
45
|
+
input_path: Path
|
|
46
|
+
title: str = "-"
|
|
47
|
+
openapi_version: str = "-"
|
|
48
|
+
api_version: str = "-"
|
|
49
|
+
paths_count: int = 0
|
|
50
|
+
operations_count: int = 0
|
|
51
|
+
schemas_count: int = 0
|
|
52
|
+
responses_count: int = 0
|
|
53
|
+
request_bodies_count: int = 0
|
|
54
|
+
parameters_count: int = 0
|
|
55
|
+
refs_count: int = 0
|
|
56
|
+
component_refs_count: int = 0
|
|
57
|
+
missing_component_refs_count: int = 0
|
|
58
|
+
resources: list[ResourceSummary] = field(default_factory=list)
|
|
59
|
+
diagnostics: list[RuntimeDiagnostic] = field(default_factory=list)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class ValidateOutput:
|
|
64
|
+
"""Output from OpenAPI validation."""
|
|
65
|
+
|
|
66
|
+
input_path: Path
|
|
67
|
+
valid: bool
|
|
68
|
+
errors: list[str] = field(default_factory=list)
|
|
69
|
+
warnings: list[str] = field(default_factory=list)
|
|
70
|
+
diagnostics: list[RuntimeDiagnostic] = field(default_factory=list)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass(frozen=True)
|
|
74
|
+
class InferOutput:
|
|
75
|
+
"""Output from OpenAPI inference."""
|
|
76
|
+
|
|
77
|
+
input_path: Path
|
|
78
|
+
output_path: Path | None = None
|
|
79
|
+
graph: Any | None = None
|
|
80
|
+
title: str = "-"
|
|
81
|
+
openapi_version: str = "-"
|
|
82
|
+
api_version: str = "-"
|
|
83
|
+
resources_count: int = 0
|
|
84
|
+
schemas_count: int = 0
|
|
85
|
+
operations_count: int = 0
|
|
86
|
+
dependencies_count: int = 0
|
|
87
|
+
alias_schemas_count: int = 0
|
|
88
|
+
schema_kind_counts: dict[str, int] = field(default_factory=dict)
|
|
89
|
+
unknown_schemas: list[UnknownSchemaSummary] = field(default_factory=list)
|
|
90
|
+
alias_schemas: list[AliasSchemaSummary] = field(default_factory=list)
|
|
91
|
+
written: list[Path] = field(default_factory=list)
|
|
92
|
+
diagnostics: list[RuntimeDiagnostic] = field(default_factory=list)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass(frozen=True)
|
|
96
|
+
class EmitOutput:
|
|
97
|
+
"""Output from code/text emission."""
|
|
98
|
+
|
|
99
|
+
input_path: Path
|
|
100
|
+
language: str
|
|
101
|
+
output_path: Path
|
|
102
|
+
dry_run: bool = False
|
|
103
|
+
planned: list[Path] = field(default_factory=list)
|
|
104
|
+
written: list[Path] = field(default_factory=list)
|
|
105
|
+
updated: list[Path] = field(default_factory=list)
|
|
106
|
+
unchanged: list[Path] = field(default_factory=list)
|
|
107
|
+
skipped: list[Path] = field(default_factory=list)
|
|
108
|
+
diagnostics: list[RuntimeDiagnostic] = field(default_factory=list)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command workflow orchestration package.
|
|
3
|
+
|
|
4
|
+
This package contains workflow functions that orchestrate the full pipeline for
|
|
5
|
+
each CLI command: inspect, infer, emit, and validate. Each workflow loads documents,
|
|
6
|
+
runs inference, creates emission plans, and delegates to presenters for output.
|
|
7
|
+
"""
|
app/workflows/emit.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""App emit workflow.
|
|
2
|
+
|
|
3
|
+
Orchestrates the full emission pipeline: OpenAPI loading, inference, contract
|
|
4
|
+
building, language adaptation, and template emission.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from app.models import EmitInput, EmitOutput, RuntimeDiagnostic, RuntimeEvent
|
|
10
|
+
from app.workflows.template_paths import resolve_template_root
|
|
11
|
+
from emission.engine import emit as run_emission
|
|
12
|
+
from inference.contract import build_api_contract
|
|
13
|
+
from inference.engine import InferenceEngine
|
|
14
|
+
from languages.discovery import resolve_language_adapter
|
|
15
|
+
from openapi.loader import load_openapi_document
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def run_emit(request: EmitInput) -> EmitOutput:
|
|
19
|
+
"""Run the emit workflow and return structured output."""
|
|
20
|
+
_notify(
|
|
21
|
+
request,
|
|
22
|
+
stage="loading_openapi",
|
|
23
|
+
message=f"Loading OpenAPI document from {request.input_path}",
|
|
24
|
+
)
|
|
25
|
+
document = load_openapi_document(request.input_path)
|
|
26
|
+
|
|
27
|
+
_notify(
|
|
28
|
+
request,
|
|
29
|
+
stage="running_inference",
|
|
30
|
+
message="Running OpenAPI inference",
|
|
31
|
+
)
|
|
32
|
+
graph = InferenceEngine().infer(document)
|
|
33
|
+
|
|
34
|
+
_notify(
|
|
35
|
+
request,
|
|
36
|
+
stage="building_contract",
|
|
37
|
+
message="Building API contract",
|
|
38
|
+
)
|
|
39
|
+
api_contract = build_api_contract(graph)
|
|
40
|
+
|
|
41
|
+
_notify(
|
|
42
|
+
request,
|
|
43
|
+
stage="resolving_language",
|
|
44
|
+
message=f"Resolving language adapter: {request.language}",
|
|
45
|
+
)
|
|
46
|
+
adapter = resolve_language_adapter(request.language)
|
|
47
|
+
template_root = resolve_template_root(
|
|
48
|
+
adapter=adapter,
|
|
49
|
+
templates_path=request.templates_path,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
_notify(
|
|
53
|
+
request,
|
|
54
|
+
stage="building_template_contract",
|
|
55
|
+
message=f"Building template contract from {template_root}",
|
|
56
|
+
)
|
|
57
|
+
template_contract = adapter.build_template_contract(
|
|
58
|
+
api=api_contract,
|
|
59
|
+
output_path=request.output_path,
|
|
60
|
+
template_root=template_root,
|
|
61
|
+
dry_run=request.dry_run,
|
|
62
|
+
progress=request.progress,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
_notify(
|
|
66
|
+
request,
|
|
67
|
+
stage="emitting_files",
|
|
68
|
+
message="Emitting files",
|
|
69
|
+
)
|
|
70
|
+
emission_result = run_emission(
|
|
71
|
+
template_contract,
|
|
72
|
+
progress=request.progress,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
_notify(
|
|
76
|
+
request,
|
|
77
|
+
stage="language_post_actions",
|
|
78
|
+
message="Running language post-actions",
|
|
79
|
+
)
|
|
80
|
+
post_result = adapter.after_emit(
|
|
81
|
+
result=emission_result,
|
|
82
|
+
progress=request.progress,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
_notify(
|
|
86
|
+
request,
|
|
87
|
+
stage="emission_complete",
|
|
88
|
+
message="Emission completed",
|
|
89
|
+
total=len(emission_result.plan.files),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
write_result = emission_result.write_result
|
|
93
|
+
diagnostics = [
|
|
94
|
+
RuntimeDiagnostic(
|
|
95
|
+
level="info",
|
|
96
|
+
message=(
|
|
97
|
+
"Emission completed: "
|
|
98
|
+
f"{len(write_result.created)} created, "
|
|
99
|
+
f"{len(write_result.updated)} updated, "
|
|
100
|
+
f"{len(write_result.unchanged)} unchanged, "
|
|
101
|
+
f"{len(write_result.skipped)} skipped."
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
diagnostics.extend(RuntimeDiagnostic(level="info", message=message) for message in post_result.diagnostics) # noqa: E501
|
|
107
|
+
|
|
108
|
+
return EmitOutput(
|
|
109
|
+
input_path=request.input_path,
|
|
110
|
+
language=request.language,
|
|
111
|
+
output_path=request.output_path,
|
|
112
|
+
dry_run=request.dry_run,
|
|
113
|
+
planned=[file.output_path for file in emission_result.plan.files],
|
|
114
|
+
written=list(write_result.created),
|
|
115
|
+
updated=list(write_result.updated),
|
|
116
|
+
unchanged=list(write_result.unchanged),
|
|
117
|
+
skipped=list(write_result.skipped),
|
|
118
|
+
diagnostics=diagnostics,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _notify(
|
|
123
|
+
request: EmitInput,
|
|
124
|
+
*,
|
|
125
|
+
stage: str,
|
|
126
|
+
message: str,
|
|
127
|
+
level: str = "info",
|
|
128
|
+
current: int | None = None,
|
|
129
|
+
total: int | None = None,
|
|
130
|
+
) -> None:
|
|
131
|
+
"""Emit a runtime progress event when a sink is provided."""
|
|
132
|
+
if request.progress is None:
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
request.progress(
|
|
136
|
+
RuntimeEvent(
|
|
137
|
+
stage=stage,
|
|
138
|
+
message=message,
|
|
139
|
+
level=level,
|
|
140
|
+
current=current,
|
|
141
|
+
total=total,
|
|
142
|
+
)
|
|
143
|
+
)
|
app/workflows/infer.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Runtime inference workflow."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import Counter
|
|
6
|
+
|
|
7
|
+
from inference.engine import InferenceEngine
|
|
8
|
+
from openapi.loader import load_openapi_document
|
|
9
|
+
from app.io.inference_output import write_inference_graph
|
|
10
|
+
from app.models import (
|
|
11
|
+
AliasSchemaSummary,
|
|
12
|
+
InferInput,
|
|
13
|
+
InferOutput,
|
|
14
|
+
RuntimeDiagnostic,
|
|
15
|
+
RuntimeEvent,
|
|
16
|
+
UnknownSchemaSummary,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def run_infer(request: InferInput) -> InferOutput:
|
|
21
|
+
"""Run inference and return structured output."""
|
|
22
|
+
_notify(request, stage="loading_openapi", message="Loading OpenAPI document")
|
|
23
|
+
document = load_openapi_document(request.input_path)
|
|
24
|
+
|
|
25
|
+
_notify(request, stage="running_inference", message="Running inference engine")
|
|
26
|
+
graph = InferenceEngine().infer(document)
|
|
27
|
+
|
|
28
|
+
written = []
|
|
29
|
+
if request.output_path is not None:
|
|
30
|
+
_notify(
|
|
31
|
+
request,
|
|
32
|
+
stage="writing_inference_output",
|
|
33
|
+
message=f"Writing inference graph to {request.output_path}",
|
|
34
|
+
)
|
|
35
|
+
written.append(write_inference_graph(graph, request.output_path))
|
|
36
|
+
|
|
37
|
+
schemas = list(getattr(graph, "schemas", []) or [])
|
|
38
|
+
alias_schemas = [schema for schema in schemas if bool(getattr(schema, "is_alias", False))]
|
|
39
|
+
unknown_schemas = [schema for schema in schemas if getattr(getattr(schema, "kind", None), "value", "") == "unknown"]
|
|
40
|
+
kind_counts = Counter(str(getattr(getattr(schema, "kind", None), "value", "-")) for schema in schemas)
|
|
41
|
+
|
|
42
|
+
_notify(request, stage="inference_complete", message="Inference completed")
|
|
43
|
+
|
|
44
|
+
return InferOutput(
|
|
45
|
+
input_path=request.input_path,
|
|
46
|
+
output_path=request.output_path,
|
|
47
|
+
graph=graph,
|
|
48
|
+
title=str(getattr(graph, "title", "-")),
|
|
49
|
+
openapi_version=str(getattr(graph, "openapi_version", "-")),
|
|
50
|
+
api_version=str(getattr(graph, "api_version", "-")),
|
|
51
|
+
resources_count=len(list(getattr(graph, "resources", []) or [])),
|
|
52
|
+
schemas_count=len(schemas),
|
|
53
|
+
operations_count=len(list(getattr(graph, "operations", []) or [])),
|
|
54
|
+
dependencies_count=len(list(getattr(graph, "dependencies", []) or [])),
|
|
55
|
+
alias_schemas_count=len(alias_schemas),
|
|
56
|
+
schema_kind_counts=dict(sorted(kind_counts.items())),
|
|
57
|
+
unknown_schemas=[
|
|
58
|
+
UnknownSchemaSummary(
|
|
59
|
+
name=str(getattr(schema, "name", "-")),
|
|
60
|
+
ref=str(getattr(schema, "ref", "-")),
|
|
61
|
+
x_codegen_kind=str(
|
|
62
|
+
getattr(schema, "x_codegen", {}).get("kind", "-") if isinstance(getattr(schema, "x_codegen", {}), dict) else "-"
|
|
63
|
+
),
|
|
64
|
+
keys=sorted(list(getattr(schema, "raw", {}).keys())) if isinstance(getattr(schema, "raw", {}), dict) else [],
|
|
65
|
+
)
|
|
66
|
+
for schema in unknown_schemas
|
|
67
|
+
],
|
|
68
|
+
alias_schemas=[
|
|
69
|
+
AliasSchemaSummary(
|
|
70
|
+
name=str(getattr(schema, "name", "-")),
|
|
71
|
+
kind=str(getattr(getattr(schema, "kind", None), "value", "-")),
|
|
72
|
+
alias_of=str(getattr(schema, "alias_of", None) or "-"),
|
|
73
|
+
resource=str(getattr(getattr(schema, "resource", None), "name", "-")),
|
|
74
|
+
)
|
|
75
|
+
for schema in alias_schemas
|
|
76
|
+
],
|
|
77
|
+
written=written,
|
|
78
|
+
diagnostics=[RuntimeDiagnostic(level="info", message="Inference completed.")],
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _notify(
|
|
83
|
+
request: InferInput,
|
|
84
|
+
*,
|
|
85
|
+
stage: str,
|
|
86
|
+
message: str,
|
|
87
|
+
level: str = "info",
|
|
88
|
+
current: int | None = None,
|
|
89
|
+
total: int | None = None,
|
|
90
|
+
) -> None:
|
|
91
|
+
if request.progress is None:
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
request.progress(
|
|
95
|
+
RuntimeEvent(
|
|
96
|
+
stage=stage,
|
|
97
|
+
message=message,
|
|
98
|
+
level=level,
|
|
99
|
+
current=current,
|
|
100
|
+
total=total,
|
|
101
|
+
)
|
|
102
|
+
)
|
app/workflows/inspect.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Runtime OpenAPI inspection workflow."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from openapi.inspector import inspect_openapi_document
|
|
6
|
+
from openapi.loader import load_openapi_document
|
|
7
|
+
from app.models import InspectInput, InspectOutput, ResourceSummary, RuntimeDiagnostic, RuntimeEvent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run_inspect(request: InspectInput) -> InspectOutput:
|
|
11
|
+
"""Inspect an OpenAPI document and return structured output."""
|
|
12
|
+
_notify(request, stage="loading_openapi", message="Loading OpenAPI document")
|
|
13
|
+
document = load_openapi_document(request.input_path)
|
|
14
|
+
|
|
15
|
+
_notify(request, stage="inspecting_openapi", message="Inspecting OpenAPI document")
|
|
16
|
+
inspection = inspect_openapi_document(document)
|
|
17
|
+
|
|
18
|
+
_notify(request, stage="inspection_complete", message="OpenAPI inspection completed")
|
|
19
|
+
|
|
20
|
+
return InspectOutput(
|
|
21
|
+
input_path=request.input_path,
|
|
22
|
+
title=str(_value(inspection, "title")),
|
|
23
|
+
openapi_version=str(_value(inspection, "openapi_version")),
|
|
24
|
+
api_version=str(_value(inspection, "api_version")),
|
|
25
|
+
paths_count=_int_value(inspection, "path_count", "paths_count"),
|
|
26
|
+
operations_count=_int_value(inspection, "operation_count", "operations_count"),
|
|
27
|
+
schemas_count=_int_value(inspection, "schema_count", "schemas_count"),
|
|
28
|
+
responses_count=_int_value(inspection, "response_count", "responses_count"),
|
|
29
|
+
request_bodies_count=_int_value(
|
|
30
|
+
inspection,
|
|
31
|
+
"request_body_count",
|
|
32
|
+
"request_bodies_count",
|
|
33
|
+
),
|
|
34
|
+
parameters_count=_int_value(inspection, "parameter_count", "parameters_count"),
|
|
35
|
+
refs_count=_int_value(inspection, "ref_count", "refs_count"),
|
|
36
|
+
component_refs_count=_int_value(
|
|
37
|
+
inspection,
|
|
38
|
+
"component_ref_count",
|
|
39
|
+
"component_refs_count",
|
|
40
|
+
),
|
|
41
|
+
missing_component_refs_count=_int_value(
|
|
42
|
+
inspection,
|
|
43
|
+
"missing_component_ref_count",
|
|
44
|
+
"missing_component_refs_count",
|
|
45
|
+
),
|
|
46
|
+
resources=[
|
|
47
|
+
ResourceSummary(
|
|
48
|
+
name=str(_value(resource, "name")),
|
|
49
|
+
path="/".join(_value(resource, "path", default=[])) or "-",
|
|
50
|
+
operations_count=_int_value(resource, "operation_count", "operations_count"),
|
|
51
|
+
)
|
|
52
|
+
for resource in _items(inspection, "resources")
|
|
53
|
+
],
|
|
54
|
+
diagnostics=[RuntimeDiagnostic(level="info", message="OpenAPI inspection completed.")],
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _items(value, name: str) -> list:
|
|
59
|
+
result = getattr(value, name, [])
|
|
60
|
+
return list(result or [])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _value(value, *names: str, default="-"):
|
|
64
|
+
for name in names:
|
|
65
|
+
if hasattr(value, name):
|
|
66
|
+
result = getattr(value, name)
|
|
67
|
+
return default if result is None else result
|
|
68
|
+
return default
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _int_value(value, *names: str) -> int:
|
|
72
|
+
result = _value(value, *names, default=0)
|
|
73
|
+
try:
|
|
74
|
+
return int(result)
|
|
75
|
+
except (TypeError, ValueError):
|
|
76
|
+
return 0
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _notify(
|
|
80
|
+
request: InspectInput,
|
|
81
|
+
*,
|
|
82
|
+
stage: str,
|
|
83
|
+
message: str,
|
|
84
|
+
level: str = "info",
|
|
85
|
+
current: int | None = None,
|
|
86
|
+
total: int | None = None,
|
|
87
|
+
) -> None:
|
|
88
|
+
if request.progress is None:
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
request.progress(
|
|
92
|
+
RuntimeEvent(
|
|
93
|
+
stage=stage,
|
|
94
|
+
message=message,
|
|
95
|
+
level=level,
|
|
96
|
+
current=current,
|
|
97
|
+
total=total,
|
|
98
|
+
)
|
|
99
|
+
)
|