scaffold-ca-python 0.3.0__py3-none-any.whl → 0.4.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.
@@ -10,43 +10,53 @@ from scaffold_ca_python.commands._shared import (
10
10
  build_and_persist,
11
11
  console,
12
12
  ensure_absent,
13
+ fail,
13
14
  print_dry_run_tree,
14
15
  require_project_root,
15
16
  validate_name_or_exit,
16
17
  )
17
18
  from scaffold_ca_python.core.module_builder import ModuleBuilder
18
- from scaffold_ca_python.core.name_utils import to_snake_case, validate_module_name
19
- from scaffold_ca_python.core.project_detector import load_project_context
19
+ from scaffold_ca_python.core.name_utils import strip_usecase_suffix, to_snake_case, validate_module_name
20
+ from scaffold_ca_python.core.project_detector import load_project_context, resolve_tests_root
20
21
  from scaffold_ca_python.factory.simple.use_case_factory import UseCaseFactory
21
22
  from scaffold_ca_python.models.context import ModuleContext
22
23
  from scaffold_ca_python.models.layer import Layer
23
24
 
24
25
  _GUC_HELP = "Scaffold a use case in domain/usecase/. Alias 'guc'."
25
26
  _GUC_EPILOG = (
26
- "Example:\n\n * scaffold guc --name create_order\n\n * scaffold generate-use-case --name create_order\n\n"
27
+ "Example:\n\n * scaffold guc --name create_order (creates domain/usecase/create_order_usecase.py)\n\n"
28
+ " * scaffold generate-use-case --name create_order\n\n"
27
29
  )
28
30
 
29
31
 
30
32
  def _generate_use_case_impl(name: str, dry_run: bool) -> None:
31
33
  validate_name_or_exit(name, validate_module_name)
34
+ base = strip_usecase_suffix(to_snake_case(name))
35
+ if not base:
36
+ fail(
37
+ f"Use case name '{name}' is only the 'usecase' suffix.",
38
+ hint="Use a descriptive name such as 'create_order'.",
39
+ )
32
40
  project_root = require_project_root()
33
41
  project_ctx = load_project_context(project_root)
34
- module_ctx = ModuleContext(name=name, layer=Layer.DOMAIN_USECASE, project=project_ctx)
42
+ module_ctx = ModuleContext(name=base, layer=Layer.DOMAIN_USECASE, project=project_ctx)
35
43
 
36
- snake = to_snake_case(name)
37
- src_path = project_root / "src" / project_ctx.python_package / "domain" / "usecase" / f"{snake}.py"
38
- ensure_absent(
39
- src_path,
40
- project_root,
41
- kind="File",
42
- hint="Choose a different name or remove the existing file first.",
43
- )
44
+ module = f"{base}_usecase"
45
+ src_path = project_root / "src" / project_ctx.python_package / "domain" / "usecase" / f"{module}.py"
46
+ test_path = resolve_tests_root(project_root) / "domain" / "usecase" / f"test_{module}.py"
47
+ for path in (src_path, test_path):
48
+ ensure_absent(
49
+ path,
50
+ project_root,
51
+ kind="File",
52
+ hint="Choose a different name or remove the existing file first.",
53
+ )
44
54
 
45
55
  builder = ModuleBuilder(project_root=project_root, project_ctx=project_ctx, module_ctx=module_ctx, dry_run=dry_run)
46
56
  created = build_and_persist(UseCaseFactory(), builder)
47
57
 
48
58
  if dry_run:
49
- print_dry_run_tree(snake, created, project_root)
59
+ print_dry_run_tree(module, created, project_root)
50
60
  return
51
61
 
