svc-infra 0.1.620__py3-none-any.whl → 0.1.621__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/cli/cmds/docs/docs_cmds.py +137 -84
- {svc_infra-0.1.620.dist-info → svc_infra-0.1.621.dist-info}/METADATA +1 -1
- {svc_infra-0.1.620.dist-info → svc_infra-0.1.621.dist-info}/RECORD +5 -5
- {svc_infra-0.1.620.dist-info → svc_infra-0.1.621.dist-info}/WHEEL +0 -0
- {svc_infra-0.1.620.dist-info → svc_infra-0.1.621.dist-info}/entry_points.txt +0 -0
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import importlib.util
|
|
4
4
|
import os
|
|
5
|
+
import sys
|
|
5
6
|
from importlib.metadata import PackageNotFoundError, distribution
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Dict, List
|
|
@@ -10,7 +11,15 @@ import click
|
|
|
10
11
|
import typer
|
|
11
12
|
from typer.core import TyperGroup
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
|
|
15
|
+
def _norm(name: str) -> str:
|
|
16
|
+
"""Normalize a topic name for stable CLI commands.
|
|
17
|
+
|
|
18
|
+
- Lowercase
|
|
19
|
+
- Replace spaces and underscores with hyphens
|
|
20
|
+
- Strip leading/trailing whitespace
|
|
21
|
+
"""
|
|
22
|
+
return name.strip().lower().replace(" ", "-").replace("_", "-")
|
|
14
23
|
|
|
15
24
|
|
|
16
25
|
def _discover_fs_topics(docs_dir: Path) -> Dict[str, Path]:
|
|
@@ -18,7 +27,7 @@ def _discover_fs_topics(docs_dir: Path) -> Dict[str, Path]:
|
|
|
18
27
|
if docs_dir.exists() and docs_dir.is_dir():
|
|
19
28
|
for p in sorted(docs_dir.glob("*.md")):
|
|
20
29
|
if p.is_file():
|
|
21
|
-
topics[p.stem
|
|
30
|
+
topics[_norm(p.stem)] = p
|
|
22
31
|
return topics
|
|
23
32
|
|
|
24
33
|
|
|
@@ -46,7 +55,7 @@ def _discover_pkg_topics() -> Dict[str, Path]:
|
|
|
46
55
|
s = str(f)
|
|
47
56
|
if not s.startswith("docs/") or not s.endswith(".md"):
|
|
48
57
|
continue
|
|
49
|
-
topic_name = Path(s).stem
|
|
58
|
+
topic_name = _norm(Path(s).stem)
|
|
50
59
|
try:
|
|
51
60
|
abs_path = Path(dist.locate_file(f))
|
|
52
61
|
if abs_path.exists() and abs_path.is_file():
|
|
@@ -55,60 +64,145 @@ def _discover_pkg_topics() -> Dict[str, Path]:
|
|
|
55
64
|
# Best effort; continue to next
|
|
56
65
|
continue
|
|
57
66
|
|
|
58
|
-
# 2) Fallback: site-packages sibling 'docs/' directory
|
|
67
|
+
# 2) Fallback: site-packages sibling 'docs/' directory (and repo-root docs in editable installs)
|
|
59
68
|
try:
|
|
60
69
|
spec = importlib.util.find_spec("svc_infra")
|
|
61
70
|
if spec and spec.submodule_search_locations:
|
|
62
71
|
pkg_dir = Path(next(iter(spec.submodule_search_locations)))
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
candidates = [
|
|
73
|
+
pkg_dir.parent / "docs", # site-packages/docs OR src/docs
|
|
74
|
+
pkg_dir / "docs", # site-packages/svc_infra/docs OR src/svc_infra/docs
|
|
75
|
+
pkg_dir.parent.parent
|
|
76
|
+
/ "docs", # repo-root/docs when running editable from repo (src/svc_infra → ../../docs)
|
|
77
|
+
]
|
|
78
|
+
for candidate in candidates:
|
|
79
|
+
if candidate.exists() and candidate.is_dir():
|
|
80
|
+
for p in sorted(candidate.glob("*.md")):
|
|
81
|
+
if p.is_file():
|
|
82
|
+
topics.setdefault(_norm(p.stem), p)
|
|
83
|
+
# If one candidate had docs, that's sufficient
|
|
84
|
+
if any(k for k in topics):
|
|
85
|
+
break
|
|
68
86
|
except Exception:
|
|
69
87
|
# Optional fallback only
|
|
70
88
|
pass
|
|
71
89
|
|
|
90
|
+
# 3) Last-resort: scan sys.path entries that look like site-/dist-packages for a top-level docs/
|
|
91
|
+
# directory containing markdown files. This covers non-standard installs/editable modes.
|
|
92
|
+
try:
|
|
93
|
+
if not topics:
|
|
94
|
+
for entry in sys.path:
|
|
95
|
+
try:
|
|
96
|
+
if not entry or ("site-packages" not in entry and "dist-packages" not in entry):
|
|
97
|
+
continue
|
|
98
|
+
docs_dir = Path(entry) / "docs"
|
|
99
|
+
if docs_dir.exists() and docs_dir.is_dir():
|
|
100
|
+
found = _discover_fs_topics(docs_dir)
|
|
101
|
+
if found:
|
|
102
|
+
# Merge but do not override anything already found
|
|
103
|
+
for k, v in found.items():
|
|
104
|
+
topics.setdefault(k, v)
|
|
105
|
+
# If we found one valid docs dir, it's enough
|
|
106
|
+
break
|
|
107
|
+
except Exception:
|
|
108
|
+
continue
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
# 4) Parse dist-info/RECORD or egg-info/SOURCES.txt to enumerate docs if available
|
|
113
|
+
try:
|
|
114
|
+
if not topics:
|
|
115
|
+
spec = importlib.util.find_spec("svc_infra")
|
|
116
|
+
base_dir: Path | None = None
|
|
117
|
+
if spec and spec.submodule_search_locations:
|
|
118
|
+
base_dir = Path(next(iter(spec.submodule_search_locations))).parent
|
|
119
|
+
# Fallback to first site-packages on sys.path
|
|
120
|
+
if base_dir is None:
|
|
121
|
+
for entry in sys.path:
|
|
122
|
+
if entry and "site-packages" in entry:
|
|
123
|
+
base_dir = Path(entry)
|
|
124
|
+
break
|
|
125
|
+
if base_dir and base_dir.exists():
|
|
126
|
+
# Check for both hyphen and underscore dist-info names
|
|
127
|
+
candidates = list(base_dir.glob("svc_infra-*.dist-info")) + list(
|
|
128
|
+
base_dir.glob("svc-infra-*.dist-info")
|
|
129
|
+
)
|
|
130
|
+
for di in candidates:
|
|
131
|
+
record = di / "RECORD"
|
|
132
|
+
if record.exists():
|
|
133
|
+
try:
|
|
134
|
+
for line in record.read_text(
|
|
135
|
+
encoding="utf-8", errors="ignore"
|
|
136
|
+
).splitlines():
|
|
137
|
+
rel = line.split(",", 1)[0]
|
|
138
|
+
if rel.startswith("docs/") and rel.endswith(".md"):
|
|
139
|
+
abs_p = base_dir / rel
|
|
140
|
+
if abs_p.exists() and abs_p.is_file():
|
|
141
|
+
topics.setdefault(_norm(Path(rel).stem), abs_p)
|
|
142
|
+
except Exception:
|
|
143
|
+
continue
|
|
144
|
+
# egg-info fallback
|
|
145
|
+
if not topics:
|
|
146
|
+
egg_candidates = list(base_dir.glob("svc_infra-*.egg-info")) + list(
|
|
147
|
+
base_dir.glob("svc-infra-*.egg-info")
|
|
148
|
+
)
|
|
149
|
+
for ei in egg_candidates:
|
|
150
|
+
sources = ei / "SOURCES.txt"
|
|
151
|
+
if sources.exists():
|
|
152
|
+
try:
|
|
153
|
+
for rel in sources.read_text(
|
|
154
|
+
encoding="utf-8", errors="ignore"
|
|
155
|
+
).splitlines():
|
|
156
|
+
rel = rel.strip()
|
|
157
|
+
if rel.startswith("docs/") and rel.endswith(".md"):
|
|
158
|
+
abs_p = base_dir / rel
|
|
159
|
+
if abs_p.exists() and abs_p.is_file():
|
|
160
|
+
topics.setdefault(_norm(Path(rel).stem), abs_p)
|
|
161
|
+
except Exception:
|
|
162
|
+
continue
|
|
163
|
+
except Exception:
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
# 5) Deep fallback: recursively search site-packages/dist-packages for any 'docs' folder
|
|
167
|
+
# containing markdown files (limited depth to keep overhead reasonable).
|
|
168
|
+
try:
|
|
169
|
+
if not topics:
|
|
170
|
+
for entry in sys.path:
|
|
171
|
+
if not entry or ("site-packages" not in entry and "dist-packages" not in entry):
|
|
172
|
+
continue
|
|
173
|
+
base = Path(entry)
|
|
174
|
+
if not base.exists() or not base.is_dir():
|
|
175
|
+
continue
|
|
176
|
+
base_parts = len(base.parts)
|
|
177
|
+
for root, dirs, files in os.walk(base):
|
|
178
|
+
root_path = Path(root)
|
|
179
|
+
# Limit search depth to avoid expensive scans
|
|
180
|
+
if len(root_path.parts) - base_parts > 4:
|
|
181
|
+
# prune
|
|
182
|
+
dirs[:] = []
|
|
183
|
+
continue
|
|
184
|
+
if root_path.name == "docs":
|
|
185
|
+
for p in sorted(root_path.glob("*.md")):
|
|
186
|
+
if p.is_file():
|
|
187
|
+
topics.setdefault(_norm(p.stem), p)
|
|
188
|
+
# do not break; there might be multiple doc dirs
|
|
189
|
+
except Exception:
|
|
190
|
+
pass
|
|
191
|
+
|
|
72
192
|
return topics
|
|
73
193
|
|
|
74
194
|
|
|
75
195
|
def _resolve_docs_dir(ctx: click.Context) -> Path | None:
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
current: click.Context | None = ctx
|
|
79
|
-
while current is not None:
|
|
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)
|
|
83
|
-
path = path.expanduser()
|
|
84
|
-
if path.exists():
|
|
85
|
-
return path
|
|
86
|
-
current = current.parent
|
|
87
|
-
|
|
88
|
-
# Env var next
|
|
89
|
-
env_dir = os.getenv("SVC_INFRA_DOCS_DIR")
|
|
90
|
-
if env_dir:
|
|
91
|
-
p = Path(env_dir).expanduser()
|
|
92
|
-
if p.exists():
|
|
93
|
-
return p
|
|
94
|
-
|
|
95
|
-
# Project docs
|
|
96
|
-
root = resolve_project_root()
|
|
97
|
-
proj_docs = root / "docs"
|
|
98
|
-
if proj_docs.exists():
|
|
99
|
-
return proj_docs
|
|
196
|
+
# Deprecated: we no longer read docs from arbitrary paths or env.
|
|
197
|
+
# All docs are sourced from the packaged svc-infra distribution only.
|
|
100
198
|
return None
|
|
101
199
|
|
|
102
200
|
|
|
103
201
|
class DocsGroup(TyperGroup):
|
|
104
202
|
def list_commands(self, ctx: click.Context) -> List[str]:
|
|
105
203
|
names: List[str] = list(super().list_commands(ctx) or [])
|
|
106
|
-
dir_to_use = _resolve_docs_dir(ctx)
|
|
107
|
-
fs = _discover_fs_topics(dir_to_use) if dir_to_use else {}
|
|
108
204
|
pkg = _discover_pkg_topics()
|
|
109
|
-
|
|
110
|
-
names.extend([k for k in fs.keys()])
|
|
111
|
-
names.extend([k for k in pkg.keys() if k not in fs])
|
|
205
|
+
names.extend([k for k in pkg.keys()])
|
|
112
206
|
# Deduplicate and sort
|
|
113
207
|
return sorted({*names})
|
|
114
208
|
|
|
@@ -118,19 +212,7 @@ class DocsGroup(TyperGroup):
|
|
|
118
212
|
if cmd is not None:
|
|
119
213
|
return cmd
|
|
120
214
|
|
|
121
|
-
#
|
|
122
|
-
dir_to_use = _resolve_docs_dir(ctx)
|
|
123
|
-
fs = _discover_fs_topics(dir_to_use) if dir_to_use else {}
|
|
124
|
-
if name in fs:
|
|
125
|
-
file_path = fs[name]
|
|
126
|
-
|
|
127
|
-
@click.command(name=name)
|
|
128
|
-
def _show_fs() -> None:
|
|
129
|
-
click.echo(file_path.read_text(encoding="utf-8", errors="replace"))
|
|
130
|
-
|
|
131
|
-
return _show_fs
|
|
132
|
-
|
|
133
|
-
# Packaged fallback
|
|
215
|
+
# Packaged topics only
|
|
134
216
|
pkg = _discover_pkg_topics()
|
|
135
217
|
if name in pkg:
|
|
136
218
|
file_path = pkg[name]
|
|
@@ -151,21 +233,10 @@ def register(app: typer.Typer) -> None:
|
|
|
151
233
|
|
|
152
234
|
@docs_app.callback(invoke_without_command=True)
|
|
153
235
|
def _docs_options(
|
|
154
|
-
docs_dir: Path | None = typer.Option(
|
|
155
|
-
None,
|
|
156
|
-
"--docs-dir",
|
|
157
|
-
help="Path to a docs directory to read from (overrides env/project root)",
|
|
158
|
-
),
|
|
159
236
|
topic: str | None = typer.Option(None, "--topic", help="Topic to show directly"),
|
|
160
237
|
) -> None:
|
|
161
|
-
"""Support --
|
|
238
|
+
"""Support --topic at group level (packaged docs only)."""
|
|
162
239
|
if topic:
|
|
163
|
-
ctx = click.get_current_context()
|
|
164
|
-
dir_to_use = _resolve_docs_dir(ctx)
|
|
165
|
-
fs = _discover_fs_topics(dir_to_use) if dir_to_use else {}
|
|
166
|
-
if topic in fs:
|
|
167
|
-
typer.echo(fs[topic].read_text(encoding="utf-8", errors="replace"))
|
|
168
|
-
raise typer.Exit(code=0)
|
|
169
240
|
pkg = _discover_pkg_topics()
|
|
170
241
|
if topic in pkg:
|
|
171
242
|
typer.echo(pkg[topic].read_text(encoding="utf-8", errors="replace"))
|
|
@@ -174,36 +245,18 @@ def register(app: typer.Typer) -> None:
|
|
|
174
245
|
|
|
175
246
|
@docs_app.command("list", help="List available documentation topics")
|
|
176
247
|
def list_topics() -> None:
|
|
177
|
-
ctx = click.get_current_context()
|
|
178
|
-
root = resolve_project_root()
|
|
179
|
-
dir_to_use = _resolve_docs_dir(ctx)
|
|
180
|
-
fs = _discover_fs_topics(dir_to_use) if dir_to_use else {}
|
|
181
248
|
pkg = _discover_pkg_topics()
|
|
182
249
|
|
|
183
|
-
# Print
|
|
250
|
+
# Print packaged topics only
|
|
184
251
|
def _print(name: str, path: Path) -> None:
|
|
185
|
-
|
|
186
|
-
rel = path.relative_to(root)
|
|
187
|
-
typer.echo(f"{name}\t{rel}")
|
|
188
|
-
except Exception:
|
|
189
|
-
# For packaged topics, path will be site-packages absolute path
|
|
190
|
-
typer.echo(f"{name}\t{path}")
|
|
252
|
+
typer.echo(f"{name}\t{path}")
|
|
191
253
|
|
|
192
|
-
for name, path in fs.items():
|
|
193
|
-
_print(name, path)
|
|
194
254
|
for name, path in pkg.items():
|
|
195
|
-
|
|
196
|
-
_print(name, path)
|
|
255
|
+
_print(name, path)
|
|
197
256
|
|
|
198
257
|
# Also support a generic "show" command
|
|
199
258
|
@docs_app.command("show", help="Show docs for a topic (alternative to dynamic subcommand)")
|
|
200
259
|
def show(topic: str) -> None:
|
|
201
|
-
ctx = click.get_current_context()
|
|
202
|
-
dir_to_use = _resolve_docs_dir(ctx)
|
|
203
|
-
fs = _discover_fs_topics(dir_to_use) if dir_to_use else {}
|
|
204
|
-
if topic in fs:
|
|
205
|
-
typer.echo(fs[topic].read_text(encoding="utf-8", errors="replace"))
|
|
206
|
-
return
|
|
207
260
|
pkg = _discover_pkg_topics()
|
|
208
261
|
if topic in pkg:
|
|
209
262
|
typer.echo(pkg[topic].read_text(encoding="utf-8", errors="replace"))
|
|
@@ -142,7 +142,7 @@ svc_infra/cli/cmds/db/sql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
|
142
142
|
svc_infra/cli/cmds/db/sql/alembic_cmds.py,sha256=uCreHg69Zf6B5gbv9Dm39jCRk6q2KQy_05A-75IP0Fg,9650
|
|
143
143
|
svc_infra/cli/cmds/db/sql/sql_export_cmds.py,sha256=YpkguUJFeFApMphVkhOJllTi25ejlsQaJarMe6vJD54,2685
|
|
144
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=
|
|
145
|
+
svc_infra/cli/cmds/docs/docs_cmds.py,sha256=fbdnTEfzx3HNNXK3zL5fa_Lk3FowzHBcfS_VHQah7VQ,10720
|
|
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
|
|
@@ -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.621.dist-info/METADATA,sha256=QD-ETuukvgwtsMr9N5ylyIOBUgPW6yms4kDLJbFN1H4,8106
|
|
300
|
+
svc_infra-0.1.621.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
301
|
+
svc_infra-0.1.621.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
|
|
302
|
+
svc_infra-0.1.621.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|