csrd-utils 0.3.43__tar.gz → 0.3.44__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 (87) hide show
  1. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/PKG-INFO +1 -1
  2. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/pyproject.toml +1 -1
  3. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/__main__.py +0 -3
  4. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/augmentor.py +32 -3
  5. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/doctor.py +15 -3
  6. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/generator.py +39 -0
  7. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/.gitignore +0 -0
  8. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/README.md +0 -0
  9. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/__init__.py +0 -0
  10. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/audit.py +0 -0
  11. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/cluster.py +0 -0
  12. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/caching/conftest.fragment.py +0 -0
  13. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/caching/docker-compose.fragment.yaml +0 -0
  14. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/caching/manifest.yaml +0 -0
  15. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/caching/requirements.fragment +0 -0
  16. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/caching/settings.fragment.py +0 -0
  17. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/caching/src/app/cache.py +0 -0
  18. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/caching/tests/test_cache.py +0 -0
  19. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/logging/conftest.fragment.py +0 -0
  20. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/logging/manifest.yaml +0 -0
  21. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/logging/requirements.fragment +0 -0
  22. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/logging/settings.fragment.py +0 -0
  23. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/logging/src/app/middleware/logging.py +0 -0
  24. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/logging/tests/test_logging_middleware.py +0 -0
  25. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/metrics/conftest.fragment.py +0 -0
  26. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/metrics/docker-compose.fragment.yaml +0 -0
  27. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/metrics/manifest.yaml +0 -0
  28. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/metrics/requirements.fragment +0 -0
  29. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/metrics/settings.fragment.py +0 -0
  30. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/metrics/src/app/middleware/metrics.py +0 -0
  31. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/metrics/tests/test_metrics_middleware.py +0 -0
  32. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/tracing/conftest.fragment.py +0 -0
  33. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/tracing/manifest.yaml +0 -0
  34. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/tracing/requirements.fragment +0 -0
  35. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/tracing/settings.fragment.py +0 -0
  36. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/tracing/src/app/middleware/tracing.py +0 -0
  37. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/tracing/tests/test_tracing_middleware.py +0 -0
  38. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/workers/conftest.fragment.py +0 -0
  39. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/workers/docker-compose.fragment.yaml +0 -0
  40. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/workers/manifest.yaml +0 -0
  41. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/workers/requirements.fragment +0 -0
  42. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/workers/settings.fragment.py +0 -0
  43. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/workers/src/app/workers/__init__.py +0 -0
  44. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/workers/src/app/workers/celery_app.py +0 -0
  45. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/workers/src/app/workers/tasks.py +0 -0
  46. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/features/workers/tests/test_workers.py +0 -0
  47. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/fragments.py +0 -0
  48. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/resources.py +0 -0
  49. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/README.md +0 -0
  50. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/cookiecutter.json +0 -0
  51. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/hooks/post_gen_project.py +0 -0
  52. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/.cookiecutterignore +0 -0
  53. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/.dockerignore +0 -0
  54. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
  55. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
  56. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
  57. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/cache.py +0 -0
  58. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/delegates/__init__.py +0 -0
  59. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/delegates/upstreams.py +0 -0
  60. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
  61. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/delegates.py +0 -0
  62. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/item_repository.py +0 -0
  63. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/worker_broker.py +0 -0
  64. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/docker-compose.yml +0 -0
  65. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/middleware/__init__.py +0 -0
  66. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/middleware/logging.py +0 -0
  67. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/py.typed +0 -0
  68. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/pyproject.toml +0 -0
  69. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
  70. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/repositories/item_repository.py +0 -0
  71. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
  72. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
  73. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/__init__.py +0 -0
  74. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/acceptance/test_logging_middleware.py +0 -0
  75. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/conftest.py +0 -0
  76. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/test_items.py +0 -0
  77. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_cache.py +0 -0
  78. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_database_settings.py +0 -0
  79. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_delegates.py +0 -0
  80. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_settings_contracts.py +0 -0
  81. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_worker_broker.py +0 -0
  82. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
  83. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
  84. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
  85. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/items_view.py +0 -0
  86. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/workers/__init__.py +0 -0
  87. {csrd_utils-0.3.43 → csrd_utils-0.3.44}/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.43
