csrd-utils 0.3.36__tar.gz → 0.3.38__tar.gz

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 (89) hide show
  1. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/PKG-INFO +1 -1
  2. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/pyproject.toml +1 -1
  3. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/__main__.py +13 -7
  4. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/augmentor.py +41 -8
  5. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/doctor.py +31 -6
  6. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/generator.py +1 -1
  7. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/templates/cookiecutter-service/hooks/post_gen_project.py +12 -12
  8. csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/.cookiecutterignore +1 -0
  9. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}}/Dockerfile +2 -2
  10. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}}/README.md +1 -2
  11. csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/.env.example +0 -44
  12. csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/.gitignore +0 -64
  13. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/.gitignore +0 -0
  14. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/README.md +0 -0
  15. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/__init__.py +0 -0
  16. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/audit.py +0 -0
  17. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/cluster.py +0 -0
  18. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/caching/conftest.fragment.py +0 -0
  19. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/caching/docker-compose.fragment.yaml +0 -0
  20. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/caching/manifest.yaml +0 -0
  21. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/caching/requirements.fragment +0 -0
  22. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/caching/settings.fragment.py +0 -0
  23. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/caching/src/app/cache.py +0 -0
  24. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/caching/tests/test_cache.py +0 -0
  25. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/logging/conftest.fragment.py +0 -0
  26. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/logging/manifest.yaml +0 -0
  27. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/logging/requirements.fragment +0 -0
  28. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/logging/settings.fragment.py +0 -0
  29. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/logging/src/app/middleware/logging.py +0 -0
  30. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/logging/tests/test_logging_middleware.py +0 -0
  31. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/metrics/conftest.fragment.py +0 -0
  32. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/metrics/docker-compose.fragment.yaml +0 -0
  33. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/metrics/manifest.yaml +0 -0
  34. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/metrics/requirements.fragment +0 -0
  35. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/metrics/settings.fragment.py +0 -0
  36. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/metrics/src/app/middleware/metrics.py +0 -0
  37. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/metrics/tests/test_metrics_middleware.py +0 -0
  38. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/tracing/conftest.fragment.py +0 -0
  39. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/tracing/manifest.yaml +0 -0
  40. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/tracing/requirements.fragment +0 -0
  41. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/tracing/settings.fragment.py +0 -0
  42. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/tracing/src/app/middleware/tracing.py +0 -0
  43. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/tracing/tests/test_tracing_middleware.py +0 -0
  44. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/workers/conftest.fragment.py +0 -0
  45. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/workers/docker-compose.fragment.yaml +0 -0
  46. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/workers/manifest.yaml +0 -0
  47. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/workers/requirements.fragment +0 -0
  48. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/workers/settings.fragment.py +0 -0
  49. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/workers/src/app/workers/__init__.py +0 -0
  50. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/workers/src/app/workers/celery_app.py +0 -0
  51. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/workers/src/app/workers/tasks.py +0 -0
  52. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/features/workers/tests/test_workers.py +0 -0
  53. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/fragments.py +0 -0
  54. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/resources.py +0 -0
  55. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/templates/cookiecutter-service/README.md +0 -0
  56. {csrd_utils-0.3.36 → csrd_utils-0.3.38}/src/csrd_utils/templates/cookiecutter-service/cookiecutter.json +0 -0
  57. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}}/.dockerignore +0 -0
  58. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}}/docker-compose.yml +0 -0
  59. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}}/pyproject.toml +0 -0
  60. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}}/requirements.txt +0 -0
  61. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
  62. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/cache.py +0 -0
  63. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/delegates/__init__.py +0 -0
  64. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/delegates/upstreams.py +0 -0
  65. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
  66. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/dependencies/delegates.py +0 -0
  67. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/dependencies/item_repository.py +0 -0
  68. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/dependencies/worker_broker.py +0 -0
  69. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/middleware/__init__.py +0 -0
  70. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/middleware/logging.py +0 -0
  71. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/py.typed +0 -0
  72. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
  73. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/repositories/item_repository.py +0 -0
  74. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
  75. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
  76. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
  77. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
  78. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/views/unversioned/items_view.py +0 -0
  79. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/workers/__init__.py +0 -0
  80. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/src}/{{cookiecutter.__service_name_snake}}/workers/tasks.py +0 -0
  81. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}}/tests/__init__.py +0 -0
  82. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}}/tests/acceptance/test_logging_middleware.py +0 -0
  83. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}}/tests/conftest.py +0 -0
  84. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}}/tests/test_items.py +0 -0
  85. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}}/tests/unit/test_cache.py +0 -0
  86. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}}/tests/unit/test_database_settings.py +0 -0
  87. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}}/tests/unit/test_delegates.py +0 -0
  88. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}}/tests/unit/test_settings_contracts.py +0 -0
  89. {csrd_utils-0.3.36/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}} → csrd_utils-0.3.38/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}}/tests/unit/test_worker_broker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csrd-utils
