csrd-utils 0.3.10__tar.gz → 0.3.12__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 (57) hide show
  1. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/PKG-INFO +1 -1
  2. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/pyproject.toml +1 -1
  3. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/__main__.py +10 -3
  4. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/cluster.py +2 -0
  5. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/hooks/post_gen_project.py +7 -0
  6. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/README.md +11 -0
  7. csrd_utils-0.3.12/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/acceptance/test_logging_middleware.py +7 -0
  8. csrd_utils-0.3.12/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/unit/test_cache.py +9 -0
  9. csrd_utils-0.3.12/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/unit/test_database_settings.py +8 -0
  10. csrd_utils-0.3.12/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/unit/test_delegates.py +17 -0
  11. csrd_utils-0.3.12/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/unit/test_worker_broker.py +8 -0
  12. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/.gitignore +0 -0
  13. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/README.md +0 -0
  14. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/__init__.py +0 -0
  15. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/augmentor.py +0 -0
  16. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/doctor.py +0 -0
  17. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/features/workers/conftest.fragment.py +0 -0
  18. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/features/workers/docker-compose.fragment.yaml +0 -0
  19. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/features/workers/manifest.yaml +0 -0
  20. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/features/workers/requirements.fragment +0 -0
  21. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/features/workers/settings.fragment.py +0 -0
  22. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/features/workers/src/app/workers/__init__.py +0 -0
  23. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/features/workers/src/app/workers/celery_app.py +0 -0
  24. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/features/workers/src/app/workers/tasks.py +0 -0
  25. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/features/workers/tests/test_workers.py +0 -0
  26. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/fragments.py +0 -0
  27. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/generator.py +0 -0
  28. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/resources.py +0 -0
  29. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/README.md +0 -0
  30. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/cookiecutter.json +0 -0
  31. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/.dockerignore +0 -0
  32. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/.env.example +0 -0
  33. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/Dockerfile +0 -0
  34. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/docker-compose.yml +0 -0
  35. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/requirements.txt +0 -0
  36. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/__init__.py +0 -0
  37. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/cache.py +0 -0
  38. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/delegates/__init__.py +0 -0
  39. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/delegates/upstreams.py +0 -0
  40. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/dependencies/__init__.py +0 -0
  41. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/dependencies/delegates.py +0 -0
  42. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/dependencies/item_repository.py +0 -0
  43. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/dependencies/worker_broker.py +0 -0
  44. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/middleware/__init__.py +0 -0
  45. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/middleware/logging.py +0 -0
  46. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/repositories/__init__.py +0 -0
  47. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/repositories/item_repository.py +0 -0
  48. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/settings.py +0 -0
  49. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/views/__init__.py +0 -0
  50. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/views/unversioned/__init__.py +0 -0
  51. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/views/unversioned/health_view.py +0 -0
  52. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/views/unversioned/items_view.py +0 -0
  53. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/workers/__init__.py +0 -0
  54. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/workers/tasks.py +0 -0
  55. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/__init__.py +0 -0
  56. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/conftest.py +0 -0
  57. {csrd_utils-0.3.10 → csrd_utils-0.3.12}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/test_items.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csrd-utils
3
- Version: 0.3.10
3
+ Version: 0.3.12
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.10"
3
+ version = "0.3.12"
4
4
  description = "CLI utilities for csrd service generation and feature augmentation"
5
5
  license = { text = "MIT" }
6
6
  requires-python = ">=3.12"
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import argparse
6
6
  import json
7
+ from contextlib import suppress
7
8
  from importlib.metadata import version
8
9
  from pathlib import Path
9
10
  from typing import TypedDict
@@ -75,6 +76,12 @@ def _prompt_int(label: str, default: int) -> int:
75
76
  print("ERROR: Enter a valid integer")
76
77
 
77
78
 
79
+ def _print_cancelled() -> None:
80
+ # Avoid surfacing a traceback if a second Ctrl-C lands while printing.
81
+ with suppress(KeyboardInterrupt):
82
+ print("\nCancelled.")
83
+
84
+
78
85
  def _collect_new_service_config(args: argparse.Namespace) -> GenerateServiceArgs:
79
86
  explicit_interactive = getattr(args, "interactive", None)
80
87
  is_explicit_interactive = explicit_interactive is True
@@ -386,7 +393,7 @@ def main() -> int:
386
393
  print(f"ERROR: {exc}")
387
394
  return 1
388
395
  except KeyboardInterrupt:
389
- print("\nCancelled.")
396
+ _print_cancelled()
390
397
  return 1
391
398
  print(f"Generated service at: {generated}")
392
399
  return 0
@@ -399,11 +406,11 @@ def main() -> int:
399
406
  cluster_config["output_dir"],
400
407
  force=cluster_config["overwrite_if_exists"],
401
408
  )
402
- except (FileExistsError, ValueError) as exc:
409
+ except (FileExistsError, FileNotFoundError, ValueError) as exc:
403
410
  print(f"ERROR: {exc}")
404
411
  return 1
405
412
  except KeyboardInterrupt:
406
- print("\nCancelled.")
413
+ _print_cancelled()
407
414
  return 1
408
415
  print(f"Generated cluster at: {generated}")
409
416
  return 0