3
+ Version: 0.3.44
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.43"
3
+ version = "0.3.44"
4
4
  description = "CLI utilities for csrd service generation and feature augmentation"
5
5
  license = { text = "MIT" }
6
6
  requires-python = ">=3.12"
@@ -598,7 +598,6 @@ def _is_service_root(path: Path) -> bool:
598
598
  return (
599
599
  path.is_dir()
600
600
  and (path / "requirements.txt").is_file()
601
- and (path / "tests").is_dir()
602
601
  and (path / "pyproject.toml").is_file()
603
602
  )
604
603
 
@@ -651,8 +650,6 @@ def _workspace_root_for_service(service_root: Path) -> Path | None:
651
650
  def _validate_service_root(service_root: Path) -> None:
652
651
  if not service_root.is_dir():
653
652
  raise ValueError(f"Service path is not a directory: {service_root}")
654
- if not (service_root / "tests").is_dir():
655
- raise ValueError(f"Missing required directory: {service_root / 'tests'}")
656
653
  if not (service_root / "requirements.txt").is_file():
657
654
  raise ValueError(f"Missing required file: {service_root / 'requirements.txt'}")
658
655
  if not (service_root / "pyproject.toml").is_file():
@@ -66,6 +66,20 @@ class ServiceAugmentor:
66
66
  )
67
67
  return candidate if candidate is not None else self.service_root / "src" / "app"
68
68
 
69
+ @property
70
+ def _workspace_root(self) -> Path | None:
71
+ parent = self.service_root.parent
72
+ if parent.name == "src" and (parent.parent / ".csrd-workspace").is_file():
73
+ return parent.parent
74
+ return None
75
+
76
+ @property
77
+ def _tests_dir(self) -> Path:
78
+ workspace_root = self._workspace_root
79
+ if workspace_root is not None:
80
+ return workspace_root / "tests"
81
+ return self.service_root / "tests"
82
+
69
83
  def load_feature(self, feature_name: str) -> bool:
70
84
  """
71
85
  Load and validate feature manifest.
@@ -108,7 +122,7 @@ class ServiceAugmentor:
108
122
  bool
109
123
  True if valid
110
124
  """
111
- required_dirs = [str(self._pkg_dir.relative_to(self.service_root)), "tests"]
125
+ required_dirs = [str(self._pkg_dir.relative_to(self.service_root))]
112
126
  required_files = ["requirements.txt"]
113
127
 
114
128
  for d in required_dirs:
@@ -316,7 +330,12 @@ class ServiceAugmentor:
316
330
  return True # Not required
317
331
 
318
332
  try:
319
- conftest_path = self.service_root / "tests/conftest.py"
333
+ if self._workspace_root is not None:
334
+ conftest_path = (
335
+ self._tests_dir / "acceptance" / self.service_root.name / "conftest.py"
336
+ )
337
+ else:
338
+ conftest_path = self._tests_dir / "conftest.py"
320
339
  if not conftest_path.exists():
321
340
  print("⚠ conftest.py not found, skipping")
322
341
  return True
@@ -401,7 +420,17 @@ class ServiceAugmentor:
401
420
  for src_file in test_dir.rglob("*"):
402
421
  if src_file.is_file():
403
422
  rel_path = src_file.relative_to(test_dir)
404
- dest_file = self.service_root / "tests" / rel_path
423
+ if self._workspace_root is not None:
424
+ parts = rel_path.parts
425
+ if parts and parts[0] in {"unit", "acceptance"}:
426
+ scoped_rel = Path(parts[0]) / self.service_root.name / Path(*parts[1:])
427
+ elif rel_path.name == "conftest.py":
428
+ scoped_rel = Path("acceptance") / self.service_root.name / "conftest.py"
429
+ else:
430
+ scoped_rel = Path("acceptance") / self.service_root.name / rel_path
431
+ dest_file = self._tests_dir / scoped_rel
432
+ else:
433
+ dest_file = self._tests_dir / rel_path
405
434
 
406
435
  dest_file.parent.mkdir(parents=True, exist_ok=True)
