csrd-utils 0.3.35__tar.gz → 0.3.37__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 (90) hide show
  1. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/PKG-INFO +1 -1
  2. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/pyproject.toml +1 -1
  3. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/__main__.py +13 -7
  4. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/augmentor.py +28 -8
  5. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/doctor.py +19 -6
  6. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/cookiecutter.json +2 -4
  7. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/hooks/post_gen_project.py +1 -1
  8. csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/.cookiecutterignore +1 -0
  9. csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/Dockerfile +12 -0
  10. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/README.md +1 -2
  11. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/pyproject.toml +2 -2
  12. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/unit/test_cache.py +1 -1
  13. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/unit/test_database_settings.py +1 -1
  14. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/unit/test_delegates.py +2 -2
  15. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/unit/test_settings_contracts.py +1 -1
  16. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/unit/test_worker_broker.py +2 -2
  17. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/settings.py +1 -1
  18. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/workers/tasks.py +2 -2
  19. csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/.env.example +0 -44
  20. csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/.gitignore +0 -64
  21. csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/Dockerfile +0 -13
  22. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/.gitignore +0 -0
  23. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/README.md +0 -0
  24. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/__init__.py +0 -0
  25. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/audit.py +0 -0
  26. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/cluster.py +0 -0
  27. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/conftest.fragment.py +0 -0
  28. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/docker-compose.fragment.yaml +0 -0
  29. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/manifest.yaml +0 -0
  30. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/requirements.fragment +0 -0
  31. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/settings.fragment.py +0 -0
  32. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/src/app/cache.py +0 -0
  33. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/tests/test_cache.py +0 -0
  34. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/logging/conftest.fragment.py +0 -0
  35. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/logging/manifest.yaml +0 -0
  36. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/logging/requirements.fragment +0 -0
  37. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/logging/settings.fragment.py +0 -0
  38. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/logging/src/app/middleware/logging.py +0 -0
  39. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/logging/tests/test_logging_middleware.py +0 -0
  40. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/conftest.fragment.py +0 -0
  41. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/docker-compose.fragment.yaml +0 -0
  42. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/manifest.yaml +0 -0
  43. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/requirements.fragment +0 -0
  44. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/settings.fragment.py +0 -0
  45. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/src/app/middleware/metrics.py +0 -0
  46. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/tests/test_metrics_middleware.py +0 -0
  47. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/tracing/conftest.fragment.py +0 -0
  48. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/tracing/manifest.yaml +0 -0
  49. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/tracing/requirements.fragment +0 -0
  50. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/tracing/settings.fragment.py +0 -0
  51. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/tracing/src/app/middleware/tracing.py +0 -0
  52. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/tracing/tests/test_tracing_middleware.py +0 -0
  53. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/conftest.fragment.py +0 -0
  54. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/docker-compose.fragment.yaml +0 -0
  55. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/manifest.yaml +0 -0
  56. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/requirements.fragment +0 -0
  57. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/settings.fragment.py +0 -0
  58. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/src/app/workers/__init__.py +0 -0
  59. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/src/app/workers/celery_app.py +0 -0
  60. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/src/app/workers/tasks.py +0 -0
  61. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/tests/test_workers.py +0 -0
  62. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/fragments.py +0 -0
  63. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/generator.py +0 -0
  64. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/resources.py +0 -0
  65. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/README.md +0 -0
  66. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/.dockerignore +0 -0
  67. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/docker-compose.yml +0 -0
  68. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/requirements.txt +0 -0
  69. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/__init__.py +0 -0
  70. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/acceptance/test_logging_middleware.py +0 -0
  71. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/conftest.py +0 -0
  72. {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/test_items.py +0 -0
  73. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/__init__.py +0 -0
  74. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/cache.py +0 -0
  75. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/delegates/__init__.py +0 -0
  76. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/delegates/upstreams.py +0 -0
  77. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/dependencies/__init__.py +0 -0
  78. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/dependencies/delegates.py +0 -0
  79. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/dependencies/item_repository.py +0 -0
  80. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/dependencies/worker_broker.py +0 -0
  81. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/middleware/__init__.py +0 -0
  82. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/middleware/logging.py +0 -0
  83. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/py.typed +0 -0
  84. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/repositories/__init__.py +0 -0
  85. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/repositories/item_repository.py +0 -0
  86. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/views/__init__.py +0 -0
  87. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/views/unversioned/__init__.py +0 -0
  88. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/views/unversioned/health_view.py +0 -0
  89. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/views/unversioned/items_view.py +0 -0
  90. {csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.service_name_snake}} → csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/{{cookiecutter.__service_name_snake}}}/workers/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csrd-utils
3
- Version: 0.3.35
3
+ Version: 0.3.37
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.35"
3
+ version = "0.3.37"
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,22 @@ 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 (flat layout: <service_root>/<pkg>)."""
43
+ candidate = next(
44
+ (
45
+ p
46
+ for p in self.service_root.iterdir()
47
+ if p.is_dir()
48
+ and not p.name.startswith(".")
49
+ and p.name != "tests"
50
+ and (p / "__init__.py").is_file()
51
+ ),
52
+ None,
53
+ )
54
+ return candidate if candidate is not None else self.service_root / "src" / "app"
55
+
40
56
  def load_feature(self, feature_name: str) -> bool:
41
57
  """
