frappectl 0.13.1__tar.gz
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-0.13.1/.github/workflows/ci.yml +216 -0
- frappectl-0.13.1/.github/workflows/publish.yml +52 -0
- frappectl-0.13.1/.github/workflows/release.yml +51 -0
- frappectl-0.13.1/.gitignore +11 -0
- frappectl-0.13.1/.pre-commit-config.yaml +56 -0
- frappectl-0.13.1/.releaserc +19 -0
- frappectl-0.13.1/LICENSE +21 -0
- frappectl-0.13.1/PHILOSOPHY.md +8 -0
- frappectl-0.13.1/PKG-INFO +177 -0
- frappectl-0.13.1/README.md +156 -0
- frappectl-0.13.1/pyproject.toml +56 -0
- frappectl-0.13.1/src/frappectl/__init__.py +8 -0
- frappectl-0.13.1/src/frappectl/__main__.py +4 -0
- frappectl-0.13.1/src/frappectl/cli.py +148 -0
- frappectl-0.13.1/src/frappectl/client.py +591 -0
- frappectl-0.13.1/src/frappectl/commands/__init__.py +1 -0
- frappectl-0.13.1/src/frappectl/commands/api.py +101 -0
- frappectl-0.13.1/src/frappectl/commands/assistant.py +270 -0
- frappectl-0.13.1/src/frappectl/commands/auth.py +391 -0
- frappectl-0.13.1/src/frappectl/commands/doc.py +346 -0
- frappectl-0.13.1/src/frappectl/commands/doctype.py +133 -0
- frappectl-0.13.1/src/frappectl/commands/file.py +137 -0
- frappectl-0.13.1/src/frappectl/commands/guide.py +73 -0
- frappectl-0.13.1/src/frappectl/commands/method.py +295 -0
- frappectl-0.13.1/src/frappectl/commands/report.py +93 -0
- frappectl-0.13.1/src/frappectl/commands/update.py +345 -0
- frappectl-0.13.1/src/frappectl/config.py +492 -0
- frappectl-0.13.1/src/frappectl/errors.py +186 -0
- frappectl-0.13.1/src/frappectl/helpers.py +149 -0
- frappectl-0.13.1/src/frappectl/oauth.py +456 -0
- frappectl-0.13.1/src/frappectl/output.py +155 -0
- frappectl-0.13.1/src/frappectl/py.typed +0 -0
- frappectl-0.13.1/src/frappectl/session.py +67 -0
- frappectl-0.13.1/tests/conftest.py +35 -0
- frappectl-0.13.1/tests/integration/test_smoke.py +206 -0
- frappectl-0.13.1/tests/test_assistant.py +131 -0
- frappectl-0.13.1/tests/test_cli.py +298 -0
- frappectl-0.13.1/tests/test_client.py +330 -0
- frappectl-0.13.1/tests/test_config.py +330 -0
- frappectl-0.13.1/tests/test_errors.py +64 -0
- frappectl-0.13.1/tests/test_file.py +66 -0
- frappectl-0.13.1/tests/test_helpers.py +80 -0
- frappectl-0.13.1/tests/test_method.py +475 -0
- frappectl-0.13.1/tests/test_oauth.py +283 -0
- frappectl-0.13.1/tests/test_update.py +336 -0
- frappectl-0.13.1/uv.lock +828 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [develop]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [develop]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v7
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39
|
|
23
|
+
with:
|
|
24
|
+
python-version: ${{ matrix.python-version }}
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: uv sync --extra dev
|
|
28
|
+
|
|
29
|
+
- name: Run tests
|
|
30
|
+
run: uv run pytest
|
|
31
|
+
|
|
32
|
+
pre-commit:
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v7
|
|
36
|
+
|
|
37
|
+
- name: Install uv
|
|
38
|
+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39
|
|
39
|
+
with:
|
|
40
|
+
python-version: "3.14"
|
|
41
|
+
|
|
42
|
+
- name: Run pre-commit
|
|
43
|
+
env:
|
|
44
|
+
SKIP: no-commit-to-branch
|
|
45
|
+
run: uvx pre-commit run --all-files --show-diff-on-failure
|
|
46
|
+
|
|
47
|
+
# The integration suite runs on pull requests (and manual dispatches) but not
|
|
48
|
+
# on push, so branch pushes stay fast. Its result is rolled up by the `ci`
|
|
49
|
+
# gate job below.
|
|
50
|
+
integration:
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
if: github.event_name != 'push'
|
|
53
|
+
|
|
54
|
+
services:
|
|
55
|
+
mariadb:
|
|
56
|
+
image: mariadb:11.4
|
|
57
|
+
env:
|
|
58
|
+
MARIADB_ROOT_PASSWORD: root
|
|
59
|
+
ports:
|
|
60
|
+
- 3306:3306
|
|
61
|
+
# tmpfs for the datadir keeps the ephemeral CI DB fast (mirrors Frappe's own CI).
|
|
62
|
+
options: >-
|
|
63
|
+
--health-cmd="healthcheck.sh --connect --innodb_initialized"
|
|
64
|
+
--health-interval=5s
|
|
65
|
+
--health-timeout=5s
|
|
66
|
+
--health-retries=10
|
|
67
|
+
--tmpfs=/var/lib/mysql:rw,noexec,nosuid,size=2g
|
|
68
|
+
|
|
69
|
+
redis:
|
|
70
|
+
image: redis:7
|
|
71
|
+
ports:
|
|
72
|
+
- 6379:6379
|
|
73
|
+
options: >-
|
|
74
|
+
--health-cmd="redis-cli ping"
|
|
75
|
+
--health-interval=5s
|
|
76
|
+
--health-timeout=5s
|
|
77
|
+
--health-retries=10
|
|
78
|
+
|
|
79
|
+
env:
|
|
80
|
+
# The site the CLI talks to. Served as the bench's default site, so the
|
|
81
|
+
# CLI can reach it over plain 127.0.0.1 without any /etc/hosts juggling.
|
|
82
|
+
SITE: test.localhost
|
|
83
|
+
|
|
84
|
+
steps:
|
|
85
|
+
- name: Checkout frappectl
|
|
86
|
+
uses: actions/checkout@v7
|
|
87
|
+
|
|
88
|
+
# --- The CLI under test (managed by uv, isolated from the bench env) ---
|
|
89
|
+
- name: Install uv
|
|
90
|
+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39
|
|
91
|
+
with:
|
|
92
|
+
python-version: "3.14"
|
|
93
|
+
|
|
94
|
+
- name: Install frappectl
|
|
95
|
+
run: uv sync --extra dev
|
|
96
|
+
|
|
97
|
+
# --- Toolchain for provisioning a Frappe bench ---
|
|
98
|
+
- name: Set up Node
|
|
99
|
+
uses: actions/setup-node@v6
|
|
100
|
+
with:
|
|
101
|
+
node-version: 24
|
|
102
|
+
|
|
103
|
+
# Warm yarn's download cache so `bench init` installs frappe's node deps
|
|
104
|
+
# from disk instead of re-fetching them from the registry every run. The
|
|
105
|
+
# run_id in the key always writes a fresh cache; restore-keys pulls the
|
|
106
|
+
# newest prior one so it stays current as frappe's deps drift.
|
|
107
|
+
- name: Cache yarn packages
|
|
108
|
+
uses: actions/cache@v4
|
|
109
|
+
with:
|
|
110
|
+
path: ~/.cache/yarn
|
|
111
|
+
key: yarn-${{ runner.os }}-${{ github.run_id }}
|
|
112
|
+
restore-keys: |
|
|
113
|
+
yarn-${{ runner.os }}-
|
|
114
|
+
|
|
115
|
+
- name: Install system dependencies
|
|
116
|
+
run: |
|
|
117
|
+
sudo apt-get update
|
|
118
|
+
sudo apt-get install -y --no-install-recommends \
|
|
119
|
+
build-essential pkg-config \
|
|
120
|
+
|
|
121
|
+
- name: Install Bench
|
|
122
|
+
run: |
|
|
123
|
+
uv tool install frappe-bench
|
|
124
|
+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
125
|
+
|
|
126
|
+
- name: Provision a Frappe site
|
|
127
|
+
run: |
|
|
128
|
+
set -euo pipefail
|
|
129
|
+
|
|
130
|
+
bench init --skip-assets --no-backups \
|
|
131
|
+
--skip-redis-config-generation \
|
|
132
|
+
--frappe-branch develop ~/frappe-bench
|
|
133
|
+
cd ~/frappe-bench
|
|
134
|
+
|
|
135
|
+
# Point every redis role at the service container instead of bench's
|
|
136
|
+
# own bundled redis, so we don't need honcho/`bench start` running.
|
|
137
|
+
bench set-config -g redis_cache "redis://localhost:6379"
|
|
138
|
+
bench set-config -g redis_queue "redis://localhost:6379"
|
|
139
|
+
bench set-config -g redis_socketio "redis://localhost:6379"
|
|
140
|
+
bench set-config -g use_mysqlclient 0
|
|
141
|
+
|
|
142
|
+
# DB root creds + admin password via global config — the path Frappe's
|
|
143
|
+
# own CI uses, rather than the (renamed) new-site CLI flags.
|
|
144
|
+
bench set-config -g root_login root
|
|
145
|
+
bench set-config -g root_password root
|
|
146
|
+
bench set-config -g admin_password admin
|
|
147
|
+
|
|
148
|
+
bench new-site "$SITE" \
|
|
149
|
+
--db-type mariadb \
|
|
150
|
+
--db-host 127.0.0.1 \
|
|
151
|
+
--db-name test_frappe \
|
|
152
|
+
--db-password test_frappe \
|
|
153
|
+
--verbose
|
|
154
|
+
bench use "$SITE"
|
|
155
|
+
|
|
156
|
+
- name: Generate Administrator API keys
|
|
157
|
+
run: |
|
|
158
|
+
set -euo pipefail
|
|
159
|
+
cd ~/frappe-bench
|
|
160
|
+
bench --site "$SITE" execute \
|
|
161
|
+
frappe.core.doctype.user.user.generate_keys \
|
|
162
|
+
--kwargs '{"user": "Administrator"}' > keys.out
|
|
163
|
+
python - <<'PY'
|
|
164
|
+
import json, os, re
|
|
165
|
+
raw = open(os.path.expanduser("~/frappe-bench/keys.out")).read()
|
|
166
|
+
match = re.search(r'\{[^{}]*"api_secret"[^{}]*\}', raw, re.S)
|
|
167
|
+
keys = json.loads(match.group(0))
|
|
168
|
+
with open(os.environ["GITHUB_ENV"], "a") as env:
|
|
169
|
+
env.write(f"FRAPPE_API_KEY={keys['api_key']}\n")
|
|
170
|
+
env.write(f"FRAPPE_API_SECRET={keys['api_secret']}\n")
|
|
171
|
+
PY
|
|
172
|
+
|
|
173
|
+
- name: Serve the site
|
|
174
|
+
run: |
|
|
175
|
+
cd ~/frappe-bench
|
|
176
|
+
bench start &
|
|
177
|
+
for i in $(seq 1 30); do
|
|
178
|
+
if curl -sf http://test.localhost:8000/api/method/ping >/dev/null; then
|
|
179
|
+
echo "site is up"; exit 0
|
|
180
|
+
fi
|
|
181
|
+
sleep 2
|
|
182
|
+
done
|
|
183
|
+
echo "site did not come up in time" >&2
|
|
184
|
+
exit 1
|
|
185
|
+
|
|
186
|
+
- name: Run integration tests
|
|
187
|
+
run: uv run pytest tests/integration -m integration -v
|
|
188
|
+
env:
|
|
189
|
+
FRAPPE_SITE: http://test.localhost:8000
|
|
190
|
+
|
|
191
|
+
# Single roll-up status check — make THIS the only required check in the
|
|
192
|
+
# ruleset. It aggregates the individual jobs so the required-checks list never
|
|
193
|
+
# has to be maintained. `!cancelled()` lets it run even when integration is
|
|
194
|
+
# skipped (on push); it then fails unless every job that should have run for
|
|
195
|
+
# this event succeeded.
|
|
196
|
+
ci:
|
|
197
|
+
name: ci
|
|
198
|
+
runs-on: ubuntu-latest
|
|
199
|
+
needs: [test, pre-commit, integration]
|
|
200
|
+
if: ${{ !cancelled() }}
|
|
201
|
+
steps:
|
|
202
|
+
- name: Verify all checks passed
|
|
203
|
+
run: |
|
|
204
|
+
echo "test=${{ needs.test.result }} pre-commit=${{ needs['pre-commit'].result }} integration=${{ needs.integration.result }} (event=${{ github.event_name }})"
|
|
205
|
+
|
|
206
|
+
# Unit tests and linters must pass on every event.
|
|
207
|
+
[ "${{ needs.test.result }}" = "success" ] || { echo "::error::unit tests failed"; exit 1; }
|
|
208
|
+
[ "${{ needs['pre-commit'].result }}" = "success" ] || { echo "::error::pre-commit failed"; exit 1; }
|
|
209
|
+
|
|
210
|
+
# Integration runs on everything except push; require it there, ignore
|
|
211
|
+
# its (skipped) result on push.
|
|
212
|
+
if [ "${{ github.event_name }}" != "push" ]; then
|
|
213
|
+
[ "${{ needs.integration.result }}" = "success" ] || { echo "::error::integration tests failed"; exit 1; }
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
echo "All required checks passed."
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
# Publish to PyPI whenever semantic-release cuts a GitHub release. This fires
|
|
4
|
+
# because release.yml authenticates with a PAT (RELEASE_TOKEN) — releases
|
|
5
|
+
# created with the default GITHUB_TOKEN would not trigger other workflows.
|
|
6
|
+
#
|
|
7
|
+
# Authentication is PyPI "trusted publishing" (OIDC): no API token secret.
|
|
8
|
+
# PyPI must be configured with a trusted publisher for the `frappectl`
|
|
9
|
+
# project pointing at this repo, workflow `publish.yml`, environment `pypi`.
|
|
10
|
+
on:
|
|
11
|
+
release:
|
|
12
|
+
types: [published]
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
publish:
|
|
19
|
+
name: Publish to PyPI
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
environment:
|
|
22
|
+
name: pypi
|
|
23
|
+
url: https://pypi.org/project/frappectl/
|
|
24
|
+
permissions:
|
|
25
|
+
id-token: write # mint the OIDC token PyPI trusted publishing verifies
|
|
26
|
+
|
|
27
|
+
steps:
|
|
28
|
+
# Build from the released tag, not the branch tip: a commit landing on
|
|
29
|
+
# develop between the release and this run must not leak into the wheel.
|
|
30
|
+
- name: Checkout release tag
|
|
31
|
+
uses: actions/checkout@v7
|
|
32
|
+
with:
|
|
33
|
+
ref: ${{ github.event.release.tag_name }}
|
|
34
|
+
persist-credentials: false
|
|
35
|
+
|
|
36
|
+
- name: Install uv
|
|
37
|
+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39
|
|
38
|
+
with:
|
|
39
|
+
python-version: "3.14"
|
|
40
|
+
|
|
41
|
+
- name: Build sdist and wheel
|
|
42
|
+
run: uv build
|
|
43
|
+
|
|
44
|
+
# Catch a broken artifact before it is immortalised on PyPI: install the
|
|
45
|
+
# wheel into a clean environment and run both entry points.
|
|
46
|
+
- name: Smoke-test the wheel
|
|
47
|
+
run: |
|
|
48
|
+
uvx --from ./dist/frappectl-*.whl frappectl --version
|
|
49
|
+
uvx --from ./dist/frappectl-*.whl fr guide > /dev/null
|
|
50
|
+
|
|
51
|
+
- name: Publish to PyPI
|
|
52
|
+
run: uv publish
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Every push to develop cuts a release. No CI gate is needed here: nothing
|
|
4
|
+
# reaches develop except through the merge queue, which already runs the full
|
|
5
|
+
# suite (unit tests, linters, integration) before fast-forwarding the branch.
|
|
6
|
+
# semantic-release is a no-op when a push carries no releasable commits (e.g.
|
|
7
|
+
# the bot's own `chore(release):` commit), so re-runs are harmless.
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
branches: [develop]
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
release:
|
|
17
|
+
name: Release
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- name: Checkout Entire Repository
|
|
21
|
+
uses: actions/checkout@v7
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0
|
|
24
|
+
persist-credentials: false
|
|
25
|
+
|
|
26
|
+
- name: Setup Node.js
|
|
27
|
+
uses: actions/setup-node@v6
|
|
28
|
+
with:
|
|
29
|
+
node-version: 22
|
|
30
|
+
|
|
31
|
+
# `uv` is needed by the semantic-release prepare step, which runs
|
|
32
|
+
# `uv lock` to keep the lockfile's project version in sync with the
|
|
33
|
+
# bumped pyproject.toml (both are committed in the release commit).
|
|
34
|
+
- name: Install uv
|
|
35
|
+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39
|
|
36
|
+
with:
|
|
37
|
+
python-version: "3.14"
|
|
38
|
+
|
|
39
|
+
- name: Setup dependencies
|
|
40
|
+
run: |
|
|
41
|
+
npm install @semantic-release/git @semantic-release/exec --no-save
|
|
42
|
+
|
|
43
|
+
- name: Create Release
|
|
44
|
+
env:
|
|
45
|
+
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
46
|
+
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
47
|
+
GIT_AUTHOR_NAME: "Frappe CLI Bot"
|
|
48
|
+
GIT_AUTHOR_EMAIL: "developers@frappe.io"
|
|
49
|
+
GIT_COMMITTER_NAME: "Frappe CLI Bot"
|
|
50
|
+
GIT_COMMITTER_EMAIL: "developers@frappe.io"
|
|
51
|
+
run: npx semantic-release
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
exclude: '.git'
|
|
2
|
+
default_stages: [pre-commit]
|
|
3
|
+
default_install_hook_types: [pre-commit, commit-msg]
|
|
4
|
+
fail_fast: false
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
repos:
|
|
8
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
9
|
+
rev: v6.0.0
|
|
10
|
+
hooks:
|
|
11
|
+
- id: trailing-whitespace
|
|
12
|
+
exclude: ".*json$|.*txt$|.*csv|.*md|.*svg"
|
|
13
|
+
- id: no-commit-to-branch
|
|
14
|
+
args: ['--branch', 'develop']
|
|
15
|
+
- id: check-merge-conflict
|
|
16
|
+
- id: check-ast
|
|
17
|
+
- id: check-json
|
|
18
|
+
- id: check-toml
|
|
19
|
+
- id: check-yaml
|
|
20
|
+
- id: debug-statements
|
|
21
|
+
|
|
22
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
23
|
+
rev: v0.14.10
|
|
24
|
+
hooks:
|
|
25
|
+
- id: ruff
|
|
26
|
+
name: "Run ruff import sorter"
|
|
27
|
+
args: ["--select=I", "--fix"]
|
|
28
|
+
|
|
29
|
+
- id: ruff
|
|
30
|
+
name: "Run ruff linter"
|
|
31
|
+
|
|
32
|
+
- id: ruff-format
|
|
33
|
+
name: "Run ruff formatter"
|
|
34
|
+
|
|
35
|
+
# mypy runs through uv so it resolves against the locked project + typed deps
|
|
36
|
+
# (typer, httpx, rich…). An isolated pre-commit env wouldn't see them and would
|
|
37
|
+
# drown in false import errors. It reads its config (strict, files = ["src"])
|
|
38
|
+
# from pyproject.toml, so it types the whole package regardless of staged files.
|
|
39
|
+
- repo: local
|
|
40
|
+
hooks:
|
|
41
|
+
- id: mypy
|
|
42
|
+
name: "Run mypy (strict)"
|
|
43
|
+
entry: uv run --extra dev mypy
|
|
44
|
+
language: system
|
|
45
|
+
types: [python]
|
|
46
|
+
pass_filenames: false
|
|
47
|
+
always_run: true
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
ci:
|
|
51
|
+
autoupdate_schedule: weekly
|
|
52
|
+
# `uv` isn't available on the hosted pre-commit.ci runners, so the local
|
|
53
|
+
# mypy hook can't run there. It still runs locally and in the GitHub Actions
|
|
54
|
+
# `pre-commit` job (which installs uv) — the check that actually gates merges.
|
|
55
|
+
skip: [mypy]
|
|
56
|
+
submodules: false
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"branches": ["develop"],
|
|
3
|
+
"plugins": [
|
|
4
|
+
"@semantic-release/commit-analyzer",
|
|
5
|
+
"@semantic-release/release-notes-generator",
|
|
6
|
+
[
|
|
7
|
+
"@semantic-release/exec", {
|
|
8
|
+
"prepareCmd": "sed -i -E 's/^version = \"[0-9]+\\.[0-9]+\\.[0-9]+\"/version = \"${nextRelease.version}\"/' pyproject.toml && uv lock"
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
[
|
|
12
|
+
"@semantic-release/git", {
|
|
13
|
+
"assets": ["pyproject.toml", "uv.lock"],
|
|
14
|
+
"message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"@semantic-release/github"
|
|
18
|
+
]
|
|
19
|
+
}
|
frappectl-0.13.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Frappe Technologies Pvt Ltd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Philosophy
|
|
2
|
+
|
|
3
|
+
- **For humans and agents, equally.** A TTY gets rich tables and prompts; a pipe (or `--json`) gets clean JSON on stdout and nothing else. Same command, both audiences.
|
|
4
|
+
- **A thin, honest wrapper.** Verbs map to the Frappe REST API v2 with little magic, and `api` is always the escape hatch — the CLI never hides the platform underneath.
|
|
5
|
+
- **Predictable I/O contract.** stdout is data, stderr is chatter. Exit `0`/`1`/`2`. Errors are plain text with a tip pointing at the fix.
|
|
6
|
+
- **Safe by default.** Mutations refuse to run non-interactively without `--yes`; updates are optimistic unless `--force`.
|
|
7
|
+
- **Credentials are the human's job.** Key/secret live in env or the OS keyring — never plaintext, never mutated by the CLI, never set up for you.
|
|
8
|
+
- **Self-orienting.** `guide` and `doctype show` let anyone learn the surface before touching a live site. Orient, don't guess.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: frappectl
|
|
3
|
+
Version: 0.13.1
|
|
4
|
+
Summary: frappectl — a command-line client for Frappe sites, built for humans and AI agents.
|
|
5
|
+
Project-URL: Homepage, https://github.com/frappe/frappectl
|
|
6
|
+
Author-email: Ankush Menat <ankush@frappe.io>
|
|
7
|
+
License: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: api,cli,erpnext,frappe
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Requires-Dist: authlib>=1.3
|
|
12
|
+
Requires-Dist: httpx>=0.27
|
|
13
|
+
Requires-Dist: keyring>=24.0
|
|
14
|
+
Requires-Dist: rich>=13.0
|
|
15
|
+
Requires-Dist: typer>=0.12
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
18
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
19
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# frappectl
|
|
23
|
+
|
|
24
|
+
[](https://github.com/frappe/frappectl/actions/workflows/ci.yml)
|
|
25
|
+
[](#license)
|
|
26
|
+
|
|
27
|
+
A command-line REST API v2 client for Frappe v16+ — built for humans and AI agents.
|
|
28
|
+
|
|
29
|
+
Using an agent? Start with `frappectl guide`. It fits site selection, core verbs,
|
|
30
|
+
filtering and the raw-API escape hatch on one screen.
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
frappectl auth login https://erp.example.com # key/secret → OS keyring
|
|
34
|
+
frappectl -s raven doc list "Raven Channel" --json # -s/--site selects a profile
|
|
35
|
+
|
|
36
|
+
frappectl doc list "Sales Invoice" -f status=Overdue -f 'grand_total>1000' \
|
|
37
|
+
--fields name,customer,grand_total --all --json
|
|
38
|
+
frappectl doc get "Sales Invoice" SINV-0001
|
|
39
|
+
frappectl doc create ToDo --set description="Follow up" --set priority=High
|
|
40
|
+
cat invoice.json | frappectl doc create "Sales Invoice"
|
|
41
|
+
frappectl doc submit "Sales Invoice" SINV-0001
|
|
42
|
+
frappectl doc delete ToDo abc123 --yes
|
|
43
|
+
|
|
44
|
+
frappectl doctype show "Sales Invoice" --json # discover the schema first
|
|
45
|
+
frappectl report run "Accounts Receivable" -f company="Frappe" --json
|
|
46
|
+
frappectl file upload ./contract.pdf --doctype "Sales Invoice" --name SINV-0001 --private
|
|
47
|
+
frappectl api method/frappe.client.get_count -F doctype=User
|
|
48
|
+
frappectl api method/gameplan.api.get_unread_count # raw API, when verbs aren't enough
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
uv tool install frappectl
|
|
55
|
+
# or: pip install frappectl
|
|
56
|
+
# development version: uv tool install git+https://github.com/frappe/frappectl
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This installs two commands: `frappectl` and its short alias `fr`.
|
|
60
|
+
|
|
61
|
+
## Authentication
|
|
62
|
+
|
|
63
|
+
frappectl supports 3 authentication paths.
|
|
64
|
+
|
|
65
|
+
### Environment variables
|
|
66
|
+
|
|
67
|
+
Environment variables always win and never touch the keyring.
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
export FRAPPE_SITE=https://erp.example.com
|
|
71
|
+
export FRAPPE_API_KEY=xxxxxxxx
|
|
72
|
+
export FRAPPE_API_SECRET=yyyyyyyy
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Stored profiles
|
|
76
|
+
|
|
77
|
+
Generate an API key + secret from **User → Settings → API Access**, then log in:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
frappectl auth login https://erp.example.com
|
|
81
|
+
frappectl auth login https://raven.example.com --name raven
|
|
82
|
+
frappectl auth login https://raven.example.com --name raven --default
|
|
83
|
+
frappectl auth list
|
|
84
|
+
frappectl auth default raven # change the default
|
|
85
|
+
frappectl -s raven doc list "Raven Channel" # select per command
|
|
86
|
+
frappectl auth whoami
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The site URL lives in `~/.config/frappe/config.json`; the secret lives in the **OS
|
|
90
|
+
keyring**. There is no plaintext fallback. If the machine has no keyring, use environment
|
|
91
|
+
variables.
|
|
92
|
+
|
|
93
|
+
### OAuth
|
|
94
|
+
|
|
95
|
+
Interactive login selects OAuth by default. It stores no secret; short-lived access
|
|
96
|
+
tokens refresh automatically.
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
frappectl auth login https://erp.example.com
|
|
100
|
+
frappectl auth login https://erp.example.com --client-id <public-client-id>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Sites with dynamic client registration create the client automatically. Frappe v15+
|
|
104
|
+
supports this by default; other sites need a pre-registered public `--client-id`.
|
|
105
|
+
|
|
106
|
+
OAuth needs a terminal and local browser, so it isn't the headless path. Use `--oauth`
|
|
107
|
+
to skip the authentication-method prompt.
|
|
108
|
+
|
|
109
|
+
### Read-only profiles
|
|
110
|
+
|
|
111
|
+
A read-only profile refuses every unsafe HTTP method **before the request leaves your
|
|
112
|
+
machine**. This removes the obvious production footgun: an exploratory command can't
|
|
113
|
+
accidentally mutate the site.
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
frappectl auth login https://prod.example.com --name prod --read-only
|
|
117
|
+
frappectl auth configure prod --read-only # lock an existing profile
|
|
118
|
+
frappectl auth configure prod --writable # allow writes again
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
This blocks `doc create/update/delete/submit`, `file upload`, and method calls through
|
|
122
|
+
`api`/`method`. Reads such as `doc list/get`, `doctype show`, and `report run` keep
|
|
123
|
+
working.
|
|
124
|
+
|
|
125
|
+
For environment-variable auth, set `FRAPPE_READ_ONLY=1`. To invoke a whitelisted read
|
|
126
|
+
method, make the safe verb explicit: `frappectl api method/… -X GET`.
|
|
127
|
+
|
|
128
|
+
## Output and scripting
|
|
129
|
+
|
|
130
|
+
- TTY → rich tables, colours and confirmation prompts.
|
|
131
|
+
- Pipe or `--json` → clean JSON on stdout. Logs and errors stay on stderr, so piping to
|
|
132
|
+
`jq` is safe.
|
|
133
|
+
- Mutations → refuse to run non-interactively without `--yes`.
|
|
134
|
+
- Exit codes → `0` success, `1` failure, `2` usage error.
|
|
135
|
+
- `--debug` → traces method, URL and headers to stderr. Credentials are redacted. For
|
|
136
|
+
endpoints that expose it, such as `doc list`, it also prints server-side SQL.
|
|
137
|
+
|
|
138
|
+
Server error detail is plain text; HTML is stripped. `--debug` never touches stdout, so
|
|
139
|
+
it won't corrupt `--json` output.
|
|
140
|
+
|
|
141
|
+
## Commands
|
|
142
|
+
|
|
143
|
+
| Command | What it does |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `frappectl doc list <DocType>` | List documents. Supports `-f`, `--fields`, `--limit` and `--all`. |
|
|
146
|
+
| `frappectl doc get <DocType> <name>` | Fetch one document. |
|
|
147
|
+
| `frappectl doc create <DocType>` | Create from `--set` scalars and/or piped/`--input` JSON. |
|
|
148
|
+
| `frappectl doc update <DocType> <name>` | Update optimistically; use `--force` to override. |
|
|
149
|
+
| `frappectl doc delete <DocType> <name>` | Delete after confirmation, or pass `--yes`. |
|
|
150
|
+
| `frappectl doc submit\|cancel\|amend` | Run document lifecycle actions. |
|
|
151
|
+
| `frappectl doctype list` / `frappectl doctype show <name>` | Discover doctypes and schema. |
|
|
152
|
+
| `frappectl report run <name>` | Run a report with the same filter syntax as `doc list`. |
|
|
153
|
+
| `frappectl file upload\|download` | Transfer files; upload supports `--doctype/--name` and `--private`. |
|
|
154
|
+
| `frappectl api <path>` | Call raw v2 APIs: `frappectl api method/<path> -F key=value`. |
|
|
155
|
+
| `frappectl guide` | Print the agent primer; no site or authentication needed. |
|
|
156
|
+
| `frappectl assistant [pi\|claude\|codex]` | Launch a coding agent configured as a Frappe assistant. |
|
|
157
|
+
| `frappectl update` | Self-upgrade through the `uv`/`pip` backend used for installation. |
|
|
158
|
+
|
|
159
|
+
### Filtering
|
|
160
|
+
|
|
161
|
+
Simple filters use `-f`. Repeat the flag to combine them:
|
|
162
|
+
|
|
163
|
+
```sh
|
|
164
|
+
frappectl doc list ToDo -f status=Open -f priority=High
|
|
165
|
+
frappectl doc list "Sales Invoice" -f 'grand_total>1000' -f 'customer like %Inc%'
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
For `in`, `between`, child-table filters or anything else that doesn't fit cleanly in a
|
|
169
|
+
shell argument, pass full Frappe filter JSON:
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
frappectl doc list "Sales Invoice" --filters-json '[["status","in",["Paid","Overdue"]]]'
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT
|