csrd-utils 0.3.44__tar.gz → 0.3.46__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.44 → csrd_utils-0.3.46}/PKG-INFO +1 -1
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/pyproject.toml +1 -1
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/__main__.py +4 -8
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/cluster.py +31 -14
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/generator.py +140 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/conftest.py +1 -5
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/test_items.py +4 -4
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_cache.py +1 -1
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_settings_contracts.py +5 -5
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_worker_broker.py +1 -1
- csrd_utils-0.3.44/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/pyproject.toml +0 -61
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/.gitignore +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/README.md +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/__init__.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/audit.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/augmentor.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/doctor.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/caching/conftest.fragment.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/caching/docker-compose.fragment.yaml +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/caching/manifest.yaml +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/caching/requirements.fragment +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/caching/settings.fragment.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/caching/src/app/cache.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/caching/tests/test_cache.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/logging/conftest.fragment.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/logging/manifest.yaml +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/logging/requirements.fragment +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/logging/settings.fragment.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/logging/src/app/middleware/logging.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/logging/tests/test_logging_middleware.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/metrics/conftest.fragment.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/metrics/docker-compose.fragment.yaml +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/metrics/manifest.yaml +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/metrics/requirements.fragment +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/metrics/settings.fragment.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/metrics/src/app/middleware/metrics.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/metrics/tests/test_metrics_middleware.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/tracing/conftest.fragment.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/tracing/manifest.yaml +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/tracing/requirements.fragment +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/tracing/settings.fragment.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/tracing/src/app/middleware/tracing.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/tracing/tests/test_tracing_middleware.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/workers/conftest.fragment.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/workers/docker-compose.fragment.yaml +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/workers/manifest.yaml +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/workers/requirements.fragment +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/workers/settings.fragment.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/workers/src/app/workers/__init__.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/workers/src/app/workers/celery_app.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/workers/src/app/workers/tasks.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/workers/tests/test_workers.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/fragments.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/resources.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/README.md +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/cookiecutter.json +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/hooks/post_gen_project.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/.cookiecutterignore +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/.dockerignore +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/Dockerfile +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/README.md +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/__init__.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/cache.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/delegates/__init__.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/delegates/upstreams.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/__init__.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/delegates.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/item_repository.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/dependencies/worker_broker.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/docker-compose.yml +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/middleware/__init__.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/middleware/logging.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/py.typed +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/repositories/__init__.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/repositories/item_repository.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/requirements.txt +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/settings.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/__init__.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/acceptance/test_logging_middleware.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_database_settings.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/tests/unit/test_delegates.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/__init__.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/__init__.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/health_view.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/views/unversioned/items_view.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.__service_name_snake}}/workers/__init__.py +0 -0
- {csrd_utils-0.3.44 → csrd_utils-0.3.46}/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.46
|
|
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
|
|
@@ -784,9 +784,7 @@ def _extract_service(config: ExtractWorkspaceServiceArgs) -> dict[str, object]:
|
|
|
784
784
|
_load_workspace_cluster_spec(target_workspace), source_service_spec, target_workspace.name
|
|
785
785
|
)
|
|
786
786
|
_save_workspace_cluster_spec(target_workspace, target_spec)
|
|
787
|
-
target_cluster_root = generate_cluster(
|
|
788
|
-
target_spec, target_workspace, force=True, workspace_mode=True
|
|
789
|
-
)
|
|
787
|
+
target_cluster_root = generate_cluster(target_spec, target_workspace, force=True)
|
|
790
788
|
|
|
791
789
|
source_cluster_root = None
|
|
792
790
|
if mode == "move":
|
|
@@ -795,9 +793,7 @@ def _extract_service(config: ExtractWorkspaceServiceArgs) -> dict[str, object]:
|
|
|
795
793
|
_load_workspace_cluster_spec(source_workspace), service_root.name, source_workspace.name
|
|
796
794
|
)
|
|
797
795
|
_save_workspace_cluster_spec(source_workspace, source_spec)
|
|
798
|
-
source_cluster_root = generate_cluster(
|
|
799
|
-
source_spec, source_workspace, force=True, workspace_mode=True
|
|
800
|
-
)
|
|
796
|
+
source_cluster_root = generate_cluster(source_spec, source_workspace, force=True)
|
|
801
797
|
|
|
802
798
|
return {
|
|
803
799
|
"ok": True,
|
|
@@ -897,7 +893,7 @@ def _maybe_update_workspace_cluster(
|
|
|
897
893
|
service_config,
|
|
898
894
|
overwrite=service_config["overwrite_if_exists"],
|
|
899
895
|
)
|
|
900
|
-
return generate_cluster(updated_spec, workspace_root, force=True
|
|
896
|
+
return generate_cluster(updated_spec, workspace_root, force=True)
|
|
901
897
|
|
|
902
898
|
service_roots = _workspace_service_roots(workspace_root)
|
|
903
899
|
generated_name = generated_service.name
|
|
@@ -914,7 +910,7 @@ def _maybe_update_workspace_cluster(
|
|
|
914
910
|
}
|
|
915
911
|
normalized = normalize_cluster_spec(bootstrap_spec)
|
|
916
912
|
_save_workspace_cluster_spec(workspace_root, normalized)
|
|
917
|
-
return generate_cluster(normalized, workspace_root, force=True
|
|
913
|
+
return generate_cluster(normalized, workspace_root, force=True)
|
|
918
914
|
|
|
919
915
|
|
|
920
916
|
def _build_parser() -> argparse.ArgumentParser:
|
|
@@ -426,6 +426,21 @@ def _build_readme(spec: ClusterSpec) -> str:
|
|
|
426
426
|
lines.append(f"- `{service['name']}` ({service['type']}) on port `{service['port']}`")
|
|
427
427
|
lines.extend(
|
|
428
428
|
[
|
|
429
|
+
"",
|
|
430
|
+
"## Tests",
|
|
431
|
+
"",
|
|
432
|
+
"From the workspace root, run all tests:",
|
|
433
|
+
"",
|
|
434
|
+
"```bash",
|
|
435
|
+
"pytest",
|
|
436
|
+
"```",
|
|
437
|
+
"",
|
|
438
|
+
"Run tests for a specific service:",
|
|
439
|
+
"",
|
|
440
|
+
"```bash",
|
|
441
|
+
"pytest tests/unit/{service_name}",
|
|
442
|
+
"pytest tests/acceptance/{service_name}",
|
|
443
|
+
"```",
|
|
429
444
|
"",
|
|
430
445
|
"## Run",
|
|
431
446
|
"",
|
|
@@ -456,23 +471,20 @@ def _build_smoke(spec: ClusterSpec) -> str:
|
|
|
456
471
|
return "\n".join(lines) + "\n"
|
|
457
472
|
|
|
458
473
|
|
|
459
|
-
def generate_cluster(
|
|
460
|
-
|
|
461
|
-
) -> Path:
|
|
462
|
-
normalized = normalize_cluster_spec(spec)
|
|
463
|
-
|
|
464
|
-
if workspace_mode:
|
|
465
|
-
# In workspace mode, write files directly to output_dir (workspace root)
|
|
466
|
-
cluster_root = output_dir.resolve()
|
|
467
|
-
else:
|
|
468
|
-
# In standalone mode, create a subdirectory with cluster name
|
|
469
|
-
cluster_root = output_dir.resolve() / normalized["cluster"]["name"]
|
|
474
|
+
def generate_cluster(spec: ClusterSpec, output_dir: Path, force: bool = False) -> Path:
|
|
475
|
+
"""Generate cluster scaffold in workspace mode.
|
|
470
476
|
|
|
477
|
+
Always operates at workspace root (no standalone mode).
|
|
478
|
+
"""
|
|
479
|
+
normalized = normalize_cluster_spec(spec)
|
|
480
|
+
cluster_root = output_dir.resolve()
|
|
471
481
|
cluster_root.mkdir(parents=True, exist_ok=True)
|
|
472
482
|
|
|
473
|
-
if
|
|
483
|
+
# In workspace mode, check if we'd overwrite existing cluster artifacts
|
|
484
|
+
compose_file = cluster_root / "docker-compose.yml"
|
|
485
|
+
if compose_file.exists() and not force:
|
|
474
486
|
raise FileExistsError(
|
|
475
|
-
f"
|
|
487
|
+
f"Cluster files already exist in {cluster_root}. Use --force to overwrite."
|
|
476
488
|
)
|
|
477
489
|
|
|
478
490
|
compose = render_compose(normalized)
|
|
@@ -480,7 +492,12 @@ def generate_cluster(
|
|
|
480
492
|
yaml.safe_dump(compose, sort_keys=False), encoding="utf-8"
|
|
481
493
|
)
|
|
482
494
|
(cluster_root / ".env.example").write_text(_build_env(normalized), encoding="utf-8")
|
|
483
|
-
|
|
495
|
+
|
|
496
|
+
# In workspace mode, preserve existing README (workspace-level, not cluster-specific)
|
|
497
|
+
readme_path = cluster_root / "README.md"
|
|
498
|
+
if not readme_path.exists():
|
|
499
|
+
readme_path.write_text(_build_readme(normalized), encoding="utf-8")
|
|
500
|
+
|
|
484
501
|
smoke_path = cluster_root / "smoke.sh"
|
|
485
502
|
smoke_path.write_text(_build_smoke(normalized), encoding="utf-8")
|
|
486
503
|
smoke_path.chmod(0o755)
|
|
@@ -9,6 +9,111 @@ from cookiecutter.main import cookiecutter
|
|
|
9
9
|
|
|
10
10
|
from .resources import templates_path
|
|
11
11
|
|
|
12
|
+
_WORKSPACE_PYPROJECT_BASE = """\
|
|
13
|
+
[build-system]
|
|
14
|
+
requires = ["hatchling"]
|
|
15
|
+
build-backend = "hatchling.build"
|
|
16
|
+
|
|
17
|
+
[project]
|
|
18
|
+
name = "workspace"
|
|
19
|
+
version = "0.1.0"
|
|
20
|
+
description = "Multi-service workspace"
|
|
21
|
+
readme = "README.md"
|
|
22
|
+
requires-python = ">=3.12"
|
|
23
|
+
license = {text = "MIT"}
|
|
24
|
+
|
|
25
|
+
dependencies = [
|
|
26
|
+
"fastapi>=0.110",
|
|
27
|
+
"uvicorn[standard]>=0.27",
|
|
28
|
+
"pydantic[email]>=2.5",
|
|
29
|
+
"csrd-versioning @ git+https://github.com/csrd-api/fastapi-common.git#subdirectory=packages/versioning",
|
|
30
|
+
"csrd-lifespan @ git+https://github.com/csrd-api/fastapi-common.git#subdirectory=packages/lifespan",
|
|
31
|
+
"csrd-repository @ git+https://github.com/csrd-api/fastapi-common.git#subdirectory=packages/repository",
|
|
32
|
+
"csrd-context @ git+https://github.com/csrd-api/fastapi-common.git#subdirectory=packages/context",
|
|
33
|
+
"httpx>=0.25",
|
|
34
|
+
"celery[redis]>=5.3",
|
|
35
|
+
"celery[amqp]>=5.3",
|
|
36
|
+
"redis>=5.0",
|
|
37
|
+
"csrd-logging @ git+https://github.com/csrd-api/fastapi-common.git#subdirectory=packages/logging",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
dev = [
|
|
42
|
+
"pytest>=7.4",
|
|
43
|
+
"pytest-asyncio>=0.21",
|
|
44
|
+
"mypy>=1.7",
|
|
45
|
+
"ruff>=0.1",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
asyncio_mode = "auto"
|
|
50
|
+
testpaths = ["tests"]
|
|
51
|
+
pythonpath = ["src"]
|
|
52
|
+
|
|
53
|
+
[tool.mypy]
|
|
54
|
+
python_version = "3.12"
|
|
55
|
+
warn_return_any = true
|
|
56
|
+
warn_unused_configs = true
|
|
57
|
+
disallow_untyped_defs = true
|
|
58
|
+
strict = true
|
|
59
|
+
|
|
60
|
+
[tool.ruff]
|
|
61
|
+
target-version = "py312"
|
|
62
|
+
line-length = 100
|
|
63
|
+
|
|
64
|
+
[tool.ruff.lint]
|
|
65
|
+
select = ["E", "F", "W", "I", "N", "UP", "BLE", "FBT", "A", "C4", "PIE", "SIM", "RUF"]
|
|
66
|
+
ignore = ["E501"]
|
|
67
|
+
|
|
68
|
+
[tool.ruff.lint.isort]
|
|
69
|
+
known-first-party = [""]
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
_WORKSPACE_README = """\
|
|
73
|
+
# Workspace
|
|
74
|
+
|
|
75
|
+
Generated workspace with one or more microservices.
|
|
76
|
+
|
|
77
|
+
## Run tests
|
|
78
|
+
|
|
79
|
+
From the workspace root, run all tests:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pytest
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Run tests for a specific service:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pytest tests/unit/{service_name}
|
|
89
|
+
pytest tests/acceptance/{service_name}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Run tests in watch mode (requires `pytest-watch`):
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
ptw
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Run services
|
|
99
|
+
|
|
100
|
+
See individual service README files under `src/{service_name}/README.md` for instructions on running each service.
|
|
101
|
+
|
|
102
|
+
## Docker Compose
|
|
103
|
+
|
|
104
|
+
Run all services and infrastructure:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
docker compose up --build
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Check service health:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
./smoke.sh
|
|
114
|
+
```
|
|
115
|
+
"""
|
|
116
|
+
|
|
12
117
|
|
|
13
118
|
def _build_context(
|
|
14
119
|
*,
|
|
@@ -123,6 +228,12 @@ def generate_service(
|
|
|
123
228
|
_move_docker_files_to_workspace_root(generated_path, output_root, name)
|
|
124
229
|
_move_tests_to_workspace_root(generated_path, output_root, name)
|
|
125
230
|
|
|
231
|
+
# Ensure workspace root has a pyproject.toml with pytest config.
|
|
232
|
+
_write_workspace_pyproject(output_root)
|
|
233
|
+
|
|
234
|
+
# Ensure workspace root has a README with test running instructions.
|
|
235
|
+
_write_workspace_readme(output_root)
|
|
236
|
+
|
|
126
237
|
return generated_path
|
|
127
238
|
|
|
128
239
|
|
|
@@ -193,3 +304,32 @@ def _move_tests_to_workspace_root(
|
|
|
193
304
|
shutil.copy2(src_file, acceptance_dst / filename)
|
|
194
305
|
|
|
195
306
|
shutil.rmtree(tests_src, ignore_errors=True)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _write_workspace_pyproject(workspace_root: Path) -> None:
|
|
310
|
+
"""Write workspace-level pyproject.toml with full build/project/tool configuration.
|
|
311
|
+
|
|
312
|
+
Creates complete pyproject.toml on first generation. On subsequent generations,
|
|
313
|
+
only writes if the file doesn't exist or lacks [project] section (user customization).
|
|
314
|
+
"""
|
|
315
|
+
dst = workspace_root / "pyproject.toml"
|
|
316
|
+
if dst.exists():
|
|
317
|
+
existing = dst.read_text(encoding="utf-8")
|
|
318
|
+
# If user already has a [project] section, assume they customized it
|
|
319
|
+
if "[project]" in existing:
|
|
320
|
+
return
|
|
321
|
+
# Otherwise write the full base config
|
|
322
|
+
dst.write_text(_WORKSPACE_PYPROJECT_BASE, encoding="utf-8")
|
|
323
|
+
else:
|
|
324
|
+
dst.write_text(_WORKSPACE_PYPROJECT_BASE, encoding="utf-8")
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _write_workspace_readme(workspace_root: Path) -> None:
|
|
328
|
+
"""Write workspace-level README.md with test running and setup instructions.
|
|
329
|
+
|
|
330
|
+
If the file does not exist it is created from scratch. If it already exists
|
|
331
|
+
it is left untouched (the user may have customised it).
|
|
332
|
+
"""
|
|
333
|
+
dst = workspace_root / "README.md"
|
|
334
|
+
if not dst.exists():
|
|
335
|
+
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"
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["hatchling"]
|
|
3
|
-
build-backend = "hatchling.build"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "{{ cookiecutter.service_name }}"
|
|
7
|
-
version = "0.1.0"
|
|
8
|
-
description = "{{ cookiecutter.service_description }}"
|
|
9
|
-
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.12"
|
|
11
|
-
license = {text = "MIT"}
|
|
12
|
-
authors = [{name = "Generated by csrd", email = "generated@csrd.local"}]
|
|
13
|
-
keywords = ["fastapi", "microservice", "csrd"]
|
|
14
|
-
|
|
15
|
-
dependencies = [
|
|
16
|
-
"fastapi>=0.110",
|
|
17
|
-
"uvicorn[standard]>=0.27",
|
|
18
|
-
"pydantic[email]>=2.5",
|
|
19
|
-
"csrd-versioning @ git+https://github.com/csrd-api/fastapi-common.git#subdirectory=packages/versioning",
|
|
20
|
-
"csrd-lifespan @ git+https://github.com/csrd-api/fastapi-common.git#subdirectory=packages/lifespan",
|
|
21
|
-
"csrd-repository @ git+https://github.com/csrd-api/fastapi-common.git#subdirectory=packages/repository",
|
|
22
|
-
"csrd-context @ git+https://github.com/csrd-api/fastapi-common.git#subdirectory=packages/context",
|
|
23
|
-
"httpx>=0.25",
|
|
24
|
-
"celery[redis]>=5.3",
|
|
25
|
-
"celery[amqp]>=5.3",
|
|
26
|
-
"redis>=5.0",
|
|
27
|
-
"csrd-logging @ git+https://github.com/csrd-api/fastapi-common.git#subdirectory=packages/logging",
|
|
28
|
-
]
|
|
29
|
-
|
|
30
|
-
[project.optional-dependencies]
|
|
31
|
-
dev = [
|
|
32
|
-
"pytest>=7.4",
|
|
33
|
-
"pytest-asyncio>=0.21",
|
|
34
|
-
"mypy>=1.7",
|
|
35
|
-
"ruff>=0.1",
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
[tool.hatch.build.targets.wheel]
|
|
39
|
-
packages = ["{{cookiecutter.__service_name_snake}}"]
|
|
40
|
-
|
|
41
|
-
[tool.pytest.ini_options]
|
|
42
|
-
asyncio_mode = "auto"
|
|
43
|
-
testpaths = ["tests"]
|
|
44
|
-
|
|
45
|
-
[tool.mypy]
|
|
46
|
-
python_version = "3.12"
|
|
47
|
-
warn_return_any = true
|
|
48
|
-
warn_unused_configs = true
|
|
49
|
-
disallow_untyped_defs = true
|
|
50
|
-
strict = true
|
|
51
|
-
|
|
52
|
-
[tool.ruff]
|
|
53
|
-
target-version = "py312"
|
|
54
|
-
line-length = 100
|
|
55
|
-
|
|
56
|
-
[tool.ruff.lint]
|
|
57
|
-
select = ["E", "F", "W", "I", "N", "UP", "BLE", "FBT", "A", "C4", "PIE", "SIM", "RUF"]
|
|
58
|
-
ignore = ["E501"]
|
|
59
|
-
|
|
60
|
-
[tool.ruff.lint.isort]
|
|
61
|
-
known-first-party = ["{{cookiecutter.__service_name_snake}}"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/caching/conftest.fragment.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/caching/docker-compose.fragment.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/caching/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/caching/settings.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/logging/conftest.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/logging/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/logging/settings.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/metrics/conftest.fragment.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/metrics/docker-compose.fragment.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/metrics/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/metrics/settings.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/tracing/conftest.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/tracing/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/tracing/settings.fragment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/workers/conftest.fragment.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/workers/docker-compose.fragment.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/workers/requirements.fragment
RENAMED
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/workers/settings.fragment.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/workers/src/app/workers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/workers/src/app/workers/tasks.py
RENAMED
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/src/csrd_utils/features/workers/tests/test_workers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csrd_utils-0.3.44 → csrd_utils-0.3.46}/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
|