frappectl 0.13.1__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.
frappectl/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """frappectl — a command-line client for Frappe sites."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("frappectl")
7
+ except PackageNotFoundError:
8
+ __version__ = "0.0.0+unknown"
frappectl/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from frappectl.cli import app
2
+
3
+ if __name__ == "__main__":
4
+ app()
frappectl/cli.py ADDED
@@ -0,0 +1,148 @@
1
+ """frappectl — top-level Typer app and global options."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from typing import Optional
7
+
8
+ import typer
9
+
10
+ from . import __version__
11
+ from .commands import api as api_cmd
12
+ from .commands import assistant as assistant_cmd
13
+ from .commands import auth, doc, doctype, file, report
14
+ from .commands import guide as guide_cmd
15
+ from .commands import method as method_cmd
16
+ from .commands import update as update_cmd
17
+ from .config import ConfigError
18
+ from .errors import FrappeError, UsageError
19
+ from .output import Ctx, fail
20
+
21
+ app = typer.Typer(
22
+ name="frappectl",
23
+ help="frappectl — a command-line client for Frappe sites.",
24
+ no_args_is_help=True,
25
+ add_completion=True,
26
+ rich_markup_mode="rich",
27
+ context_settings={"help_option_names": ["-h", "--help"]},
28
+ )
29
+
30
+ app.add_typer(auth.app, name="auth")
31
+ app.add_typer(doc.app, name="doc")
32
+ app.add_typer(doctype.app, name="doctype")
33
+ app.add_typer(file.app, name="file")
34
+ app.add_typer(report.app, name="report")
35
+ app.add_typer(method_cmd.app, name="method")
36
+ # `frappectl api <path>` — single command, mounted directly.
37
+ app.command(name="api", help=api_cmd.api.__doc__)(api_cmd.api)
38
+ # `frappectl guide` — static usage primer; the first thing an agent should run.
39
+ app.command(name="guide", help=guide_cmd.guide.__doc__)(guide_cmd.guide)
40
+ # `frappectl update` — self-upgrade via the uv/pip backend it was installed with.
41
+ app.command(name="update", help=update_cmd.update.__doc__)(update_cmd.update)
42
+ # `frappectl assistant [tool] -- <args>` — launch a CLI agent as a Frappe
43
+ # assistant. allow_extra_args/ignore_unknown_options let trailing args (and
44
+ # the child tool's own flags) pass through untouched via ctx.args.
45
+ app.command(
46
+ name="assistant",
47
+ help=assistant_cmd.assistant.__doc__,
48
+ context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
49
+ )(assistant_cmd.assistant)
50
+
51
+
52
+ def _version_callback(value: bool) -> None:
53
+ if value:
54
+ typer.echo(f"frappectl {__version__}")
55
+ raise typer.Exit()
56
+
57
+
58
+ @app.callback()
59
+ def _root(
60
+ ctx: typer.Context,
61
+ site: Optional[str] = typer.Option(
62
+ None, "-s", "--site", help="Profile to use (overrides the default profile)."
63
+ ),
64
+ json_out: bool = typer.Option(
65
+ False, "--json", help="Force JSON output (default when piped)."
66
+ ),
67
+ yes: bool = typer.Option(
68
+ False, "--yes", "-y", help="Assume yes for confirmation prompts."
69
+ ),
70
+ debug: bool = typer.Option(
71
+ False,
72
+ "--debug",
73
+ help="Print each HTTP request (and server SQL, where available) to stderr.",
74
+ ),
75
+ version: bool = typer.Option(
76
+ False,
77
+ "--version",
78
+ callback=_version_callback,
79
+ is_eager=True,
80
+ help="Show version.",
81
+ ),
82
+ ) -> None:
83
+ """Global options apply to every subcommand."""
84
+ ctx.obj = Ctx(json_mode=json_out, assume_yes=yes, profile=site, debug=debug)
85
+ # Passive, best-effort "a newer version exists" nudge. No-op in JSON/piped
86
+ # mode and for dev checkouts; network-throttled to once a day.
87
+ update_cmd.notify_if_outdated(ctx.obj)
88
+
89
+
90
+ # Global flags that must reach the top-level callback. We hoist them to the
91
+ # front of argv so they work in gh-style trailing position too, e.g.
92
+ # frappectl doc list "Sales Invoice" --json
93
+ # is rewritten to `frappectl --json doc list "Sales Invoice"`.
94
+ _VALUELESS_GLOBALS = {"--json", "--yes", "-y", "--debug"}
95
+ _VALUED_GLOBALS = {"-s", "--site"}
96
+
97
+
98
+ def _hoist_globals(argv: list[str]) -> list[str]:
99
+ hoisted: list[str] = []
100
+ rest: list[str] = []
101
+ i = 0
102
+ seen_ddash = False
103
+ while i < len(argv):
104
+ tok = argv[i]
105
+ if tok == "--":
106
+ seen_ddash = True
107
+ rest.append(tok)
108
+ i += 1
109
+ continue
110
+ if not seen_ddash and tok in _VALUELESS_GLOBALS:
111
+ # Normalize -y to --yes for the callback.
112
+ hoisted.append("--yes" if tok == "-y" else tok)
113
+ i += 1
114
+ continue
115
+ if not seen_ddash and tok in _VALUED_GLOBALS:
116
+ hoisted.append("--site")
117
+ if i + 1 < len(argv):
118
+ hoisted.append(argv[i + 1])
119
+ i += 2
120
+ else:
121
+ i += 1
122
+ continue
123
+ if not seen_ddash and (tok.startswith("--site=") or tok.startswith("-s=")):
124
+ hoisted.extend(["--site", tok.split("=", 1)[1]])
125
+ i += 1
126
+ continue
127
+ rest.append(tok)
128
+ i += 1
129
+ return hoisted + rest
130
+
131
+
132
+ def main() -> None:
133
+ sys.argv = [sys.argv[0]] + _hoist_globals(sys.argv[1:])
134
+ try:
135
+ app()
136
+ except UsageError as e:
137
+ fail(e.message, 2)
138
+ sys.exit(2)
139
+ except FrappeError as e:
140
+ fail(e.message, 1)
141
+ sys.exit(1)
142
+ except ConfigError as e:
143
+ fail(str(e), 2)
144
+ sys.exit(2)
145
+
146
+
147
+ if __name__ == "__main__":
148
+ main()