csrd-utils 0.3.25__tar.gz → 0.3.27__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 (88) hide show
  1. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/PKG-INFO +12 -1
  2. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/README.md +11 -0
  3. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/pyproject.toml +1 -1
  4. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/__main__.py +77 -0
  5. csrd_utils-0.3.27/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/unit/test_settings_contracts.py +73 -0
  6. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/.gitignore +0 -0
  7. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/__init__.py +0 -0
  8. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/audit.py +0 -0
  9. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/augmentor.py +0 -0
  10. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/cluster.py +0 -0
  11. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/doctor.py +0 -0
  12. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/caching/conftest.fragment.py +0 -0
  13. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/caching/docker-compose.fragment.yaml +0 -0
  14. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/caching/manifest.yaml +0 -0
  15. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/caching/requirements.fragment +0 -0
  16. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/caching/settings.fragment.py +0 -0
  17. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/caching/src/app/cache.py +0 -0
  18. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/caching/tests/test_cache.py +0 -0
  19. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/logging/conftest.fragment.py +0 -0
  20. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/logging/manifest.yaml +0 -0
  21. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/logging/requirements.fragment +0 -0
  22. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/logging/settings.fragment.py +0 -0
  23. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/logging/src/app/middleware/logging.py +0 -0
  24. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/logging/tests/test_logging_middleware.py +0 -0
  25. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/metrics/conftest.fragment.py +0 -0
  26. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/metrics/docker-compose.fragment.yaml +0 -0
  27. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/metrics/manifest.yaml +0 -0
  28. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/metrics/requirements.fragment +0 -0
  29. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/metrics/settings.fragment.py +0 -0
  30. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/metrics/src/app/middleware/metrics.py +0 -0
  31. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/metrics/tests/test_metrics_middleware.py +0 -0
  32. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/tracing/conftest.fragment.py +0 -0
  33. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/tracing/manifest.yaml +0 -0
  34. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/tracing/requirements.fragment +0 -0
  35. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/tracing/settings.fragment.py +0 -0
  36. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/tracing/src/app/middleware/tracing.py +0 -0
  37. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/tracing/tests/test_tracing_middleware.py +0 -0
  38. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/workers/conftest.fragment.py +0 -0
  39. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/workers/docker-compose.fragment.yaml +0 -0
  40. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/workers/manifest.yaml +0 -0
  41. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/workers/requirements.fragment +0 -0
  42. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/workers/settings.fragment.py +0 -0
  43. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/workers/src/app/workers/__init__.py +0 -0
  44. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/workers/src/app/workers/celery_app.py +0 -0
  45. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/workers/src/app/workers/tasks.py +0 -0
  46. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/features/workers/tests/test_workers.py +0 -0
  47. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/fragments.py +0 -0
  48. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/generator.py +0 -0
  49. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/resources.py +0 -0
  50. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/README.md +0 -0
  51. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/cookiecutter.json +0 -0
  52. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/hooks/post_gen_project.py +0 -0
  53. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/.dockerignore +0 -0
  54. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/.env.example +0 -0
  55. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/.gitignore +0 -0
  56. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/Dockerfile +0 -0
  57. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/README.md +0 -0
  58. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/docker-compose.yml +0 -0
  59. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/pyproject.toml +0 -0
  60. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/requirements.txt +0 -0
  61. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/__init__.py +0 -0
  62. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/cache.py +0 -0
  63. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/delegates/__init__.py +0 -0
  64. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/delegates/upstreams.py +0 -0
  65. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/dependencies/__init__.py +0 -0
  66. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/dependencies/delegates.py +0 -0
  67. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/dependencies/item_repository.py +0 -0
  68. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/dependencies/worker_broker.py +0 -0
  69. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/middleware/__init__.py +0 -0
  70. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/middleware/logging.py +0 -0
  71. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/py.typed +0 -0
  72. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/repositories/__init__.py +0 -0
  73. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/repositories/item_repository.py +0 -0
  74. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/settings.py +0 -0
  75. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/views/__init__.py +0 -0
  76. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/views/unversioned/__init__.py +0 -0
  77. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/views/unversioned/health_view.py +0 -0
  78. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/views/unversioned/items_view.py +0 -0
  79. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/workers/__init__.py +0 -0
  80. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/src/app/workers/tasks.py +0 -0
  81. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/__init__.py +0 -0
  82. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/acceptance/test_logging_middleware.py +0 -0
  83. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/conftest.py +0 -0
  84. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/test_items.py +0 -0
  85. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/unit/test_cache.py +0 -0
  86. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/unit/test_database_settings.py +0 -0
  87. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/unit/test_delegates.py +0 -0
  88. {csrd_utils-0.3.25 → csrd_utils-0.3.27}/src/csrd_utils/templates/cookiecutter-service/{{cookiecutter.service_name}}/tests/unit/test_worker_broker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csrd-utils