52
62
  msg = (
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from pathlib import Path
6
6
 
7
+ from scaffold_ca_python.core.name_utils import strip_usecase_suffix
7
8
  from scaffold_ca_python.core.project_detector import resolve_tests_root
8
9
 
9
10
 
@@ -11,7 +12,7 @@ def find_module_targets(project_root: Path, pkg: str, snake: str) -> list[tuple[
11
12
  """Return ``(src_path, test_path | None)`` pairs for every match of *snake*.
12
13
 
13
14
  Searches in order: domain/model/<snake>/ (package), domain/model/<snake>.py (legacy flat),
14
- domain/usecase, driven_adapters,
15
+ domain/usecase (``<base>_usecase.py`` first, then legacy ``<snake>.py``), driven_adapters,
15
16
  entry_points, helpers.
16
17
  """
17
18
  src_root = project_root / "src" / pkg
@@ -24,9 +25,25 @@ def find_module_targets(project_root: Path, pkg: str, snake: str) -> list[tuple[
24
25
  model_pkg_test = tests_root / "domain" / "model" / snake
25
26
  candidates.append((model_pkg, model_pkg_test if model_pkg_test.exists() else None))
26
27
 
28
+ usecase_pairs: list[tuple[Path, Path]] = []
29
+ base = strip_usecase_suffix(snake)
30
+ if base:
31
+ usecase_pairs.append(
32
+ (
33
+ src_root / "domain" / "usecase" / f"{base}_usecase.py",
34
+ tests_root / "domain" / "usecase" / f"test_{base}_usecase.py",
35
+ )
36
+ )
37
+ legacy_usecase = (
38
+ src_root / "domain" / "usecase" / f"{snake}.py",
39
+ tests_root / "domain" / "usecase" / f"test_{snake}.py",
40
+ )
41
+ if legacy_usecase not in usecase_pairs:
42
+ usecase_pairs.append(legacy_usecase)
43
+
27
44
  for src_rel, test_rel in [
28
45
  (src_root / "domain" / "model" / f"{snake}.py", tests_root / "domain" / "model" / f"test_{snake}.py"),
29
- (src_root / "domain" / "usecase" / f"{snake}.py", tests_root / "domain" / "usecase" / f"test_{snake}.py"),
46
+ *usecase_pairs,
30
47
  ]:
31
48
  if src_rel.exists():
32
49
  candidates.append((src_rel, test_rel if test_rel.exists() else None))
@@ -9,6 +9,9 @@ NAME_RE = re.compile(r"^[a-z][a-z0-9]*([_-][a-z0-9]+)*$")
9
9
  # Stricter pattern for module/class-generating commands: snake_case only (no hyphens).
10
10
  MODULE_NAME_RE = re.compile(r"^[a-z][a-z0-9]*(_[a-z0-9]+)*$")
11
11
 
12
+ # Trailing use case suffixes (applied to "_" + name so a bare "usecase" also matches)
13
+ _USECASE_SUFFIX_RE = re.compile(r"(?:_(?:usecase|use_case))+$")
14
+
12
15
  # Matches transitions: lowercase→uppercase and letter→digit boundary runs
13
16
  _CAMEL_BOUNDARY_RE = re.compile(r"(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])")
14
17
 
@@ -50,6 +53,11 @@ def to_snake_case(name: str) -> str:
50
53
  return s.lower()
51
54
 
52
55
 
56
+ def strip_usecase_suffix(name: str) -> str:
57
+ """Remove every trailing ``_usecase`` / ``_use_case`` suffix from *name* (may return an empty string)."""
58
+ return _USECASE_SUFFIX_RE.sub("", f"_{name}").removeprefix("_")
59
+
60
+
53
61
  def to_kebab_case(name: str) -> str:
54
62
  """Convert *name* (PascalCase, snake_case, or already kebab-case) to kebab-case."""
55
63
  # Normalise underscores to hyphens first (snake_case support)
@@ -22,7 +22,7 @@ class DeleteModuleFactory(ModuleFactory):
22
22
 
23
23
  Finds and deletes modules across:
24
24
  - domain/model/<snake>/ (package) or domain/model/<snake>.py (legacy)
25
- - domain/usecase/<snake>.py
25
+ - domain/usecase/<base>_usecase.py or domain/usecase/<snake>.py (legacy)
26
26
  - infrastructure/driven_adapters/<snake>/
27
27
  - infrastructure/entry_points/<snake>/
28
28
  - infrastructure/helpers/<snake>/
@@ -19,8 +19,8 @@ class UseCaseFactory(ModuleFactory):
19
19
  """Build domain use case files.
20
20
 
21
21
  Queues files:
22
- - src/domain/usecase/{snake_name}.py
23
- - tests/domain/usecase/test_{snake_name}.py
22
+ - src/domain/usecase/{snake_name}_usecase.py
23
+ - tests/domain/usecase/test_{snake_name}_usecase.py
24
24
 
25
25
  No dependencies injected (async/await are built-in).
26
26
  """
@@ -45,14 +45,14 @@ class UseCaseFactory(ModuleFactory):
45
45
 
46
46
  # Add source file
47
47
  builder.add_file(
48
- src_dir / f"{snake_name}.py",
48
+ src_dir / f"{snake_name}_usecase.py",
49
49
  builder.render(f"{base}/use_case.py.jinja2", ctx_dict),
50
50
  template_name=f"{base}/use_case.py.jinja2",
51
51
  )
52
52
 
53
53
  # Add test file
54
54
  builder.add_file(
55
- test_dir / f"test_{snake_name}.py",
55
+ test_dir / f"test_{snake_name}_usecase.py",
56
56
  builder.render(f"{base}/test_use_case.py.jinja2", ctx_dict),
57
57
  template_name=f"{base}/test_use_case.py.jinja2",
58
58
  is_test=True,
@@ -2,7 +2,7 @@
2
2
 
3
3
  import pytest
4
4
 
5
- from {{ project.python_package }}.domain.usecase.{{ module_name }} import {{ class_name }}UseCase
5
+ from {{ project.python_package }}.domain.usecase.{{ module_name }}_usecase import {{ class_name }}UseCase
6
6
 
7
7
 
8
8
  @pytest.mark.asyncio
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: scaffold-ca-python
3
- Version: 0.3.0
3
+ Version: 0.4.0
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
@@ -128,7 +128,7 @@ scaffold gm --name order --dry-run
128
128
 
129
129
  ### scaffold guc
130
130
 
131
- Scaffold a use case in `domain/usecase/`.
131
+ Scaffold a use case in `domain/usecase/`. The module is always named `<name>_usecase.py` and the class `<Name>UseCase`. Writing `_usecase` in `--name` is optional and the suffix is never duplicated: `create_order`, `create_order_usecase` and `create_order_use_case` all create `create_order_usecase.py` with `CreateOrderUseCase`.
132
132
 
133
133
  ```bash
134
134
  scaffold guc --name create_order
@@ -11,15 +11,15 @@ scaffold_ca_python/commands/generate_helper.py,sha256=O98ua5Fj3judGxF_vmTYbm_j-q
11
11
  scaffold_ca_python/commands/generate_model.py,sha256=GBHPkoTYrABNdmdILWGLuQFM4ppzYbuHyunjhc-o4Dc,3387
12
12
  scaffold_ca_python/commands/generate_pipeline.py,sha256=Ip-rN4nSbwaUbtJ49awAs4LDURsrDTQsZdG5kBCLdyE,3773
13
13
  scaffold_ca_python/commands/generate_project.py,sha256=5KRsPI_WiWQVY1d_0AblUHKdDQCWpXJA7MaOCHae0fE,5312
14
- scaffold_ca_python/commands/generate_use_case.py,sha256=N72h2BAyNgbCLiphryrwF1aDXkDL5NNj14Lsq7D3YFE,2947
14
+ scaffold_ca_python/commands/generate_use_case.py,sha256=3uKYZik9eF23Z9QMM2psGNWuW2AhxkB468urum5N8WA,3441
15
15
  scaffold_ca_python/commands/update_project.py,sha256=7bu2neRasxEtYK2rIvx0CFISSx9D64BNCkijp90zWGk,2367
16
16
  scaffold_ca_python/commands/validate_structure.py,sha256=p_bYTgOl4_TrC80z-v2teSsssc9xghlkCYlUp3SuzHo,2526
17
17
  scaffold_ca_python/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
18
  scaffold_ca_python/core/file_writer.py,sha256=KdnNrq33Jsvqs1IQf0jbUKdXls4jBSWAizw4uJhMalU,5561
19
19
  scaffold_ca_python/core/mcp_tool_spec.py,sha256=mPLN7vOSgWFWxHQQxmr0fWEaimHWI-9A0oP7ob73-rU,10602
20
20
  scaffold_ca_python/core/module_builder.py,sha256=AHC3szEdfS3i3q74IM6USZTHWEUK6QizL6NEKcHCrrI,6837
21
- scaffold_ca_python/core/module_locator.py,sha256=VnvD3-g6NvSGJReYamBuoU1s72NdoHgau-SlpaB3xEg,1958
22
- scaffold_ca_python/core/name_utils.py,sha256=195Qb36CafP4CGRomzSAm3Hmm0C-Qld3I90boPAV-7A,2583
21
+ scaffold_ca_python/core/module_locator.py,sha256=FFAwA7Vajxg0JgHoVXc1HpvdB67clvkv_BFVwiQDxHA,2552
22
+ scaffold_ca_python/core/name_utils.py,sha256=IpXAieBKFe7QbOY4K8gNsjr4NXUfJX5MBCy2bQyPIk4,2958
23
23
  scaffold_ca_python/core/openapi_routes.py,sha256=RCQS7mHufWJZgbLI1cxGli-xGbU7p7WI95fHJhDnIss,1098
24
24
  scaffold_ca_python/core/project_detector.py,sha256=vcDAFCGgQVDMQ2MivJVUmn5Yyj58vGpTbzKhZTBQNtw,3524
25
25
  scaffold_ca_python/core/pyproject_writer.py,sha256=zpxgubt-XSZR5domgL4LG_9AWD9b_cNrDM8fuHmZRzE,7792
@@ -40,10 +40,10 @@ scaffold_ca_python/factory/entry_points/ep_restapi.py,sha256=PFkt9TH1aN6YO8w4zAe
40
40
  scaffold_ca_python/factory/pipelines/__init__.py,sha256=0YPpPGsZ6A3-udHTSZDyUHleLFLQQh1V5IL6AB0qJ6g,557
41
41
  scaffold_ca_python/factory/pipelines/pipeline_factory.py,sha256=s4fpuyhdxXiwko-LlM6Pv-QJdRGQJb17KVrC-DW6ojo,842
42
42
  scaffold_ca_python/factory/simple/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
- scaffold_ca_python/factory/simple/delete_module_factory.py,sha256=hvDZNz8TULYx0zES3697-jnrzDQT6v6nxqPV0H9-mWo,1651
43
+ scaffold_ca_python/factory/simple/delete_module_factory.py,sha256=v_Kl-m7bxvCwUsdQuCPq66uPksuLFLYvbsTNdE4Cs8Q,1696
44
44
  scaffold_ca_python/factory/simple/helper_factory.py,sha256=nBVOu_kGBVrEGnXFa918GPo-UFlcW-Y5-Uk6bwYWuhQ,2295
45
45
  scaffold_ca_python/factory/simple/model_factory.py,sha256=YdUpBvGgUrz4zzXMG2p8d6PWit62B9esjLZG49dT5PU,2467
46
- scaffold_ca_python/factory/simple/use_case_factory.py,sha256=znCyynIGmWgc4P8icJXfb2xSiNZB2Cu4SGVacDrpSdY,1954
46
+ scaffold_ca_python/factory/simple/use_case_factory.py,sha256=CUY2SkneFWv-KukK-Cdn2KoLZwgYukNa25Hv15SQYx8,1986
47
47
  scaffold_ca_python/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
48
  scaffold_ca_python/models/context.py,sha256=Itp-YyPgY-0yNYA9YlCXrmPfa5nEVklGAOJQGHfBdHE,1909
49
49
  scaffold_ca_python/models/file_operation.py,sha256=R5Tu3KBvUnSmH5QZXBQTPoQjvr4YdH1YOKsKS9ICH_Q,1698
@@ -119,10 +119,10 @@ scaffold_ca_python/templates/project/application/config/container.py.jinja2,sha2
119
119
  scaffold_ca_python/templates/project/application/config/driven_adapters_container.py.jinja2,sha256=WhRpupvLP8tOJgp4IJ0A5RitntfRPVFjCDUjhAhBt8U,464
120
120
  scaffold_ca_python/templates/project/application/config/resource_container.py.jinja2,sha256=k3wy8uSD7GJZNEf379fv2lMEzdq5eaNqmoaOw9Jq1vs,643
121
121
  scaffold_ca_python/templates/project/application/config/usecases_container.py.jinja2,sha256=3fEHe6mkXkDHlfO5nhLqzLB7ccTpqwtjHZfLyfysbro,527
122
- scaffold_ca_python/templates/use_case/test_use_case.py.jinja2,sha256=3K1bNKDBQc29rcv8UHUZnROCUmcFSCwJhbm0xP9Uako,364
122
+ scaffold_ca_python/templates/use_case/test_use_case.py.jinja2,sha256=spomnRHeXWiWjy3QHQt839yIMFDUqA7h-Wq4KfZxAyg,372
123
123
  scaffold_ca_python/templates/use_case/use_case.py.jinja2,sha256=KPtaK1wvZ-7qHdDZyuVfqXTPYwJdNUMPXQZGuKZFA7Y,217
124
- scaffold_ca_python-0.3.0.dist-info/METADATA,sha256=u5KDkAfy1Wb0HVt1Jrb2MzZr2Nqgb9xnwOZhhmmwVKw,7187
125
- scaffold_ca_python-0.3.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
126
- scaffold_ca_python-0.3.0.dist-info/entry_points.txt,sha256=4PyYg6mNCvAlt2yF_o1-QR2CBXiowYGpNyqcP88tu6I,104
127
- scaffold_ca_python-0.3.0.dist-info/licenses/LICENSE,sha256=3ZZEP7DK_h3lEjS8r-DvQCec2q7zAqqq7HmSq6cuL0E,1068
128
- scaffold_ca_python-0.3.0.dist-info/RECORD,,
124
+ scaffold_ca_python-0.4.0.dist-info/METADATA,sha256=7LQgO7e31QQ8vmmP1PfqwXgaWnQRrLIIpengbcw8mf4,7475
125
+ scaffold_ca_python-0.4.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
126
+ scaffold_ca_python-0.4.0.dist-info/entry_points.txt,sha256=4PyYg6mNCvAlt2yF_o1-QR2CBXiowYGpNyqcP88tu6I,104
127
+ scaffold_ca_python-0.4.0.dist-info/licenses/LICENSE,sha256=3ZZEP7DK_h3lEjS8r-DvQCec2q7zAqqq7HmSq6cuL0E,1068
128
+ scaffold_ca_python-0.4.0.dist-info/RECORD,,