svc-infra 0.1.613__py3-none-any.whl → 0.1.615__py3-none-any.whl
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.
Potentially problematic release.
This version of svc-infra might be problematic. Click here for more details.
- svc_infra/api/fastapi/middleware/errors/handlers.py +30 -7
- svc_infra/cli/__init__.py +4 -0
- svc_infra/cli/cmds/__init__.py +2 -0
- svc_infra/cli/cmds/docs/docs_cmds.py +69 -0
- {svc_infra-0.1.613.dist-info → svc_infra-0.1.615.dist-info}/METADATA +1 -1
- {svc_infra-0.1.613.dist-info → svc_infra-0.1.615.dist-info}/RECORD +8 -7
- {svc_infra-0.1.613.dist-info → svc_infra-0.1.615.dist-info}/WHEEL +0 -0
- {svc_infra-0.1.613.dist-info → svc_infra-0.1.615.dist-info}/entry_points.txt +0 -0
|
@@ -46,6 +46,7 @@ def problem_response(
|
|
|
46
46
|
code: str | None = None,
|
|
47
47
|
errors: list[dict] | None = None,
|
|
48
48
|
trace_id: str | None = None,
|
|
49
|
+
headers: dict[str, str] | None = None,
|
|
49
50
|
) -> Response:
|
|
50
51
|
body: Dict[str, Any] = {
|
|
51
52
|
"type": type_uri,
|
|
@@ -62,7 +63,7 @@ def problem_response(
|
|
|
62
63
|
body["errors"] = errors
|
|
63
64
|
if trace_id:
|
|
64
65
|
body["trace_id"] = trace_id
|
|
65
|
-
return JSONResponse(status_code=status, content=body, media_type=PROBLEM_MT)
|
|
66
|
+
return JSONResponse(status_code=status, content=body, media_type=PROBLEM_MT, headers=headers)
|
|
66
67
|
|
|
67
68
|
|
|
68
69
|
def register_error_handlers(app):
|
|
@@ -104,14 +105,25 @@ def register_error_handlers(app):
|
|
|
104
105
|
@app.exception_handler(HTTPException)
|
|
105
106
|
async def handle_http_exception(request: Request, exc: HTTPException):
|
|
106
107
|
trace_id = _trace_id_from_request(request)
|
|
107
|
-
title = {
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
title = {
|
|
109
|
+
401: "Unauthorized",
|
|
110
|
+
403: "Forbidden",
|
|
111
|
+
404: "Not Found",
|
|
112
|
+
429: "Too Many Requests",
|
|
113
|
+
}.get(exc.status_code, "Error")
|
|
110
114
|
detail = (
|
|
111
115
|
exc.detail
|
|
112
116
|
if not IS_PROD or exc.status_code < 500
|
|
113
117
|
else "Something went wrong. Please contact support."
|
|
114
118
|
)
|
|
119
|
+
# Preserve headers set on the exception (e.g., Retry-After for rate limits)
|
|
120
|
+
hdrs: dict[str, str] | None = None
|
|
121
|
+
try:
|
|
122
|
+
if getattr(exc, "headers", None):
|
|
123
|
+
# FastAPI/Starlette exceptions store headers as a dict[str, str]
|
|
124
|
+
hdrs = dict(getattr(exc, "headers")) # type: ignore[arg-type]
|
|
125
|
+
except Exception:
|
|
126
|
+
hdrs = None
|
|
115
127
|
return problem_response(
|
|
116
128
|
status=exc.status_code,
|
|
117
129
|
title=title,
|
|
@@ -119,19 +131,29 @@ def register_error_handlers(app):
|
|
|
119
131
|
code=title.replace(" ", "_").upper(),
|
|
120
132
|
instance=str(request.url),
|
|
121
133
|
trace_id=trace_id,
|
|
134
|
+
headers=hdrs,
|
|
122
135
|
)
|
|
123
136
|
|
|
124
137
|
@app.exception_handler(StarletteHTTPException)
|
|
125
138
|
async def handle_starlette_http_exception(request: Request, exc: StarletteHTTPException):
|
|
126
139
|
trace_id = _trace_id_from_request(request)
|
|
127
|
-
title = {
|
|
128
|
-
|
|
129
|
-
|
|
140
|
+
title = {
|
|
141
|
+
401: "Unauthorized",
|
|
142
|
+
403: "Forbidden",
|
|
143
|
+
404: "Not Found",
|
|
144
|
+
429: "Too Many Requests",
|
|
145
|
+
}.get(exc.status_code, "Error")
|
|
130
146
|
detail = (
|
|
131
147
|
exc.detail
|
|
132
148
|
if not IS_PROD or exc.status_code < 500
|
|
133
149
|
else "Something went wrong. Please contact support."
|
|
134
150
|
)
|
|
151
|
+
hdrs: dict[str, str] | None = None
|
|
152
|
+
try:
|
|
153
|
+
if getattr(exc, "headers", None):
|
|
154
|
+
hdrs = dict(getattr(exc, "headers")) # type: ignore[arg-type]
|
|
155
|
+
except Exception:
|
|
156
|
+
hdrs = None
|
|
135
157
|
return problem_response(
|
|
136
158
|
status=exc.status_code,
|
|
137
159
|
title=title,
|
|
@@ -139,6 +161,7 @@ def register_error_handlers(app):
|
|
|
139
161
|
code=title.replace(" ", "_").upper(),
|
|
140
162
|
instance=str(request.url),
|
|
141
163
|
trace_id=trace_id,
|
|
164
|
+
headers=hdrs,
|
|
142
165
|
)
|
|
143
166
|
|
|
144
167
|
@app.exception_handler(IntegrityError)
|
svc_infra/cli/__init__.py
CHANGED
|
@@ -6,6 +6,7 @@ from svc_infra.cli.cmds import (
|
|
|
6
6
|
_HELP,
|
|
7
7
|
jobs_app,
|
|
8
8
|
register_alembic,
|
|
9
|
+
register_docs,
|
|
9
10
|
register_dx,
|
|
10
11
|
register_mongo,
|
|
11
12
|
register_mongo_scaffold,
|
|
@@ -40,6 +41,9 @@ app.add_typer(jobs_app, name="jobs")
|
|
|
40
41
|
# -- sdk commands ---
|
|
41
42
|
register_sdk(app)
|
|
42
43
|
|
|
44
|
+
# -- docs commands ---
|
|
45
|
+
register_docs(app)
|
|
46
|
+
|
|
43
47
|
|
|
44
48
|
def main():
|
|
45
49
|
app()
|
svc_infra/cli/cmds/__init__.py
CHANGED
|
@@ -5,6 +5,7 @@ from svc_infra.cli.cmds.db.nosql.mongo.mongo_scaffold_cmds import (
|
|
|
5
5
|
from svc_infra.cli.cmds.db.sql.alembic_cmds import register as register_alembic
|
|
6
6
|
from svc_infra.cli.cmds.db.sql.sql_export_cmds import register as register_sql_export
|
|
7
7
|
from svc_infra.cli.cmds.db.sql.sql_scaffold_cmds import register as register_sql_scaffold
|
|
8
|
+
from svc_infra.cli.cmds.docs.docs_cmds import register as register_docs
|
|
8
9
|
from svc_infra.cli.cmds.dx import register_dx
|
|
9
10
|
from svc_infra.cli.cmds.jobs.jobs_cmds import app as jobs_app
|
|
10
11
|
from svc_infra.cli.cmds.obs.obs_cmds import register as register_obs
|
|
@@ -22,5 +23,6 @@ __all__ = [
|
|
|
22
23
|
"jobs_app",
|
|
23
24
|
"register_sdk",
|
|
24
25
|
"register_dx",
|
|
26
|
+
"register_docs",
|
|
25
27
|
"_HELP",
|
|
26
28
|
]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, List, Tuple
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from svc_infra.app.root import resolve_project_root
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _discover_docs(root: Path) -> List[Tuple[str, Path]]:
|
|
12
|
+
"""Return a list of (topic, file_path) for top-level Markdown files under docs/.
|
|
13
|
+
|
|
14
|
+
Topic is the filename stem (e.g. security.md -> "security").
|
|
15
|
+
"""
|
|
16
|
+
docs_dir = root / "docs"
|
|
17
|
+
topics: List[Tuple[str, Path]] = []
|
|
18
|
+
if not docs_dir.exists() or not docs_dir.is_dir():
|
|
19
|
+
return topics
|
|
20
|
+
for p in sorted(docs_dir.glob("*.md")):
|
|
21
|
+
if p.is_file():
|
|
22
|
+
topics.append((p.stem.replace(" ", "-"), p))
|
|
23
|
+
return topics
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def register(app: typer.Typer) -> None:
|
|
27
|
+
"""Register the `docs` command group and dynamic topic subcommands."""
|
|
28
|
+
|
|
29
|
+
root = resolve_project_root()
|
|
30
|
+
discovered = _discover_docs(root)
|
|
31
|
+
|
|
32
|
+
# Build help text listing available topics
|
|
33
|
+
if discovered:
|
|
34
|
+
topic_names = ", ".join(name for name, _ in discovered)
|
|
35
|
+
docs_help = (
|
|
36
|
+
f"Show docs from the repository's docs/ directory.\n\nAvailable topics: {topic_names}"
|
|
37
|
+
)
|
|
38
|
+
else:
|
|
39
|
+
docs_help = "Show docs from the repository's docs/ directory.\n\nNo topics discovered."
|
|
40
|
+
|
|
41
|
+
docs_app = typer.Typer(no_args_is_help=True, help=docs_help, add_completion=False)
|
|
42
|
+
|
|
43
|
+
@docs_app.command("list", help="List available documentation topics")
|
|
44
|
+
def list_topics() -> None:
|
|
45
|
+
for name, path in discovered:
|
|
46
|
+
typer.echo(f"{name}\t{path.relative_to(root)}")
|
|
47
|
+
|
|
48
|
+
# Freeze mapping for use in dynamic commands and generic fallback
|
|
49
|
+
topic_map: Dict[str, Path] = {name: path for name, path in discovered}
|
|
50
|
+
|
|
51
|
+
def _make_topic_cmd(topic: str, file_path: Path):
|
|
52
|
+
@docs_app.command(name=topic, help=f"Show docs for topic: {topic}")
|
|
53
|
+
def _show_topic() -> None: # noqa: WPS430 (nested function OK for closure)
|
|
54
|
+
content = file_path.read_text(encoding="utf-8", errors="replace")
|
|
55
|
+
typer.echo(content)
|
|
56
|
+
|
|
57
|
+
for name, path in discovered:
|
|
58
|
+
_make_topic_cmd(name, path)
|
|
59
|
+
|
|
60
|
+
# Optional generic fallback: allow `svc-infra docs --topic <name>`
|
|
61
|
+
@docs_app.callback(invoke_without_command=True)
|
|
62
|
+
def _maybe_show_topic(topic: str = typer.Option(None, "--topic", help="Topic to show")) -> None: # type: ignore[no-redef]
|
|
63
|
+
if topic:
|
|
64
|
+
p = topic_map.get(topic)
|
|
65
|
+
if not p:
|
|
66
|
+
raise typer.BadParameter(f"Unknown topic: {topic}")
|
|
67
|
+
typer.echo(p.read_text(encoding="utf-8", errors="replace"))
|
|
68
|
+
|
|
69
|
+
app.add_typer(docs_app, name="docs")
|
|
@@ -76,7 +76,7 @@ svc_infra/api/fastapi/middleware/debug.py,sha256=H3jBKvdPkr2KHUEMGnqWBPZ0tG6Fgw-
|
|
|
76
76
|
svc_infra/api/fastapi/middleware/errors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
77
77
|
svc_infra/api/fastapi/middleware/errors/catchall.py,sha256=TG0W71UCDbfgLNdIaIv6mBSwZA_etMp5GquwKcAwYbI,1842
|
|
78
78
|
svc_infra/api/fastapi/middleware/errors/exceptions.py,sha256=857_bdMgQugf8rb7U6ZaTZV3aiFTfBzFaUg80YUfAYE,475
|
|
79
|
-
svc_infra/api/fastapi/middleware/errors/handlers.py,sha256=
|
|
79
|
+
svc_infra/api/fastapi/middleware/errors/handlers.py,sha256=pQMVs5n627vcKkDFEaUzx5wCYeUcU-h6acWzh27RIEQ,7993
|
|
80
80
|
svc_infra/api/fastapi/middleware/idempotency.py,sha256=vnBQgMWzJVaF8oWgfw2ATjEKCyQifDeGPUc9z1N7ebE,5051
|
|
81
81
|
svc_infra/api/fastapi/middleware/idempotency_store.py,sha256=BQN_Cq_jf_cuZRhze4EF5v0lOMQXpUWoRo7CsSTprug,5528
|
|
82
82
|
svc_infra/api/fastapi/middleware/optimistic_lock.py,sha256=9lOMBI4VNIVndXnrMmgSq4qeR7xPjNR1H9d1F71M5S8,1271
|
|
@@ -126,9 +126,9 @@ svc_infra/cache/resources.py,sha256=BhvPAZvCQ-fitUdniGEOOE4g1ZvljdCA_R5pR8WfJz4,
|
|
|
126
126
|
svc_infra/cache/tags.py,sha256=9URw4BRlnb4QFAYpDI36fMms6642xq4TeV9jqsEjzE8,2625
|
|
127
127
|
svc_infra/cache/ttl.py,sha256=_lWvNx1CTE4RcFEOUYkADd7_k4I13SLmtK0AMRUq2OM,1945
|
|
128
128
|
svc_infra/cache/utils.py,sha256=-LWr5IiJCNm3pwaoeCVlxNknnO2ChNKFcAGlFU98kjg,4856
|
|
129
|
-
svc_infra/cli/__init__.py,sha256=
|
|
129
|
+
svc_infra/cli/__init__.py,sha256=enGeMhxOjfeClic51C4QB2Car3DDZD3A9P9R_8YfYHQ,926
|
|
130
130
|
svc_infra/cli/__main__.py,sha256=5BjNuyet8AY-POwoF5rGt722rHQ7tJ0Vf0UFUfzzi-I,58
|
|
131
|
-
svc_infra/cli/cmds/__init__.py,sha256=
|
|
131
|
+
svc_infra/cli/cmds/__init__.py,sha256=xKVXpMP_fD7jfmYonxWxh5LKHUQiuIFaJgkpqtkPt-M,1051
|
|
132
132
|
svc_infra/cli/cmds/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
133
133
|
svc_infra/cli/cmds/db/nosql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
134
134
|
svc_infra/cli/cmds/db/nosql/mongo/README.md,sha256=0u3XLeoBd0XQzXwwfEiFISMIij11TJ9iOGzrysBvsFk,1788
|
|
@@ -139,6 +139,7 @@ svc_infra/cli/cmds/db/sql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
|
139
139
|
svc_infra/cli/cmds/db/sql/alembic_cmds.py,sha256=kkAu8sfBLWbb9ApMS95b7b_c6GifqvPaRsO7K8icMVI,9649
|
|
140
140
|
svc_infra/cli/cmds/db/sql/sql_export_cmds.py,sha256=6MxoQO-9upoXg0cl1RHIqz96yXFVGidiBYp_ewhB0E0,2700
|
|
141
141
|
svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py,sha256=eNTCqHXOxgl9H3WTbGVn9BHXYwCpjIEJsDqhEFdrYMM,4613
|
|
142
|
+
svc_infra/cli/cmds/docs/docs_cmds.py,sha256=3GCoRKjnRr_re__7TuelDbcaouJawHlz0AphYyI0DIE,2575
|
|
142
143
|
svc_infra/cli/cmds/dx/__init__.py,sha256=wQtl3-kOgoESlpVkjl3YFtqkOnQSIvVsOdutiaZFejM,197
|
|
143
144
|
svc_infra/cli/cmds/dx/dx_cmds.py,sha256=XTKUJzS3UIYn6h3CHzDEWKYJaWn0TzGiUCq3OeW27E0,3326
|
|
144
145
|
svc_infra/cli/cmds/help.py,sha256=wGfZFMYaR2ZPwW2JwKDU7M3m4AtdCd8GRQ412AmEBUM,758
|
|
@@ -292,7 +293,7 @@ svc_infra/webhooks/fastapi.py,sha256=BCNvGNxukf6dC2a4i-6en-PrjBGV19YvCWOot5lXWsA
|
|
|
292
293
|
svc_infra/webhooks/router.py,sha256=6JvAVPMEth_xxHX-IsIOcyMgHX7g1H0OVxVXKLuMp9w,1596
|
|
293
294
|
svc_infra/webhooks/service.py,sha256=hWgiJRXKBwKunJOx91C7EcLUkotDtD3Xp0RT6vj2IC0,1797
|
|
294
295
|
svc_infra/webhooks/signing.py,sha256=NCwdZzmravUe7HVIK_uXK0qqf12FG-_MVsgPvOw6lsM,784
|
|
295
|
-
svc_infra-0.1.
|
|
296
|
-
svc_infra-0.1.
|
|
297
|
-
svc_infra-0.1.
|
|
298
|
-
svc_infra-0.1.
|
|
296
|
+
svc_infra-0.1.615.dist-info/METADATA,sha256=VFmBWPFEQHoLsz2vTyHO2DMDkN9C70YlI3zAJolFyJg,8106
|
|
297
|
+
svc_infra-0.1.615.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
298
|
+
svc_infra-0.1.615.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
|
|
299
|
+
svc_infra-0.1.615.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|