3
- Version: 0.3.25
3
+ Version: 0.3.27
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
@@ -40,10 +40,21 @@ csrd feature add workers --service .
40
40
  csrd doctor --service .
41
41
  csrd audit # if cwd is workspace, audits all services
42
42
  csrd audit --service . # explicitly audit one service path
43
+ csrd completion bash # print bash completion script
43
44
  ```
44
45
 
45
46
  `feature plan --json` is useful for CI or tooling wrappers.
46
47
 
48
+ ### Bash tab completion
49
+
50
+ Enable command/subcommand completion in bash:
51
+
52
+ ```bash
53
+ source <(csrd completion bash)
54
+ ```
55
+
56
+ Then examples like `csrd ne<TAB>` and `csrd new wor<TAB>` complete to `new` and `workspace`.
57
+
47
58
  ### Workspace extraction command
48
59
 
49
60
  Service extraction into a sibling workspace is supported:
@@ -27,10 +27,21 @@ csrd feature add workers --service .
27
27
  csrd doctor --service .
28
28
  csrd audit # if cwd is workspace, audits all services
29
29
  csrd audit --service . # explicitly audit one service path
30
+ csrd completion bash # print bash completion script
30
31
  ```
31
32
 
32
33
  `feature plan --json` is useful for CI or tooling wrappers.
33
34
 
35
+ ### Bash tab completion
36
+
37
+ Enable command/subcommand completion in bash:
38
+
39
+ ```bash
40
+ source <(csrd completion bash)
41
+ ```
42
+
43
+ Then examples like `csrd ne<TAB>` and `csrd new wor<TAB>` complete to `new` and `workspace`.
44
+
34
45
  ### Workspace extraction command
35
46
 
36
47
  Service extraction into a sibling workspace is supported:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "csrd-utils"
3
- version = "0.3.25"
3
+ version = "0.3.27"
4
4
  description = "CLI utilities for csrd service generation and feature augmentation"
5
5
  license = { text = "MIT" }
6
6
  requires-python = ">=3.12"
@@ -71,6 +71,75 @@ WORKSPACE_MARKER = ".csrd-workspace"
71
71
  WORKSPACE_CLUSTER_SPEC = Path(".csrd") / "cluster.yaml"
72
72
 
73
73
 
74
+ def _parser_shape(
75
+ parser: argparse.ArgumentParser,
76
+ chain: tuple[str, ...] = (),
77
+ ) -> tuple[dict[tuple[str, ...], list[str]], dict[tuple[str, ...], list[str]]]:
78
+ commands: dict[tuple[str, ...], list[str]] = {}
79
+ options: dict[tuple[str, ...], list[str]] = {}
80
+
81
+ subcommands: list[str] = []
82
+ flags: list[str] = []
83
+ for action in parser._actions:
84
+ if isinstance(action, argparse._SubParsersAction):
85
+ subcommands.extend(sorted(action.choices.keys()))
86
+ for name, subparser in action.choices.items():
87
+ child_chain = (*chain, name)
88
+ child_commands, child_options = _parser_shape(subparser, child_chain)
89
+ commands.update(child_commands)
90
+ options.update(child_options)
91
+ else:
92
+ flags.extend([item for item in action.option_strings if item.startswith("-")])
93
+
94
+ commands[chain] = sorted(dict.fromkeys(subcommands))
95
+ options[chain] = sorted(dict.fromkeys(flags))
96
+ return commands, options
97
+
98
+
99
+ def _render_bash_completion(parser: argparse.ArgumentParser) -> str:
100
+ commands, options = _parser_shape(parser)
101
+
102
+ lines = [
103
+ "# bash completion for csrd",
104
+ "_csrd_completion() {",
105
+ " local cur key token",
106
+ ' cur="${COMP_WORDS[COMP_CWORD]}"',
107
+ ' key=""',
108
+ "",
109
+ ' for token in "${COMP_WORDS[@]:1:COMP_CWORD-1}"; do',
110
+ ' case "$token" in',
111
+ " -*) continue ;;",
112
+ " esac",
113
+ ' if [[ -n "$key" ]]; then',
114
+ ' key="$key $token"',
115
+ " else",
116
+ ' key="$token"',
117
+ " fi",
118
+ " done",
119
+ "",
120
+ ' case "$key" in',
121
+ ]
122
+
123
+ for chain in sorted(commands.keys()):
124
+ key = " ".join(chain)
125
+ command_candidates = commands.get(chain, [])
126
+ option_candidates = options.get(chain, [])
127
+ candidates = " ".join(command_candidates + option_candidates).strip()
128
+ if not candidates:
129
+ continue
130
+ lines.append(f' "{key}") COMPREPLY=( $(compgen -W "{candidates}" -- "$cur") ) ;;')
131
+
132
+ lines.extend(
133
+ [
134
+ " *) COMPREPLY=() ;;",
135
+ " esac",
136
+ "}",
137
+ "complete -F _csrd_completion csrd csrd-utils csrt",
138
+ ]
139
+ )
140
+ return "\n".join(lines) + "\n"
141
+
142
+
74
143
  def _workspace_services_dir(workspace_root: Path) -> Path:
75
144
  return workspace_root / "src"
76
145
 
@@ -947,6 +1016,10 @@ def _build_parser() -> argparse.ArgumentParser:
947
1016
  extract.add_argument("--json", action="store_true", help="Print extraction result as JSON")
948
1017
  extract.add_argument("--force", action="store_true", help="Overwrite target service path")
949
1018
 
1019
+ completion = sub.add_parser("completion", help="Shell completion helpers")
1020
+ completion_sub = completion.add_subparsers(dest="completion_shell", required=True)
1021
+ completion_sub.add_parser("bash", help="Print bash completion script")
1022
+
950
1023
  return parser
951
1024
 
952
1025
 
@@ -1180,6 +1253,10 @@ def main() -> int:
1180
1253
  print(f"Refreshed source cluster scaffold at: {result['source_cluster']}")
1181
1254
  return 0
1182
1255
 
1256
+ if args.command == "completion" and args.completion_shell == "bash":
1257
+ print(_render_bash_completion(parser), end="")
1258
+ return 0
1259
+
1183
1260
  if args.command == "doctor":
1184
1261
  return _run_doctor(args.service, args.json)
1185
1262
 
@@ -0,0 +1,73 @@
1
+ """Unit tests for settings validator branches and package exports."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ from types import SimpleNamespace
7
+
8
+ import pytest
9
+
10
+ from app.settings import Settings
11
+
12
+
13
+ def test_validate_db_credentials_returns_self_without_database() -> None:
14
+ candidate = SimpleNamespace(has_database=False)
15
+ assert Settings.validate_db_credentials(candidate) is candidate
16
+
17
+
18
+ def test_validate_db_credentials_returns_self_for_sqlite() -> None:
19
+ candidate = SimpleNamespace(has_database=True, db_kind="sqlite")
20
+ assert Settings.validate_db_credentials(candidate) is candidate
21
+
22
+
23
+ def test_validate_db_credentials_skips_non_production_when_not_enforced() -> None:
24
+ candidate = SimpleNamespace(
25
+ has_database=True,
26
+ db_kind="postgres",
27
+ enforce_strong_db_credentials=False,
28
+ app_env="development",
29
+ )
30
+ assert Settings.validate_db_credentials(candidate) is candidate
31
+
32
+
33
+ @pytest.mark.parametrize(
34
+ ("field", "value", "expected"),
35
+ [
36
+ ("db_user", "service_user", "DB_USER"),
37
+ ("db_password", "change_me", "DB_PASSWORD"),
38
+ ("db_name", "service_db", "DB_NAME"),
39
+ ],
40
+ )
41
+ def test_validate_db_credentials_rejects_weak_defaults(
42
+ field: str,
43
+ value: str,
44
+ expected: str,
45
+ ) -> None:
46
+ candidate = SimpleNamespace(
47
+ has_database=True,
48
+ db_kind="postgres",
49
+ enforce_strong_db_credentials=True,
50
+ app_env="production",
51
+ db_user="app_user",
52
+ db_password="StrongPass123!",
53
+ db_name="inventory_db",
54
+ )
55
+ setattr(candidate, field, value)
56
+
57
+ with pytest.raises(ValueError, match=expected):
58
+ Settings.validate_db_credentials(candidate)
59
+
60
+
61
+ def test_app_package_init_modules_expose_tuple_all() -> None:
62
+ for module_name in [
63
+ "app.delegates",
64
+ "app.dependencies",
65
+ "app.middleware",
66
+ "app.repositories",
67
+ "app.workers",
68
+ ]:
69
+ module = importlib.import_module(module_name)
70
+ exports = getattr(module, "__all__", ())
71
+ assert isinstance(exports, tuple)
72
+ for exported_name in exports:
73
+ assert hasattr(module, exported_name)
File without changes