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.
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/PKG-INFO +1 -1
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/pyproject.toml +1 -1
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/__main__.py +13 -7
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/augmentor.py +28 -8
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/doctor.py +19 -6
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/cookiecutter.json +2 -4
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/hooks/post_gen_project.py +1 -1
- csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/.cookiecutterignore +1 -0
- csrd_utils-0.3.37/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/Dockerfile +12 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/README.md +1 -2
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/pyproject.toml +2 -2
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/.env.example +0 -44
- csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/.gitignore +0 -64
- csrd_utils-0.3.35/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/Dockerfile +0 -13
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/.gitignore +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/README.md +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/__init__.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/audit.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/cluster.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/conftest.fragment.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/docker-compose.fragment.yaml +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/manifest.yaml +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/requirements.fragment +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/settings.fragment.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/src/app/cache.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/tests/test_cache.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/logging/conftest.fragment.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/logging/manifest.yaml +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/logging/requirements.fragment +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/logging/settings.fragment.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/logging/src/app/middleware/logging.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/logging/tests/test_logging_middleware.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/conftest.fragment.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/docker-compose.fragment.yaml +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/manifest.yaml +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/requirements.fragment +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/settings.fragment.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/src/app/middleware/metrics.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/tests/test_metrics_middleware.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/tracing/conftest.fragment.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/tracing/manifest.yaml +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/tracing/requirements.fragment +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/tracing/settings.fragment.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/tracing/src/app/middleware/tracing.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/tracing/tests/test_tracing_middleware.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/conftest.fragment.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/docker-compose.fragment.yaml +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/manifest.yaml +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/requirements.fragment +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/settings.fragment.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/src/app/workers/__init__.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/src/app/workers/celery_app.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/src/app/workers/tasks.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/tests/test_workers.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/fragments.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/generator.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/resources.py +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/README.md +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/.dockerignore +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/docker-compose.yml +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/requirements.txt +0 -0
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/__init__.py +0 -0
- {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
- {csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/conftest.py +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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.
|
|
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
|
|
@@ -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
|
|
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 = [
|
|
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.
|
|
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.
|
|
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
|
-
|
|
421
|
-
|
|
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 = ["
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
"
|
|
26
|
-
"
|
|
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.
|
|
17
|
+
PKG = "{{ cookiecutter.__service_name_snake }}"
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def _prune_disabled_files() -> None:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.ruff_cache/
|
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
61
|
+
known-first-party = ["{{cookiecutter.__service_name_snake}}"]
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
|
|
5
|
-
from {{cookiecutter.
|
|
6
|
-
from {{cookiecutter.
|
|
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.
|
|
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.
|
|
6
|
-
from {{cookiecutter.
|
|
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:
|
|
@@ -3,10 +3,10 @@ from celery import Celery
|
|
|
3
3
|
|
|
4
4
|
from ..settings import settings
|
|
5
5
|
|
|
6
|
-
celery_app = Celery("{{ cookiecutter.
|
|
6
|
+
celery_app = Celery("{{ cookiecutter.__service_name_snake }}", broker=settings.worker_broker_url)
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
@celery_app.task(name="{{ cookiecutter.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/conftest.fragment.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/docker-compose.fragment.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/caching/settings.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/logging/conftest.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/logging/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/logging/settings.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/conftest.fragment.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/docker-compose.fragment.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/metrics/settings.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/tracing/conftest.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/tracing/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/tracing/settings.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/conftest.fragment.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/docker-compose.fragment.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/settings.fragment.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/src/app/workers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/src/app/workers/tasks.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/features/workers/tests/test_workers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.35 → csrd_utils-0.3.37}/src/csrd_utils/templates/cookiecutter-service/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|