svc-infra 0.1.620__py3-none-any.whl → 0.1.622__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 +138 -126
- {svc_infra-0.1.620.dist-info → svc_infra-0.1.622.dist-info}/METADATA +1 -1
- {svc_infra-0.1.620.dist-info → svc_infra-0.1.622.dist-info}/RECORD +5 -5
- {svc_infra-0.1.620.dist-info → svc_infra-0.1.622.dist-info}/WHEEL +0 -0
- {svc_infra-0.1.620.dist-info → svc_infra-0.1.622.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
|
|
@@ -12,26 +13,77 @@ from typer.core import TyperGroup
|
|
|
12
13
|
|
|
13
14
|
from svc_infra.app.root import resolve_project_root
|
|
14
15
|
|
|
16
|
+
# ---------- small helpers ----------
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
|
|
19
|
+
def _norm(name: str) -> str:
|
|
20
|
+
return name.strip().lower().replace(" ", "-").replace("_", "-")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _md_topics_in(dirpath: Path) -> Dict[str, Path]:
|
|
17
24
|
topics: Dict[str, Path] = {}
|
|
18
|
-
if
|
|
19
|
-
for p in sorted(
|
|
25
|
+
if dirpath.exists() and dirpath.is_dir():
|
|
26
|
+
for p in sorted(dirpath.glob("*.md")):
|
|
20
27
|
if p.is_file():
|
|
21
|
-
topics[p.stem
|
|
28
|
+
topics[_norm(p.stem)] = p
|
|
22
29
|
return topics
|
|
23
30
|
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
"""Discover docs packaged under 'docs/' in the installed distribution.
|
|
32
|
+
# ---------- where could docs live after install? ----------
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
installed package directory in site-packages.
|
|
34
|
+
|
|
35
|
+
def _candidate_docs_dirs(ctx: click.Context) -> List[Path]:
|
|
31
36
|
"""
|
|
32
|
-
|
|
37
|
+
Return likely directories that contain the shipped docs (*.md), ordered by priority.
|
|
38
|
+
Covers:
|
|
39
|
+
1) explicit --docs-dir / env var
|
|
40
|
+
2) in-repo (editable install): <repo>/docs relative to src/svc_infra
|
|
41
|
+
3) wheel installs: <site-packages>/docs
|
|
42
|
+
4) wheel .data area: <site-packages>/{name}-{ver}.data/**/docs
|
|
43
|
+
"""
|
|
44
|
+
out: List[Path] = []
|
|
45
|
+
|
|
46
|
+
# 1) explicit override (--docs-dir or env)
|
|
47
|
+
# Walk up parent contexts to see Typer's group option.
|
|
48
|
+
cur: click.Context | None = ctx
|
|
49
|
+
while cur is not None:
|
|
50
|
+
docs_dir_opt = (cur.params or {}).get("docs_dir")
|
|
51
|
+
if docs_dir_opt:
|
|
52
|
+
p = docs_dir_opt if isinstance(docs_dir_opt, Path) else Path(docs_dir_opt)
|
|
53
|
+
p = p.expanduser()
|
|
54
|
+
if p.exists():
|
|
55
|
+
out.append(p)
|
|
56
|
+
return out # explicit override wins
|
|
57
|
+
cur = cur.parent
|
|
33
58
|
|
|
34
|
-
|
|
59
|
+
env_dir = os.getenv("SVC_INFRA_DOCS_DIR")
|
|
60
|
+
if env_dir:
|
|
61
|
+
p = Path(env_dir).expanduser()
|
|
62
|
+
if p.exists():
|
|
63
|
+
out.append(p)
|
|
64
|
+
return out # explicit override wins
|
|
65
|
+
|
|
66
|
+
# locate installed package dir: .../site-packages/svc_infra
|
|
67
|
+
pkg_dir: Path | None = None
|
|
68
|
+
spec = importlib.util.find_spec("svc_infra")
|
|
69
|
+
if spec and spec.submodule_search_locations:
|
|
70
|
+
pkg_dir = Path(next(iter(spec.submodule_search_locations)))
|
|
71
|
+
|
|
72
|
+
# 2) in-repo editable install: src/svc_infra -> ../../docs
|
|
73
|
+
if pkg_dir:
|
|
74
|
+
repo_root_docs = pkg_dir.parent.parent / "docs"
|
|
75
|
+
if repo_root_docs.exists():
|
|
76
|
+
out.append(repo_root_docs)
|
|
77
|
+
|
|
78
|
+
# 3) wheel installs often end up with a top-level site-packages/docs
|
|
79
|
+
top_level_docs = pkg_dir.parent / "docs"
|
|
80
|
+
if top_level_docs.exists():
|
|
81
|
+
out.append(top_level_docs)
|
|
82
|
+
|
|
83
|
+
# 4) wheel .data layout: <site-packages>/{dist-name}-{version}.data/**/docs
|
|
84
|
+
# This catches Poetry's include=docs/**/* paths installed by pip.
|
|
85
|
+
# We compute sibling candidates off site-packages base.
|
|
86
|
+
site_pkgs: Path | None = pkg_dir.parent if pkg_dir else None
|
|
35
87
|
dist = None
|
|
36
88
|
for name in ("svc-infra", "svc_infra"):
|
|
37
89
|
try:
|
|
@@ -40,113 +92,95 @@ def _discover_pkg_topics() -> Dict[str, Path]:
|
|
|
40
92
|
except PackageNotFoundError:
|
|
41
93
|
dist = None
|
|
42
94
|
|
|
43
|
-
if dist is not None:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
except Exception:
|
|
55
|
-
# Best effort; continue to next
|
|
95
|
+
if site_pkgs and dist is not None:
|
|
96
|
+
# normalized dist name (hyphen/underscore forms both happen in practice)
|
|
97
|
+
dist_name = dist.metadata.get("Name", "svc-infra")
|
|
98
|
+
dist_ver = dist.version
|
|
99
|
+
data_candidates = [
|
|
100
|
+
site_pkgs / f"{dist_name}-{dist_ver}.data",
|
|
101
|
+
site_pkgs / f"{dist_name.replace('-', '_')}-{dist_ver}.data",
|
|
102
|
+
site_pkgs / f"{dist_name.replace('_', '-')}-{dist_ver}.data",
|
|
103
|
+
]
|
|
104
|
+
for data_dir in data_candidates:
|
|
105
|
+
if not data_dir.exists():
|
|
56
106
|
continue
|
|
107
|
+
# common wheel data subfolders
|
|
108
|
+
for sub in ("purelib", "platlib", "data"):
|
|
109
|
+
d = data_dir / sub / "docs"
|
|
110
|
+
if d.exists():
|
|
111
|
+
out.append(d)
|
|
112
|
+
# fallback: search shallowly for any docs/ folder inside .data
|
|
113
|
+
for root, dirs, _files in os.walk(data_dir):
|
|
114
|
+
root_path = Path(root)
|
|
115
|
+
# limit depth (cheap)
|
|
116
|
+
if len(root_path.parts) - len(data_dir.parts) > 3:
|
|
117
|
+
dirs[:] = []
|
|
118
|
+
continue
|
|
119
|
+
if root_path.name == "docs":
|
|
120
|
+
out.append(root_path)
|
|
121
|
+
|
|
122
|
+
# 5) extremely defensive: scan sys.path entries that look like site-/dist-packages for top-level docs/
|
|
123
|
+
for entry in sys.path:
|
|
124
|
+
if not entry or ("site-packages" not in entry and "dist-packages" not in entry):
|
|
125
|
+
continue
|
|
126
|
+
p = Path(entry) / "docs"
|
|
127
|
+
if p.exists():
|
|
128
|
+
out.append(p)
|
|
57
129
|
|
|
58
|
-
#
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
except Exception:
|
|
69
|
-
# Optional fallback only
|
|
70
|
-
pass
|
|
71
|
-
|
|
72
|
-
return topics
|
|
130
|
+
# de-dup while preserving order
|
|
131
|
+
seen = set()
|
|
132
|
+
uniq: List[Path] = []
|
|
133
|
+
for p in out:
|
|
134
|
+
if p.exists():
|
|
135
|
+
key = str(p.resolve())
|
|
136
|
+
if key not in seen:
|
|
137
|
+
seen.add(key)
|
|
138
|
+
uniq.append(p)
|
|
139
|
+
return uniq
|
|
73
140
|
|
|
74
141
|
|
|
75
|
-
def
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if
|
|
85
|
-
|
|
86
|
-
|
|
142
|
+
def _discover_topics(ctx: click.Context) -> Dict[str, Path]:
|
|
143
|
+
topics: Dict[str, Path] = {}
|
|
144
|
+
for d in _candidate_docs_dirs(ctx):
|
|
145
|
+
found = _md_topics_in(d)
|
|
146
|
+
# do not override earlier (higher-priority) sources
|
|
147
|
+
for k, v in found.items():
|
|
148
|
+
topics.setdefault(k, v)
|
|
149
|
+
if topics:
|
|
150
|
+
# one dir with content is enough for most setups
|
|
151
|
+
# (comment out this break if you *want* deep merging)
|
|
152
|
+
break
|
|
153
|
+
return topics
|
|
87
154
|
|
|
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
155
|
|
|
95
|
-
|
|
96
|
-
root = resolve_project_root()
|
|
97
|
-
proj_docs = root / "docs"
|
|
98
|
-
if proj_docs.exists():
|
|
99
|
-
return proj_docs
|
|
100
|
-
return None
|
|
156
|
+
# ---------- Typer group ----------
|
|
101
157
|
|
|
102
158
|
|
|
103
159
|
class DocsGroup(TyperGroup):
|
|
104
160
|
def list_commands(self, ctx: click.Context) -> List[str]:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
fs = _discover_fs_topics(dir_to_use) if dir_to_use else {}
|
|
108
|
-
pkg = _discover_pkg_topics()
|
|
109
|
-
# FS topics win on conflicts; add both for visibility
|
|
110
|
-
names.extend([k for k in fs.keys()])
|
|
111
|
-
names.extend([k for k in pkg.keys() if k not in fs])
|
|
112
|
-
# Deduplicate and sort
|
|
113
|
-
return sorted({*names})
|
|
161
|
+
topics = _discover_topics(ctx)
|
|
162
|
+
return sorted(topics.keys())
|
|
114
163
|
|
|
115
164
|
def get_command(self, ctx: click.Context, name: str) -> click.Command | None:
|
|
116
|
-
# Built-ins first (e.g., list, show)
|
|
117
165
|
cmd = super().get_command(ctx, name)
|
|
118
166
|
if cmd is not None:
|
|
119
167
|
return cmd
|
|
120
168
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|
134
|
-
pkg = _discover_pkg_topics()
|
|
135
|
-
if name in pkg:
|
|
136
|
-
file_path = pkg[name]
|
|
169
|
+
key = _norm(name)
|
|
170
|
+
topics = _discover_topics(ctx)
|
|
171
|
+
if key in topics:
|
|
172
|
+
file_path = topics[key]
|
|
137
173
|
|
|
138
174
|
@click.command(name=name)
|
|
139
|
-
def
|
|
175
|
+
def _show() -> None:
|
|
140
176
|
click.echo(file_path.read_text(encoding="utf-8", errors="replace"))
|
|
141
177
|
|
|
142
|
-
return
|
|
178
|
+
return _show
|
|
143
179
|
|
|
144
180
|
return None
|
|
145
181
|
|
|
146
182
|
|
|
147
183
|
def register(app: typer.Typer) -> None:
|
|
148
|
-
"""Register the `docs` command group with dynamic topic subcommands."""
|
|
149
|
-
|
|
150
184
|
docs_app = typer.Typer(no_args_is_help=True, add_completion=False, cls=DocsGroup)
|
|
151
185
|
|
|
152
186
|
@docs_app.callback(invoke_without_command=True)
|
|
@@ -154,21 +188,15 @@ def register(app: typer.Typer) -> None:
|
|
|
154
188
|
docs_dir: Path | None = typer.Option(
|
|
155
189
|
None,
|
|
156
190
|
"--docs-dir",
|
|
157
|
-
help="Path to a docs directory to read from (overrides
|
|
191
|
+
help="Path to a docs directory to read from (overrides packaged docs)",
|
|
158
192
|
),
|
|
159
193
|
topic: str | None = typer.Option(None, "--topic", help="Topic to show directly"),
|
|
160
194
|
) -> None:
|
|
161
|
-
"""Support --docs-dir and --topic at group level."""
|
|
162
195
|
if topic:
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
typer.echo(fs[topic].read_text(encoding="utf-8", errors="replace"))
|
|
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"))
|
|
196
|
+
key = _norm(topic)
|
|
197
|
+
topics = _discover_topics(click.get_current_context())
|
|
198
|
+
if key in topics:
|
|
199
|
+
typer.echo(topics[key].read_text(encoding="utf-8", errors="replace"))
|
|
172
200
|
raise typer.Exit(code=0)
|
|
173
201
|
raise typer.BadParameter(f"Unknown topic: {topic}")
|
|
174
202
|
|
|
@@ -176,37 +204,21 @@ def register(app: typer.Typer) -> None:
|
|
|
176
204
|
def list_topics() -> None:
|
|
177
205
|
ctx = click.get_current_context()
|
|
178
206
|
root = resolve_project_root()
|
|
179
|
-
|
|
180
|
-
fs = _discover_fs_topics(dir_to_use) if dir_to_use else {}
|
|
181
|
-
pkg = _discover_pkg_topics()
|
|
207
|
+
topics = _discover_topics(ctx)
|
|
182
208
|
|
|
183
|
-
|
|
184
|
-
def _print(name: str, path: Path) -> None:
|
|
209
|
+
for name, path in topics.items():
|
|
185
210
|
try:
|
|
186
211
|
rel = path.relative_to(root)
|
|
187
212
|
typer.echo(f"{name}\t{rel}")
|
|
188
213
|
except Exception:
|
|
189
|
-
# For packaged topics, path will be site-packages absolute path
|
|
190
214
|
typer.echo(f"{name}\t{path}")
|
|
191
215
|
|
|
192
|
-
for name, path in fs.items():
|
|
193
|
-
_print(name, path)
|
|
194
|
-
for name, path in pkg.items():
|
|
195
|
-
if name not in fs:
|
|
196
|
-
_print(name, path)
|
|
197
|
-
|
|
198
|
-
# Also support a generic "show" command
|
|
199
216
|
@docs_app.command("show", help="Show docs for a topic (alternative to dynamic subcommand)")
|
|
200
217
|
def show(topic: str) -> None:
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
typer.echo(fs[topic].read_text(encoding="utf-8", errors="replace"))
|
|
206
|
-
return
|
|
207
|
-
pkg = _discover_pkg_topics()
|
|
208
|
-
if topic in pkg:
|
|
209
|
-
typer.echo(pkg[topic].read_text(encoding="utf-8", errors="replace"))
|
|
218
|
+
key = _norm(topic)
|
|
219
|
+
topics = _discover_topics(click.get_current_context())
|
|
220
|
+
if key in topics:
|
|
221
|
+
typer.echo(topics[key].read_text(encoding="utf-8", errors="replace"))
|
|
210
222
|
return
|
|
211
223
|
raise typer.BadParameter(f"Unknown topic: {topic}")
|
|
212
224
|
|
|
@@ -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=zauAUG04yI3qbGXSyb5p_UhvvHvXSvirx1fuaA6HMOI,7816
|
|
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.622.dist-info/METADATA,sha256=GrIXBicqMBcCZRf97UzGyQwQJmCl_9eRKXJzfTfmvdE,8106
|
|
300
|
+
svc_infra-0.1.622.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
301
|
+
svc_infra-0.1.622.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
|
|
302
|
+
svc_infra-0.1.622.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|