3
- Version: 0.3.36
3
+ Version: 0.3.38
4
4
  Summary: CLI utilities for csrd service generation and feature augmentation
5
5
  Project-URL: Repository, https://github.com/csrd-api/fastapi-common
6
6
  Project-URL: Documentation, https://github.com/csrd-api/fastapi-common/tree/main/packages/utils
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "csrd-utils"
3
- version = "0.3.36"
3
+ version = "0.3.38"
4
4
  description = "CLI utilities for csrd service generation and feature augmentation"
5
5
  license = { text = "MIT" }
6
6
  requires-python = ">=3.12"
@@ -580,6 +580,16 @@ def _append_service_to_workspace_cluster(
580
580
  return normalized
581
581
 
582
582
 
583
+ def _is_service_root(path: Path) -> bool:
584
+ """Return True if path looks like a csrd service (flat layout)."""
585
+ return (
586
+ path.is_dir()
587
+ and (path / "requirements.txt").is_file()
588
+ and (path / "tests").is_dir()
589
+ and (path / "pyproject.toml").is_file()
590
+ )
591
+
592
+
583
593
  def _workspace_service_roots(workspace_root: Path) -> list[Path]:
584
594
  roots: set[Path] = set()
585
595
 
@@ -591,11 +601,7 @@ def _workspace_service_roots(workspace_root: Path) -> list[Path]:
591
601
  continue
592
602
  if skip_names and child.name in skip_names:
593
603
  continue
594
- if (
595
- (child / "src" / "app").is_dir()
596
- and (child / "tests").is_dir()
597
- and (child / "requirements.txt").is_file()
598
- ):
604
+ if _is_service_root(child):
599
605
  roots.add(child.resolve())
600
606
 
601
607
  services_root = _workspace_services_dir(workspace_root)
@@ -632,12 +638,12 @@ def _workspace_root_for_service(service_root: Path) -> Path | None:
632
638
  def _validate_service_root(service_root: Path) -> None:
633
639
  if not service_root.is_dir():
634
640
  raise ValueError(f"Service path is not a directory: {service_root}")
635
- if not (service_root / "src" / "app").is_dir():
636
- raise ValueError(f"Missing required directory: {service_root / 'src' / 'app'}")
637
641
  if not (service_root / "tests").is_dir():
638
642
  raise ValueError(f"Missing required directory: {service_root / 'tests'}")
639
643
  if not (service_root / "requirements.txt").is_file():
640
644
  raise ValueError(f"Missing required file: {service_root / 'requirements.txt'}")
645
+ if not (service_root / "pyproject.toml").is_file():
646
+ raise ValueError(f"Missing required file: {service_root / 'pyproject.toml'}")
641
647
 
642
648
 
643
649
  def _collect_workspace_extract_config(args: argparse.Namespace) -> ExtractWorkspaceServiceArgs:
@@ -37,6 +37,35 @@ class ServiceAugmentor:
37
37
  self.applied_changes: list[str] = []
38
38
  self.backups: dict[str, str] = {}
39
39
 
40
+ @property
41
+ def _pkg_dir(self) -> Path:
42
+ """Return the service package directory — src/ layout first, then flat."""
43
+ src_dir = self.service_root / "src"
44
+ if src_dir.is_dir():
45
+ candidate = next(
46
+ (
47
+ p
48
+ for p in src_dir.iterdir()
49
+ if p.is_dir() and not p.name.startswith(".") and (p / "__init__.py").is_file()
50
+ ),
51
+ None,
52
+ )
53
+ if candidate is not None:
54
+ return candidate
55
+ # Flat layout fallback
56
+ candidate = next(
57
+ (
58
+ p
59
+ for p in self.service_root.iterdir()
60
+ if p.is_dir()
61
+ and not p.name.startswith(".")
62
+ and p.name not in {"tests", "src"}
63
+ and (p / "__init__.py").is_file()
64
+ ),
65
+ None,
66
+ )
67
+ return candidate if candidate is not None else self.service_root / "src" / "app"
68
+
40
69
  def load_feature(self, feature_name: str) -> bool:
41
70
  """
42
71
  Load and validate feature manifest.
@@ -72,14 +101,14 @@ class ServiceAugmentor:
72
101
  """
73
102
  Validate that service has required structure.
74
103
 
75
- Checks for src/app/, docker-compose.yml, etc.
104
+ Checks for <pkg>/, docker-compose.yml, etc.
76
105
 
77
106
  Returns
78
107
  -------
79
108
  bool
80
109
  True if valid
81
110
  """
82
- required_dirs = ["src/app", "tests"]
111
+ required_dirs = [str(self._pkg_dir.relative_to(self.service_root)), "tests"]
83
112
  required_files = ["requirements.txt"]
84
113
 
85
114
  for d in required_dirs:
@@ -245,7 +274,7 @@ class ServiceAugmentor:
245
274
  return True # Not required
246
275
 
247
276
  try:
248
- settings_path = self.service_root / "src/app/settings.py"
277
+ settings_path = self._pkg_dir / "settings.py"
249
278
  if not settings_path.exists():
250
279
  print("⚠ settings.py not found, skipping")
251
280
  return True
@@ -330,12 +359,11 @@ class ServiceAugmentor:
330
359
  return True
331
360
 
332
361
  try:
333
- # Copy entire tree
362
+ # Copy entire tree into the service package directory
334
363
  for src_file in feature_dir.rglob("*"):
335
364
  if src_file.is_file():
336
- # Map to destination
337
365
  rel_path = src_file.relative_to(feature_dir)
338
- dest_file = self.service_root / "src" / "app" / rel_path
366
+ dest_file = self._pkg_dir / rel_path
339
367
 
340
368
  # Create parent dirs
341
369
  dest_file.parent.mkdir(parents=True, exist_ok=True)
@@ -417,8 +445,13 @@ class ServiceAugmentor:
417
445
  # Step 2.5: Check if feature already applied (idempotency)
418
446
  creates = self.manifest.get("creates", [])
419
447
  if creates:
420
- first_created_file = Path(creates[0])
421
- full_path = self.service_root / first_created_file
448
+ first_created_path = Path(creates[0])
449
+ # Feature manifests use src/app/ prefix — remap to flat pkg layout
450
+ try:
451
+ rel = first_created_path.relative_to("src/app")
452
+ full_path = self._pkg_dir / rel
453
+ except ValueError:
454
+ full_path = self.service_root / first_created_path
422
455
  if full_path.exists():
423
456
  print(f"✅ Feature '{feature_name}' already applied (detected {creates[0]})")
424
457
  return True, []
@@ -19,8 +19,8 @@ def run_doctor(service_root: Path) -> DoctorReport:
19
19
  errors: list[str] = []
20
20
  warnings: list[str] = []
21
21
 
22
- required_dirs = ["src/app", "tests"]
23
- required_files = ["requirements.txt"]
22
+ required_dirs = ["tests"]
23
+ required_files = ["requirements.txt", "pyproject.toml"]
24
24
 
25
25
  compose_candidates = [root / "docker-compose.yml"]
26
26
  if root.parent.name == "src" and (root.parent.parent / ".csrd-workspace").is_file():
@@ -37,10 +37,35 @@ def run_doctor(service_root: Path) -> DoctorReport:
37
37
  if not any(path.is_file() for path in compose_candidates):
38
38
  errors.append("Missing required file: docker-compose.yml")
39
39
 
40
- marker_files = {
41
- "src/app/settings.py": "# [INSERT: fields]",
42
- "tests/conftest.py": "# [INSERT: fixtures]",
43
- }
40
+ # Find the service package dir — check src/ layout first, then flat layout
41
+ src_dir = root / "src"
42
+ pkg_dir = None
43
+ if src_dir.is_dir():
44
+ pkg_dir = next(
45
+ (
46
+ p
47
+ for p in src_dir.iterdir()
48
+ if p.is_dir() and not p.name.startswith(".") and (p / "__init__.py").is_file()
49
+ ),
50
+ None,
51
+ )
52
+ if pkg_dir is None:
53
+ pkg_dir = next(
54
+ (
55
+ p
56
+ for p in root.iterdir()
57
+ if p.is_dir()
58
+ and not p.name.startswith(".")
59
+ and p.name not in {"tests", "src"}
60
+ and (p / "__init__.py").is_file()
61
+ ),
62
+ None,
63
+ )
64
+
65
+ marker_files: dict[str, str] = {}
66
+ if pkg_dir is not None:
67
+ marker_files[f"{pkg_dir.name}/settings.py"] = "# [INSERT: fields]"
68
+ marker_files["tests/conftest.py"] = "# [INSERT: fixtures]"
44
69
  for rel, marker in marker_files.items():
45
70
  path = root / rel
46
71
  if path.exists():
@@ -67,7 +67,7 @@ def generate_service(
67
67
  """Generate a service from the bundled cookiecutter-service template."""
68
68
  output_root = output_dir.resolve()
69
69
  output_root.mkdir(parents=True, exist_ok=True)
70
- target = output_root / name
70
+ target = output_root / name.replace("-", "_")
71
71
 
72
72
  if target.exists() and any(target.iterdir()) and not overwrite_if_exists:
73
73
  raise FileExistsError(
@@ -31,32 +31,32 @@ def _prune_disabled_files() -> None:
31
31
  path.rmdir()
32
32
 
33
33
  if not HAS_DATABASE:
34
- _unlink(f"{PKG}/dependencies/item_repository.py")
35
- _unlink(f"{PKG}/repositories/item_repository.py")
34
+ _unlink(f"src/{PKG}/dependencies/item_repository.py")
35
+ _unlink(f"src/{PKG}/repositories/item_repository.py")
36
36
  _unlink("tests/unit/test_database_settings.py")
37
37
 
38
38
  if not HAS_DELEGATES:
39
- _unlink(f"{PKG}/dependencies/delegates.py")
40
- _unlink(f"{PKG}/delegates/upstreams.py")
39
+ _unlink(f"src/{PKG}/dependencies/delegates.py")
40
+ _unlink(f"src/{PKG}/delegates/upstreams.py")
41
41
  _unlink("tests/unit/test_delegates.py")
42
42
 
43
43
  if not HAS_WORKERS:
44
- _unlink(f"{PKG}/dependencies/worker_broker.py")
45
- _unlink(f"{PKG}/workers/tasks.py")
44
+ _unlink(f"src/{PKG}/dependencies/worker_broker.py")
45
+ _unlink(f"src/{PKG}/workers/tasks.py")
46
46
  _unlink("tests/unit/test_worker_broker.py")
47
47
 
48
48
  if not HAS_CACHING:
49
- _unlink(f"{PKG}/cache.py")
49
+ _unlink(f"src/{PKG}/cache.py")
50
50
  _unlink("tests/unit/test_cache.py")
51
51
 
52
52
  if not HAS_STRUCTURED_LOGGING:
53
- _unlink(f"{PKG}/middleware/logging.py")
53
+ _unlink(f"src/{PKG}/middleware/logging.py")
54
54
  _unlink("tests/acceptance/test_logging_middleware.py")
55
55
 
56
- _rmdir_if_empty(f"{PKG}/repositories")
57
- _rmdir_if_empty(f"{PKG}/delegates")
58
- _rmdir_if_empty(f"{PKG}/workers")
59
- _rmdir_if_empty(f"{PKG}/middleware")
56
+ _rmdir_if_empty(f"src/{PKG}/repositories")
57
+ _rmdir_if_empty(f"src/{PKG}/delegates")
58
+ _rmdir_if_empty(f"src/{PKG}/workers")
59
+ _rmdir_if_empty(f"src/{PKG}/middleware")
60
60
  _rmdir_if_empty("tests/unit")
61
61
  _rmdir_if_empty("tests/acceptance")
62
62
 
@@ -8,6 +8,6 @@ RUN pip install --no-cache-dir -r /app/requirements.txt
8
8
  COPY src /app/src
9
9
 
10
10
  ENV PYTHONPATH=/app/src
11
- ENV PORT=8080
11
+ ENV PORT={{ cookiecutter.port }}
12
12
 
13
- CMD uvicorn app:build_app --factory --host 0.0.0.0 --port ${PORT}
13
+ CMD uvicorn {{cookiecutter.__service_name_snake}}:build_app --factory --host 0.0.0.0 --port ${PORT}
@@ -24,8 +24,7 @@
24
24
  ## Run locally
25
25
 
26
26
  ```bash
27
- pip install -r requirements.txt
28
- PYTHONPATH=src uvicorn app:build_app --factory --reload --port {{ cookiecutter.port }}
27
+ PYTHONPATH=src uvicorn {{cookiecutter.__service_name_snake}}:build_app --factory --reload --port {{ cookiecutter.port }}
29
28
  ```
30
29
 
31
30
  ## Run with compose
@@ -1,44 +0,0 @@
1
- APP_NAME={{ cookiecutter.service_name }}
2
- PORT={{ cookiecutter.port }}
3
- APP_ENV=development
4
- ENFORCE_STRONG_DB_CREDENTIALS=false
5
- INCLUDE_ACTUATOR_ENDPOINTS=false
6
-
7
- {%- if cookiecutter.has_database %}
8
- DB_KIND={{ cookiecutter.database }}
9
- {%- if cookiecutter.database == "sqlite" %}
10
- DB_PATH=./service.db
11
- {%- elif cookiecutter.database == "maria" %}
12
- DB_HOST=localhost
13
- DB_PORT=3306
14
- DB_USER=service_user
15
- DB_PASSWORD=change_me
16
- DB_NAME=service_db
17
- MYSQL_ROOT_PASSWORD=change_me_root
18
- {%- else %}
19
- DB_HOST=localhost
20
- DB_PORT=5432
21
- DB_USER=service_user
22
- DB_PASSWORD=change_me
23
- DB_NAME=service_db
24
- {%- endif %}
25
- {%- endif %}
26
-
27
- {%- if cookiecutter.has_delegates %}
28
- # Comma-separated upstream service URLs.
29
- UPSTREAMS={{ cookiecutter.upstreams }}
30
- {%- endif %}
31
-
32
- {%- if cookiecutter.has_workers %}
33
- {%- if cookiecutter.broker == "redis" %}
34
- WORKER_BROKER_URL=redis://localhost:6380/1
35
- {%- else %}
36
- RABBITMQ_USER=service_rabbit
37
- RABBITMQ_PASSWORD=change_me
38
- WORKER_BROKER_URL=amqp://service_rabbit:change_me@localhost:5672/
39
- {%- endif %}
40
- {%- endif %}
41
-
42
- {%- if cookiecutter.has_caching %}
43
- CACHE_URL=redis://localhost:6379/0
44
- {%- endif %}
@@ -1,64 +0,0 @@
1
- # Python
2
- __pycache__/
3
- *.py[cod]
4
- *$py.class
5
- *.so
6
- .Python
7
- build/
8
- develop-eggs/
9
- dist/
10
- downloads/
11
- eggs/
12
- .eggs/
13
- lib/
14
- lib64/
15
- parts/
16
- sdist/
17
- var/
18
- wheels/
19
- pip-wheel-metadata/
20
- share/python-wheels/
21
- *.egg-info/
22
- .installed.cfg
23
- *.egg
24
- MANIFEST
25
-
26
- # Virtual environments
27
- .venv
28
- venv/
29
- ENV/
30
- env/
31
-
32
- # IDEs
33
- .vscode/
34
- .idea/
35
- *.swp
36
- *.swo
37
- *~
38
- .DS_Store
39
-
40
- # Testing
41
- .pytest_cache/
42
- .coverage
43
- htmlcov/
44
- .tox/
45
- .hypothesis/
46
-
47
- # Type checking
48
- .mypy_cache/
49
- .dmypy.json
50
- dmypy.json
51
-
52
- # Development
53
- *.log
54
- .env
55
- .env.local
56
- .env.*.local
57
-
58
- # Docker
59
- .dockerignore
60
- Dockerfile
61
-
62
- # Services
63
- .csrd-workspace
64
- .csrd/
File without changes
File without changes