scaffold-ca-python 0.1.1__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.
- scaffold_ca_python/__init__.py +1 -0
- scaffold_ca_python/cli.py +39 -0
- scaffold_ca_python/commands/__init__.py +0 -0
- scaffold_ca_python/commands/delete_module.py +216 -0
- scaffold_ca_python/commands/generate_driven_adapter.py +182 -0
- scaffold_ca_python/commands/generate_entry_point.py +304 -0
- scaffold_ca_python/commands/generate_helper.py +135 -0
- scaffold_ca_python/commands/generate_model.py +134 -0
- scaffold_ca_python/commands/generate_pipeline.py +158 -0
- scaffold_ca_python/commands/generate_project.py +189 -0
- scaffold_ca_python/commands/generate_use_case.py +136 -0
- scaffold_ca_python/commands/update_project.py +84 -0
- scaffold_ca_python/commands/validate_structure.py +90 -0
- scaffold_ca_python/core/__init__.py +0 -0
- scaffold_ca_python/core/file_writer.py +128 -0
- scaffold_ca_python/core/module_builder.py +127 -0
- scaffold_ca_python/core/name_utils.py +59 -0
- scaffold_ca_python/core/project_detector.py +93 -0
- scaffold_ca_python/core/pyproject_writer.py +169 -0
- scaffold_ca_python/core/structure_validator.py +142 -0
- scaffold_ca_python/core/template_renderer.py +100 -0
- scaffold_ca_python/factory/__init__.py +16 -0
- scaffold_ca_python/factory/driven_adapters/__init__.py +0 -0
- scaffold_ca_python/factory/driven_adapters/da_generic.py +65 -0
- scaffold_ca_python/factory/driven_adapters/da_rest_consumer.py +64 -0
- scaffold_ca_python/factory/driven_adapters/da_secrets.py +64 -0
- scaffold_ca_python/factory/entry_points/__init__.py +0 -0
- scaffold_ca_python/factory/entry_points/ep_agent.py +91 -0
- scaffold_ca_python/factory/entry_points/ep_generic.py +75 -0
- scaffold_ca_python/factory/entry_points/ep_mcp.py +138 -0
- scaffold_ca_python/factory/entry_points/ep_restapi.py +133 -0
- scaffold_ca_python/factory/simple/__init__.py +0 -0
- scaffold_ca_python/factory/simple/delete_module_factory.py +85 -0
- scaffold_ca_python/factory/simple/helper_factory.py +67 -0
- scaffold_ca_python/factory/simple/model_factory.py +57 -0
- scaffold_ca_python/factory/simple/use_case_factory.py +59 -0
- scaffold_ca_python/models/__init__.py +0 -0
- scaffold_ca_python/models/context.py +60 -0
- scaffold_ca_python/models/file_operation.py +47 -0
- scaffold_ca_python/models/layer.py +41 -0
- scaffold_ca_python/models/violation.py +26 -0
- scaffold_ca_python/templates/__init__.py +0 -0
- scaffold_ca_python/templates/driven_adapter/generic/__init__.py.jinja2 +1 -0
- scaffold_ca_python/templates/driven_adapter/generic/adapter.py.jinja2 +18 -0
- scaffold_ca_python/templates/driven_adapter/generic/test_adapter.py.jinja2 +22 -0
- scaffold_ca_python/templates/driven_adapter/rest_consumer/__init__.py.jinja2 +1 -0
- scaffold_ca_python/templates/driven_adapter/rest_consumer/rest_consumer.py.jinja2 +27 -0
- scaffold_ca_python/templates/driven_adapter/rest_consumer/test_rest_consumer.py.jinja2 +24 -0
- scaffold_ca_python/templates/driven_adapter/secrets/__init__.py.jinja2 +1 -0
- scaffold_ca_python/templates/driven_adapter/secrets/secrets_adapter.py.jinja2 +37 -0
- scaffold_ca_python/templates/driven_adapter/secrets/test_secrets_adapter.py.jinja2 +26 -0
- scaffold_ca_python/templates/entry_point/agent/__init__.py.jinja2 +1 -0
- scaffold_ca_python/templates/entry_point/agent/agent.py.jinja2 +49 -0
- scaffold_ca_python/templates/entry_point/agent/card.py.jinja2 +15 -0
- scaffold_ca_python/templates/entry_point/agent/entrypoint_main.py.jinja2 +13 -0
- scaffold_ca_python/templates/entry_point/agent/test_agent.py.jinja2 +20 -0
- scaffold_ca_python/templates/entry_point/generic/__init__.py.jinja2 +1 -0
- scaffold_ca_python/templates/entry_point/generic/entrypoint_main.py.jinja2 +13 -0
- scaffold_ca_python/templates/entry_point/generic/handler.py.jinja2 +13 -0
- scaffold_ca_python/templates/entry_point/generic/test_handler.py.jinja2 +35 -0
- scaffold_ca_python/templates/entry_point/mcp/__init__.py.jinja2 +1 -0
- scaffold_ca_python/templates/entry_point/mcp/app.py.jinja2 +51 -0
- scaffold_ca_python/templates/entry_point/mcp/prompts.py.jinja2 +22 -0
- scaffold_ca_python/templates/entry_point/mcp/resources.py.jinja2 +22 -0
- scaffold_ca_python/templates/entry_point/mcp/server.py.jinja2 +27 -0
- scaffold_ca_python/templates/entry_point/mcp/test_app.py.jinja2 +32 -0
- scaffold_ca_python/templates/entry_point/mcp/test_prompts.py.jinja2 +40 -0
- scaffold_ca_python/templates/entry_point/mcp/test_resources.py.jinja2 +47 -0
- scaffold_ca_python/templates/entry_point/mcp/test_tools.py.jinja2 +40 -0
- scaffold_ca_python/templates/entry_point/mcp/tools.py.jinja2 +22 -0
- scaffold_ca_python/templates/entry_point/restapi/__init__.py.jinja2 +1 -0
- scaffold_ca_python/templates/entry_point/restapi/app.py.jinja2 +78 -0
- scaffold_ca_python/templates/entry_point/restapi/exception_handler.py.jinja2 +35 -0
- scaffold_ca_python/templates/entry_point/restapi/health.py.jinja2 +13 -0
- scaffold_ca_python/templates/entry_point/restapi/rest_controller.py.jinja2 +26 -0
- scaffold_ca_python/templates/entry_point/restapi/server.py.jinja2 +5 -0
- scaffold_ca_python/templates/entry_point/restapi/test_app.py.jinja2 +22 -0
- scaffold_ca_python/templates/entry_point/restapi/test_exception_handler.py.jinja2 +44 -0
- scaffold_ca_python/templates/entry_point/restapi/test_rest_controller.py.jinja2 +35 -0
- scaffold_ca_python/templates/entry_point/restapi/test_server.py.jinja2 +15 -0
- scaffold_ca_python/templates/helper/__init__.py.jinja2 +1 -0
- scaffold_ca_python/templates/helper/helper.py.jinja2 +7 -0
- scaffold_ca_python/templates/helper/test_helper.py.jinja2 +8 -0
- scaffold_ca_python/templates/model/model.py.jinja2 +9 -0
- scaffold_ca_python/templates/model/test_model.py.jinja2 +8 -0
- scaffold_ca_python/templates/pipeline/azure/azure_pipelines.yml.jinja2 +28 -0
- scaffold_ca_python/templates/pipeline/github/ci.yml.jinja2 +34 -0
- scaffold_ca_python/templates/project/README.jinja2 +30 -0
- scaffold_ca_python/templates/project/application/config/__init__.py.jinja2 +1 -0
- scaffold_ca_python/templates/project/application/config/config.py.jinja2 +12 -0
- scaffold_ca_python/templates/project/application/config/container.py.jinja2 +17 -0
- scaffold_ca_python/templates/project/application/config/driven_adapters_container.py.jinja2 +14 -0
- scaffold_ca_python/templates/project/application/config/resource_container.py.jinja2 +17 -0
- scaffold_ca_python/templates/project/application/config/usecases_container.py.jinja2 +16 -0
- scaffold_ca_python/templates/project/dockerfile.jinja2 +22 -0
- scaffold_ca_python/templates/project/dockerignore.jinja2 +19 -0
- scaffold_ca_python/templates/project/gitignore.jinja2 +64 -0
- scaffold_ca_python/templates/project/layer_init.jinja2 +1 -0
- scaffold_ca_python/templates/project/main.py.jinja2 +10 -0
- scaffold_ca_python/templates/project/mypy_ini.jinja2 +5 -0
- scaffold_ca_python/templates/project/pyproject_toml.jinja2 +66 -0
- scaffold_ca_python/templates/project/python_version.jinja2 +1 -0
- scaffold_ca_python/templates/use_case/test_use_case.py.jinja2 +12 -0
- scaffold_ca_python/templates/use_case/use_case.py.jinja2 +9 -0
- scaffold_ca_python-0.1.1.dist-info/METADATA +285 -0
- scaffold_ca_python-0.1.1.dist-info/RECORD +109 -0
- scaffold_ca_python-0.1.1.dist-info/WHEEL +4 -0
- scaffold_ca_python-0.1.1.dist-info/entry_points.txt +3 -0
- scaffold_ca_python-0.1.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""StructureValidator: AST-based Clean Architecture import checker (T059)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from scaffold_ca_python.core.name_utils import ScaffoldError
|
|
9
|
+
from scaffold_ca_python.models.layer import FORBIDDEN_IMPORTS, Layer
|
|
10
|
+
from scaffold_ca_python.models.violation import ValidationReport, Violation
|
|
11
|
+
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
# Path → Layer mapping (filesystem directory names use underscores)
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
_PATH_SEGMENTS_TO_LAYER: list[tuple[tuple[str, ...], Layer]] = [
|
|
17
|
+
(("domain", "model"), Layer.DOMAIN_MODEL),
|
|
18
|
+
(("domain", "usecase"), Layer.DOMAIN_USECASE),
|
|
19
|
+
(("infrastructure", "entry_points"), Layer.ENTRY_POINTS),
|
|
20
|
+
(("infrastructure", "driven_adapters"), Layer.DRIVEN_ADAPTERS),
|
|
21
|
+
(("infrastructure", "helpers"), Layer.HELPERS),
|
|
22
|
+
(("application",), Layer.APPLICATION),
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _layer_from_path_parts(parts: tuple[str, ...]) -> Layer | None:
|
|
27
|
+
"""Return the Layer for *parts* (relative to the package root), or None."""
|
|
28
|
+
for segments, layer in _PATH_SEGMENTS_TO_LAYER:
|
|
29
|
+
n = len(segments)
|
|
30
|
+
if len(parts) >= n and parts[:n] == segments:
|
|
31
|
+
return layer
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _layer_from_module(module: str, python_package: str) -> Layer | None:
|
|
36
|
+
"""Return the Layer that *module* belongs to, or None if not project-internal."""
|
|
37
|
+
parts = tuple(module.split("."))
|
|
38
|
+
if not parts or parts[0] != python_package:
|
|
39
|
+
return None
|
|
40
|
+
return _layer_from_path_parts(parts[1:])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _make_hint(source: Layer, target: Layer) -> str:
|
|
44
|
+
if source in (Layer.DOMAIN_MODEL, Layer.DOMAIN_USECASE):
|
|
45
|
+
return f"Define a port interface in {source} and inject the adapter. Do not import directly from {target}."
|
|
46
|
+
return f"{source} must not import from {target}. Pass dependencies via constructor injection instead."
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
# Validator
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class StructureValidator:
|
|
55
|
+
"""Walks ``src/`` and reports Clean Architecture import violations."""
|
|
56
|
+
|
|
57
|
+
def validate(self, project_root: Path) -> ValidationReport:
|
|
58
|
+
"""Scan all ``.py`` files under ``src/`` and return a ``ValidationReport``.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
project_root: Root of the scaffolded project (contains ``pyproject.toml``).
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
A :class:`~scaffold_ca_python.models.violation.ValidationReport`.
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
ScaffoldError: If ``src/`` does not exist under *project_root*.
|
|
68
|
+
"""
|
|
69
|
+
src = project_root / "src"
|
|
70
|
+
if not src.exists():
|
|
71
|
+
raise ScaffoldError("Could not locate src/ under project root.")
|
|
72
|
+
|
|
73
|
+
py_files = sorted(src.rglob("*.py"))
|
|
74
|
+
violations: list[Violation] = []
|
|
75
|
+
files_scanned = 0
|
|
76
|
+
|
|
77
|
+
for py_file in py_files:
|
|
78
|
+
# Determine source layer from the file path: skip files outside known layers.
|
|
79
|
+
try:
|
|
80
|
+
rel = py_file.relative_to(src)
|
|
81
|
+
except ValueError:
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
parts = rel.parts
|
|
85
|
+
# parts[0] = python_package, parts[1:] = layer path
|
|
86
|
+
if len(parts) < 2:
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
python_package = parts[0]
|
|
90
|
+
source_layer = _layer_from_path_parts(parts[1:])
|
|
91
|
+
if source_layer is None:
|
|
92
|
+
continue # file outside any registered layer directory
|
|
93
|
+
|
|
94
|
+
files_scanned += 1
|
|
95
|
+
forbidden = FORBIDDEN_IMPORTS.get(source_layer, [])
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
content = py_file.read_text(encoding="utf-8")
|
|
99
|
+
tree = ast.parse(content, filename=str(py_file))
|
|
100
|
+
except SyntaxError:
|
|
101
|
+
# Non-fatal — warn and continue scanning other files.
|
|
102
|
+
import warnings
|
|
103
|
+
|
|
104
|
+
warnings.warn(f"could not parse '{py_file}' — skipping", stacklevel=2)
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
for node in ast.walk(tree):
|
|
108
|
+
if isinstance(node, ast.ImportFrom) and node.module:
|
|
109
|
+
target = _layer_from_module(node.module, python_package)
|
|
110
|
+
if target is not None and target in forbidden:
|
|
111
|
+
names = ", ".join(a.name for a in node.names)
|
|
112
|
+
violations.append(
|
|
113
|
+
Violation(
|
|
114
|
+
source_file=py_file,
|
|
115
|
+
line_number=node.lineno,
|
|
116
|
+
import_statement=f"from {node.module} import {names}",
|
|
117
|
+
source_layer=source_layer,
|
|
118
|
+
target_layer=target,
|
|
119
|
+
resolution_hint=_make_hint(source_layer, target),
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
elif isinstance(node, ast.Import):
|
|
124
|
+
for alias in node.names:
|
|
125
|
+
target = _layer_from_module(alias.name, python_package)
|
|
126
|
+
if target is not None and target in forbidden:
|
|
127
|
+
violations.append(
|
|
128
|
+
Violation(
|
|
129
|
+
source_file=py_file,
|
|
130
|
+
line_number=node.lineno,
|
|
131
|
+
import_statement=f"import {alias.name}",
|
|
132
|
+
source_layer=source_layer,
|
|
133
|
+
target_layer=target,
|
|
134
|
+
resolution_hint=_make_hint(source_layer, target),
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return ValidationReport(
|
|
139
|
+
project_root=project_root,
|
|
140
|
+
files_scanned=files_scanned,
|
|
141
|
+
violations=violations,
|
|
142
|
+
)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""template_renderer: Jinja2 rendering via importlib.resources (T024)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib.resources
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from jinja2 import BaseLoader, Environment, TemplateNotFound
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
from scaffold_ca_python.core.name_utils import ScaffoldError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _ResourceLoader(BaseLoader):
|
|
15
|
+
"""Jinja2 loader backed by ``importlib.resources.files()``."""
|
|
16
|
+
|
|
17
|
+
_PACKAGE = "scaffold_ca_python.templates"
|
|
18
|
+
|
|
19
|
+
def get_source(
|
|
20
|
+
self,
|
|
21
|
+
environment: Environment,
|
|
22
|
+
template: str,
|
|
23
|
+
) -> tuple[str, str | None, Any]:
|
|
24
|
+
try:
|
|
25
|
+
ref = importlib.resources.files(self._PACKAGE).joinpath(template)
|
|
26
|
+
source = ref.read_text(encoding="utf-8")
|
|
27
|
+
return source, str(ref), None
|
|
28
|
+
except (FileNotFoundError, TypeError) as exc:
|
|
29
|
+
raise TemplateNotFound(template) from exc
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TemplateRenderer:
|
|
33
|
+
"""Load and render Jinja2 templates from the ``scaffold_ca_python.templates`` package.
|
|
34
|
+
|
|
35
|
+
Usage::
|
|
36
|
+
|
|
37
|
+
renderer = TemplateRenderer()
|
|
38
|
+
|
|
39
|
+
# Render a packaged template
|
|
40
|
+
output = renderer.render("domain_model.py.j2", module_ctx)
|
|
41
|
+
|
|
42
|
+
# Render an inline template string (useful for testing / simple cases)
|
|
43
|
+
output = renderer.render_string("class {{ class_name }}:", ctx_dict)
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self) -> None:
|
|
47
|
+
self._env = Environment(
|
|
48
|
+
loader=_ResourceLoader(),
|
|
49
|
+
autoescape=False,
|
|
50
|
+
keep_trailing_newline=True,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# ------------------------------------------------------------------
|
|
54
|
+
# Public API
|
|
55
|
+
# ------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
def render(self, template_name: str, context: BaseModel | dict[str, Any]) -> str:
|
|
58
|
+
"""Render *template_name* with the fields from *context*.
|
|
59
|
+
|
|
60
|
+
*context* may be a Pydantic ``BaseModel`` (``model_dump()`` is called
|
|
61
|
+
automatically) or a plain ``dict``.
|
|
62
|
+
|
|
63
|
+
Raises
|
|
64
|
+
------
|
|
65
|
+
ScaffoldError
|
|
66
|
+
When the template is not found in the package.
|
|
67
|
+
"""
|
|
68
|
+
ctx_dict = self._to_dict(context)
|
|
69
|
+
try:
|
|
70
|
+
tmpl = self._env.get_template(template_name)
|
|
71
|
+
except TemplateNotFound as exc:
|
|
72
|
+
raise ScaffoldError(
|
|
73
|
+
f"Template {template_name!r} not found in scaffold_ca_python.templates. "
|
|
74
|
+
"Hint: ensure the template file exists in the package."
|
|
75
|
+
) from exc
|
|
76
|
+
return tmpl.render(**ctx_dict)
|
|
77
|
+
|
|
78
|
+
def render_string(self, source: str, context: BaseModel | dict[str, Any]) -> str:
|
|
79
|
+
"""Render an inline *source* string with *context*.
|
|
80
|
+
|
|
81
|
+
Primarily useful for tests and one-off generation.
|
|
82
|
+
"""
|
|
83
|
+
ctx_dict = self._to_dict(context)
|
|
84
|
+
tmpl = self._env.from_string(source)
|
|
85
|
+
return tmpl.render(**ctx_dict)
|
|
86
|
+
|
|
87
|
+
# ------------------------------------------------------------------
|
|
88
|
+
# Internal helpers
|
|
89
|
+
# ------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def _to_dict(context: BaseModel | dict[str, Any]) -> dict[str, Any]:
|
|
93
|
+
"""Convert a Pydantic model or plain dict to a render context dict."""
|
|
94
|
+
if isinstance(context, dict):
|
|
95
|
+
return context
|
|
96
|
+
# Pydantic v2 BaseModel
|
|
97
|
+
if hasattr(context, "model_dump"):
|
|
98
|
+
return context.model_dump()
|
|
99
|
+
# Fallback: try __dict__
|
|
100
|
+
return dict(vars(context))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Factory contracts for scaffold module generation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Protocol, runtime_checkable
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from scaffold_ca_python.core.module_builder import ModuleBuilder
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@runtime_checkable
|
|
12
|
+
class ModuleFactory(Protocol):
|
|
13
|
+
"""Contract for type-specific module generators."""
|
|
14
|
+
|
|
15
|
+
def build(self, builder: ModuleBuilder) -> None:
|
|
16
|
+
"""Populate *builder* with file/dependency operations for this module type."""
|
|
File without changes
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""DrivenAdapterGeneric factory implementation (T029)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from scaffold_ca_python.core.name_utils import to_snake_case
|
|
8
|
+
from scaffold_ca_python.core.project_detector import resolve_tests_root
|
|
9
|
+
from scaffold_ca_python.factory import ModuleFactory
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from scaffold_ca_python.core.module_builder import ModuleBuilder
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DrivenAdapterGeneric(ModuleFactory):
|
|
16
|
+
"""Factory for Generic driven-adapter type."""
|
|
17
|
+
|
|
18
|
+
def build(self, builder: ModuleBuilder) -> None:
|
|
19
|
+
"""Build Generic driven-adapter files.
|
|
20
|
+
|
|
21
|
+
Queues files:
|
|
22
|
+
- src/infrastructure/driven_adapters/<snake_name>/__init__.py
|
|
23
|
+
- src/infrastructure/driven_adapters/<snake_name>/<snake_name>_adapter.py
|
|
24
|
+
- tests/infrastructure/driven_adapters/<snake_name>/test_<snake_name>_adapter.py
|
|
25
|
+
|
|
26
|
+
No dependencies injected for generic type.
|
|
27
|
+
"""
|
|
28
|
+
project_root = builder.project_root
|
|
29
|
+
pkg = builder.project_ctx.python_package
|
|
30
|
+
|
|
31
|
+
# Get adapter name from module context
|
|
32
|
+
adapter_name = builder.module_ctx.name if builder.module_ctx else "CustomAdapter"
|
|
33
|
+
snake_name = to_snake_case(adapter_name)
|
|
34
|
+
|
|
35
|
+
# Setup directory paths
|
|
36
|
+
src_dir = project_root / "src" / pkg / "infrastructure" / "driven_adapters" / snake_name
|
|
37
|
+
test_dir = resolve_tests_root(project_root) / "infrastructure" / "driven_adapters" / snake_name
|
|
38
|
+
|
|
39
|
+
# Template base path for driven_adapter/generic
|
|
40
|
+
base = "driven_adapter/generic"
|
|
41
|
+
|
|
42
|
+
# Build context dict from module context
|
|
43
|
+
ctx_dict = {}
|
|
44
|
+
if builder.module_ctx:
|
|
45
|
+
ctx_dict.update(builder.module_ctx.model_dump())
|
|
46
|
+
|
|
47
|
+
# Add source files
|
|
48
|
+
builder.add_file(
|
|
49
|
+
src_dir / "__init__.py",
|
|
50
|
+
builder.render(f"{base}/__init__.py.jinja2", ctx_dict),
|
|
51
|
+
template_name=f"{base}/__init__.py.jinja2",
|
|
52
|
+
)
|
|
53
|
+
builder.add_file(
|
|
54
|
+
src_dir / f"{snake_name}_adapter.py",
|
|
55
|
+
builder.render(f"{base}/adapter.py.jinja2", ctx_dict),
|
|
56
|
+
template_name=f"{base}/adapter.py.jinja2",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Add test files
|
|
60
|
+
builder.add_file(
|
|
61
|
+
test_dir / f"test_{snake_name}_adapter.py",
|
|
62
|
+
builder.render(f"{base}/test_adapter.py.jinja2", ctx_dict),
|
|
63
|
+
template_name=f"{base}/test_adapter.py.jinja2",
|
|
64
|
+
is_test=True,
|
|
65
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""DrivenAdapterRestConsumer factory implementation (T025)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from scaffold_ca_python.core.project_detector import resolve_tests_root
|
|
8
|
+
from scaffold_ca_python.factory import ModuleFactory
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from scaffold_ca_python.core.module_builder import ModuleBuilder
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DrivenAdapterRestConsumer(ModuleFactory):
|
|
15
|
+
"""Factory for REST Consumer driven-adapter type."""
|
|
16
|
+
|
|
17
|
+
def build(self, builder: ModuleBuilder) -> None:
|
|
18
|
+
"""Build REST Consumer driven-adapter files and dependencies.
|
|
19
|
+
|
|
20
|
+
Queues files:
|
|
21
|
+
- src/infrastructure/driven_adapters/rest_consumer/__init__.py
|
|
22
|
+
- src/infrastructure/driven_adapters/rest_consumer/rest_consumer.py
|
|
23
|
+
- tests/infrastructure/driven_adapters/rest_consumer/test_rest_consumer.py
|
|
24
|
+
|
|
25
|
+
Injects:
|
|
26
|
+
- httpx>=0.27
|
|
27
|
+
"""
|
|
28
|
+
project_root = builder.project_root
|
|
29
|
+
pkg = builder.project_ctx.python_package
|
|
30
|
+
|
|
31
|
+
# Setup directory paths
|
|
32
|
+
src_dir = project_root / "src" / pkg / "infrastructure" / "driven_adapters" / "rest_consumer"
|
|
33
|
+
test_dir = resolve_tests_root(project_root) / "infrastructure" / "driven_adapters" / "rest_consumer"
|
|
34
|
+
|
|
35
|
+
# Template base path for driven_adapter/rest_consumer
|
|
36
|
+
base = "driven_adapter/rest_consumer"
|
|
37
|
+
|
|
38
|
+
# Build context dict from module context
|
|
39
|
+
ctx_dict = {}
|
|
40
|
+
if builder.module_ctx:
|
|
41
|
+
ctx_dict.update(builder.module_ctx.model_dump())
|
|
42
|
+
|
|
43
|
+
# Add source files
|
|
44
|
+
builder.add_file(
|
|
45
|
+
src_dir / "__init__.py",
|
|
46
|
+
builder.render(f"{base}/__init__.py.jinja2", ctx_dict),
|
|
47
|
+
template_name=f"{base}/__init__.py.jinja2",
|
|
48
|
+
)
|
|
49
|
+
builder.add_file(
|
|
50
|
+
src_dir / "rest_consumer.py",
|
|
51
|
+
builder.render(f"{base}/rest_consumer.py.jinja2", ctx_dict),
|
|
52
|
+
template_name=f"{base}/rest_consumer.py.jinja2",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Add test files
|
|
56
|
+
builder.add_file(
|
|
57
|
+
test_dir / "test_rest_consumer.py",
|
|
58
|
+
builder.render(f"{base}/test_rest_consumer.py.jinja2", ctx_dict),
|
|
59
|
+
template_name=f"{base}/test_rest_consumer.py.jinja2",
|
|
60
|
+
is_test=True,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Add dependency
|
|
64
|
+
builder.add_dependency("httpx>=0.27")
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""DrivenAdapterSecrets factory implementation (T027)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from scaffold_ca_python.core.project_detector import resolve_tests_root
|
|
8
|
+
from scaffold_ca_python.factory import ModuleFactory
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from scaffold_ca_python.core.module_builder import ModuleBuilder
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DrivenAdapterSecrets(ModuleFactory):
|
|
15
|
+
"""Factory for Secrets driven-adapter type."""
|
|
16
|
+
|
|
17
|
+
def build(self, builder: ModuleBuilder) -> None:
|
|
18
|
+
"""Build Secrets driven-adapter files and dependencies.
|
|
19
|
+
|
|
20
|
+
Queues files:
|
|
21
|
+
- src/infrastructure/driven_adapters/secrets/__init__.py
|
|
22
|
+
- src/infrastructure/driven_adapters/secrets/secrets_adapter.py
|
|
23
|
+
- tests/infrastructure/driven_adapters/secrets/test_secrets_adapter.py
|
|
24
|
+
|
|
25
|
+
Injects:
|
|
26
|
+
- boto3>=1.34
|
|
27
|
+
"""
|
|
28
|
+
project_root = builder.project_root
|
|
29
|
+
pkg = builder.project_ctx.python_package
|
|
30
|
+
|
|
31
|
+
# Setup directory paths
|
|
32
|
+
src_dir = project_root / "src" / pkg / "infrastructure" / "driven_adapters" / "secrets"
|
|
33
|
+
test_dir = resolve_tests_root(project_root) / "infrastructure" / "driven_adapters" / "secrets"
|
|
34
|
+
|
|
35
|
+
# Template base path for driven_adapter/secrets
|
|
36
|
+
base = "driven_adapter/secrets"
|
|
37
|
+
|
|
38
|
+
# Build context dict from module context
|
|
39
|
+
ctx_dict = {}
|
|
40
|
+
if builder.module_ctx:
|
|
41
|
+
ctx_dict.update(builder.module_ctx.model_dump())
|
|
42
|
+
|
|
43
|
+
# Add source files
|
|
44
|
+
builder.add_file(
|
|
45
|
+
src_dir / "__init__.py",
|
|
46
|
+
builder.render(f"{base}/__init__.py.jinja2", ctx_dict),
|
|
47
|
+
template_name=f"{base}/__init__.py.jinja2",
|
|
48
|
+
)
|
|
49
|
+
builder.add_file(
|
|
50
|
+
src_dir / "secrets_adapter.py",
|
|
51
|
+
builder.render(f"{base}/secrets_adapter.py.jinja2", ctx_dict),
|
|
52
|
+
template_name=f"{base}/secrets_adapter.py.jinja2",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Add test files
|
|
56
|
+
builder.add_file(
|
|
57
|
+
test_dir / "test_secrets_adapter.py",
|
|
58
|
+
builder.render(f"{base}/test_secrets_adapter.py.jinja2", ctx_dict),
|
|
59
|
+
template_name=f"{base}/test_secrets_adapter.py.jinja2",
|
|
60
|
+
is_test=True,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Add dependency
|
|
64
|
+
builder.add_dependency("boto3>=1.34")
|
|
File without changes
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""EntryPointAgent factory implementation (T017)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from scaffold_ca_python.core.project_detector import resolve_tests_root
|
|
8
|
+
from scaffold_ca_python.factory import ModuleFactory
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from scaffold_ca_python.core.module_builder import ModuleBuilder
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EntryPointAgent(ModuleFactory):
|
|
15
|
+
"""Factory for Agent entry-point type."""
|
|
16
|
+
|
|
17
|
+
def build(self, builder: ModuleBuilder) -> None:
|
|
18
|
+
"""Build Agent entry-point files and dependencies.
|
|
19
|
+
|
|
20
|
+
Queues files:
|
|
21
|
+
- src/infrastructure/entry_points/agent/__init__.py
|
|
22
|
+
- src/infrastructure/entry_points/agent/agent.py
|
|
23
|
+
- src/infrastructure/entry_points/agent/card.py
|
|
24
|
+
- tests/infrastructure/entry_points/agent/test_agent.py
|
|
25
|
+
|
|
26
|
+
Overwrites:
|
|
27
|
+
- src/main.py (if exists)
|
|
28
|
+
|
|
29
|
+
Injects:
|
|
30
|
+
- a2a-sdk>=0.1
|
|
31
|
+
"""
|
|
32
|
+
project_root = builder.project_root
|
|
33
|
+
pkg = builder.project_ctx.python_package
|
|
34
|
+
|
|
35
|
+
# Setup directory paths
|
|
36
|
+
src_dir = project_root / "src" / pkg / "infrastructure" / "entry_points" / "agent"
|
|
37
|
+
test_dir = resolve_tests_root(project_root) / "infrastructure" / "entry_points" / "agent"
|
|
38
|
+
main_py = project_root / "src" / pkg / "main.py"
|
|
39
|
+
|
|
40
|
+
# Template base path for entry_point/agent
|
|
41
|
+
base = "entry_point/agent"
|
|
42
|
+
|
|
43
|
+
# Build context dict from module context and params
|
|
44
|
+
ctx_dict = {}
|
|
45
|
+
if builder.module_ctx:
|
|
46
|
+
ctx_dict.update(builder.module_ctx.model_dump())
|
|
47
|
+
|
|
48
|
+
# Add enable_kafka and enable_mcp_client if present in params
|
|
49
|
+
if builder.get_param("enable_kafka"):
|
|
50
|
+
ctx_dict["enable_kafka"] = True
|
|
51
|
+
if builder.get_param("enable_mcp_client"):
|
|
52
|
+
ctx_dict["enable_mcp_client"] = True
|
|
53
|
+
|
|
54
|
+
# Add source files
|
|
55
|
+
def tpl(f: str) -> str:
|
|
56
|
+
return f"{base}/{f}"
|
|
57
|
+
|
|
58
|
+
builder.add_file(
|
|
59
|
+
src_dir / "__init__.py",
|
|
60
|
+
builder.render(f"{base}/__init__.py.jinja2", ctx_dict),
|
|
61
|
+
template_name=tpl("__init__.py.jinja2"),
|
|
62
|
+
)
|
|
63
|
+
builder.add_file(
|
|
64
|
+
src_dir / "agent.py",
|
|
65
|
+
builder.render(f"{base}/agent.py.jinja2", ctx_dict),
|
|
66
|
+
template_name=tpl("agent.py.jinja2"),
|
|
67
|
+
)
|
|
68
|
+
builder.add_file(
|
|
69
|
+
src_dir / "card.py",
|
|
70
|
+
builder.render(f"{base}/card.py.jinja2", ctx_dict),
|
|
71
|
+
template_name=tpl("card.py.jinja2"),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Add test files
|
|
75
|
+
builder.add_file(
|
|
76
|
+
test_dir / "test_agent.py",
|
|
77
|
+
builder.render(f"{base}/test_agent.py.jinja2", ctx_dict),
|
|
78
|
+
template_name=tpl("test_agent.py.jinja2"),
|
|
79
|
+
is_test=True,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Overwrite main.py
|
|
83
|
+
builder.add_file(
|
|
84
|
+
main_py,
|
|
85
|
+
builder.render(f"{base}/entrypoint_main.py.jinja2", ctx_dict),
|
|
86
|
+
template_name=tpl("entrypoint_main.py.jinja2"),
|
|
87
|
+
overwrite=True,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Add dependency
|
|
91
|
+
builder.add_dependency("a2a-sdk>=0.1")
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""EntryPointGeneric factory implementation (T021)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from scaffold_ca_python.core.project_detector import resolve_tests_root
|
|
8
|
+
from scaffold_ca_python.factory import ModuleFactory
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from scaffold_ca_python.core.module_builder import ModuleBuilder
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EntryPointGeneric(ModuleFactory):
|
|
15
|
+
"""Factory for Generic entry-point type."""
|
|
16
|
+
|
|
17
|
+
def build(self, builder: ModuleBuilder) -> None:
|
|
18
|
+
"""Build Generic entry-point files.
|
|
19
|
+
|
|
20
|
+
Queues files:
|
|
21
|
+
- src/infrastructure/entry_points/generic/__init__.py
|
|
22
|
+
- src/infrastructure/entry_points/generic/entry_point.py
|
|
23
|
+
- tests/infrastructure/entry_points/generic/test_entry_point.py
|
|
24
|
+
|
|
25
|
+
Overwrites:
|
|
26
|
+
- src/main.py (if exists)
|
|
27
|
+
|
|
28
|
+
No dependencies injected for generic type.
|
|
29
|
+
"""
|
|
30
|
+
project_root = builder.project_root
|
|
31
|
+
pkg = builder.project_ctx.python_package
|
|
32
|
+
|
|
33
|
+
# Setup directory paths
|
|
34
|
+
src_dir = project_root / "src" / pkg / "infrastructure" / "entry_points" / "generic"
|
|
35
|
+
test_dir = resolve_tests_root(project_root) / "infrastructure" / "entry_points" / "generic"
|
|
36
|
+
main_py = project_root / "src" / pkg / "main.py"
|
|
37
|
+
|
|
38
|
+
# Template base path for entry_point/generic
|
|
39
|
+
base = "entry_point/generic"
|
|
40
|
+
|
|
41
|
+
# Build context dict from module context
|
|
42
|
+
ctx_dict = {}
|
|
43
|
+
if builder.module_ctx:
|
|
44
|
+
ctx_dict.update(builder.module_ctx.model_dump())
|
|
45
|
+
|
|
46
|
+
# Add source files
|
|
47
|
+
def tpl(f: str) -> str:
|
|
48
|
+
return f"{base}/{f}"
|
|
49
|
+
|
|
50
|
+
builder.add_file(
|
|
51
|
+
src_dir / "__init__.py",
|
|
52
|
+
builder.render(f"{base}/__init__.py.jinja2", ctx_dict),
|
|
53
|
+
template_name=tpl("__init__.py.jinja2"),
|
|
54
|
+
)
|
|
55
|
+
builder.add_file(
|
|
56
|
+
src_dir / "entry_point.py",
|
|
57
|
+
builder.render(f"{base}/handler.py.jinja2", ctx_dict),
|
|
58
|
+
template_name=tpl("handler.py.jinja2"),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Add test files
|
|
62
|
+
builder.add_file(
|
|
63
|
+
test_dir / "test_entry_point.py",
|
|
64
|
+
builder.render(f"{base}/test_handler.py.jinja2", ctx_dict),
|
|
65
|
+
template_name=tpl("test_handler.py.jinja2"),
|
|
66
|
+
is_test=True,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Overwrite main.py
|
|
70
|
+
builder.add_file(
|
|
71
|
+
main_py,
|
|
72
|
+
builder.render(f"{base}/entrypoint_main.py.jinja2", ctx_dict),
|
|
73
|
+
template_name=tpl("entrypoint_main.py.jinja2"),
|
|
74
|
+
overwrite=True,
|
|
75
|
+
)
|