csrd-utils 0.3.43__tar.gz → 0.3.45__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.43 → csrd_utils-0.3.45}/PKG-INFO +1 -1
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/pyproject.toml +1 -1
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/__main__.py +0 -3
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/augmentor.py +32 -3
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/doctor.py +15 -3
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/generator.py +133 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/pyproject.toml +0 -3
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/conftest.py +1 -5
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/test_items.py +4 -4
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_cache.py +1 -1
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_settings_contracts.py +5 -5
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_worker_broker.py +1 -1
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/.gitignore +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/README.md +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/__init__.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/audit.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/cluster.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/caching/conftest.fragment.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/caching/docker-compose.fragment.yaml +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/caching/manifest.yaml +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/caching/requirements.fragment +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/caching/settings.fragment.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/caching/src/app/cache.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/caching/tests/test_cache.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/logging/conftest.fragment.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/logging/manifest.yaml +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/logging/requirements.fragment +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/logging/settings.fragment.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/logging/src/app/middleware/logging.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/logging/tests/test_logging_middleware.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/metrics/conftest.fragment.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/metrics/docker-compose.fragment.yaml +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/metrics/manifest.yaml +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/metrics/requirements.fragment +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/metrics/settings.fragment.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/metrics/src/app/middleware/metrics.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/metrics/tests/test_metrics_middleware.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/tracing/conftest.fragment.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/tracing/manifest.yaml +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/tracing/requirements.fragment +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/tracing/settings.fragment.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/tracing/src/app/middleware/tracing.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/tracing/tests/test_tracing_middleware.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/workers/conftest.fragment.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/workers/docker-compose.fragment.yaml +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/workers/manifest.yaml +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/workers/requirements.fragment +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/workers/settings.fragment.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/workers/src/app/workers/__init__.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/workers/src/app/workers/celery_app.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/workers/src/app/workers/tasks.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/workers/tests/test_workers.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/fragments.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/resources.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/README.md +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/cookiecutter.json +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/hooks/post_gen_project.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/.cookiecutterignore +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/.dockerignore +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/cache.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/delegates/__init__.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/delegates/upstreams.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/delegates.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/item_repository.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/worker_broker.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/docker-compose.yml +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/middleware/__init__.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/middleware/logging.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/py.typed +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/repositories/item_repository.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/__init__.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/acceptance/test_logging_middleware.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_database_settings.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_delegates.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/items_view.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/workers/__init__.py +0 -0
- {csrd_utils-0.3.43 → csrd_utils-0.3.45}/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.45
|
|
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
|
|
@@ -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))
|
|
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
|
-
|
|
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
|
-
|
|
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 = [
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
@@ -9,6 +9,61 @@ from cookiecutter.main import cookiecutter
|
|
|
9
9
|
|
|
10
10
|
from .resources import templates_path
|
|
11
11
|
|
|
12
|
+
_WORKSPACE_PYTEST_TOML = """\
|
|
13
|
+
# Workspace-level tool configuration.
|
|
14
|
+
# Add [project] and [build-system] sections if you need to publish this workspace as a package.
|
|
15
|
+
|
|
16
|
+
[tool.pytest.ini_options]
|
|
17
|
+
asyncio_mode = "auto"
|
|
18
|
+
testpaths = ["tests"]
|
|
19
|
+
pythonpath = ["src"]
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
_WORKSPACE_README = """\
|
|
23
|
+
# Workspace
|
|
24
|
+
|
|
25
|
+
Generated workspace with one or more microservices.
|
|
26
|
+
|
|
27
|
+
## Run tests
|
|
28
|
+
|
|
29
|
+
From the workspace root, run all tests:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pytest
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Run tests for a specific service:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pytest tests/unit/{service_name}
|
|
39
|
+
pytest tests/acceptance/{service_name}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Run tests in watch mode (requires `pytest-watch`):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
ptw
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Run services
|
|
49
|
+
|
|
50
|
+
See individual service README files under `src/{service_name}/README.md` for instructions on running each service.
|
|
51
|
+
|
|
52
|
+
## Docker Compose
|
|
53
|
+
|
|
54
|
+
Run all services and infrastructure:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
docker compose up --build
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Check service health:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
./smoke.sh
|
|
64
|
+
```
|
|
65
|
+
"""
|
|
66
|
+
|
|
12
67
|
|
|
13
68
|
def _build_context(
|
|
14
69
|
*,
|
|
@@ -121,6 +176,13 @@ def generate_service(
|
|
|
121
176
|
|
|
122
177
|
# Move docker files to workspace root and update Dockerfile
|
|
123
178
|
_move_docker_files_to_workspace_root(generated_path, output_root, name)
|
|
179
|
+
_move_tests_to_workspace_root(generated_path, output_root, name)
|
|
180
|
+
|
|
181
|
+
# Ensure workspace root has a pyproject.toml with pytest config.
|
|
182
|
+
_write_workspace_pyproject(output_root)
|
|
183
|
+
|
|
184
|
+
# Ensure workspace root has a README with test running instructions.
|
|
185
|
+
_write_workspace_readme(output_root)
|
|
124
186
|
|
|
125
187
|
return generated_path
|
|
126
188
|
|
|
@@ -154,3 +216,74 @@ def _move_docker_files_to_workspace_root(
|
|
|
154
216
|
)
|
|
155
217
|
dockerfile_dst.write_text(dockerfile_content)
|
|
156
218
|
dockerfile_src.unlink()
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _move_tests_to_workspace_root(
|
|
222
|
+
service_path: Path, workspace_root: Path, service_name: str
|
|
223
|
+
) -> None:
|
|
224
|
+
"""Move generated tests into workspace-level namespaced suites."""
|
|
225
|
+
service_name_snake = service_name.replace("-", "_")
|
|
226
|
+
tests_src = service_path / "tests"
|
|
227
|
+
if not tests_src.is_dir():
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
tests_root = workspace_root / "tests"
|
|
231
|
+
unit_dst = tests_root / "unit" / service_name_snake
|
|
232
|
+
acceptance_dst = tests_root / "acceptance" / service_name_snake
|
|
233
|
+
unit_dst.mkdir(parents=True, exist_ok=True)
|
|
234
|
+
acceptance_dst.mkdir(parents=True, exist_ok=True)
|
|
235
|
+
|
|
236
|
+
def _copy_tree(src_dir: Path, dst_dir: Path) -> None:
|
|
237
|
+
if not src_dir.is_dir():
|
|
238
|
+
return
|
|
239
|
+
for src_file in src_dir.rglob("*"):
|
|
240
|
+
if src_file.is_file():
|
|
241
|
+
rel = src_file.relative_to(src_dir)
|
|
242
|
+
dst_file = dst_dir / rel
|
|
243
|
+
dst_file.parent.mkdir(parents=True, exist_ok=True)
|
|
244
|
+
shutil.copy2(src_file, dst_file)
|
|
245
|
+
|
|
246
|
+
_copy_tree(tests_src / "unit", unit_dst)
|
|
247
|
+
_copy_tree(tests_src / "acceptance", acceptance_dst)
|
|
248
|
+
|
|
249
|
+
# Service-level API tests belong under acceptance for workspace execution.
|
|
250
|
+
top_level_acceptance = ["test_items.py", "conftest.py"]
|
|
251
|
+
for filename in top_level_acceptance:
|
|
252
|
+
src_file = tests_src / filename
|
|
253
|
+
if src_file.is_file():
|
|
254
|
+
shutil.copy2(src_file, acceptance_dst / filename)
|
|
255
|
+
|
|
256
|
+
shutil.rmtree(tests_src, ignore_errors=True)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _write_workspace_pyproject(workspace_root: Path) -> None:
|
|
260
|
+
"""Write or update workspace-level pyproject.toml with pytest configuration.
|
|
261
|
+
|
|
262
|
+
If the file does not exist it is created from scratch. If it already exists
|
|
263
|
+
and already contains a ``[tool.pytest.ini_options]`` section it is left
|
|
264
|
+
untouched (the user may have customised it). Otherwise the pytest block is
|
|
265
|
+
appended so that a second ``generate_service`` call on the same workspace
|
|
266
|
+
gains the config without clobbering prior edits.
|
|
267
|
+
"""
|
|
268
|
+
dst = workspace_root / "pyproject.toml"
|
|
269
|
+
if dst.exists():
|
|
270
|
+
existing = dst.read_text(encoding="utf-8")
|
|
271
|
+
if "[tool.pytest.ini_options]" in existing:
|
|
272
|
+
return
|
|
273
|
+
dst.write_text(
|
|
274
|
+
existing.rstrip() + "\n\n" + _WORKSPACE_PYTEST_TOML,
|
|
275
|
+
encoding="utf-8",
|
|
276
|
+
)
|
|
277
|
+
else:
|
|
278
|
+
dst.write_text(_WORKSPACE_PYTEST_TOML, encoding="utf-8")
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _write_workspace_readme(workspace_root: Path) -> None:
|
|
282
|
+
"""Write workspace-level README.md with test running and setup instructions.
|
|
283
|
+
|
|
284
|
+
If the file does not exist it is created from scratch. If it already exists
|
|
285
|
+
it is left untouched (the user may have customised it).
|
|
286
|
+
"""
|
|
287
|
+
dst = workspace_root / "README.md"
|
|
288
|
+
if not dst.exists():
|
|
289
|
+
dst.write_text(_WORKSPACE_README, encoding="utf-8")
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
"""Shared test fixtures for {{ cookiecutter.service_name }}."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
import sys
|
|
5
|
-
from pathlib import Path
|
|
6
4
|
|
|
7
5
|
import pytest
|
|
8
6
|
from fastapi.testclient import TestClient
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
from app import build_app
|
|
8
|
+
from {{cookiecutter.__service_name_snake}} import build_app
|
|
13
9
|
|
|
14
10
|
HAS_DATABASE = {{"True" if cookiecutter.has_database else "False"}}
|
|
15
11
|
DB_KIND = "{{ cookiecutter.database }}"
|
|
@@ -111,7 +111,7 @@ def test_delegate_create_and_list(monkeypatch, client):
|
|
|
111
111
|
return {"id": "d1", "name": payload["name"], "created_at": "2026-01-01T00:00:00Z"}
|
|
112
112
|
return {"{{ cookiecutter.model_name_plural | lower | replace(' ', '_') }}": [{"id": "d1", "name": "X", "created_at": "t"}]}
|
|
113
113
|
|
|
114
|
-
monkeypatch.setattr("
|
|
114
|
+
monkeypatch.setattr("{{cookiecutter.__service_name_snake}}.views.unversioned.items_view._delegate_request", fake_delegate_request)
|
|
115
115
|
|
|
116
116
|
created = client.post(
|
|
117
117
|
"/api/{{ cookiecutter.model_name_plural | lower | replace(' ', '_') }}", json={"name": "X"}
|
|
@@ -130,7 +130,7 @@ def test_delegate_error_mapping(monkeypatch, client):
|
|
|
130
130
|
async def fake_delegate_request(_method: str, _path: str, payload=None):
|
|
131
131
|
raise HTTPException(status_code=404, detail="not found")
|
|
132
132
|
|
|
133
|
-
monkeypatch.setattr("
|
|
133
|
+
monkeypatch.setattr("{{cookiecutter.__service_name_snake}}.views.unversioned.items_view._delegate_request", fake_delegate_request)
|
|
134
134
|
response = client.get("/api/{{ cookiecutter.model_name_plural | lower | replace(' ', '_') }}/missing")
|
|
135
135
|
assert response.status_code == 404
|
|
136
136
|
|
|
@@ -147,7 +147,7 @@ def test_delegate_get_update_upsert_delete(monkeypatch, client):
|
|
|
147
147
|
return {"id": "d2", "deleted": True}
|
|
148
148
|
return {}
|
|
149
149
|
|
|
150
|
-
monkeypatch.setattr("
|
|
150
|
+
monkeypatch.setattr("{{cookiecutter.__service_name_snake}}.views.unversioned.items_view._delegate_request", fake_delegate_request)
|
|
151
151
|
|
|
152
152
|
fetched = client.get("/api/{{ cookiecutter.model_name_plural | lower | replace(' ', '_') }}/d2")
|
|
153
153
|
assert fetched.status_code == 200
|
|
@@ -176,7 +176,7 @@ def test_delegate_list_invalid_payload_returns_502(monkeypatch, client):
|
|
|
176
176
|
async def fake_delegate_request(_method: str, _path: str, payload=None):
|
|
177
177
|
return {"{{ cookiecutter.model_name_plural | lower | replace(' ', '_') }}": "bad"}
|
|
178
178
|
|
|
179
|
-
monkeypatch.setattr("
|
|
179
|
+
monkeypatch.setattr("{{cookiecutter.__service_name_snake}}.views.unversioned.items_view._delegate_request", fake_delegate_request)
|
|
180
180
|
response = client.get("/api/{{ cookiecutter.model_name_plural | lower | replace(' ', '_') }}")
|
|
181
181
|
assert response.status_code == 502
|
|
182
182
|
{%- endif %}
|
|
@@ -17,7 +17,7 @@ def test_cache_client_uses_decode_responses() -> None:
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def test_cache_client_uses_runtime_cache_url() -> None:
|
|
20
|
-
settings_mod = importlib.import_module("
|
|
20
|
+
settings_mod = importlib.import_module("{{cookiecutter.__service_name_snake}}.settings")
|
|
21
21
|
original = settings_mod.settings.cache_url
|
|
22
22
|
try:
|
|
23
23
|
settings_mod.settings.cache_url = "redis://cache-host:6381/2"
|
|
@@ -73,11 +73,11 @@ def test_validate_db_credentials_rejects_weak_defaults(
|
|
|
73
73
|
|
|
74
74
|
def test_app_package_init_modules_expose_tuple_all() -> None:
|
|
75
75
|
for module_name in [
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
76
|
+
"{{cookiecutter.__service_name_snake}}.delegates",
|
|
77
|
+
"{{cookiecutter.__service_name_snake}}.dependencies",
|
|
78
|
+
"{{cookiecutter.__service_name_snake}}.middleware",
|
|
79
|
+
"{{cookiecutter.__service_name_snake}}.repositories",
|
|
80
|
+
"{{cookiecutter.__service_name_snake}}.workers",
|
|
81
81
|
]:
|
|
82
82
|
module = importlib.import_module(module_name)
|
|
83
83
|
exports = getattr(module, "__all__", ())
|
|
@@ -16,7 +16,7 @@ def test_ping_task_returns_pong() -> None:
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def test_worker_broker_url_reflects_runtime_settings() -> None:
|
|
19
|
-
settings_mod = importlib.import_module("
|
|
19
|
+
settings_mod = importlib.import_module("{{cookiecutter.__service_name_snake}}.settings")
|
|
20
20
|
original = settings_mod.settings.worker_broker_url
|
|
21
21
|
try:
|
|
22
22
|
settings_mod.settings.worker_broker_url = "amqp://runtime-test"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/caching/conftest.fragment.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/caching/docker-compose.fragment.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/caching/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/caching/settings.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/logging/conftest.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/logging/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/logging/settings.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/metrics/conftest.fragment.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/metrics/docker-compose.fragment.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/metrics/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/metrics/settings.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/tracing/conftest.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/tracing/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/tracing/settings.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/workers/conftest.fragment.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/workers/docker-compose.fragment.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/workers/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/workers/settings.fragment.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/workers/src/app/workers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/workers/src/app/workers/tasks.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/src/csrd_utils/features/workers/tests/test_workers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.43 → csrd_utils-0.3.45}/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
|