svc-infra 0.1.618__py3-none-any.whl → 0.1.620__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/ratelimit.py +13 -3
- svc_infra/cli/__init__.py +17 -11
- svc_infra/cli/cmds/db/nosql/mongo/mongo_cmds.py +4 -3
- svc_infra/cli/cmds/db/nosql/mongo/mongo_scaffold_cmds.py +4 -4
- svc_infra/cli/cmds/db/sql/alembic_cmds.py +11 -10
- svc_infra/cli/cmds/db/sql/sql_export_cmds.py +2 -4
- svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py +3 -3
- svc_infra/cli/cmds/docs/docs_cmds.py +54 -29
- svc_infra/cli/cmds/obs/obs_cmds.py +4 -3
- svc_infra/db/nosql/mongo/README.md +13 -13
- svc_infra/mcp/svc_infra_mcp.py +41 -26
- {svc_infra-0.1.618.dist-info → svc_infra-0.1.620.dist-info}/METADATA +1 -1
- {svc_infra-0.1.618.dist-info → svc_infra-0.1.620.dist-info}/RECORD +15 -15
- {svc_infra-0.1.618.dist-info → svc_infra-0.1.620.dist-info}/WHEEL +0 -0
- {svc_infra-0.1.618.dist-info → svc_infra-0.1.620.dist-info}/entry_points.txt +0 -0
|
@@ -50,9 +50,19 @@ class SimpleRateLimitMiddleware(BaseHTTPMiddleware):
|
|
|
50
50
|
tenant_id = await _resolve_tenant_id(request)
|
|
51
51
|
except Exception:
|
|
52
52
|
tenant_id = None
|
|
53
|
-
# Fallback
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
# Fallback header behavior:
|
|
54
|
+
# - If tenancy context is unavailable (minimal builds), accept header by default so
|
|
55
|
+
# unit/integration tests can exercise per-tenant scoping without full auth state.
|
|
56
|
+
# - If tenancy is available, only trust the header when explicitly allowed.
|
|
57
|
+
if not tenant_id:
|
|
58
|
+
if _resolve_tenant_id is None:
|
|
59
|
+
tenant_id = request.headers.get("X-Tenant-Id") or request.headers.get(
|
|
60
|
+
"X-Tenant-ID"
|
|
61
|
+
)
|
|
62
|
+
elif self._allow_untrusted_tenant_header:
|
|
63
|
+
tenant_id = request.headers.get("X-Tenant-Id") or request.headers.get(
|
|
64
|
+
"X-Tenant-ID"
|
|
65
|
+
)
|
|
56
66
|
|
|
57
67
|
key = self.key_fn(request)
|
|
58
68
|
if self.scope_by_tenant and tenant_id:
|
svc_infra/cli/__init__.py
CHANGED
|
@@ -20,17 +20,23 @@ from svc_infra.cli.foundation.typer_bootstrap import pre_cli
|
|
|
20
20
|
app = typer.Typer(no_args_is_help=True, add_completion=False, help=_HELP)
|
|
21
21
|
pre_cli(app)
|
|
22
22
|
|
|
23
|
-
# --- sql
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
23
|
+
# --- sql group ---
|
|
24
|
+
sql_app = typer.Typer(no_args_is_help=True, add_completion=False, help="SQL commands")
|
|
25
|
+
register_alembic(sql_app)
|
|
26
|
+
register_sql_scaffold(sql_app)
|
|
27
|
+
register_sql_export(sql_app)
|
|
28
|
+
app.add_typer(sql_app, name="sql")
|
|
29
|
+
|
|
30
|
+
# --- mongo group ---
|
|
31
|
+
mongo_app = typer.Typer(no_args_is_help=True, add_completion=False, help="MongoDB commands")
|
|
32
|
+
register_mongo(mongo_app)
|
|
33
|
+
register_mongo_scaffold(mongo_app)
|
|
34
|
+
app.add_typer(mongo_app, name="mongo")
|
|
35
|
+
|
|
36
|
+
# -- obs group ---
|
|
37
|
+
obs_app = typer.Typer(no_args_is_help=True, add_completion=False, help="Observability commands")
|
|
38
|
+
register_obs(obs_app)
|
|
39
|
+
app.add_typer(obs_app, name="obs")
|
|
34
40
|
|
|
35
41
|
# -- dx commands ---
|
|
36
42
|
register_dx(app)
|
|
@@ -188,6 +188,7 @@ def cmd_ping(
|
|
|
188
188
|
|
|
189
189
|
|
|
190
190
|
def register(app: typer.Typer) -> None:
|
|
191
|
-
app
|
|
192
|
-
app.command("
|
|
193
|
-
app.command("
|
|
191
|
+
# Attach to 'mongo' group app
|
|
192
|
+
app.command("prepare")(cmd_prepare)
|
|
193
|
+
app.command("setup-and-prepare")(cmd_setup_and_prepare)
|
|
194
|
+
app.command("ping")(cmd_ping)
|
|
@@ -127,7 +127,7 @@ def register(app: typer.Typer) -> None:
|
|
|
127
127
|
• mongo-scaffold-schemas
|
|
128
128
|
• mongo-scaffold-resources
|
|
129
129
|
"""
|
|
130
|
-
app.command("
|
|
131
|
-
app.command("
|
|
132
|
-
app.command("
|
|
133
|
-
app.command("
|
|
130
|
+
app.command("scaffold")(cmd_scaffold)
|
|
131
|
+
app.command("scaffold-documents")(cmd_scaffold_documents)
|
|
132
|
+
app.command("scaffold-schemas")(cmd_scaffold_schemas)
|
|
133
|
+
app.command("scaffold-resources")(cmd_scaffold_resources)
|
|
@@ -201,16 +201,17 @@ def cmd_setup_and_migrate(
|
|
|
201
201
|
|
|
202
202
|
|
|
203
203
|
def register(app: typer.Typer) -> None:
|
|
204
|
-
app
|
|
205
|
-
app.command("
|
|
206
|
-
app.command("
|
|
207
|
-
app.command("
|
|
208
|
-
app.command("
|
|
209
|
-
app.command("
|
|
210
|
-
app.command("
|
|
211
|
-
app.command("
|
|
212
|
-
app.command("
|
|
213
|
-
app.command("
|
|
204
|
+
# Register under the 'sql' group app
|
|
205
|
+
app.command("init")(cmd_init)
|
|
206
|
+
app.command("revision")(cmd_revision)
|
|
207
|
+
app.command("upgrade")(cmd_upgrade)
|
|
208
|
+
app.command("downgrade")(cmd_downgrade)
|
|
209
|
+
app.command("current")(cmd_current)
|
|
210
|
+
app.command("history")(cmd_history)
|
|
211
|
+
app.command("stamp")(cmd_stamp)
|
|
212
|
+
app.command("merge-heads")(cmd_merge_heads)
|
|
213
|
+
app.command("setup-and-migrate")(cmd_setup_and_migrate)
|
|
214
|
+
app.command("seed")(cmd_seed)
|
|
214
215
|
|
|
215
216
|
|
|
216
217
|
def _import_callable(path: str):
|
|
@@ -17,10 +17,7 @@ try: # SQLAlchemy async extras are optional
|
|
|
17
17
|
except Exception: # pragma: no cover - fallback when async extras unavailable
|
|
18
18
|
AsyncEngine = None # type: ignore[assignment]
|
|
19
19
|
|
|
20
|
-
app = typer.Typer(help="SQL data export commands")
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
@app.command("export-tenant")
|
|
24
21
|
def export_tenant(
|
|
25
22
|
table: str = typer.Argument(..., help="Qualified table name to export (e.g., public.items)"),
|
|
26
23
|
tenant_id: str = typer.Option(..., "--tenant-id", help="Tenant id value to filter by."),
|
|
@@ -79,4 +76,5 @@ def export_tenant(
|
|
|
79
76
|
|
|
80
77
|
|
|
81
78
|
def register(app_root: typer.Typer) -> None:
|
|
82
|
-
|
|
79
|
+
# Attach directly to the provided 'sql' group app
|
|
80
|
+
app_root.command("export-tenant")(export_tenant)
|
|
@@ -134,6 +134,6 @@ def cmd_scaffold_schemas(
|
|
|
134
134
|
|
|
135
135
|
|
|
136
136
|
def register(app: typer.Typer) -> None:
|
|
137
|
-
app.command("
|
|
138
|
-
app.command("
|
|
139
|
-
app.command("
|
|
137
|
+
app.command("scaffold")(cmd_scaffold)
|
|
138
|
+
app.command("scaffold-models")(cmd_scaffold_models)
|
|
139
|
+
app.command("scaffold-schemas")(cmd_scaffold_schemas)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import importlib.util
|
|
3
4
|
import os
|
|
4
5
|
from importlib.metadata import PackageNotFoundError, distribution
|
|
5
6
|
from pathlib import Path
|
|
@@ -24,31 +25,50 @@ def _discover_fs_topics(docs_dir: Path) -> Dict[str, Path]:
|
|
|
24
25
|
def _discover_pkg_topics() -> Dict[str, Path]:
|
|
25
26
|
"""Discover docs packaged under 'docs/' in the installed distribution.
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
Works in external projects without a local docs/ by inspecting the wheel
|
|
29
|
+
metadata and, as a fallback, searching for a top-level docs/ next to the
|
|
30
|
+
installed package directory in site-packages.
|
|
29
31
|
"""
|
|
30
32
|
topics: Dict[str, Path] = {}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
files = getattr(dist, "files", None) or []
|
|
37
|
-
for f in files:
|
|
38
|
-
# f is a PackagePath; string form like 'docs/topic.md'
|
|
39
|
-
s = str(f)
|
|
40
|
-
if not s.startswith("docs/") or not s.endswith(".md"):
|
|
41
|
-
continue
|
|
42
|
-
name = Path(s).stem.replace(" ", "-")
|
|
33
|
+
|
|
34
|
+
# 1) Prefer distribution metadata (RECORD) for both hyphen/underscore names
|
|
35
|
+
dist = None
|
|
36
|
+
for name in ("svc-infra", "svc_infra"):
|
|
43
37
|
try:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
38
|
+
dist = distribution(name)
|
|
39
|
+
break
|
|
40
|
+
except PackageNotFoundError:
|
|
41
|
+
dist = None
|
|
42
|
+
|
|
43
|
+
if dist is not None:
|
|
44
|
+
files = getattr(dist, "files", None) or []
|
|
45
|
+
for f in files:
|
|
46
|
+
s = str(f)
|
|
47
|
+
if not s.startswith("docs/") or not s.endswith(".md"):
|
|
48
|
+
continue
|
|
49
|
+
topic_name = Path(s).stem.replace(" ", "-")
|
|
50
|
+
try:
|
|
51
|
+
abs_path = Path(dist.locate_file(f))
|
|
52
|
+
if abs_path.exists() and abs_path.is_file():
|
|
53
|
+
topics[topic_name] = abs_path
|
|
54
|
+
except Exception:
|
|
55
|
+
# Best effort; continue to next
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
# 2) Fallback: site-packages sibling 'docs/' directory
|
|
59
|
+
try:
|
|
60
|
+
spec = importlib.util.find_spec("svc_infra")
|
|
61
|
+
if spec and spec.submodule_search_locations:
|
|
62
|
+
pkg_dir = Path(next(iter(spec.submodule_search_locations)))
|
|
63
|
+
candidate = pkg_dir.parent / "docs"
|
|
64
|
+
if candidate.exists() and candidate.is_dir():
|
|
65
|
+
for p in sorted(candidate.glob("*.md")):
|
|
66
|
+
if p.is_file():
|
|
67
|
+
topics.setdefault(p.stem.replace(" ", "-"), p)
|
|
68
|
+
except Exception:
|
|
69
|
+
# Optional fallback only
|
|
70
|
+
pass
|
|
71
|
+
|
|
52
72
|
return topics
|
|
53
73
|
|
|
54
74
|
|
|
@@ -57,19 +77,21 @@ def _resolve_docs_dir(ctx: click.Context) -> Path | None:
|
|
|
57
77
|
# executes subcommands in child contexts that do not inherit params.
|
|
58
78
|
current: click.Context | None = ctx
|
|
59
79
|
while current is not None:
|
|
60
|
-
|
|
61
|
-
if
|
|
62
|
-
path =
|
|
80
|
+
docs_dir_opt = (current.params or {}).get("docs_dir")
|
|
81
|
+
if docs_dir_opt:
|
|
82
|
+
path = docs_dir_opt if isinstance(docs_dir_opt, Path) else Path(docs_dir_opt)
|
|
63
83
|
path = path.expanduser()
|
|
64
84
|
if path.exists():
|
|
65
85
|
return path
|
|
66
86
|
current = current.parent
|
|
87
|
+
|
|
67
88
|
# Env var next
|
|
68
89
|
env_dir = os.getenv("SVC_INFRA_DOCS_DIR")
|
|
69
90
|
if env_dir:
|
|
70
91
|
p = Path(env_dir).expanduser()
|
|
71
92
|
if p.exists():
|
|
72
93
|
return p
|
|
94
|
+
|
|
73
95
|
# Project docs
|
|
74
96
|
root = resolve_project_root()
|
|
75
97
|
proj_docs = root / "docs"
|
|
@@ -88,16 +110,15 @@ class DocsGroup(TyperGroup):
|
|
|
88
110
|
names.extend([k for k in fs.keys()])
|
|
89
111
|
names.extend([k for k in pkg.keys() if k not in fs])
|
|
90
112
|
# Deduplicate and sort
|
|
91
|
-
|
|
92
|
-
return uniq
|
|
113
|
+
return sorted({*names})
|
|
93
114
|
|
|
94
115
|
def get_command(self, ctx: click.Context, name: str) -> click.Command | None:
|
|
95
|
-
# Built-ins first (e.g., list)
|
|
116
|
+
# Built-ins first (e.g., list, show)
|
|
96
117
|
cmd = super().get_command(ctx, name)
|
|
97
118
|
if cmd is not None:
|
|
98
119
|
return cmd
|
|
99
120
|
|
|
100
|
-
# Dynamic topic resolution
|
|
121
|
+
# Dynamic topic resolution from FS
|
|
101
122
|
dir_to_use = _resolve_docs_dir(ctx)
|
|
102
123
|
fs = _discover_fs_topics(dir_to_use) if dir_to_use else {}
|
|
103
124
|
if name in fs:
|
|
@@ -145,6 +166,10 @@ def register(app: typer.Typer) -> None:
|
|
|
145
166
|
if topic in fs:
|
|
146
167
|
typer.echo(fs[topic].read_text(encoding="utf-8", errors="replace"))
|
|
147
168
|
raise typer.Exit(code=0)
|
|
169
|
+
pkg = _discover_pkg_topics()
|
|
170
|
+
if topic in pkg:
|
|
171
|
+
typer.echo(pkg[topic].read_text(encoding="utf-8", errors="replace"))
|
|
172
|
+
raise typer.Exit(code=0)
|
|
148
173
|
raise typer.BadParameter(f"Unknown topic: {topic}")
|
|
149
174
|
|
|
150
175
|
@docs_app.command("list", help="List available documentation topics")
|
|
@@ -182,6 +182,7 @@ def scaffold(target: str = typer.Option(..., help="compose|railway|k8s|fly")):
|
|
|
182
182
|
|
|
183
183
|
|
|
184
184
|
def register(app: typer.Typer) -> None:
|
|
185
|
-
app
|
|
186
|
-
app.command("
|
|
187
|
-
app.command("
|
|
185
|
+
# Attach to 'obs' group app
|
|
186
|
+
app.command("up")(up)
|
|
187
|
+
app.command("down")(down)
|
|
188
|
+
app.command("scaffold")(scaffold)
|
|
@@ -29,17 +29,17 @@ We provide four CLI commands. You can register them on your Typer app or invoke
|
|
|
29
29
|
|
|
30
30
|
### Commands
|
|
31
31
|
|
|
32
|
-
- `mongo
|
|
33
|
-
- `mongo
|
|
34
|
-
- `mongo
|
|
35
|
-
- `mongo
|
|
32
|
+
- `mongo scaffold` — create both document **and** CRUD schemas
|
|
33
|
+
- `mongo scaffold-documents` — create only the **document** model (Pydantic)
|
|
34
|
+
- `mongo scaffold-schemas` — create only the **CRUD schemas**
|
|
35
|
+
- `mongo scaffold-resources` — create a starter `resources.py` with a `RESOURCES` list
|
|
36
36
|
|
|
37
37
|
### Typical usage
|
|
38
38
|
|
|
39
39
|
#### A) Scaffold documents + schemas together
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
yourapp mongo
|
|
42
|
+
yourapp mongo scaffold \
|
|
43
43
|
--entity-name Product \
|
|
44
44
|
--documents-dir ./src/your_app/products \
|
|
45
45
|
--schemas-dir ./src/your_app/products \
|
|
@@ -57,7 +57,7 @@ src/your_app/products/schemas.py # ProductRead/ProductCreate/ProductUpdate
|
|
|
57
57
|
B) Documents only
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
yourapp mongo
|
|
60
|
+
yourapp mongo scaffold-documents \
|
|
61
61
|
--dest-dir ./src/your_app/products \
|
|
62
62
|
--entity-name Product \
|
|
63
63
|
--documents-filename product_doc.py
|
|
@@ -66,7 +66,7 @@ yourapp mongo-scaffold-documents \
|
|
|
66
66
|
C) Schemas only
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
|
-
yourapp mongo
|
|
69
|
+
yourapp mongo scaffold-schemas \
|
|
70
70
|
--dest-dir ./src/your_app/products \
|
|
71
71
|
--entity-name Product \
|
|
72
72
|
--schemas-filename product_schemas.py
|
|
@@ -75,7 +75,7 @@ yourapp mongo-scaffold-schemas \
|
|
|
75
75
|
D) Starter resources.py
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
|
-
yourapp mongo
|
|
78
|
+
yourapp mongo scaffold-resources \
|
|
79
79
|
--dest-dir ./src/your_app/mongo \
|
|
80
80
|
--filename resources.py \
|
|
81
81
|
--overwrite
|
|
@@ -131,7 +131,7 @@ There are two flavors:
|
|
|
131
131
|
A) Async, minimal (connect, create collections, apply indexes)
|
|
132
132
|
|
|
133
133
|
```bash
|
|
134
|
-
yourapp mongo
|
|
134
|
+
yourapp mongo prepare \
|
|
135
135
|
--resources your_app.mongo.resources:RESOURCES \
|
|
136
136
|
--mongo-url "$MONGO_URL" \
|
|
137
137
|
--mongo-db "$MONGO_DB"
|
|
@@ -140,7 +140,7 @@ yourapp mongo-prepare \
|
|
|
140
140
|
B) Synchronous wrapper (end-to-end convenience)
|
|
141
141
|
|
|
142
142
|
```bash
|
|
143
|
-
yourapp mongo
|
|
143
|
+
yourapp mongo setup-and-prepare \
|
|
144
144
|
--resources your_app.mongo.resources:RESOURCES \
|
|
145
145
|
--mongo-url "$MONGO_URL" \
|
|
146
146
|
--mongo-db "$MONGO_DB"
|
|
@@ -149,7 +149,7 @@ yourapp mongo-setup-and-prepare \
|
|
|
149
149
|
You can also ping connectivity:
|
|
150
150
|
|
|
151
151
|
```bash
|
|
152
|
-
yourapp mongo
|
|
152
|
+
yourapp mongo ping --mongo-url "$MONGO_URL" --mongo-db "$MONGO_DB"
|
|
153
153
|
```
|
|
154
154
|
|
|
155
155
|
Behind the scenes, preparation also locks a service ID to a DB name to prevent accidental cross-DB usage. You can pass --allow-rebind if you intentionally move environments.
|
|
@@ -430,9 +430,9 @@ NoSqlResource(
|
|
|
430
430
|
• If using explicit schemas with PyObjectId, make sure model_config.json_encoders includes {PyObjectId: str}.
|
|
431
431
|
• When using auto-schemas, we expose ObjectId-like fields as str so no custom encoder is needed.
|
|
432
432
|
• Connected to wrong DB name
|
|
433
|
-
|
|
433
|
+
• The system locks a service_id to the DB name once prepared. If you change DBs, run `mongo prepare` with --allow-rebind.
|
|
434
434
|
• Indexes not created
|
|
435
|
-
|
|
435
|
+
• Double-check RESOURCES[indexes]. Run `mongo prepare` again and inspect the output dictionary of created indexes.
|
|
436
436
|
|
|
437
437
|
⸻
|
|
438
438
|
|
svc_infra/mcp/svc_infra_mcp.py
CHANGED
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
from enum import Enum
|
|
4
4
|
|
|
5
5
|
from ai_infra.llm.tools.custom.cli import cli_cmd_help, cli_subcmd_help
|
|
6
|
+
from svc_infra.app.env import prepare_env
|
|
7
|
+
from svc_infra.cli.foundation.runner import run_from_root
|
|
6
8
|
from ai_infra.mcp.server.tools import mcp_from_functions
|
|
7
9
|
|
|
8
10
|
CLI_PROG = "svc-infra"
|
|
@@ -18,33 +20,34 @@ async def svc_infra_cmd_help() -> dict:
|
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
class Subcommand(str, Enum):
|
|
21
|
-
# SQL commands
|
|
22
|
-
sql_init = "sql
|
|
23
|
-
sql_revision = "sql
|
|
24
|
-
sql_upgrade = "sql
|
|
25
|
-
sql_downgrade = "sql
|
|
26
|
-
sql_current = "sql
|
|
27
|
-
sql_history = "sql
|
|
28
|
-
sql_stamp = "sql
|
|
29
|
-
sql_merge_heads = "sql
|
|
30
|
-
sql_setup_and_migrate = "sql
|
|
31
|
-
sql_scaffold = "sql
|
|
32
|
-
sql_scaffold_models = "sql
|
|
33
|
-
sql_scaffold_schemas = "sql
|
|
23
|
+
# SQL group commands
|
|
24
|
+
sql_init = "sql init"
|
|
25
|
+
sql_revision = "sql revision"
|
|
26
|
+
sql_upgrade = "sql upgrade"
|
|
27
|
+
sql_downgrade = "sql downgrade"
|
|
28
|
+
sql_current = "sql current"
|
|
29
|
+
sql_history = "sql history"
|
|
30
|
+
sql_stamp = "sql stamp"
|
|
31
|
+
sql_merge_heads = "sql merge-heads"
|
|
32
|
+
sql_setup_and_migrate = "sql setup-and-migrate"
|
|
33
|
+
sql_scaffold = "sql scaffold"
|
|
34
|
+
sql_scaffold_models = "sql scaffold-models"
|
|
35
|
+
sql_scaffold_schemas = "sql scaffold-schemas"
|
|
36
|
+
sql_export_tenant = "sql export-tenant"
|
|
34
37
|
|
|
35
|
-
# Mongo commands
|
|
36
|
-
mongo_prepare = "mongo
|
|
37
|
-
mongo_setup_and_prepare = "mongo
|
|
38
|
-
mongo_ping = "mongo
|
|
39
|
-
mongo_scaffold = "mongo
|
|
40
|
-
mongo_scaffold_documents = "mongo
|
|
41
|
-
mongo_scaffold_schemas = "mongo
|
|
42
|
-
mongo_scaffold_resources = "mongo
|
|
38
|
+
# Mongo group commands
|
|
39
|
+
mongo_prepare = "mongo prepare"
|
|
40
|
+
mongo_setup_and_prepare = "mongo setup-and-prepare"
|
|
41
|
+
mongo_ping = "mongo ping"
|
|
42
|
+
mongo_scaffold = "mongo scaffold"
|
|
43
|
+
mongo_scaffold_documents = "mongo scaffold-documents"
|
|
44
|
+
mongo_scaffold_schemas = "mongo scaffold-schemas"
|
|
45
|
+
mongo_scaffold_resources = "mongo scaffold-resources"
|
|
43
46
|
|
|
44
|
-
# Observability commands
|
|
45
|
-
obs_up = "obs
|
|
46
|
-
obs_down = "obs
|
|
47
|
-
obs_scaffold = "obs
|
|
47
|
+
# Observability group commands
|
|
48
|
+
obs_up = "obs up"
|
|
49
|
+
obs_down = "obs down"
|
|
50
|
+
obs_scaffold = "obs scaffold"
|
|
48
51
|
|
|
49
52
|
|
|
50
53
|
async def svc_infra_subcmd_help(subcommand: Subcommand) -> dict:
|
|
@@ -52,7 +55,19 @@ async def svc_infra_subcmd_help(subcommand: Subcommand) -> dict:
|
|
|
52
55
|
Get help text for a specific subcommand of svc-infra CLI.
|
|
53
56
|
(Enum keeps a tight schema; function signature remains simple.)
|
|
54
57
|
"""
|
|
55
|
-
|
|
58
|
+
tokens = subcommand.value.split()
|
|
59
|
+
if len(tokens) == 1:
|
|
60
|
+
return await cli_subcmd_help(CLI_PROG, subcommand)
|
|
61
|
+
|
|
62
|
+
root = prepare_env()
|
|
63
|
+
text = await run_from_root(root, CLI_PROG, [*tokens, "--help"])
|
|
64
|
+
return {
|
|
65
|
+
"ok": True,
|
|
66
|
+
"action": "subcommand_help",
|
|
67
|
+
"subcommand": subcommand.value,
|
|
68
|
+
"project_root": str(root),
|
|
69
|
+
"help": text,
|
|
70
|
+
}
|
|
56
71
|
|
|
57
72
|
|
|
58
73
|
mcp = mcp_from_functions(
|
|
@@ -80,7 +80,7 @@ svc_infra/api/fastapi/middleware/errors/handlers.py,sha256=pQMVs5n627vcKkDFEaUzx
|
|
|
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
|
|
83
|
-
svc_infra/api/fastapi/middleware/ratelimit.py,sha256=
|
|
83
|
+
svc_infra/api/fastapi/middleware/ratelimit.py,sha256=Zw55_vlSVz4aqwr7gZ1P53HHZMO6fYUUQ7TXBzjEbw8,5014
|
|
84
84
|
svc_infra/api/fastapi/middleware/ratelimit_store.py,sha256=LmJR8-kkW42rzOjls9lG1SBtCKjVY7L2Y_bNKHNY3-A,2553
|
|
85
85
|
svc_infra/api/fastapi/middleware/request_id.py,sha256=Iru7ypTdK_n76lwziEGDWoVF4FKS0Ps1PMASYmzK8ek,768
|
|
86
86
|
svc_infra/api/fastapi/middleware/request_size_limit.py,sha256=AcGqaB-F7Tbhg-at7ViT4Bpifst34jFneDBlUBjgo5I,1248
|
|
@@ -129,27 +129,27 @@ svc_infra/cache/resources.py,sha256=BhvPAZvCQ-fitUdniGEOOE4g1ZvljdCA_R5pR8WfJz4,
|
|
|
129
129
|
svc_infra/cache/tags.py,sha256=9URw4BRlnb4QFAYpDI36fMms6642xq4TeV9jqsEjzE8,2625
|
|
130
130
|
svc_infra/cache/ttl.py,sha256=_lWvNx1CTE4RcFEOUYkADd7_k4I13SLmtK0AMRUq2OM,1945
|
|
131
131
|
svc_infra/cache/utils.py,sha256=-LWr5IiJCNm3pwaoeCVlxNknnO2ChNKFcAGlFU98kjg,4856
|
|
132
|
-
svc_infra/cli/__init__.py,sha256=
|
|
132
|
+
svc_infra/cli/__init__.py,sha256=3H3RePgCv_fCw9mFzYkbdn1VEeiZ1T88_BdloqrlEPY,1321
|
|
133
133
|
svc_infra/cli/__main__.py,sha256=5BjNuyet8AY-POwoF5rGt722rHQ7tJ0Vf0UFUfzzi-I,58
|
|
134
134
|
svc_infra/cli/cmds/__init__.py,sha256=xKVXpMP_fD7jfmYonxWxh5LKHUQiuIFaJgkpqtkPt-M,1051
|
|
135
135
|
svc_infra/cli/cmds/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
136
136
|
svc_infra/cli/cmds/db/nosql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
137
137
|
svc_infra/cli/cmds/db/nosql/mongo/README.md,sha256=0u3XLeoBd0XQzXwwfEiFISMIij11TJ9iOGzrysBvsFk,1788
|
|
138
138
|
svc_infra/cli/cmds/db/nosql/mongo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
139
|
-
svc_infra/cli/cmds/db/nosql/mongo/mongo_cmds.py,sha256=
|
|
140
|
-
svc_infra/cli/cmds/db/nosql/mongo/mongo_scaffold_cmds.py,sha256=
|
|
139
|
+
svc_infra/cli/cmds/db/nosql/mongo/mongo_cmds.py,sha256=s5oAlZ9bYndiRhtmi9gSbcJPvR82bwCGRbxAF_ziBx4,6125
|
|
140
|
+
svc_infra/cli/cmds/db/nosql/mongo/mongo_scaffold_cmds.py,sha256=AR2sNt8Faxa6vAJ3MxGSWqLU54DMOL_r72hDtnEk4Pg,4253
|
|
141
141
|
svc_infra/cli/cmds/db/sql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
142
|
-
svc_infra/cli/cmds/db/sql/alembic_cmds.py,sha256=
|
|
143
|
-
svc_infra/cli/cmds/db/sql/sql_export_cmds.py,sha256=
|
|
144
|
-
svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py,sha256=
|
|
145
|
-
svc_infra/cli/cmds/docs/docs_cmds.py,sha256=
|
|
142
|
+
svc_infra/cli/cmds/db/sql/alembic_cmds.py,sha256=uCreHg69Zf6B5gbv9Dm39jCRk6q2KQy_05A-75IP0Fg,9650
|
|
143
|
+
svc_infra/cli/cmds/db/sql/sql_export_cmds.py,sha256=YpkguUJFeFApMphVkhOJllTi25ejlsQaJarMe6vJD54,2685
|
|
144
|
+
svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py,sha256=MKc_T_tY1Y_wQl7XTlq8GhYWMMI1q1_vcFZVPOEcNUg,4601
|
|
145
|
+
svc_infra/cli/cmds/docs/docs_cmds.py,sha256=vRDhddel_X9MCfCVRvV7_Ns7sB3awfwSVWV0HqKLRKg,7653
|
|
146
146
|
svc_infra/cli/cmds/dx/__init__.py,sha256=wQtl3-kOgoESlpVkjl3YFtqkOnQSIvVsOdutiaZFejM,197
|
|
147
147
|
svc_infra/cli/cmds/dx/dx_cmds.py,sha256=XTKUJzS3UIYn6h3CHzDEWKYJaWn0TzGiUCq3OeW27E0,3326
|
|
148
148
|
svc_infra/cli/cmds/help.py,sha256=wGfZFMYaR2ZPwW2JwKDU7M3m4AtdCd8GRQ412AmEBUM,758
|
|
149
149
|
svc_infra/cli/cmds/jobs/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
150
150
|
svc_infra/cli/cmds/jobs/jobs_cmds.py,sha256=l-w5GuR82GWR_F1CA7WPYAM895XBD8TQj_hZ6retBv0,1252
|
|
151
151
|
svc_infra/cli/cmds/obs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
152
|
-
svc_infra/cli/cmds/obs/obs_cmds.py,sha256=
|
|
152
|
+
svc_infra/cli/cmds/obs/obs_cmds.py,sha256=cAFzkO6j6FCZZxn5ch93Gq4TflV5Bx-6l3gyhKemxqM,6505
|
|
153
153
|
svc_infra/cli/cmds/sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
154
154
|
svc_infra/cli/cmds/sdk/sdk_cmds.py,sha256=xzEbhA-L5bXMxf-DFzYXkdITfC4ua1Lt8I9x6PoEax0,2886
|
|
155
155
|
svc_infra/cli/foundation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -169,7 +169,7 @@ svc_infra/db/nosql/constants.py,sha256=Z9bJImxwb8D7vovASFegv8XMwaWcM28tsKJV2Sjyw
|
|
|
169
169
|
svc_infra/db/nosql/core.py,sha256=hWda-OsiutD_7wVLI_S6QkkH6gHK8oHbVzRPjNfIBxY,4634
|
|
170
170
|
svc_infra/db/nosql/indexes.py,sha256=XCVeSM73x3iF1bWITlyvYOr89dkcgliRcXUMTPg8Tmo,2302
|
|
171
171
|
svc_infra/db/nosql/management.py,sha256=YreJXcNpPBAQTRGvyXjDK4pJhNL87hljT_2-i7CAlvg,3468
|
|
172
|
-
svc_infra/db/nosql/mongo/README.md,sha256=
|
|
172
|
+
svc_infra/db/nosql/mongo/README.md,sha256=rFGck7VMGOMh9PZ8KkgDbxzZfbsWAoTJg5UWGlvK4oM,11687
|
|
173
173
|
svc_infra/db/nosql/mongo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
174
174
|
svc_infra/db/nosql/mongo/client.py,sha256=dAhbi6cnhrTz6AgHW48hrXt-ptVl3X8_b75yAyjoOa0,1325
|
|
175
175
|
svc_infra/db/nosql/mongo/settings.py,sha256=uat8k2FlgBmcEpcKXgzYvIK-_Zi4n-lnFEjlMqr23q8,522
|
|
@@ -228,7 +228,7 @@ svc_infra/jobs/redis_queue.py,sha256=wgmWKslF1dkYscJe49UgUX7gwEuGyOUWEb0-pn82I3g
|
|
|
228
228
|
svc_infra/jobs/scheduler.py,sha256=dTUEEyEuTVHNmJT8wPdMu4YjnTN7R_YW67gtCKpqC7M,1180
|
|
229
229
|
svc_infra/jobs/worker.py,sha256=T2A575_mnieJHPOYU_FseubLA_HQf9pB4CkRgzRJBHU,694
|
|
230
230
|
svc_infra/mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
231
|
-
svc_infra/mcp/svc_infra_mcp.py,sha256=
|
|
231
|
+
svc_infra/mcp/svc_infra_mcp.py,sha256=x8wEB1YhnM2X3_3AUmKl6qKmQcL63dGbJ71WlzigHs8,2423
|
|
232
232
|
svc_infra/obs/README.md,sha256=pmd6AyFZW3GCCi0sr3uTHrPj5KgAI8rrXw8QPkrf1R8,8021
|
|
233
233
|
svc_infra/obs/__init__.py,sha256=t5DgkiuuhHnfAHChzYqCI1-Fpr68iQ0A1nHOLFIlAuM,75
|
|
234
234
|
svc_infra/obs/add.py,sha256=Qa8pswZDxspIn3oniqe8NYeHmVhFwiYOYxF9xNAyCOs,4016
|
|
@@ -296,7 +296,7 @@ svc_infra/webhooks/fastapi.py,sha256=BCNvGNxukf6dC2a4i-6en-PrjBGV19YvCWOot5lXWsA
|
|
|
296
296
|
svc_infra/webhooks/router.py,sha256=6JvAVPMEth_xxHX-IsIOcyMgHX7g1H0OVxVXKLuMp9w,1596
|
|
297
297
|
svc_infra/webhooks/service.py,sha256=hWgiJRXKBwKunJOx91C7EcLUkotDtD3Xp0RT6vj2IC0,1797
|
|
298
298
|
svc_infra/webhooks/signing.py,sha256=NCwdZzmravUe7HVIK_uXK0qqf12FG-_MVsgPvOw6lsM,784
|
|
299
|
-
svc_infra-0.1.
|
|
300
|
-
svc_infra-0.1.
|
|
301
|
-
svc_infra-0.1.
|
|
302
|
-
svc_infra-0.1.
|
|
299
|
+
svc_infra-0.1.620.dist-info/METADATA,sha256=Ii8wzmysVqUARGfBUES-2TQIMzhyeAGvGimkSTMIpYg,8106
|
|
300
|
+
svc_infra-0.1.620.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
301
|
+
svc_infra-0.1.620.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
|
|
302
|
+
svc_infra-0.1.620.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|