csrd-utils 0.3.52__tar.gz → 0.3.55__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.52 → csrd_utils-0.3.55}/PKG-INFO +1 -1
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/pyproject.toml +1 -1
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/__main__.py +71 -2
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/augmentor.py +25 -4
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/cluster.py +13 -4
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/generator.py +14 -0
- csrd_utils-0.3.55/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/__init__.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/.gitignore +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/README.md +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/__init__.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/audit.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/doctor.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/caching/conftest.fragment.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/caching/docker-compose.fragment.yaml +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/caching/manifest.yaml +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/caching/requirements.fragment +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/caching/settings.fragment.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/caching/src/app/cache.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/caching/tests/test_cache.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/logging/conftest.fragment.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/logging/manifest.yaml +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/logging/requirements.fragment +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/logging/settings.fragment.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/logging/src/app/middleware/logging.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/logging/tests/test_logging_middleware.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/metrics/conftest.fragment.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/metrics/docker-compose.fragment.yaml +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/metrics/manifest.yaml +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/metrics/requirements.fragment +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/metrics/settings.fragment.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/metrics/src/app/middleware/metrics.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/metrics/tests/test_metrics_middleware.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/tracing/conftest.fragment.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/tracing/manifest.yaml +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/tracing/requirements.fragment +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/tracing/settings.fragment.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/tracing/src/app/middleware/tracing.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/tracing/tests/test_tracing_middleware.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/workers/conftest.fragment.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/workers/docker-compose.fragment.yaml +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/workers/manifest.yaml +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/workers/requirements.fragment +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/workers/settings.fragment.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/workers/src/app/workers/__init__.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/workers/src/app/workers/celery_app.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/workers/src/app/workers/tasks.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/workers/tests/test_workers.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/fragments.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/resources.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/README.md +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/cookiecutter.json +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/hooks/post_gen_project.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/.cookiecutterignore +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/.dockerignore +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/cache.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/delegates/__init__.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/delegates/upstreams.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/delegates.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/item_repository.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/worker_broker.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/docker-compose.yml +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/middleware/__init__.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/middleware/logging.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/repositories/item_repository.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/__init__.py +0 -0
- /csrd_utils-0.3.52/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/py.typed → /csrd_utils-0.3.55/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/acceptance/__init__.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/acceptance/test_logging_middleware.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/conftest.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/test_items.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_cache.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_database_settings.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_delegates.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_settings_contracts.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_worker_broker.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/items_view.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/workers/__init__.py +0 -0
- {csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/workers/tasks.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.55
|
|
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
|
|
@@ -213,12 +213,19 @@ def _collect_new_service_config(args: argparse.Namespace) -> GenerateServiceArgs
|
|
|
213
213
|
has_database = args.database != "none"
|
|
214
214
|
selected_database = "sqlite" if not has_database else args.database
|
|
215
215
|
normalized_name = _normalize_service_name(args.name)
|
|
216
|
+
# Auto-advance port when default (8080) is already taken in the workspace spec.
|
|
217
|
+
output_root = args.output.resolve()
|
|
218
|
+
effective_port = (
|
|
219
|
+
_next_free_port(output_root, args.port)
|
|
220
|
+
if _is_workspace(output_root) and args.port == 8080
|
|
221
|
+
else args.port
|
|
222
|
+
)
|
|
216
223
|
return GenerateServiceArgs(
|
|
217
224
|
{
|
|
218
225
|
"name": normalized_name,
|
|
219
226
|
"output_dir": args.output,
|
|
220
227
|
"description": args.description,
|
|
221
|
-
"port":
|
|
228
|
+
"port": effective_port,
|
|
222
229
|
"model_name": args.model_name,
|
|
223
230
|
"model_name_plural": args.model_name_plural,
|
|
224
231
|
"has_database": has_database,
|
|
@@ -242,7 +249,15 @@ def _collect_new_service_config(args: argparse.Namespace) -> GenerateServiceArgs
|
|
|
242
249
|
else:
|
|
243
250
|
output_dir = Path(_prompt_text("Output directory", str(args.output)))
|
|
244
251
|
description = _prompt_text("Service description", args.description)
|
|
245
|
-
|
|
252
|
+
|
|
253
|
+
# Suggest the next available port based on what's already in the workspace spec.
|
|
254
|
+
workspace_root_candidate = output_dir.resolve()
|
|
255
|
+
default_port = (
|
|
256
|
+
_next_free_port(workspace_root_candidate, args.port)
|
|
257
|
+
if _is_workspace(workspace_root_candidate)
|
|
258
|
+
else args.port
|
|
259
|
+
)
|
|
260
|
+
port = _prompt_int("HTTP port", default_port)
|
|
246
261
|
model_name = _prompt_text("Model name", args.model_name)
|
|
247
262
|
|
|
248
263
|
plural_default = args.model_name_plural
|
|
@@ -461,6 +476,22 @@ def _is_workspace(path: Path) -> bool:
|
|
|
461
476
|
return (path / WORKSPACE_MARKER).is_file()
|
|
462
477
|
|
|
463
478
|
|
|
479
|
+
def _next_free_port(workspace_root: Path, start: int = 8080) -> int:
|
|
480
|
+
"""Return the lowest port >= start that is not already used in the workspace cluster spec."""
|
|
481
|
+
spec_path = workspace_root / WORKSPACE_CLUSTER_SPEC
|
|
482
|
+
if not spec_path.is_file():
|
|
483
|
+
return start
|
|
484
|
+
try:
|
|
485
|
+
raw = yaml.safe_load(spec_path.read_text(encoding="utf-8")) or {}
|
|
486
|
+
used = {int(svc["port"]) for svc in raw.get("services", []) if "port" in svc}
|
|
487
|
+
except Exception:
|
|
488
|
+
return start
|
|
489
|
+
port = start
|
|
490
|
+
while port in used:
|
|
491
|
+
port += 1
|
|
492
|
+
return port
|
|
493
|
+
|
|
494
|
+
|
|
464
495
|
def _service_is_interactive(args: argparse.Namespace) -> bool:
|
|
465
496
|
explicit_interactive = getattr(args, "interactive", None)
|
|
466
497
|
return bool(explicit_interactive) if explicit_interactive is not None else args.name is None
|
|
@@ -881,6 +912,37 @@ def _ensure_unique_ports(services: list[ServiceSpec], preserve_name: str) -> Non
|
|
|
881
912
|
used.add(port)
|
|
882
913
|
|
|
883
914
|
|
|
915
|
+
def _sync_workspace_service_feature(
|
|
916
|
+
workspace_root: Path, service_name: str, feature_name: str
|
|
917
|
+
) -> Path | None:
|
|
918
|
+
spec = _load_workspace_cluster_spec(workspace_root)
|
|
919
|
+
services = spec.get("services", [])
|
|
920
|
+
target = next(
|
|
921
|
+
(
|
|
922
|
+
cast(ServiceSpec, dict(service))
|
|
923
|
+
for service in services
|
|
924
|
+
if str(service.get("name", "")).strip() == service_name
|
|
925
|
+
),
|
|
926
|
+
None,
|
|
927
|
+
)
|
|
928
|
+
if target is None:
|
|
929
|
+
return None
|
|
930
|
+
|
|
931
|
+
features = list(target.get("features", []))
|
|
932
|
+
if feature_name not in features:
|
|
933
|
+
features.append(feature_name)
|
|
934
|
+
target["features"] = features
|
|
935
|
+
|
|
936
|
+
requires = list(target.get("requires", []))
|
|
937
|
+
if feature_name in {"caching", "workers"} and "redis" not in requires:
|
|
938
|
+
requires.append("redis")
|
|
939
|
+
target["requires"] = requires
|
|
940
|
+
|
|
941
|
+
updated_spec = _upsert_workspace_service(spec, target, workspace_root.name)
|
|
942
|
+
_save_workspace_cluster_spec(workspace_root, updated_spec)
|
|
943
|
+
return generate_cluster(updated_spec, workspace_root, force=True)
|
|
944
|
+
|
|
945
|
+
|
|
884
946
|
def _maybe_update_workspace_cluster(
|
|
885
947
|
workspace_root: Path,
|
|
886
948
|
service_config: GenerateServiceArgs,
|
|
@@ -1315,6 +1377,13 @@ def main() -> int:
|
|
|
1315
1377
|
if args.plan:
|
|
1316
1378
|
print("Plan validated.")
|
|
1317
1379
|
else:
|
|
1380
|
+
feature_workspace_root = _workspace_root_for_service(resolved_service)
|
|
1381
|
+
if feature_workspace_root is not None:
|
|
1382
|
+
refreshed_cluster = _sync_workspace_service_feature(
|
|
1383
|
+
feature_workspace_root, resolved_service.name, args.feature
|
|
1384
|
+
)
|
|
1385
|
+
if refreshed_cluster is not None:
|
|
1386
|
+
print(f"Refreshed cluster scaffold at: {refreshed_cluster}")
|
|
1318
1387
|
print(f"Feature '{args.feature}' added.")
|
|
1319
1388
|
if changes:
|
|
1320
1389
|
for change in changes:
|
|
@@ -80,6 +80,15 @@ class ServiceAugmentor:
|
|
|
80
80
|
return workspace_root / "tests"
|
|
81
81
|
return self.service_root / "tests"
|
|
82
82
|
|
|
83
|
+
@property
|
|
84
|
+
def _pkg_import_name(self) -> str:
|
|
85
|
+
return self._pkg_dir.name
|
|
86
|
+
|
|
87
|
+
def _rewrite_src_app_refs(self, content: str) -> str:
|
|
88
|
+
"""Map legacy feature fragment imports to the concrete service package name."""
|
|
89
|
+
pkg = self._pkg_import_name
|
|
90
|
+
return content.replace("src.app.", f"{pkg}.").replace("src.app", pkg)
|
|
91
|
+
|
|
83
92
|
def load_feature(self, feature_name: str) -> bool:
|
|
84
93
|
"""
|
|
85
94
|
Load and validate feature manifest.
|
|
@@ -247,6 +256,11 @@ class ServiceAugmentor:
|
|
|
247
256
|
if not fragment_path.exists():
|
|
248
257
|
return True # Not required
|
|
249
258
|
|
|
259
|
+
if self._workspace_root is not None:
|
|
260
|
+
# Workspace compose is generated from cluster spec, not merged via legacy fragments.
|
|
261
|
+
print("✓ Skipped docker-compose.yml fragment merge (workspace-managed compose)")
|
|
262
|
+
return True
|
|
263
|
+
|
|
250
264
|
try:
|
|
251
265
|
dc_path = self._docker_compose_path()
|
|
252
266
|
self.backup_file(dc_path)
|
|
@@ -343,7 +357,7 @@ class ServiceAugmentor:
|
|
|
343
357
|
self.backup_file(conftest_path)
|
|
344
358
|
|
|
345
359
|
base = conftest_path.read_text()
|
|
346
|
-
fragment = fragment_path.read_text()
|
|
360
|
+
fragment = self._rewrite_src_app_refs(fragment_path.read_text())
|
|
347
361
|
merged = ConfTestMerger.merge(base, fragment)
|
|
348
362
|
|
|
349
363
|
conftest_path.write_text(merged)
|
|
@@ -387,8 +401,11 @@ class ServiceAugmentor:
|
|
|
387
401
|
# Create parent dirs
|
|
388
402
|
dest_file.parent.mkdir(parents=True, exist_ok=True)
|
|
389
403
|
|
|
390
|
-
|
|
391
|
-
|
|
404
|
+
if src_file.suffix == ".py":
|
|
405
|
+
rendered = self._rewrite_src_app_refs(src_file.read_text())
|
|
406
|
+
dest_file.write_text(rendered)
|
|
407
|
+
else:
|
|
408
|
+
shutil.copy2(src_file, dest_file)
|
|
392
409
|
self.applied_changes.append(f"Created {rel_path}")
|
|
393
410
|
|
|
394
411
|
print("✓ Created feature files")
|
|
@@ -433,7 +450,11 @@ class ServiceAugmentor:
|
|
|
433
450
|
dest_file = self._tests_dir / rel_path
|
|
434
451
|
|
|
435
452
|
dest_file.parent.mkdir(parents=True, exist_ok=True)
|
|
436
|
-
|
|
453
|
+
if src_file.suffix == ".py":
|
|
454
|
+
rendered = self._rewrite_src_app_refs(src_file.read_text())
|
|
455
|
+
dest_file.write_text(rendered)
|
|
456
|
+
else:
|
|
457
|
+
shutil.copy2(src_file, dest_file)
|
|
437
458
|
self.applied_changes.append(f"Created {rel_path}")
|
|
438
459
|
|
|
439
460
|
print("✓ Created test files")
|
|
@@ -232,8 +232,14 @@ def _infer_infra(services: Iterable[ServiceSpec]) -> set[str]:
|
|
|
232
232
|
if service_type == "worker":
|
|
233
233
|
broker = service.get("broker", "redis")
|
|
234
234
|
infra.add("redis" if broker == "redis" else "rabbitmq")
|
|
235
|
+
if "workers" in service.get("features", []):
|
|
236
|
+
broker = service.get("broker", "redis")
|
|
237
|
+
infra.add("redis" if broker == "redis" else "rabbitmq")
|
|
235
238
|
if "caching" in service.get("features", []):
|
|
236
239
|
infra.add("redis")
|
|
240
|
+
for required in service.get("requires", []):
|
|
241
|
+
if required in INFRA_NAMES:
|
|
242
|
+
infra.add(required)
|
|
237
243
|
return infra
|
|
238
244
|
|
|
239
245
|
|
|
@@ -258,7 +264,7 @@ def _service_environment(service: ServiceSpec, services: list[ServiceSpec]) -> d
|
|
|
258
264
|
env["DB_PASSWORD"] = "${DB_PASSWORD:-change_me}"
|
|
259
265
|
env["DB_NAME"] = "${DB_NAME:-service_db}"
|
|
260
266
|
|
|
261
|
-
if service["type"] == "worker":
|
|
267
|
+
if service["type"] == "worker" or "workers" in service.get("features", []):
|
|
262
268
|
broker = service.get("broker", "redis")
|
|
263
269
|
if broker == "redis":
|
|
264
270
|
env["CELERY_BROKER_URL"] = "redis://redis:6379/0"
|
|
@@ -306,6 +312,9 @@ def render_compose(spec: ClusterSpec) -> dict[str, Any]:
|
|
|
306
312
|
if service["type"] == "worker":
|
|
307
313
|
requires.add("redis" if service.get("broker", "redis") == "redis" else "rabbitmq")
|
|
308
314
|
|
|
315
|
+
if "workers" in service.get("features", []):
|
|
316
|
+
requires.add("redis" if service.get("broker", "redis") == "redis" else "rabbitmq")
|
|
317
|
+
|
|
309
318
|
if "caching" in service.get("features", []):
|
|
310
319
|
requires.add("redis")
|
|
311
320
|
|
|
@@ -432,14 +441,14 @@ def _build_readme(spec: ClusterSpec) -> str:
|
|
|
432
441
|
"From the workspace root, run all tests:",
|
|
433
442
|
"",
|
|
434
443
|
"```bash",
|
|
435
|
-
"pytest",
|
|
444
|
+
"uv run pytest",
|
|
436
445
|
"```",
|
|
437
446
|
"",
|
|
438
447
|
"Run tests for a specific service:",
|
|
439
448
|
"",
|
|
440
449
|
"```bash",
|
|
441
|
-
"pytest tests/unit/{service_name}",
|
|
442
|
-
"pytest tests/acceptance/{service_name}",
|
|
450
|
+
"uv run pytest tests/unit/{service_name}",
|
|
451
|
+
"uv run pytest tests/acceptance/{service_name}",
|
|
443
452
|
"```",
|
|
444
453
|
"",
|
|
445
454
|
"## Run",
|
|
@@ -49,6 +49,7 @@ dev = [
|
|
|
49
49
|
asyncio_mode = "auto"
|
|
50
50
|
testpaths = ["tests"]
|
|
51
51
|
pythonpath = ["src"]
|
|
52
|
+
consider_namespace_packages = true
|
|
52
53
|
|
|
53
54
|
[tool.mypy]
|
|
54
55
|
python_version = "3.12"
|
|
@@ -289,6 +290,19 @@ def _move_tests_to_workspace_root(
|
|
|
289
290
|
unit_dst.mkdir(parents=True, exist_ok=True)
|
|
290
291
|
acceptance_dst.mkdir(parents=True, exist_ok=True)
|
|
291
292
|
|
|
293
|
+
# Ensure every test directory level has an __init__.py so pytest uses
|
|
294
|
+
# package-based import paths and avoids module-name collisions across services.
|
|
295
|
+
def _ensure_init(dir_path: Path) -> None:
|
|
296
|
+
init = dir_path / "__init__.py"
|
|
297
|
+
if not init.exists():
|
|
298
|
+
init.write_text("", encoding="utf-8")
|
|
299
|
+
|
|
300
|
+
_ensure_init(tests_root)
|
|
301
|
+
_ensure_init(tests_root / "unit")
|
|
302
|
+
_ensure_init(tests_root / "acceptance")
|
|
303
|
+
_ensure_init(unit_dst)
|
|
304
|
+
_ensure_init(acceptance_dst)
|
|
305
|
+
|
|
292
306
|
def _copy_tree(src_dir: Path, dst_dir: Path) -> None:
|
|
293
307
|
if not src_dir.is_dir():
|
|
294
308
|
return
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/caching/conftest.fragment.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/caching/docker-compose.fragment.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/caching/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/caching/settings.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/logging/conftest.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/logging/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/logging/settings.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/metrics/conftest.fragment.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/metrics/docker-compose.fragment.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/metrics/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/metrics/settings.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/tracing/conftest.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/tracing/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/tracing/settings.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/workers/conftest.fragment.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/workers/docker-compose.fragment.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/workers/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/workers/settings.fragment.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/workers/src/app/workers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/workers/src/app/workers/tasks.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/src/csrd_utils/features/workers/tests/test_workers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.52 → csrd_utils-0.3.55}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|