@@ -76,6 +76,8 @@ def _split_csv(raw: str) -> list[str]:
76
76
 
77
77
 
78
78
  def load_cluster_spec(spec_path: Path) -> ClusterSpec:
79
+ if not spec_path.is_file():
80
+ raise ValueError(f"Cluster spec file not found: {spec_path}")
79
81
  payload = yaml.safe_load(spec_path.read_text(encoding="utf-8"))
80
82
  if not isinstance(payload, dict):
81
83
  raise ValueError("Cluster spec must be a YAML object")
@@ -32,25 +32,32 @@ def _prune_disabled_files() -> None:
32
32
  if not HAS_DATABASE:
33
33
  _unlink("src/app/dependencies/item_repository.py")
34
34
  _unlink("src/app/repositories/item_repository.py")
35
+ _unlink("tests/unit/test_database_settings.py")
35
36
 
36
37
  if not HAS_DELEGATES:
37
38
  _unlink("src/app/dependencies/delegates.py")
38
39
  _unlink("src/app/delegates/upstreams.py")
40
+ _unlink("tests/unit/test_delegates.py")
39
41
 
40
42
  if not HAS_WORKERS:
41
43
  _unlink("src/app/dependencies/worker_broker.py")
42
44
  _unlink("src/app/workers/tasks.py")
45
+ _unlink("tests/unit/test_worker_broker.py")
43
46
 
44
47
  if not HAS_CACHING:
45
48
  _unlink("src/app/cache.py")
49
+ _unlink("tests/unit/test_cache.py")
46
50
 
47
51
  if not HAS_STRUCTURED_LOGGING:
48
52
  _unlink("src/app/middleware/logging.py")
53
+ _unlink("tests/acceptance/test_logging_middleware.py")
49
54
 
50
55
  _rmdir_if_empty("src/app/repositories")
51
56
  _rmdir_if_empty("src/app/delegates")
52
57
  _rmdir_if_empty("src/app/workers")
53
58
  _rmdir_if_empty("src/app/middleware")
59
+ _rmdir_if_empty("tests/unit")
60
+ _rmdir_if_empty("tests/acceptance")
54
61
 
55
62
 
56
63
  def main() -> int:
@@ -44,3 +44,14 @@ docker compose up --build
44
44
  - `PUT /api/{{ cookiecutter.model_name_plural | lower | replace(' ', '_') }}/{entity_id}`
45
45
  - `DELETE /api/{{ cookiecutter.model_name_plural | lower | replace(' ', '_') }}/{entity_id}`
46
46
  {%- endif %}
47
+
48
+ ## Generated tests
49
+
50
+ - Acceptance tests live under `tests/acceptance/`
51
+ - Unit tests live under `tests/unit/`
52
+ - Feature-dependent tests are generated only when corresponding dependencies are enabled:
53
+ - database -> `tests/unit/test_database_settings.py`
54
+ - delegates -> `tests/unit/test_delegates.py`
55
+ - workers -> `tests/unit/test_worker_broker.py`
56
+ - caching -> `tests/unit/test_cache.py`
57
+ - structured logging -> `tests/acceptance/test_logging_middleware.py`
@@ -0,0 +1,7 @@
1
+ """Acceptance tests for structured logging middleware behavior."""
2
+
3
+
4
+ def test_health_includes_request_duration_header(client) -> None:
5
+ response = client.get("/health")
6
+ assert response.status_code == 200
7
+ assert "X-Request-Duration-Ms" in response.headers
@@ -0,0 +1,9 @@
1
+ """Unit tests for cache dependency wiring."""
2
+
3
+ from app.cache import build_cache_client
4
+
5
+
6
+ def test_cache_client_uses_configured_host() -> None:
7
+ cache_client = build_cache_client()
8
+ host = cache_client.connection_pool.connection_kwargs.get("host")
9
+ assert host in {"localhost", "redis"}
@@ -0,0 +1,8 @@
1
+ """Unit tests for database settings wiring."""
2
+
3
+ from app.settings import settings
4
+
5
+
6
+ def test_database_settings_enabled() -> None:
7
+ assert settings.has_database is True
8
+ assert settings.db_kind in {"sqlite", "maria", "postgres"}
@@ -0,0 +1,17 @@
1
+ """Unit tests for delegate dependency wiring."""
2
+
3
+ import asyncio
4
+
5
+ from app.dependencies.delegates import upstream_delegate_factory
6
+
7
+
8
+ async def _collect_upstreams() -> list[str]:
9
+ async for upstreams in upstream_delegate_factory():
10
+ return upstreams
11
+ return []
12
+
13
+
14
+ def test_upstream_delegate_factory_returns_urls() -> None:
15
+ upstreams = asyncio.run(_collect_upstreams())
16
+ assert upstreams
17
+ assert all(url.startswith("http") for url in upstreams)
@@ -0,0 +1,8 @@
1
+ """Unit tests for worker broker dependency wiring."""
2
+
3
+ from app.dependencies.worker_broker import get_worker_broker_url
4
+
5
+
6
+ def test_worker_broker_url_uses_expected_scheme() -> None:
7
+ broker_url = get_worker_broker_url()
8
+ assert broker_url.startswith(("redis://", "amqp://"))
File without changes
File without changes