scaffold-ca-python 0.1.3__py3-none-any.whl → 0.1.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. scaffold_ca_python/commands/delete_module.py +6 -21
  2. scaffold_ca_python/commands/generate_driven_adapter.py +11 -6
  3. scaffold_ca_python/commands/generate_entry_point.py +7 -5
  4. scaffold_ca_python/commands/generate_helper.py +4 -4
  5. scaffold_ca_python/commands/generate_model.py +4 -4
  6. scaffold_ca_python/commands/generate_project.py +1 -1
  7. scaffold_ca_python/commands/generate_use_case.py +6 -4
  8. scaffold_ca_python/core/module_builder.py +36 -3
  9. scaffold_ca_python/core/name_utils.py +14 -0
  10. scaffold_ca_python/core/pyproject_writer.py +84 -1
  11. scaffold_ca_python/factory/driven_adapters/da_rest_consumer.py +5 -3
  12. scaffold_ca_python/factory/driven_adapters/da_secrets.py +2 -2
  13. scaffold_ca_python/factory/entry_points/ep_agent.py +2 -2
  14. scaffold_ca_python/factory/entry_points/ep_mcp.py +7 -7
  15. scaffold_ca_python/factory/entry_points/ep_restapi.py +8 -8
  16. scaffold_ca_python/templates/driven_adapter/rest_consumer/test_rest_consumer.py.jinja2 +1 -1
  17. scaffold_ca_python/templates/entry_point/agent/agent.py.jinja2 +5 -4
  18. scaffold_ca_python/templates/entry_point/mcp/app.py.jinja2 +6 -3
  19. scaffold_ca_python/templates/entry_point/mcp/prompts.py.jinja2 +2 -2
  20. scaffold_ca_python/templates/entry_point/mcp/resources.py.jinja2 +2 -2
  21. scaffold_ca_python/templates/entry_point/mcp/server.py.jinja2 +1 -1
  22. scaffold_ca_python/templates/entry_point/mcp/test_app.py.jinja2 +0 -2
  23. scaffold_ca_python/templates/entry_point/mcp/tools.py.jinja2 +2 -2
  24. scaffold_ca_python/templates/entry_point/restapi/app.py.jinja2 +1 -1
  25. scaffold_ca_python/templates/entry_point/restapi/test_app.py.jinja2 +3 -3
  26. scaffold_ca_python/templates/project/application/config/container.py.jinja2 +1 -1
  27. scaffold_ca_python/templates/project/application/config/driven_adapters_container.py.jinja2 +1 -1
  28. scaffold_ca_python/templates/project/application/config/resource_container.py.jinja2 +2 -2
  29. scaffold_ca_python/templates/project/pyproject_toml.jinja2 +15 -8
  30. {scaffold_ca_python-0.1.3.dist-info → scaffold_ca_python-0.1.4.dist-info}/METADATA +14 -14
  31. {scaffold_ca_python-0.1.3.dist-info → scaffold_ca_python-0.1.4.dist-info}/RECORD +34 -34
  32. {scaffold_ca_python-0.1.3.dist-info → scaffold_ca_python-0.1.4.dist-info}/WHEEL +1 -1
  33. {scaffold_ca_python-0.1.3.dist-info → scaffold_ca_python-0.1.4.dist-info}/entry_points.txt +0 -0
  34. {scaffold_ca_python-0.1.3.dist-info → scaffold_ca_python-0.1.4.dist-info}/licenses/LICENSE +0 -0
@@ -10,7 +10,7 @@ import typer
10
10
  from rich.console import Console
11
11
 
12
12
  from scaffold_ca_python.core.module_builder import ModuleBuilder
13
- from scaffold_ca_python.core.name_utils import ScaffoldError, to_snake_case, validate_name
13
+ from scaffold_ca_python.core.name_utils import ScaffoldError, to_snake_case, validate_module_name
14
14
  from scaffold_ca_python.core.project_detector import find_project_root, resolve_tests_root
15
15
  from scaffold_ca_python.factory import ModuleFactory
16
16
  from scaffold_ca_python.factory.simple.delete_module_factory import DeleteModuleFactory
@@ -24,7 +24,7 @@ _REGISTRY: dict[str, type[ModuleFactory]] = {
24
24
  }
25
25
 
26
26
  _DM_HELP = "Delete a previously generated module and its test mirror. Alias 'dm'."
27
- _DM_EPILOG = "Examples:\n\n * scaffold dm --name Order\n\n * scaffold delete-module --name MyAdapter\n\n"
27
+ _DM_EPILOG = "Examples:\n\n * scaffold dm --name order\n\n * scaffold delete-module --name my_adapter\n\n"
28
28
 
29
29
 
30
30
  # ---------------------------------------------------------------------------
@@ -32,7 +32,7 @@ _DM_EPILOG = "Examples:\n\n * scaffold dm --name Order\n\n * scaffold delete-m
32
32
  # ---------------------------------------------------------------------------
33
33
 
34
34
 
35
- def _find_module_targets(project_root: Path, snake: str) -> list[tuple[Path, Path | None]]:
35
+ def _find_module_targets(pkg: str, project_root: Path, snake: str) -> list[tuple[Path, Path | None]]:
36
36
  """Return a list of (target_path, test_path_or_None) for all matches of *snake*.
37
37
 
38
38
  Searches across:
@@ -42,7 +42,6 @@ def _find_module_targets(project_root: Path, snake: str) -> list[tuple[Path, Pat
42
42
  4. infrastructure/entry_points/<snake>/
43
43
  5. infrastructure/helpers/<snake>/
44
44
  """
45
- pkg = _get_python_package(project_root)
46
45
  src_root = project_root / "src" / pkg
47
46
  tests_root = resolve_tests_root(project_root)
48
47
 