407
436
  shutil.copy2(src_file, dest_file)
@@ -19,7 +19,7 @@ def run_doctor(service_root: Path) -> DoctorReport:
19
19
  errors: list[str] = []
20
20
  warnings: list[str] = []
21
21
 
22
- required_dirs = ["tests"]
22
+ required_dirs: list[str] = []
23
23
  required_files = ["requirements.txt", "pyproject.toml"]
24
24
 
25
25
  compose_candidates = [root / "docker-compose.yml"]
@@ -65,9 +65,21 @@ def run_doctor(service_root: Path) -> DoctorReport:
65
65
  marker_files: dict[str, str] = {}
66
66
  if pkg_dir is not None:
67
67
  marker_files[f"{pkg_dir.name}/settings.py"] = "# [INSERT: fields]"
68
- marker_files["tests/conftest.py"] = "# [INSERT: fixtures]"
68
+ workspace_root = None
69
+ if root.parent.name == "src" and (root.parent.parent / ".csrd-workspace").is_file():
70
+ workspace_root = root.parent.parent
71
+
72
+ if workspace_root is not None and pkg_dir is not None:
73
+ marker_files[f"tests/acceptance/{pkg_dir.name}/conftest.py"] = "# [INSERT: fixtures]"
74
+ else:
75
+ marker_files["tests/conftest.py"] = "# [INSERT: fixtures]"
76
+
69
77
  for rel, marker in marker_files.items():
70
- path = root / rel
78
+ path = (
79
+ (workspace_root / rel)
80
+ if workspace_root is not None and rel.startswith("tests/")
81
+ else (root / rel)
82
+ )
71
83
  if path.exists():
72
84
  text = path.read_text(encoding="utf-8")
73
85
  if marker not in text:
@@ -121,6 +121,7 @@ def generate_service(
121
121
 
122
122
  # Move docker files to workspace root and update Dockerfile
123
123
  _move_docker_files_to_workspace_root(generated_path, output_root, name)
124
+ _move_tests_to_workspace_root(generated_path, output_root, name)
124
125
 
125
126
  return generated_path
126
127
 
@@ -154,3 +155,41 @@ def _move_docker_files_to_workspace_root(
154
155
  )
155
156
  dockerfile_dst.write_text(dockerfile_content)
156
157
  dockerfile_src.unlink()
158
+
159
+
160
+ def _move_tests_to_workspace_root(
161
+ service_path: Path, workspace_root: Path, service_name: str
162
+ ) -> None:
163
+ """Move generated tests into workspace-level namespaced suites."""
164
+ service_name_snake = service_name.replace("-", "_")
165
+ tests_src = service_path / "tests"
166
+ if not tests_src.is_dir():
167
+ return
168
+
169
+ tests_root = workspace_root / "tests"
170
+ unit_dst = tests_root / "unit" / service_name_snake
171
+ acceptance_dst = tests_root / "acceptance" / service_name_snake
172
+ unit_dst.mkdir(parents=True, exist_ok=True)
173
+ acceptance_dst.mkdir(parents=True, exist_ok=True)
174
+
175
+ def _copy_tree(src_dir: Path, dst_dir: Path) -> None:
176
+ if not src_dir.is_dir():
177
+ return
178
+ for src_file in src_dir.rglob("*"):
179
+ if src_file.is_file():
180
+ rel = src_file.relative_to(src_dir)
181
+ dst_file = dst_dir / rel
182
+ dst_file.parent.mkdir(parents=True, exist_ok=True)
183
+ shutil.copy2(src_file, dst_file)
184
+
185
+ _copy_tree(tests_src / "unit", unit_dst)
186
+ _copy_tree(tests_src / "acceptance", acceptance_dst)
187
+
188
+ # Service-level API tests belong under acceptance for workspace execution.
189
+ top_level_acceptance = ["test_items.py", "conftest.py"]
190
+ for filename in top_level_acceptance:
191
+ src_file = tests_src / filename
192
+ if src_file.is_file():
193
+ shutil.copy2(src_file, acceptance_dst / filename)
194
+
195
+ shutil.rmtree(tests_src, ignore_errors=True)
File without changes
File without changes