42
58
  Load and validate feature manifest.
@@ -72,14 +88,14 @@ class ServiceAugmentor:
72
88
  """
73
89
  Validate that service has required structure.
74
90
 
75
- Checks for src/app/, docker-compose.yml, etc.
91
+ Checks for <pkg>/, docker-compose.yml, etc.
76
92
 
77
93
  Returns
78
94
  -------
79
95
  bool
80
96
  True if valid
81
97
  """
82
- required_dirs = ["src/app", "tests"]
98
+ required_dirs = [str(self._pkg_dir.relative_to(self.service_root)), "tests"]
83
99
  required_files = ["requirements.txt"]
84
100
 
85
101
  for d in required_dirs:
@@ -245,7 +261,7 @@ class ServiceAugmentor:
245
261
  return True # Not required
246
262
 
247
263
  try:
248
- settings_path = self.service_root / "src/app/settings.py"
264
+ settings_path = self._pkg_dir / "settings.py"
249
265
  if not settings_path.exists():
250
266
  print("⚠ settings.py not found, skipping")
251
267
  return True
@@ -330,12 +346,11 @@ class ServiceAugmentor:
330
346
  return True
331
347
 
332
348
  try:
333
- # Copy entire tree
349
+ # Copy entire tree into the service package directory
334
350
  for src_file in feature_dir.rglob("*"):
335
351
  if src_file.is_file():
336
- # Map to destination
337
352
  rel_path = src_file.relative_to(feature_dir)
338
- dest_file = self.service_root / "src" / "app" / rel_path
353
+ dest_file = self._pkg_dir / rel_path
339
354
 
340
355
  # Create parent dirs
341
356
  dest_file.parent.mkdir(parents=True, exist_ok=True)
@@ -417,8 +432,13 @@ class ServiceAugmentor:
417
432
  # Step 2.5: Check if feature already applied (idempotency)
418
433
  creates = self.manifest.get("creates", [])
419
434
  if creates:
420
- first_created_file = Path(creates[0])
421
- full_path = self.service_root / first_created_file
435
+ first_created_path = Path(creates[0])
436
+ # Feature manifests use src/app/ prefix — remap to flat pkg layout
437
+ try:
438
+ rel = first_created_path.relative_to("src/app")
439
+ full_path = self._pkg_dir / rel
440
+ except ValueError:
441
+ full_path = self.service_root / first_created_path
422
442
  if full_path.exists():
423
443
  print(f"✅ Feature '{feature_name}' already applied (detected {creates[0]})")
424
444
  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,23 @@ 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 (flat layout: <service_root>/<pkg>/__init__.py)
41
+ pkg_dir = next(
42
+ (
43
+ p
44
+ for p in root.iterdir()
45
+ if p.is_dir()
46
+ and not p.name.startswith(".")
47
+ and p.name not in {"tests"}
48
+ and (p / "__init__.py").is_file()
49
+ ),
50
+ None,
51
+ )
52
+
53
+ marker_files: dict[str, str] = {}
54
+ if pkg_dir is not None:
55
+ marker_files[f"{pkg_dir.name}/settings.py"] = "# [INSERT: fields]"
56
+ marker_files["tests/conftest.py"] = "# [INSERT: fixtures]"
44
57
  for rel, marker in marker_files.items():
45
58
  path = root / rel
46
59
  if path.exists():
@@ -22,8 +22,6 @@
22
22
  "port": "8080",
23
23
  "model_name": "Item",
24
24
  "model_name_plural": "{{ cookiecutter.model_name.lower() }}s",
25
- "service_name_snake": "{{ cookiecutter.service_name | replace('-', '_') }}",
26
- "service_name_pascal": "{{ cookiecutter.service_name | replace('-', ' ') | title | replace(' ', '') }}",
27
- "_service_name_snake": "{{ cookiecutter.service_name | replace('-', '_') }}",
28
- "_service_name_pascal": "{{ cookiecutter.service_name | replace('-', ' ') | title | replace(' ', '') }}"
25
+ "__service_name_snake": "{{ cookiecutter.service_name | replace('-', '_') }}",
26
+ "__service_name_pascal": "{{ cookiecutter.service_name | replace('-', ' ') | title | replace(' ', '') }}"
29
27
  }
@@ -14,7 +14,7 @@ DB_KIND = "{{ cookiecutter.database }}"
14
14
  BROKER = "{{ cookiecutter.broker }}"
15
15
  CACHE_BACKEND = "{{ cookiecutter.cache_backend }}"
16
16
  UPSTREAMS = "{{ cookiecutter.upstreams }}"
17
- PKG = "{{ cookiecutter.service_name_snake }}"
17
+ PKG = "{{ cookiecutter.__service_name_snake }}"
18
18
 
19
19
 
20
20
  def _prune_disabled_files() -> None:
@@ -0,0 +1,12 @@
1
+ FROM python:3.12-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt /app/requirements.txt
6
+ RUN pip install --no-cache-dir -r /app/requirements.txt
7
+
8
+ COPY {{cookiecutter.__service_name_snake}} /app/{{cookiecutter.__service_name_snake}}
9
+
10
+ ENV PORT={{ cookiecutter.port }}
11
+
12
+ 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
+ uvicorn {{cookiecutter.__service_name_snake}}:build_app --factory --reload --port {{ cookiecutter.port }}
29
28
  ```
30
29
 
31
30
  ## Run with compose
@@ -36,7 +36,7 @@ dev = [
36
36
  ]
37
37
 
38
38
  [tool.hatch.build.targets.wheel]
39
- packages = ["{{cookiecutter.service_name_snake}}"]
39
+ packages = ["{{cookiecutter.__service_name_snake}}"]
40
40
 
41
41
  [tool.pytest.ini_options]
42
42
  asyncio_mode = "auto"
@@ -58,4 +58,4 @@ select = ["E", "F", "W", "I", "N", "UP", "BLE", "FBT", "A", "C4", "PIE", "SIM",
58
58
  ignore = ["E501"]
59
59
 
60
60
  [tool.ruff.lint.isort]
61
- known-first-party = ["{{cookiecutter.service_name_snake}}"]
61
+ known-first-party = ["{{cookiecutter.__service_name_snake}}"]
@@ -2,7 +2,7 @@
2
2
 
3
3
  import importlib
4
4
 
5
- from {{cookiecutter.service_name_snake}}.cache import build_cache_client
5
+ from {{cookiecutter.__service_name_snake}}.cache import build_cache_client
6
6
 
7
7
 
8
8
  def test_cache_client_uses_configured_host() -> None:
@@ -1,6 +1,6 @@
1
1
  """Unit tests for database settings wiring."""
2
2
 
3
- from {{cookiecutter.service_name_snake}}.settings import settings
3
+ from {{cookiecutter.__service_name_snake}}.settings import settings
4
4
 
5
5
 
6
6
  def test_database_settings_enabled() -> None:
@@ -2,8 +2,8 @@
2
2
 
3
3
  import asyncio
4
4
 
5
- from {{cookiecutter.service_name_snake}}.dependencies.delegates import upstream_delegate_factory
6
- from {{cookiecutter.service_name_snake}}.delegates.upstreams import configured_upstreams
5
+ from {{cookiecutter.__service_name_snake}}.dependencies.delegates import upstream_delegate_factory
6
+ from {{cookiecutter.__service_name_snake}}.delegates.upstreams import configured_upstreams
7
7
 
8
8
 
9
9
  async def _collect_upstreams() -> list[str]:
@@ -7,7 +7,7 @@ from types import SimpleNamespace
7
7
 
8
8
  import pytest
9
9
 
10
- from {{cookiecutter.service_name_snake}}.settings import Settings
10
+ from {{cookiecutter.__service_name_snake}}.settings import Settings
11
11
 
12
12
 
13
13
  def test_validate_db_credentials_returns_self_without_database() -> None:
@@ -2,8 +2,8 @@
2
2
 
3
3
  import importlib
4
4
 
5
- from {{cookiecutter.service_name_snake}}.dependencies.worker_broker import get_worker_broker_url
6
- from {{cookiecutter.service_name_snake}}.workers.tasks import ping_task
5
+ from {{cookiecutter.__service_name_snake}}.dependencies.worker_broker import get_worker_broker_url
6
+ from {{cookiecutter.__service_name_snake}}.workers.tasks import ping_task
7
7
 
8
8
 
9
9
  def test_worker_broker_url_uses_expected_scheme() -> None:
@@ -69,7 +69,7 @@ class Settings(BaseSettings):
69
69
 
70
70
  weak_values = {
71
71
  "",
72
- "{{cookiecutter.service_name_snake}}",
72
+ "{{cookiecutter.__service_name_snake}}",
73
73
  "sandbox",
74
74
  "service_user",
75
75
  "service_db",
@@ -3,10 +3,10 @@ from celery import Celery
3
3
 
4
4
  from ..settings import settings
5
5
 
6
- celery_app = Celery("{{ cookiecutter._service_name_snake }}", broker=settings.worker_broker_url)
6
+ celery_app = Celery("{{ cookiecutter.__service_name_snake }}", broker=settings.worker_broker_url)
7
7
 
8
8
 
9
- @celery_app.task(name="{{ cookiecutter._service_name_snake }}.ping")
9
+ @celery_app.task(name="{{ cookiecutter.__service_name_snake }}.ping")
10
10
  def ping_task() -> str:
11
11
  return "pong"
12
12
 
@@ -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/
@@ -1,13 +0,0 @@
1
- FROM python:3.12-slim
2
-
3
- WORKDIR /app
4
-
5
- COPY requirements.txt /app/requirements.txt
6
- RUN pip install --no-cache-dir -r /app/requirements.txt
7
-
8
- COPY src /app/src
9
-
10
- ENV PYTHONPATH=/app/src
11
- ENV PORT=8080
12
-
13
- CMD uvicorn app:build_app --factory --host 0.0.0.0 --port ${PORT}
File without changes
File without changes