csrd-utils 0.3.52__tar.gz → 0.3.54__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 (86) hide show
  1. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/PKG-INFO +1 -1
  2. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/pyproject.toml +1 -1
  3. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/__main__.py +38 -0
  4. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/augmentor.py +25 -4
  5. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/cluster.py +13 -4
  6. csrd_utils-0.3.52/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/py.typed +0 -0
  7. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/.gitignore +0 -0
  8. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/README.md +0 -0
  9. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/__init__.py +0 -0
  10. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/audit.py +0 -0
  11. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/doctor.py +0 -0
  12. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/caching/conftest.fragment.py +0 -0
  13. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/caching/docker-compose.fragment.yaml +0 -0
  14. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/caching/manifest.yaml +0 -0
  15. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/caching/requirements.fragment +0 -0
  16. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/caching/settings.fragment.py +0 -0
  17. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/caching/src/app/cache.py +0 -0
  18. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/caching/tests/test_cache.py +0 -0
  19. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/logging/conftest.fragment.py +0 -0
  20. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/logging/manifest.yaml +0 -0
  21. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/logging/requirements.fragment +0 -0
  22. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/logging/settings.fragment.py +0 -0
  23. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/logging/src/app/middleware/logging.py +0 -0
  24. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/logging/tests/test_logging_middleware.py +0 -0
  25. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/metrics/conftest.fragment.py +0 -0
  26. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/metrics/docker-compose.fragment.yaml +0 -0
  27. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/metrics/manifest.yaml +0 -0
  28. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/metrics/requirements.fragment +0 -0
  29. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/metrics/settings.fragment.py +0 -0
  30. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/metrics/src/app/middleware/metrics.py +0 -0
  31. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/metrics/tests/test_metrics_middleware.py +0 -0
  32. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/tracing/conftest.fragment.py +0 -0
  33. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/tracing/manifest.yaml +0 -0
  34. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/tracing/requirements.fragment +0 -0
  35. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/tracing/settings.fragment.py +0 -0
  36. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/tracing/src/app/middleware/tracing.py +0 -0
  37. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/tracing/tests/test_tracing_middleware.py +0 -0
  38. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/workers/conftest.fragment.py +0 -0
  39. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/workers/docker-compose.fragment.yaml +0 -0
  40. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/workers/manifest.yaml +0 -0
  41. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/workers/requirements.fragment +0 -0
  42. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/workers/settings.fragment.py +0 -0
  43. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/workers/src/app/workers/__init__.py +0 -0
  44. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/workers/src/app/workers/celery_app.py +0 -0
  45. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/workers/src/app/workers/tasks.py +0 -0
  46. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/features/workers/tests/test_workers.py +0 -0
  47. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/fragments.py +0 -0
  48. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/generator.py +0 -0
  49. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/resources.py +0 -0
  50. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/README.md +0 -0
  51. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/cookiecutter.json +0 -0
  52. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/hooks/post_gen_project.py +0 -0
  53. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/.cookiecutterignore +0 -0
  54. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/.dockerignore +0 -0
  55. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
  56. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
  57. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
  58. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/cache.py +0 -0
  59. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/delegates/__init__.py +0 -0
  60. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/delegates/upstreams.py +0 -0
  61. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
  62. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/delegates.py +0 -0
  63. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/item_repository.py +0 -0
  64. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/worker_broker.py +0 -0
  65. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/docker-compose.yml +0 -0
  66. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/middleware/__init__.py +0 -0
  67. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/middleware/logging.py +0 -0
  68. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
  69. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/repositories/item_repository.py +0 -0
  70. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
  71. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
  72. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/__init__.py +0 -0
  73. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/acceptance/test_logging_middleware.py +0 -0
  74. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/conftest.py +0 -0
  75. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/test_items.py +0 -0
  76. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_cache.py +0 -0
  77. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_database_settings.py +0 -0
  78. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_delegates.py +0 -0
  79. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_settings_contracts.py +0 -0
  80. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_worker_broker.py +0 -0
  81. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
  82. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
  83. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
  84. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/items_view.py +0 -0
  85. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/workers/__init__.py +0 -0
  86. {csrd_utils-0.3.52 → csrd_utils-0.3.54}/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.52
3
+ Version: 0.3.54
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.52"
3
+ version = "0.3.54"
4
4
  description = "CLI utilities for csrd service generation and feature augmentation"
5
5
  license = { text = "MIT" }
6
6
  requires-python = ">=3.12"
@@ -881,6 +881,37 @@ def _ensure_unique_ports(services: list[ServiceSpec], preserve_name: str) -> Non
881
881
  used.add(port)
882
882
 
883
883
 
884
+ def _sync_workspace_service_feature(
885
+ workspace_root: Path, service_name: str, feature_name: str
886
+ ) -> Path | None:
887
+ spec = _load_workspace_cluster_spec(workspace_root)
888
+ services = spec.get("services", [])
889
+ target = next(
890
+ (
891
+ cast(ServiceSpec, dict(service))
892
+ for service in services
893
+ if str(service.get("name", "")).strip() == service_name
894
+ ),
895
+ None,
896
+ )
897
+ if target is None:
898
+ return None
899
+
900
+ features = list(target.get("features", []))
901
+ if feature_name not in features:
902
+ features.append(feature_name)
903
+ target["features"] = features
904
+
905
+ requires = list(target.get("requires", []))
906
+ if feature_name in {"caching", "workers"} and "redis" not in requires:
907
+ requires.append("redis")
908
+ target["requires"] = requires
909
+
910
+ updated_spec = _upsert_workspace_service(spec, target, workspace_root.name)
911
+ _save_workspace_cluster_spec(workspace_root, updated_spec)
912
+ return generate_cluster(updated_spec, workspace_root, force=True)
913
+
914
+
884
915
  def _maybe_update_workspace_cluster(
885
916
  workspace_root: Path,
886
917
  service_config: GenerateServiceArgs,
@@ -1315,6 +1346,13 @@ def main() -> int:
1315
1346
  if args.plan:
1316
1347
  print("Plan validated.")
1317
1348
  else:
1349
+ feature_workspace_root = _workspace_root_for_service(resolved_service)
1350
+ if feature_workspace_root is not None:
1351
+ refreshed_cluster = _sync_workspace_service_feature(
1352
+ feature_workspace_root, resolved_service.name, args.feature
1353
+ )
1354
+ if refreshed_cluster is not None:
1355
+ print(f"Refreshed cluster scaffold at: {refreshed_cluster}")
1318
1356
  print(f"Feature '{args.feature}' added.")
1319
1357
  if changes:
1320
1358
  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
- # Copy file
391
- shutil.copy2(src_file, dest_file)
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
- shutil.copy2(src_file, dest_file)
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",
File without changes
File without changes