@@ -77,20 +76,6 @@ def _find_module_targets(project_root: Path, snake: str) -> list[tuple[Path, Pat
77
76
  return candidates
78
77
 
79
78
 
80
- def _get_python_package(root: Path) -> str:
81
- pyproject = root / "pyproject.toml"
82
- with pyproject.open("rb") as fh:
83
- data = tomllib.load(fh)
84
- section = data.get("tool", {}).get("scaffold-ca-python", {})
85
- name = section.get("name", root.name)
86
- import re
87
-
88
- s = name.replace("-", "_")
89
- s = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", s)
90
- s = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s)
91
- return s.lower()
92
-
93
-
94
79
  # ---------------------------------------------------------------------------
95
80
  # Implementation
96
81
  # ---------------------------------------------------------------------------
@@ -99,7 +84,7 @@ def _get_python_package(root: Path) -> str:
99
84
  def _delete_module_impl(name: str, confirm: bool, dry_run: bool) -> None:
100
85
  # Validate name
101
86
  try:
102
- validate_name(name)
87
+ validate_module_name(name)
103
88
  except ScaffoldError as exc:
104
89
  console.print(f"[red]Error:[/red] {exc}")
105
90
  raise typer.Exit(code=1) from None
@@ -118,7 +103,7 @@ def _delete_module_impl(name: str, confirm: bool, dry_run: bool) -> None:
118
103
 
119
104
  # Find targets
120
105
  snake = to_snake_case(name)
121
- targets = _find_module_targets(project_root, snake)
106
+ targets = _find_module_targets(project_ctx.python_package, project_root, snake)
122
107
 
123
108
  if not targets:
124
109
  console.print(
@@ -174,7 +159,7 @@ def register(app: typer.Typer) -> None:
174
159
  def delete_module(
175
160
  ctx: typer.Context,
176
161
  name: Annotated[
177
- str | None, typer.Option("--name", help="Module name (PascalCase).", rich_help_panel="Required")
162
+ str | None, typer.Option("--name", help="Module name (snake_case).", rich_help_panel="Required")
178
163
  ] = None,
179
164
  confirm: Annotated[
180
165
  bool,
@@ -8,10 +8,11 @@ from typing import Annotated
8
8
 
9
9
  import typer
10
10
  from rich.console import Console
11
+ from rich.markup import escape
11
12
  from rich.tree import Tree
12
13
 
13
14
  from scaffold_ca_python.core.module_builder import ModuleBuilder
14
- from scaffold_ca_python.core.name_utils import ScaffoldError, to_snake_case, validate_name
15
+ from scaffold_ca_python.core.name_utils import ScaffoldError, to_snake_case, validate_module_name
15
16
  from scaffold_ca_python.core.project_detector import find_project_root
16
17
  from scaffold_ca_python.factory import ModuleFactory
17
18
  from scaffold_ca_python.factory.driven_adapters.da_generic import DrivenAdapterGeneric
@@ -37,7 +38,7 @@ _GDA_EPILOG = (
37
38
  " ==================================================\n\n"
38
39
  "Examples:\n\n"
39
40
  " * scaffold gda --type rest-consumer\n\n"
40
- " * scaffold generate-driven-adapter --type generic --name MyAdapter\n\n"
41
+ " * scaffold generate-driven-adapter --type generic --name my_adapter\n\n"
41
42
  )
42
43
 
43
44
 
@@ -55,7 +56,7 @@ def _generate_driven_adapter_impl(type_: str, name: str | None, dry_run: bool) -
55
56
  # --- Validate name if provided ---
56
57
  if name:
57
58
  try:
58
- validate_name(name)
59
+ validate_module_name(name)
59
60
  except ScaffoldError as exc:
60
61
  console.print(f"[red]Error:[/red] {exc}")
61
62
  raise typer.Exit(code=1) from None
@@ -116,8 +117,12 @@ def _generate_driven_adapter_impl(type_: str, name: str | None, dry_run: bool) -
116
117
  for p in sorted(created):
117
118
  tree.add(str(p.relative_to(project_root)))
118
119
  console.print(tree)
119
- if builder._dependencies:
120
- console.print(f"[dim]Would add to [project.dependencies]: {', '.join(builder._dependencies)}[/dim]")
120
+ if builder.pending_dependencies:
121
+ target = escape("[project.dependencies]")
122
+ console.print(f"[dim]Would add to {target}: {', '.join(builder.pending_dependencies)}[/dim]")
123
+ for grp, pkgs in builder.pending_group_dependencies.items():
124
+ target = escape(f"[dependency-groups.{grp}]")
125
+ console.print(f"[dim]Would add to {target}: {', '.join(pkgs)}[/dim]")
121
126
  return
122
127
 
123
128
  console.print(f"[green]✓[/green] Driven adapter [bold]{subdir}[/bold] created. Created {len(created)} file(s).")
@@ -146,7 +151,7 @@ def register(app: typer.Typer) -> None:
146
151
  str | None,
147
152
  typer.Option(
148
153
  "--name",
149
- help="Adapter name (required for --type generic).",
154
+ help="Adapter name in snake_case, e.g. my_adapter (required for --type generic).",
150
155
  rich_help_panel="Options",
151
156
  ),
152
157
  ] = None,
@@ -10,10 +10,11 @@ from typing import Annotated
10
10
  import typer
11
11
  import yaml
12
12
  from rich.console import Console
13
+ from rich.markup import escape
13
14
  from rich.tree import Tree
14
15
 
15
16
  from scaffold_ca_python.core.module_builder import ModuleBuilder
16
- from scaffold_ca_python.core.name_utils import ScaffoldError
17
+ from scaffold_ca_python.core.name_utils import ScaffoldError, to_snake_case
17
18
  from scaffold_ca_python.core.project_detector import find_project_root
18
19
  from scaffold_ca_python.factory import ModuleFactory
19
20
  from scaffold_ca_python.factory.entry_points.ep_agent import EntryPointAgent
@@ -59,7 +60,7 @@ def _load_project_context(project_root: Path) -> ProjectContext:
59
60
  pyproject_path = project_root / "pyproject.toml"
60
61
  with open(pyproject_path, "rb") as f:
61
62
  data = tomllib.load(f)
62
- project_name = data.get("project", {}).get("name", "project").replace("-", "_")
63
+ project_name = to_snake_case(data.get("project", {}).get("name", "project"))
63
64
  return ProjectContext(name=project_name)
64
65
 
65
66
 
@@ -190,9 +191,10 @@ def _generate_entry_point_impl(
190
191
  tree.add(str(p.relative_to(project_root)))
191
192
  console.print(tree)
192
193
 
193
- # Check if dependencies were collected by looking at builder internals
194
- if builder._dependencies:
195
- console.print(f"[dim]Would add to [project.dependencies]: {', '.join(builder._dependencies)}[/dim]")
194
+ # Report any runtime dependencies queued for injection
195
+ if builder.pending_dependencies:
196
+ target = escape("[project.dependencies]")
197
+ console.print(f"[dim]Would add to {target}: {', '.join(builder.pending_dependencies)}[/dim]")
196
198
 
197
199
  if type_ == "restapi":
198
200
  main_py = project_root / "src" / pkg / "main.py"
@@ -11,7 +11,7 @@ from rich.console import Console
11
11
  from rich.tree import Tree
12
12
 
13
13
  from scaffold_ca_python.core.module_builder import ModuleBuilder
14
- from scaffold_ca_python.core.name_utils import ScaffoldError, to_snake_case, validate_name
14
+ from scaffold_ca_python.core.name_utils import ScaffoldError, to_snake_case, validate_module_name
15
15
  from scaffold_ca_python.core.project_detector import find_project_root
16
16
  from scaffold_ca_python.factory import ModuleFactory
17
17
  from scaffold_ca_python.factory.simple.helper_factory import HelperFactory
@@ -25,13 +25,13 @@ _REGISTRY: dict[str, type[ModuleFactory]] = {
25
25
  }
26
26
 
27
27
  _GH_HELP = "Scaffold a helper utility module and test stub. Alias 'gh'."
28
- _GH_EPILOG = "Example:\n\n * scaffold gh --name JsonParser\n\n * scaffold generate-helper --name JsonParser\n\n"
28
+ _GH_EPILOG = "Example:\n\n * scaffold gh --name json_parser\n\n * scaffold generate-helper --name json_parser\n\n"
29
29
 
30
30
 
31
31
  def _generate_helper_impl(name: str, dry_run: bool) -> None:
32
32
  # --- Validate name ---
33
33
  try:
34
- validate_name(name)
34
+ validate_module_name(name)
35
35
  except ScaffoldError as exc:
36
36
  console.print(f"[red]Error:[/red] {exc}")
37
37
  raise typer.Exit(code=1) from None
@@ -100,7 +100,7 @@ def register(app: typer.Typer) -> None:
100
100
  def generate_helper(
101
101
  ctx: typer.Context,
102
102
  name: Annotated[
103
- str | None, typer.Option("--name", help="Helper name (PascalCase).", rich_help_panel="Required")
103
+ str | None, typer.Option("--name", help="Helper name (snake_case).", rich_help_panel="Required")
104
104
  ] = None,
105
105
  dry_run: Annotated[
106
106
  bool,
@@ -11,7 +11,7 @@ from rich.console import Console
11
11
  from rich.tree import Tree
12
12
 
13
13
  from scaffold_ca_python.core.module_builder import ModuleBuilder
14
- from scaffold_ca_python.core.name_utils import ScaffoldError, validate_name
14
+ from scaffold_ca_python.core.name_utils import ScaffoldError, validate_module_name
15
15
  from scaffold_ca_python.core.project_detector import find_project_root
16
16
  from scaffold_ca_python.factory import ModuleFactory
17
17
  from scaffold_ca_python.factory.simple.model_factory import ModelFactory
@@ -24,13 +24,13 @@ _REGISTRY: dict[str, type[ModuleFactory]] = {
24
24
  "model": ModelFactory,
25
25
  }
26
26
  _GM_HELP = "Scaffold a Pydantic v2 domain model and test stub. Alias 'gm'."
27
- _GM_EPILOG = "Example:\n\n * scaffold gm --name Order\n\n * scaffold generate-model --name Order\n\n"
27
+ _GM_EPILOG = "Example:\n\n * scaffold gm --name order\n\n * scaffold generate-model --name order\n\n"
28
28
 
29
29
 
30
30
  def _generate_model_impl(name: str, dry_run: bool) -> None:
31
31
  # --- Validate name ---
32
32
  try:
33
- validate_name(name)
33
+ validate_module_name(name)
34
34
  except ScaffoldError as exc:
35
35
  console.print(f"[red]Error:[/red] {exc}")
36
36
  raise typer.Exit(code=1) from None
@@ -100,7 +100,7 @@ def register(app: typer.Typer) -> None:
100
100
  def generate_model(
101
101
  ctx: typer.Context,
102
102
  name: Annotated[
103
- str | None, typer.Option("--name", help="Model name (PascalCase).", rich_help_panel="Required")
103
+ str | None, typer.Option("--name", help="Model name (snake_case).", rich_help_panel="Required")
104
104
  ] = None,
105
105
  dry_run: Annotated[
106
106
  bool,
@@ -151,7 +151,7 @@ def register(app: typer.Typer) -> None:
151
151
  str,
152
152
  typer.Option(
153
153
  "--name",
154
- help="Project name (PascalCase or snake_case).",
154
+ help="Project name (kebab-case or snake_case).",
155
155
  rich_help_panel="Required",
156
156
  ),
157
157
  ] = None, # type: ignore[assignment]
@@ -11,7 +11,7 @@ from rich.console import Console
11
11
  from rich.tree import Tree
12
12
 
13
13
  from scaffold_ca_python.core.module_builder import ModuleBuilder
14
- from scaffold_ca_python.core.name_utils import ScaffoldError, to_snake_case, validate_name
14
+ from scaffold_ca_python.core.name_utils import ScaffoldError, to_snake_case, validate_module_name
15
15
  from scaffold_ca_python.core.project_detector import find_project_root
16
16
  from scaffold_ca_python.factory import ModuleFactory
17
17
  from scaffold_ca_python.factory.simple.use_case_factory import UseCaseFactory
@@ -24,13 +24,15 @@ _REGISTRY: dict[str, type[ModuleFactory]] = {
24
24
  "use_case": UseCaseFactory,
25
25
  }
26
26
  _GUC_HELP = "Scaffold a use case in domain/usecase/. Alias 'guc'."
27
- _GUC_EPILOG = "Example:\n\n * scaffold guc --name CreateOrder\n\n * scaffold generate-use-case --name CreateOrder\n\n"
27
+ _GUC_EPILOG = (
28
+ "Example:\n\n * scaffold guc --name create_order\n\n * scaffold generate-use-case --name create_order\n\n"
29
+ )
28
30
 
29
31
 
30
32
  def _generate_use_case_impl(name: str, dry_run: bool) -> None:
31
33
  # --- Validate name ---
32
34
  try:
33
- validate_name(name)
35
+ validate_module_name(name)
34
36
  except ScaffoldError as exc:
35
37
  console.print(f"[red]Error:[/red] {exc}")
36
38
  raise typer.Exit(code=1) from None
@@ -102,7 +104,7 @@ def register(app: typer.Typer) -> None:
102
104
  def generate_use_case(
103
105
  ctx: typer.Context,
104
106
  name: Annotated[
105
- str | None, typer.Option("--name", help="Use case name (PascalCase).", rich_help_panel="Required")
107
+ str | None, typer.Option("--name", help="Use case name (snake_case).", rich_help_panel="Required")
106
108
  ] = None,
107
109
  dry_run: Annotated[
108
110
  bool,
@@ -9,9 +9,11 @@ from pydantic import BaseModel
9
9
 
10
10
  from scaffold_ca_python.core.file_writer import FileWriter
11
11
  from scaffold_ca_python.core.pyproject_writer import (
12
+ dry_run_group_inject,
12
13
  dry_run_inject,
13
14
  dry_run_scripts_update,
14
15
  inject_dependencies,
16
+ inject_group_dependencies,
15
17
  update_project_scripts,
16
18
  )
17
19
  from scaffold_ca_python.core.template_renderer import TemplateRenderer
@@ -48,6 +50,7 @@ class ModuleBuilder:
48
50
 
49
51
  self._operations: list[FileOperation] = []
50
52
  self._dependencies: list[str] = []
53
+ self._group_dependencies: dict[str, list[str]] = {}
51
54
  self._params: dict[str, object] = {}
52
55
 
53
56
  def add_file(
@@ -89,9 +92,33 @@ class ModuleBuilder:
89
92
  """Render a template through the shared renderer."""
90
93
  return self._renderer.render(template_name, context)
91
94
 
92
- def add_dependency(self, package: str) -> None:
93
- """Queue a package dependency to inject into pyproject.toml."""
94
- self._dependencies.append(package)
95
+ def add_dependency(self, package: str, *, group: str | None = None) -> None:
96
+ """Queue a package dependency to inject into pyproject.toml.
97
+
98
+ Parameters
99
+ ----------
100
+ package:
101
+ Package specifier, e.g. ``"httpx>=0.27"`` or ``"respx>=0.22"``.
102
+ group:
103
+ When ``None`` (default) the package is injected into
104
+ ``[project.dependencies]`` (runtime). When set to a group name such as
105
+ ``"test"``, the package is injected into
106
+ ``[dependency-groups.<group>]`` instead.
107
+ """
108
+ if group is None:
109
+ self._dependencies.append(package)
110
+ else:
111
+ self._group_dependencies.setdefault(group, []).append(package)
112
+
113
+ @property
114
+ def pending_dependencies(self) -> list[str]:
115
+ """Runtime dependencies queued for injection (read-only copy)."""
116
+ return list(self._dependencies)
117
+
118
+ @property
119
+ def pending_group_dependencies(self) -> dict[str, list[str]]:
120
+ """Dependency-group deps queued for injection, keyed by group (read-only copy)."""
121
+ return {group: list(pkgs) for group, pkgs in self._group_dependencies.items()}
95
122
 
96
123
  def add_param(self, key: str, value: object) -> None:
97
124
  """Store an ad-hoc value used by factories/commands."""
@@ -115,6 +142,12 @@ class ModuleBuilder:
115
142
  else:
116
143
  inject_dependencies(self.project_root, self._dependencies)
117
144
 
145
+ for grp, pkgs in self._group_dependencies.items():
146
+ if self.dry_run:
147
+ dry_run_group_inject(self.project_root, grp, pkgs)
148
+ else:
149
+ inject_group_dependencies(self.project_root, grp, pkgs)
150
+
118
151
  # Optional script update hook used by entry-point factory implementations.
119
152
  if bool(self._params.get("scripts_entry", False)):
120
153
  pkg = self.project_ctx
@@ -6,6 +6,9 @@ import re
6
6
 
7
7
  NAME_RE = re.compile(r"^[a-z][a-z0-9]*([_-][a-z0-9]+)*$")
8
8
 
9
+ # Stricter pattern for module/class-generating commands: snake_case only (no hyphens).
10
+ MODULE_NAME_RE = re.compile(r"^[a-z][a-z0-9]*(_[a-z0-9]+)*$")
11
+
9
12
  # Matches transitions: lowercase→uppercase and letter→digit boundary runs
10
13
  _CAMEL_BOUNDARY_RE = re.compile(r"(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])")
11
14
 
@@ -32,6 +35,17 @@ def validate_name(name: str) -> str:
32
35
  return name
33
36
 
34
37
 
38
+ def validate_module_name(name: str) -> str:
39
+ """Validate *name* for module/class-generating commands: snake_case only.
40
+
41
+ Raises :class:`ScaffoldError` with a hint if the name is not snake_case.
42
+ Returns the original name unchanged when valid.
43
+ """
44
+ if not MODULE_NAME_RE.match(name):
45
+ raise ScaffoldError(f"Invalid name {name!r}. Use snake_case (e.g., 'create_order').")
46
+ return name
47
+
48
+
35
49
  def to_snake_case(name: str) -> str:
36
50
  """Convert *name* (PascalCase, camelCase, kebab-case, or already snake_case) to snake_case."""
37
51
  # Normalise hyphens to underscores first (kebab-case support)
@@ -18,8 +18,9 @@ def _name_prefix(package: str) -> str:
18
18
  "fastapi>=0.100" -> "fastapi"
19
19
  "uvicorn[standard]>=0.20" -> "uvicorn"
20
20
  "a2a-sdk>=0.1" -> "a2a-sdk"
21
+ "boto3~=1.43.98" -> "boto3"
21
22
  """
22
- name = package.split(">=")[0].split("==")[0].split("<")[0].split("!=")[0]
23
+ name = package.split("~=")[0].split(">=")[0].split("==")[0].split("<")[0].split("!=")[0]
23
24
  name = name.split("[")[0]
24
25
  return name.strip().lower()
25
26
 
@@ -72,6 +73,88 @@ def inject_dependencies(project_root: Path, packages: list[str]) -> list[str]:
72
73
  return to_add
73
74
 
74
75
 
76
+ def inject_group_dependencies(project_root: Path, group: str, packages: list[str]) -> list[str]:
77
+ """Add *packages* to ``[dependency-groups.<group>]`` in ``pyproject.toml``.
78
+
79
+ Idempotent: a package whose bare name is already present is skipped. The group
80
+ is created when absent. Non-string entries (e.g. ``{include-group = ...}``) are
81
+ preserved and ignored for duplicate detection.
82
+
83
+ Parameters
84
+ ----------
85
+ project_root:
86
+ Directory containing ``pyproject.toml``.
87
+ group:
88
+ Dependency-group name (e.g. ``"test"``).
89
+ packages:
90
+ Packages to inject, e.g. ``["respx>=0.22"]``.
91
+
92
+ Returns
93
+ -------
94
+ list[str]
95
+ The packages that were actually added (empty when all were already present).
96
+
97
+ Raises
98
+ ------
99
+ FileNotFoundError
100
+ If ``pyproject.toml`` does not exist in *project_root*.
101
+ tomllib.TOMLDecodeError
102
+ If ``pyproject.toml`` is malformed.
103
+ """
104
+ pyproject = project_root / "pyproject.toml"
105
+ with pyproject.open("rb") as fh:
106
+ data = tomllib.load(fh)
107
+
108
+ groups: dict[str, list[object]] = data.setdefault("dependency-groups", {})
109
+ entries: list[object] = groups.setdefault(group, [])
110
+ existing = [e for e in entries if isinstance(e, str)]
111
+ to_add = _missing(existing, packages)
112
+ if not to_add:
113
+ return []
114
+
115
+ entries.extend(to_add)
116
+
117
+ with pyproject.open("wb") as fh:
118
+ tomli_w.dump(data, fh)
119
+
120
+ return to_add
121
+
122
+
123
+ def dry_run_group_inject(project_root: Path, group: str, packages: list[str]) -> list[str]:
124
+ """Return packages that *would* be added to ``[dependency-groups.<group>]``.
125
+
126
+ Read-only: never modifies ``pyproject.toml``.
127
+
128
+ Parameters
129
+ ----------
130
+ project_root:
131
+ Directory containing ``pyproject.toml``.
132
+ group:
133
+ Dependency-group name (e.g. ``"test"``).
134
+ packages:
135
+ Packages to check.
136
+
137
+ Returns
138
+ -------
139
+ list[str]
140
+ The packages that would be added if :func:`inject_group_dependencies` were called.
141
+
142
+ Raises
143
+ ------
144
+ FileNotFoundError
145
+ If ``pyproject.toml`` does not exist in *project_root*.
146
+ tomllib.TOMLDecodeError
147
+ If ``pyproject.toml`` is malformed.
148
+ """
149
+ pyproject = project_root / "pyproject.toml"
150
+ with pyproject.open("rb") as fh:
151
+ data = tomllib.load(fh)
152
+
153
+ entries = data.get("dependency-groups", {}).get(group, [])
154
+ existing = [e for e in entries if isinstance(e, str)]
155
+ return _missing(existing, packages)
156
+
157
+
75
158
  def dry_run_inject(project_root: Path, packages: list[str]) -> list[str]:
76
159
  """Return packages that *would* be added without writing to disk.
77
160
 
@@ -23,7 +23,8 @@ class DrivenAdapterRestConsumer(ModuleFactory):
23
23
  - tests/infrastructure/driven_adapters/rest_consumer/test_rest_consumer.py
24
24
 
25
25
  Injects:
26
- - httpx>=0.27
26
+ - httpx~=0.28.1 (runtime dependency)
27
+ - respx~=0.23.1 (test dependency-group)
27
28
  """
28
29
  project_root = builder.project_root
29
30
  pkg = builder.project_ctx.python_package
@@ -60,5 +61,6 @@ class DrivenAdapterRestConsumer(ModuleFactory):
60
61
  is_test=True,
61
62
  )
62
63
 
63
- # Add dependency
64
- builder.add_dependency("httpx>=0.27")
64
+ # Add dependencies: httpx at runtime, respx only in test group
65
+ builder.add_dependency("httpx~=0.28.1")
66
+ builder.add_dependency("respx~=0.23.1", group="test")
@@ -23,7 +23,7 @@ class DrivenAdapterSecrets(ModuleFactory):
23
23
  - tests/infrastructure/driven_adapters/secrets/test_secrets_adapter.py
24
24
 
25
25
  Injects:
26
- - boto3>=1.34
26
+ - boto3~=1.43.98
27
27
  """
28
28
  project_root = builder.project_root
29
29
  pkg = builder.project_ctx.python_package
@@ -61,4 +61,4 @@ class DrivenAdapterSecrets(ModuleFactory):
61
61
  )
62
62
 
63
63
  # Add dependency
64
- builder.add_dependency("boto3>=1.34")
64
+ builder.add_dependency("boto3~=1.43.98")
@@ -27,7 +27,7 @@ class EntryPointAgent(ModuleFactory):
27
27
  - src/main.py (if exists)
28
28
 
29
29
  Injects:
30
- - a2a-sdk>=0.1
30
+ - a2a-sdk~=1.1.4
31
31
  """
32
32
  project_root = builder.project_root
33
33
  pkg = builder.project_ctx.python_package
@@ -88,4 +88,4 @@ class EntryPointAgent(ModuleFactory):
88
88
  )
89
89
 
90
90
  # Add dependency
91
- builder.add_dependency("a2a-sdk>=0.1")
91
+ builder.add_dependency("a2a-sdk~=1.1.4")
@@ -26,8 +26,8 @@ class EntryPointMcp(ModuleFactory):
26
26
  - resources.py + test_resources.py (with_resources=True)
27
27
  - prompts.py + test_prompts.py (with_prompts=True)
28
28
 
29
- Injects: mcp>=1.0, uvicorn[standard]>=0.20, starlette>=0.40,
30
- dependency-injector>=4.49.0, pydantic-settings>=2.13.1
29
+ Injects: mcp~=1.30.0, uvicorn[standard]~=0.53.0, starlette~=1.6.0,
30
+ dependency-injector~=4.49.1, pydantic-settings~=2.15.0
31
31
 
32
32
  Sets builder params: scripts_entry_fn="main"
33
33
  """
@@ -127,11 +127,11 @@ class EntryPointMcp(ModuleFactory):
127
127
  )
128
128
 
129
129
  # Dependencies
130
- builder.add_dependency("mcp>=1.0")
131
- builder.add_dependency("uvicorn[standard]>=0.20")
132
- builder.add_dependency("starlette>=0.40")
133
- builder.add_dependency("dependency-injector>=4.49.0")
134
- builder.add_dependency("pydantic-settings>=2.13.1")
130
+ builder.add_dependency("mcp~=1.30.0") # ~=1.30.0 caps below 2.0; templates target the 1.x FastMCP API
131
+ builder.add_dependency("uvicorn[standard]~=0.53.0")
132
+ builder.add_dependency("starlette~=1.6.0")
133
+ builder.add_dependency("dependency-injector~=4.49.1")
134
+ builder.add_dependency("pydantic-settings~=2.15.0")
135
135
 
136
136
  # Signal to module_builder.persist() to update pyproject.toml scripts
137
137
  builder.add_param("scripts_entry", True)
@@ -32,10 +32,10 @@ class EntryPointRestApi(ModuleFactory):
32
32
  - src/main.py
33
33
 
34
34
  Injects:
35
- - fastapi[standard]>=0.135.2
36
- - uvicorn[standard]>=0.20
37
- - dependency-injector>=4.49.0
38
- - pydantic-settings>=2.13.1
35
+ - fastapi[standard]~=0.141.1
36
+ - uvicorn[standard]~=0.53.0
37
+ - dependency-injector~=4.49.1
38
+ - pydantic-settings~=2.15.0
39
39
  """
40
40
  project_root = builder.project_root
41
41
  pkg = builder.project_ctx.python_package
@@ -121,10 +121,10 @@ class EntryPointRestApi(ModuleFactory):
121
121
 
122
122
  # Add dependencies
123
123
  deps = [
124
- "fastapi[standard]>=0.135.2",
125
- "uvicorn[standard]>=0.20",
126
- "dependency-injector>=4.49.0",
127
- "pydantic-settings>=2.13.1",
124
+ "fastapi[standard]~=0.141.1",
125
+ "uvicorn[standard]~=0.53.0",
126
+ "dependency-injector~=4.49.1",
127
+ "pydantic-settings~=2.15.0",
128
128
  ]
129
129
  for dep in deps:
130
130
  builder.add_dependency(dep)
@@ -2,9 +2,9 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import httpx
5
6
  import pytest
6
7
  import respx
7
- import httpx
8
8
 
9
9
  from {{ project.python_package }}.infrastructure.driven_adapters.rest_consumer.rest_consumer import (
10
10
  RestConsumerAdapter,
@@ -1,15 +1,16 @@
1
1
  """A2A async agent handler stub."""
2
2
 
3
3
  from __future__ import annotations
4
+ {%- if enable_kafka %}
4
5
 
5
- {% if enable_kafka %}
6
6
  # Kafka consumer stub — replace with your actual broker configuration.
7
7
  # from aiokafka import AIOKafkaConsumer
8
- {% endif %}
9
- {% if enable_mcp_client %}
8
+ {%- endif %}
9
+ {%- if enable_mcp_client %}
10
+
10
11
  # MCP tool-call client stub — replace with your actual MCP client.
11
12
  # from mcp import ClientSession
12
- {% endif %}
13
+ {%- endif %}
13
14
 
14
15
 
15
16
  class {{ project.name }}Agent:
@@ -10,9 +10,12 @@ from starlette.routing import Mount
10
10
 
11
11
  from {{ project.python_package }}.application.config.container import Container
12
12
  from {{ project.python_package }}.infrastructure.entry_points.mcp_server import tools
13
- {% if with_resources %}from {{ project.python_package }}.infrastructure.entry_points.mcp_server import resources
14
- {% endif %}{% if with_prompts %}from {{ project.python_package }}.infrastructure.entry_points.mcp_server import prompts
15
- {% endif %}
13
+ {%- if with_resources %}
14
+ from {{ project.python_package }}.infrastructure.entry_points.mcp_server import resources
15
+ {%- endif %}
16
+ {%- if with_prompts %}
17
+ from {{ project.python_package }}.infrastructure.entry_points.mcp_server import prompts
18
+ {%- endif %}
16
19
 
17
20
  mcp: FastMCP = FastMCP("{{ project.name }}", stateless_http=True)
18
21
 
@@ -2,10 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from dependency_injector.wiring import Provide, inject
5
+ from dependency_injector.wiring import Provide, inject # noqa: F401
6
6
  from mcp.server.fastmcp import FastMCP
7
7
 
8
- from {{ project.python_package }}.application.config.container import Container
8
+ from {{ project.python_package }}.application.config.container import Container # noqa: F401
9
9
 
10
10
 
11
11
  @inject
@@ -2,10 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from dependency_injector.wiring import Provide, inject
5
+ from dependency_injector.wiring import Provide, inject # noqa: F401
6
6
  from mcp.server.fastmcp import FastMCP
7
7
 
8
- from {{ project.python_package }}.application.config.container import Container
8
+ from {{ project.python_package }}.application.config.container import Container # noqa: F401
9
9
 
10
10
 
11
11
  @inject
@@ -6,8 +6,8 @@ import sys
6
6
 
7
7
  import uvicorn
8
8
 
9
- from {{ project.python_package }}.application.config.config import settings
10
9
  from {{ project.python_package }}.application import app
10
+ from {{ project.python_package }}.application.config.config import settings
11
11
 
12
12
 
13
13
  def main() -> None:
@@ -2,8 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import inspect
6
-
7
5
  from mcp.server.fastmcp import FastMCP
8
6
  from starlette.applications import Starlette
9
7
 
@@ -2,10 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from dependency_injector.wiring import Provide, inject
5
+ from dependency_injector.wiring import Provide, inject # noqa: F401
6
6
  from mcp.server.fastmcp import FastMCP
7
7
 
8
- from {{ project.python_package }}.application.config.container import Container
8
+ from {{ project.python_package }}.application.config.container import Container # noqa: F401
9
9
 
10
10
 
11
11
  @inject
@@ -9,8 +9,8 @@ from fastapi import FastAPI
9
9
  from fastapi.exceptions import RequestValidationError
10
10
  from starlette.exceptions import HTTPException
11
11
 
12
- from {{ project.python_package }}.application.config.container import Container
13
12
  from {{ project.python_package }}.application.config.config import Settings
13
+ from {{ project.python_package }}.application.config.container import Container
14
14
  from {{ project.python_package }}.infrastructure.entry_points.api.v1 import rest_controller
15
15
  from {{ project.python_package }}.infrastructure.entry_points.api.v1.exception_handler import (
16
16
  generic_exception_handler,
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  from fastapi import FastAPI
5
+ from fastapi.testclient import TestClient
5
6
 
6
7
  from {{ project.python_package }}.application.app import create_app
7
8
 
@@ -12,9 +13,8 @@ def test_create_app_returns_fastapi_instance() -> None:
12
13
 
13
14
 
14
15
  def test_create_app_has_router_included() -> None:
15
- app = create_app()
16
- routes = [r.path for r in app.routes] # type: ignore[attr-defined]
17
- assert any("/api" in p or "/health" in p for p in routes)
16
+ client = TestClient(create_app())
17
+ assert client.get("/v1/health").status_code == 200
18
18
 
19
19
 
20
20
  def test_create_app_registers_exception_handlers() -> None:
@@ -1,8 +1,8 @@
1
1
  """Root application DI container."""
2
2
  from dependency_injector import containers, providers
3
3
 
4
- from {{ python_package }}.application.config.resource_container import ResourceContainer
5
4
  from {{ python_package }}.application.config.driven_adapters_container import DAContainer
5
+ from {{ python_package }}.application.config.resource_container import ResourceContainer
6
6
  from {{ python_package }}.application.config.usecases_container import UseCaseContainer
7
7
 
8
8
 
@@ -1,5 +1,5 @@
1
1
  """Driven-adapters DI container."""
2
- from dependency_injector import containers, providers
2
+ from dependency_injector import containers, providers # noqa: F401
3
3
 
4
4
  # Import driven-adapter classes here to wire them in the DAContainer below.
5
5
  #{{ python_package }}.infrastructure.driven_adapters.my_repo.MyRepo
@@ -1,7 +1,7 @@
1
1
  """Resource DI container — wires infrastructure resources (DB pools, HTTP clients, etc.)."""
2
- from dependency_injector import containers, providers
2
+ from dependency_injector import containers, providers # noqa: F401
3
3
 
4
- from {{ python_package }}.application.config.config import settings
4
+ from {{ python_package }}.application.config.config import settings # noqa: F401
5
5
 
6
6
  # Import resource-level classes here to wire them in the ResourceContainer below.
7
7
  #{{ python_package }}.infrastructure.driven_adapters.my_repo.MyResource
@@ -4,16 +4,19 @@ version = "0.1.0"
4
4
  description = "Clean Architecture Python project"
5
5
  requires-python = ">=3.13"
6
6
  dependencies = [
7
- "dependency-injector==4.49.0",
8
- "pydantic-settings==2.13.1",
7
+ "dependency-injector==4.49.1",
8
+ "pydantic-settings==2.15.0",
9
9
  ]
10
10
 
11
11
  [dependency-groups]
12
- dev = [
13
- "pytest==9.0.3",
12
+ test = [
13
+ "pytest==9.1.1",
14
14
  "pytest-cov==7.1.0",
15
- "pytest-asyncio==1.3.0",
16
- "ruff==0.15.10",
15
+ "pytest-asyncio==1.4.0",
16
+ ]
17
+ dev = [
18
+ "ruff==0.16.8",
19
+ { include-group = "test" },
17
20
  ]
18
21
 
19
22
  [project.scripts]
@@ -34,7 +37,6 @@ target-version = "py313"
34
37
 
35
38
  [tool.ruff.lint]
36
39
  select = ["E", "F", "I", "UP", "ANN"]
37
- ignore = ["ANN101", "ANN102"]
38
40
 
39
41
  [tool.ruff.lint.per-file-ignores]
40
42
  "src/tests/**/*.py" = ["ANN"]
@@ -62,5 +64,10 @@ exclude_also = [
62
64
  ]
63
65
 
64
66
  [tool.pytest.ini_options]
65
- addopts = "--cov={{ python_package }} --cov-fail-under=80"
67
+ addopts = "--cov={{ python_package }} --cov-fail-under=80 --import-mode=importlib"
66
68
  testpaths = ["src/tests"]
69
+ asyncio_default_fixture_loop_scope = "function"
70
+ filterwarnings = [
71
+ "ignore:.*httpx.*:DeprecationWarning",
72
+ "ignore:.*BlockingPortal.*:DeprecationWarning",
73
+ ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: scaffold-ca-python
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Scaffolder to generate Clean Architecture apps
5
5
  Project-URL: Homepage, https://github.com/bancolombia/scaffold-clean-architecture-py
6
6
  Project-URL: Repository, https://github.com/bancolombia/scaffold-clean-architecture-py
@@ -22,11 +22,11 @@ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
22
22
  Classifier: Typing :: Typed
23
23
  Requires-Python: >=3.13
24
24
  Requires-Dist: jinja2~=3.1.6
25
- Requires-Dist: pydantic~=2.13.3
25
+ Requires-Dist: pydantic~=2.13.5
26
26
  Requires-Dist: pyyaml~=6.0.3
27
27
  Requires-Dist: rich~=15.0.0
28
28
  Requires-Dist: tomli-w~=1.2.0
29
- Requires-Dist: typer~=0.25.1
29
+ Requires-Dist: typer~=0.27.2
30
30
  Description-Content-Type: text/markdown
31
31
 
32
32
  # scaffold-ca-python
@@ -80,7 +80,7 @@ cd my-app
80
80
  3. Generate a domain model.
81
81
 
82
82
  ```bash
83
- scaffold gm --name Order
83
+ scaffold gm --name order
84
84
  ```
85
85
 
86
86
  4. Validate the project structure.
@@ -108,11 +108,11 @@ scaffold ca --name my-project --dry-run
108
108
  Scaffold a Pydantic v2 domain model and its test stub.
109
109
 
110
110
  ```bash
111
- scaffold gm --name Order
111
+ scaffold gm --name order
112
112
  ```
113
113
 
114
114
  ```bash
115
- scaffold gm --name Order --dry-run
115
+ scaffold gm --name order --dry-run
116
116
  ```
117
117
 
118
118
  ### scaffold guc
@@ -120,11 +120,11 @@ scaffold gm --name Order --dry-run
120
120
  Scaffold a use case in `domain/usecase/`.
121
121
 
122
122
  ```bash
123
- scaffold guc --name CreateOrder
123
+ scaffold guc --name create_order
124
124
  ```
125
125
 
126
126
  ```bash
127
- scaffold guc --name CreateOrder --dry-run
127
+ scaffold guc --name create_order --dry-run
128
128
  ```
129
129
 
130
130
  ### scaffold gda
@@ -136,7 +136,7 @@ scaffold gda --type rest-consumer
136
136
  ```
137
137
 
138
138
  ```bash
139
- scaffold gda --type generic --name MyAdapter --dry-run
139
+ scaffold gda --type generic --name my_adapter --dry-run
140
140
  ```
141
141
 
142
142
  ### scaffold gep
@@ -156,11 +156,11 @@ scaffold gep --type mcp --with-resources --with-prompts --dry-run
156
156
  Scaffold a helper utility module and its test stub.
157
157
 
158
158
  ```bash
159
- scaffold gh --name JsonParser
159
+ scaffold gh --name json_parser
160
160
  ```
161
161
 
162
162
  ```bash
163
- scaffold gh --name JsonParser --dry-run
163
+ scaffold gh --name json_parser --dry-run
164
164
  ```
165
165
 
166
166
  ### scaffold gpipe
@@ -192,11 +192,11 @@ scaffold vs --dry-run
192
192
  Delete a previously generated module and its mirrored test file.
193
193
 
194
194
  ```bash
195
- scaffold dm --name Order
195
+ scaffold dm --name order
196
196
  ```
197
197
 
198
198
  ```bash
199
- scaffold dm --name Order --dry-run
199
+ scaffold dm --name order --dry-run
200
200
  ```
201
201
 
202
202
  ### scaffold up
@@ -217,7 +217,7 @@ scaffold update-project --dry-run
217
217
  |----------------|---------|------|
218
218
  | Python | >=3.13 | Runtime |
219
219
  | uv | latest | Dependency management, build, and publish |
220
- | Typer | >=0.25.1 | CLI framework |
220
+ | Typer | >=0.27.2 | CLI framework |
221
221
  | Rich | >=15.0.0 | Terminal output formatting |
222
222
  | Jinja2 | >=3.1 | Template engine for code generation |
223
223
  | Pydantic v2 | >=2.0 | Runtime validation of template context |
@@ -1,34 +1,34 @@
1
1
  scaffold_ca_python/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  scaffold_ca_python/cli.py,sha256=A0h0pkoNPy3EE55lApnjRuevhWb_Aa-IB7jekaaiAcM,971
3
3
  scaffold_ca_python/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- scaffold_ca_python/commands/delete_module.py,sha256=yPG3tkjI-L2TIDnnN-elCnKRGyRBozLZUE_mMrjs7b0,7547
5
- scaffold_ca_python/commands/generate_driven_adapter.py,sha256=godgMYPvSKmZ4avfRRiOnf3Iw1uJmP7JMHc50Ivq8l4,6598
6
- scaffold_ca_python/commands/generate_entry_point.py,sha256=_IgzgE5nt1_zZ2P9ozmoJusNDFTBvnwqxQLzEeTVhA4,11862
7
- scaffold_ca_python/commands/generate_helper.py,sha256=-cca-l6JghmKptK4OKMPo2J99FYWvu7LT1i3PZRfnzs,4454
8
- scaffold_ca_python/commands/generate_model.py,sha256=WxFeOqOQR5cER1YJrndJSpZvRCy2VV4va3HvMPTiy-M,4532
4
+ scaffold_ca_python/commands/delete_module.py,sha256=DZa4rTVhPpt5-HV6dCZY-qWOV18K9qns6gKCsgjDepg,7124
5
+ scaffold_ca_python/commands/generate_driven_adapter.py,sha256=ckHdHVUV5T3SSvLwPp_jQYdp-Av9j2AV836So6tMODk,6938
6
+ scaffold_ca_python/commands/generate_entry_point.py,sha256=nSEz218Ar3WzkzOZOU_7DJ5KzEwGa7w6h9XVV2WQmOw,11943
7
+ scaffold_ca_python/commands/generate_helper.py,sha256=i5zaqGvTO6nEvVOGVdOsZoDpJf1cWX7GbGaNcEYLSWY,4470
8
+ scaffold_ca_python/commands/generate_model.py,sha256=I1dMNoVMNytdMZ5mzBAF1WzJDIHMacxwLOmhH29nTIc,4546
9
9
  scaffold_ca_python/commands/generate_pipeline.py,sha256=EYPM9csDnrYB3bpnC4YSycqI3bAE8bYy1283OEEC5Ts,5537
10
- scaffold_ca_python/commands/generate_project.py,sha256=axgvckaFSwX2NqOT-1vn4AXG5wnPMy-JcMpv3MwZ_Nw,6867
11
- scaffold_ca_python/commands/generate_use_case.py,sha256=dZnIqZqRo4-r1411neqIa5Mw-U879fITdaHawpddOCU,4512
10
+ scaffold_ca_python/commands/generate_project.py,sha256=6jzX5o1451YmWYFqJIhDVCPz8kh9vDBQ4ixDddkaLm8,6867
11
+ scaffold_ca_python/commands/generate_use_case.py,sha256=w6BUdF3uXeo3dNsLN76oSYvQu-5WOqvpjwlot29sA7U,4536
12
12
  scaffold_ca_python/commands/update_project.py,sha256=cW9IuAF1yIFkAkzOJdioFv97wr7mRiid08KkqOlFmrM,2845
13
13
  scaffold_ca_python/commands/validate_structure.py,sha256=JF6Cb3IQ9d6VcjaReSHHzoXGSmqyoyMWNFplqSCFCOQ,2846
14
14
  scaffold_ca_python/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  scaffold_ca_python/core/file_writer.py,sha256=DdgD6h4lx6FhKtc0AJvOIBryHgfTE4d4ZCMOfrEAuAI,5030
16
- scaffold_ca_python/core/module_builder.py,sha256=VyJQ2MmOkGivmNT7GuRC4LryhY64OGdLMGzxPVDBC3I,4607
17
- scaffold_ca_python/core/name_utils.py,sha256=74sxYhPpYrMPTarD0tjUbvUPO1PHw49DW9O5OtiTmj8,2204
16
+ scaffold_ca_python/core/module_builder.py,sha256=rcWSWxRX4bsaWLCtCHNTAcVFZiHABA5zTn9rilUojKE,5965
17
+ scaffold_ca_python/core/name_utils.py,sha256=OAMl4YrgvaZgulemcqYpUp8iHNvR8ieTrccNVc_zQB4,2765
18
18
  scaffold_ca_python/core/project_detector.py,sha256=G-n9domRQp65uzUbb7CXJk1nFMCTb347RMvfiVoYAxI,3321
19
- scaffold_ca_python/core/pyproject_writer.py,sha256=R4JNS-b5cx4B-BH90gR8rPoVm1OMQFgFk_rRa1OlgJ8,5267
19
+ scaffold_ca_python/core/pyproject_writer.py,sha256=zpxgubt-XSZR5domgL4LG_9AWD9b_cNrDM8fuHmZRzE,7792
20
20
  scaffold_ca_python/core/structure_validator.py,sha256=Cco8Ghpjyit5t2AKxuFpCmfwsG4D9yqz3adycrz7YR8,5841
21
21
  scaffold_ca_python/core/template_renderer.py,sha256=sMu6iwlObtIYOuRRsyNZu1J-M_jkzx5yHzBwlGZkHOI,3421
22
22
  scaffold_ca_python/factory/__init__.py,sha256=6MZYzBgYxvvJwjGrYSaW1E_fNFaFAQ0EHJKSKMo_DHQ,492
23
23
  scaffold_ca_python/factory/driven_adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  scaffold_ca_python/factory/driven_adapters/da_generic.py,sha256=iKsskAkBHEmutXb5QybF_3521PnMLYlv3cpEMHClptk,2412
25
- scaffold_ca_python/factory/driven_adapters/da_rest_consumer.py,sha256=2I5NiYPSvxkbsYOwDMRI1a81vS-kS_Vt6njjjdmRMrg,2282
26
- scaffold_ca_python/factory/driven_adapters/da_secrets.py,sha256=jeNpjkvLV34aW2uzXkPHk0bmQLLwHydPJfgcG7jSbpg,2234
25
+ scaffold_ca_python/factory/driven_adapters/da_rest_consumer.py,sha256=6ybnAAHeMMJMXRl2x7rqAS36GeZS-vEf8I4L2NMOAaU,2465
26
+ scaffold_ca_python/factory/driven_adapters/da_secrets.py,sha256=Qmhdt_DSfZXx4EY2MKSBhbAoyVAWHVftbgsN2okyvZA,2240
27
27
  scaffold_ca_python/factory/entry_points/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- scaffold_ca_python/factory/entry_points/ep_agent.py,sha256=25I2YB3IFWSE7n-svj0V39U0s3mC_7EqBh61brFeWEM,3029
28
+ scaffold_ca_python/factory/entry_points/ep_agent.py,sha256=EcJZnCVW2_I-Zgbdxu1t4mXPnKg1PSinBzKwXyHGHZQ,3033
29
29
  scaffold_ca_python/factory/entry_points/ep_generic.py,sha256=4CYtmv1svJ6zJgLTqF6awXUlhSSf7Mj2IMGyv8KjhbU,2494
30
- scaffold_ca_python/factory/entry_points/ep_mcp.py,sha256=52dHrjJnr9AkZXYRyAcjWLblCzgt8cT5Ulzs9KVTEck,5396
31
- scaffold_ca_python/factory/entry_points/ep_restapi.py,sha256=7EfmtF60Mn7_C9OUledFupSnhSuhaZVUBj8OWiyYpY8,4792
30
+ scaffold_ca_python/factory/entry_points/ep_mcp.py,sha256=-MbR5u5-ibi3ZrB4Ob9L7zmmdneMfNfzsSVNnO910kQ,5473
31
+ scaffold_ca_python/factory/entry_points/ep_restapi.py,sha256=pXea8u6eVodc5sZ_m1PyhM5_ylJ5I-1lyiYZEDuldjQ,4796
32
32
  scaffold_ca_python/factory/simple/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  scaffold_ca_python/factory/simple/delete_module_factory.py,sha256=omsMgjmHbWXwV1ASZU_OAtvy0qG5BPYQCg2BDg_bw9o,3237
34
34
  scaffold_ca_python/factory/simple/helper_factory.py,sha256=vN8z7-4IxeSvqRSEWWOreFnp7jQxFB10kztuHN0CCJs,2302
@@ -45,12 +45,12 @@ scaffold_ca_python/templates/driven_adapter/generic/adapter.py.jinja2,sha256=54K
45
45
  scaffold_ca_python/templates/driven_adapter/generic/test_adapter.py.jinja2,sha256=Q_eyB5KW6Q9m391TKEcdLgMpg_Rs_JeUG51HebX1Lrk,563
46
46
  scaffold_ca_python/templates/driven_adapter/rest_consumer/__init__.py.jinja2,sha256=bq5MGxcHGw7lPknR-uuq00xo_lodxJ4c-mHro88cYnE,44
47
47
  scaffold_ca_python/templates/driven_adapter/rest_consumer/rest_consumer.py.jinja2,sha256=cgXzrREAY-517wSnpYz-p8-0sK57yhR15dsCyIAw_lI,988
48
- scaffold_ca_python/templates/driven_adapter/rest_consumer/test_rest_consumer.py.jinja2,sha256=J2GHgt7r1RdQ5T06_PdWf2-pxNRDVFe9M48xkJLpJsc,637
48
+ scaffold_ca_python/templates/driven_adapter/rest_consumer/test_rest_consumer.py.jinja2,sha256=pCHbhfGM6-TK8D8tETiBhOS0TVfY2GRGHSrmPj-6Ygc,637
49
49
  scaffold_ca_python/templates/driven_adapter/secrets/__init__.py.jinja2,sha256=n_-tUwoq5VGFv_KxlbzO1TECmPnME1LaF3nVSBwlPl4,31
50
50
  scaffold_ca_python/templates/driven_adapter/secrets/secrets_adapter.py.jinja2,sha256=uy3bl5_vM_ZnAL4WSYbL9vhoXSSAJ0KdtkCQHRiKORE,1217
51
51
  scaffold_ca_python/templates/driven_adapter/secrets/test_secrets_adapter.py.jinja2,sha256=Tuy0-gVdN7aRVnTXGrdN-at5d2cLmelwJVcw_ypdxTI,688
52
52
  scaffold_ca_python/templates/entry_point/agent/__init__.py.jinja2,sha256=pbboGjTM3_gkQ9QXzM3QFHYsfMETHzkLAseIJa41GVM,33
53
- scaffold_ca_python/templates/entry_point/agent/agent.py.jinja2,sha256=QCskQeX70dZ-3puV0LsfeGZF95EmgXRU_ZMWGPYg7ck,1583
53
+ scaffold_ca_python/templates/entry_point/agent/agent.py.jinja2,sha256=wQpDne1xXaHxikCB9MVsVQisqQiAuEeUOMGZhhBb4TM,1588
54
54
  scaffold_ca_python/templates/entry_point/agent/card.py.jinja2,sha256=M8RbWj0c8B9bjBBmCgUdAdRPcUhEETc6BEFrB5ZeKCA,415
55
55
  scaffold_ca_python/templates/entry_point/agent/entrypoint_main.py.jinja2,sha256=igDSxs4vUXV3oGW10fawgM9dhwPgO5SjAeBmmK01ZuI,247
56
56
  scaffold_ca_python/templates/entry_point/agent/test_agent.py.jinja2,sha256=DDl55Pg-m3gI_nD8mbDHbtnFqaDExBNf9z1xOLGDS8Q,591
@@ -59,22 +59,22 @@ scaffold_ca_python/templates/entry_point/generic/entrypoint_main.py.jinja2,sha25
59
59
  scaffold_ca_python/templates/entry_point/generic/handler.py.jinja2,sha256=glCst1soTiKG_gDdq5C-OogzljOJ_QooaW_boYt7gsg,417
60
60
  scaffold_ca_python/templates/entry_point/generic/test_handler.py.jinja2,sha256=gRhvVabkylQ8z06JZ9L2CHdTa83oxL1X4ldR3_DZQWg,1195
61
61
  scaffold_ca_python/templates/entry_point/mcp/__init__.py.jinja2,sha256=8J_YmIXHUh26PkaxeM8z5lf-dtK0da1T4haOM8U6Y9A,38
62
- scaffold_ca_python/templates/entry_point/mcp/app.py.jinja2,sha256=f-8p3QqsuN4ywktwrlurDDQi_HfeNzfzMAJizhJ_dwc,1909
63
- scaffold_ca_python/templates/entry_point/mcp/prompts.py.jinja2,sha256=ianMpl8Tc1lzuR1vm8tG04OI9qL2uP-dqpAfYSpYer4,737
64
- scaffold_ca_python/templates/entry_point/mcp/resources.py.jinja2,sha256=_8-z7D-N8Cihvj6HB_i3_IfUqWP9laL1FheA1C7NMs8,812
65
- scaffold_ca_python/templates/entry_point/mcp/server.py.jinja2,sha256=Hmtx-jYIkXBSw8verVr6EOultnDXrCCG14hFQYYKg90,656
66
- scaffold_ca_python/templates/entry_point/mcp/test_app.py.jinja2,sha256=Q5wAzpSCnvQsOyWsLsM3s7fEAcq0kIEdyP1SFc75hQY,983
62
+ scaffold_ca_python/templates/entry_point/mcp/app.py.jinja2,sha256=VMU2AWXAVJVT0IqTI3E6XGBGn2rR80ViSN0jNyRf3E4,1916
63
+ scaffold_ca_python/templates/entry_point/mcp/prompts.py.jinja2,sha256=PUFRtuWlEzR7bg7zY0O8dx-5tNrEZXrNZ5XgBoUMulQ,765
64
+ scaffold_ca_python/templates/entry_point/mcp/resources.py.jinja2,sha256=ZV_2WwklhSGhVFel3stS00zmPJuTlznfvFrpiM9dDKo,840
65
+ scaffold_ca_python/templates/entry_point/mcp/server.py.jinja2,sha256=gVzJm_ugY9E7-1RYIfbEKduDR71gluq4l3OB_aTRr1w,656
66
+ scaffold_ca_python/templates/entry_point/mcp/test_app.py.jinja2,sha256=MjVDhAXTz35HcCOVTsaWA--qvD_Qyn2SNG99CF6wctk,967
67
67
  scaffold_ca_python/templates/entry_point/mcp/test_prompts.py.jinja2,sha256=Y9C7z7jpjoTKANlFB6hNJUUw2abt5qF2C4Nm7MkvDvA,1566
68
68
  scaffold_ca_python/templates/entry_point/mcp/test_resources.py.jinja2,sha256=2jHfPqyJ4TJZJupxe8T7gVz2ysaqZHcAog6186ie2CI,1763
69
69
  scaffold_ca_python/templates/entry_point/mcp/test_tools.py.jinja2,sha256=bd_8nvLuqPApKIYQoBhEokMYZrPYFUn1pnDEIOVxwCo,1550
70
- scaffold_ca_python/templates/entry_point/mcp/tools.py.jinja2,sha256=xstzbPwYjiLG6InhWjD7Xtteyf_jLbrgislelwC-wUM,725
70
+ scaffold_ca_python/templates/entry_point/mcp/tools.py.jinja2,sha256=Eqxa-pc_Mf4L0QvWMefKeg43bnjxwYuaNXRg6I9H1pQ,753
71
71
  scaffold_ca_python/templates/entry_point/restapi/__init__.py.jinja2,sha256=BrpLExG7nc6cEOYWpJKymUXEvGVc1lU9sb_kBw_HPYg,36
72
- scaffold_ca_python/templates/entry_point/restapi/app.py.jinja2,sha256=u5ltDQ_WQ-5JESV8nsV3_Nueb4ZZQJ5tn59ShGncYGo,2626
72
+ scaffold_ca_python/templates/entry_point/restapi/app.py.jinja2,sha256=HQQqi8ZiLTaNIt9g9_iJyjxAGt6KXj4lqGcJLI1Qx9s,2626
73
73
  scaffold_ca_python/templates/entry_point/restapi/exception_handler.py.jinja2,sha256=_wON4AvW6liZutgHx-TGWrBpJOC8q0ZY3mPhIS8Aveg,1343
74
74
  scaffold_ca_python/templates/entry_point/restapi/health.py.jinja2,sha256=nKEtxuoAQ4Le9SGhM3Wz9K77XcCu7wnvBgjhquGqr3E,244
75
75
  scaffold_ca_python/templates/entry_point/restapi/rest_controller.py.jinja2,sha256=rKsSdx0arV_K87HL-VRyI7HFpSD3k-jfK9zIG4A03xc,728
76
76
  scaffold_ca_python/templates/entry_point/restapi/server.py.jinja2,sha256=F6AYRMP3q02WMm09RWbX3tAEYG_6PkoDYI3c5A8AIdc,207
77
- scaffold_ca_python/templates/entry_point/restapi/test_app.py.jinja2,sha256=QSmEIIwVhyVi4uCNz3PDsEUu2aFNpbqm0-EP50JzH08,669
77
+ scaffold_ca_python/templates/entry_point/restapi/test_app.py.jinja2,sha256=YC2j7SI9eXsIx_ymNMHP1pqRpegWQ4Xc2MJrKwFcK7s,647
78
78
  scaffold_ca_python/templates/entry_point/restapi/test_exception_handler.py.jinja2,sha256=O7Sqf1d9mPTeB0DDXtnyCrbKE4o2zNsgYzf-PdD3rU4,1361
79
79
  scaffold_ca_python/templates/entry_point/restapi/test_rest_controller.py.jinja2,sha256=Un01qIR8L5bgLRKbChWptrbTCL4t45PAmrXwKMvKShQ,1142
80
80
  scaffold_ca_python/templates/entry_point/restapi/test_server.py.jinja2,sha256=7_mDfqP14onocc1RjJ732ODpoUAVPiB1Zsf5WEqAlaI,426
@@ -92,18 +92,18 @@ scaffold_ca_python/templates/project/gitignore.jinja2,sha256=ZRf2cRtqbe2d48RfWJW
92
92
  scaffold_ca_python/templates/project/layer_init.jinja2,sha256=oIlTSrce4ozqEKUU_1TB4473ufiE0SiGG_PJmEkT4FA,25
93
93
  scaffold_ca_python/templates/project/main.py.jinja2,sha256=di_ujhkFafXqJUymSdVjEF0tihQ_0xKbKSIyV91rPpk,161
94
94
  scaffold_ca_python/templates/project/mypy_ini.jinja2,sha256=SJ_UiLRZ8wBJ7hqSo3QDeMSsempBfaGvqiR_ASyIxR0,93
95
- scaffold_ca_python/templates/project/pyproject_toml.jinja2,sha256=dSKZYxUIhUBxvIrtX1y7dJPaYLzGfpR8Tvg3xYZnRz8,1374
95
+ scaffold_ca_python/templates/project/pyproject_toml.jinja2,sha256=O8G8S3f7rmRzQkjuRj2n_445bGD8fBDZu6S0QkwSzi0,1574
96
96
  scaffold_ca_python/templates/project/python_version.jinja2,sha256=Auc1s9_hwygz61ULf_j_oX9fK8P6HnuuYaj1o4g845g,5
97
97
  scaffold_ca_python/templates/project/application/config/__init__.py.jinja2,sha256=L9xWIW2Gd3VhRdx1RfUf7efBrIuFf3ft54eLQT1tG9g,32
98
98
  scaffold_ca_python/templates/project/application/config/config.py.jinja2,sha256=218sZQ1115odJcoI7W6-1kiFjkWjO56d80fqQXk4dyM,323
99
- scaffold_ca_python/templates/project/application/config/container.py.jinja2,sha256=zQmDuu9f8Z0hCbC6iu9hbzusam8pvDOhUprbF0Vhkxk,697
100
- scaffold_ca_python/templates/project/application/config/driven_adapters_container.py.jinja2,sha256=D4-i4Ym_y7RIc5QIdibBS9WiFSrGLFyD9tX7k_A8AAg,450
101
- scaffold_ca_python/templates/project/application/config/resource_container.py.jinja2,sha256=cfp6ZshJkgOkMRLkPGvDy0ACyEEqWbdxw7sOKwT54-E,615
99
+ scaffold_ca_python/templates/project/application/config/container.py.jinja2,sha256=zhGtAtqp6h73IpO-k6Bj2qgI7BmshfULDh9smBRr-D0,697
100
+ scaffold_ca_python/templates/project/application/config/driven_adapters_container.py.jinja2,sha256=WhRpupvLP8tOJgp4IJ0A5RitntfRPVFjCDUjhAhBt8U,464
101
+ scaffold_ca_python/templates/project/application/config/resource_container.py.jinja2,sha256=k3wy8uSD7GJZNEf379fv2lMEzdq5eaNqmoaOw9Jq1vs,643
102
102
  scaffold_ca_python/templates/project/application/config/usecases_container.py.jinja2,sha256=3fEHe6mkXkDHlfO5nhLqzLB7ccTpqwtjHZfLyfysbro,527
103
103
  scaffold_ca_python/templates/use_case/test_use_case.py.jinja2,sha256=3K1bNKDBQc29rcv8UHUZnROCUmcFSCwJhbm0xP9Uako,364
104
104
  scaffold_ca_python/templates/use_case/use_case.py.jinja2,sha256=KPtaK1wvZ-7qHdDZyuVfqXTPYwJdNUMPXQZGuKZFA7Y,217
105
- scaffold_ca_python-0.1.3.dist-info/METADATA,sha256=TQZQjlRUV-92ifKHrTLAYX_1cOUJGAkUcKD-pB2L4QM,6798
106
- scaffold_ca_python-0.1.3.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
107
- scaffold_ca_python-0.1.3.dist-info/entry_points.txt,sha256=4PyYg6mNCvAlt2yF_o1-QR2CBXiowYGpNyqcP88tu6I,104
108
- scaffold_ca_python-0.1.3.dist-info/licenses/LICENSE,sha256=3ZZEP7DK_h3lEjS8r-DvQCec2q7zAqqq7HmSq6cuL0E,1068
109
- scaffold_ca_python-0.1.3.dist-info/RECORD,,
105
+ scaffold_ca_python-0.1.4.dist-info/METADATA,sha256=Uk7rsmYTy1wNQRZZVXquueaB2t-KUqrLpFJx52hIvLM,6803
106
+ scaffold_ca_python-0.1.4.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
107
+ scaffold_ca_python-0.1.4.dist-info/entry_points.txt,sha256=4PyYg6mNCvAlt2yF_o1-QR2CBXiowYGpNyqcP88tu6I,104
108
+ scaffold_ca_python-0.1.4.dist-info/licenses/LICENSE,sha256=3ZZEP7DK_h3lEjS8r-DvQCec2q7zAqqq7HmSq6cuL0E,1068
109
+ scaffold_ca_python-0.1.4.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.32.0
2
+ Generator: hatchling 1.32.4
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any