papycli 0.5.2__tar.gz → 0.6.0__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.
- papycli-0.6.0/.github/workflows/release.yml +152 -0
- {papycli-0.5.2 → papycli-0.6.0}/CLAUDE.md +17 -3
- {papycli-0.5.2 → papycli-0.6.0}/PKG-INFO +57 -4
- {papycli-0.5.2 → papycli-0.6.0}/README.ja.md +55 -3
- {papycli-0.5.2 → papycli-0.6.0}/README.md +56 -3
- {papycli-0.5.2 → papycli-0.6.0}/design_doc.md +72 -3
- {papycli-0.5.2 → papycli-0.6.0}/pyproject.toml +1 -1
- {papycli-0.5.2 → papycli-0.6.0}/src/papycli/api_call.py +145 -12
- papycli-0.6.0/src/papycli/checker.py +126 -0
- {papycli-0.5.2 → papycli-0.6.0}/src/papycli/completion.py +60 -12
- {papycli-0.5.2 → papycli-0.6.0}/src/papycli/config.py +34 -5
- {papycli-0.5.2 → papycli-0.6.0}/src/papycli/main.py +134 -2
- papycli-0.6.0/src/papycli/request_filter.py +132 -0
- papycli-0.6.0/tests/test_api_call.py +567 -0
- papycli-0.6.0/tests/test_checker.py +219 -0
- {papycli-0.5.2 → papycli-0.6.0}/tests/test_completion.py +107 -7
- {papycli-0.5.2 → papycli-0.6.0}/tests/test_config.py +54 -0
- {papycli-0.5.2 → papycli-0.6.0}/tests/test_main.py +239 -0
- papycli-0.6.0/tests/test_request_filter.py +320 -0
- {papycli-0.5.2 → papycli-0.6.0}/uv.lock +1 -1
- papycli-0.5.2/.github/workflows/release.yml +0 -77
- papycli-0.5.2/tests/test_api_call.py +0 -285
- {papycli-0.5.2 → papycli-0.6.0}/.gitignore +0 -0
- {papycli-0.5.2 → papycli-0.6.0}/.python-version +0 -0
- {papycli-0.5.2 → papycli-0.6.0}/LICENSE +0 -0
- {papycli-0.5.2 → papycli-0.6.0}/examples/docker-compose.yml +0 -0
- {papycli-0.5.2 → papycli-0.6.0}/examples/petstore-oas3.json +0 -0
- {papycli-0.5.2 → papycli-0.6.0}/src/papycli/__init__.py +0 -0
- {papycli-0.5.2 → papycli-0.6.0}/src/papycli/i18n.py +0 -0
- {papycli-0.5.2 → papycli-0.6.0}/src/papycli/init_cmd.py +0 -0
- {papycli-0.5.2 → papycli-0.6.0}/src/papycli/spec_loader.py +0 -0
- {papycli-0.5.2 → papycli-0.6.0}/src/papycli/summary.py +0 -0
- {papycli-0.5.2 → papycli-0.6.0}/tests/conftest.py +0 -0
- {papycli-0.5.2 → papycli-0.6.0}/tests/test_i18n.py +0 -0
- {papycli-0.5.2 → papycli-0.6.0}/tests/test_init_cmd.py +0 -0
- {papycli-0.5.2 → papycli-0.6.0}/tests/test_spec_loader.py +0 -0
- {papycli-0.5.2 → papycli-0.6.0}/tests/test_summary.py +0 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
name: Build distribution
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Verify tag version matches pyproject.toml
|
|
16
|
+
run: |
|
|
17
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
18
|
+
PKG_VERSION=$(python3 -c "
|
|
19
|
+
import tomllib
|
|
20
|
+
with open('pyproject.toml', 'rb') as f:
|
|
21
|
+
print(tomllib.load(f)['project']['version'])
|
|
22
|
+
")
|
|
23
|
+
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
|
|
24
|
+
echo "::error::Version mismatch: tag=$TAG_VERSION, pyproject.toml=$PKG_VERSION"
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
echo "Version OK: $PKG_VERSION"
|
|
28
|
+
|
|
29
|
+
- uses: astral-sh/setup-uv@v5
|
|
30
|
+
|
|
31
|
+
- name: Build wheel and sdist
|
|
32
|
+
run: uv build
|
|
33
|
+
|
|
34
|
+
- uses: actions/upload-artifact@v4
|
|
35
|
+
with:
|
|
36
|
+
name: dist
|
|
37
|
+
path: dist/
|
|
38
|
+
|
|
39
|
+
github-release:
|
|
40
|
+
name: Create GitHub Release (draft)
|
|
41
|
+
needs: build
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
permissions:
|
|
44
|
+
contents: write
|
|
45
|
+
models: read
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v4
|
|
48
|
+
with:
|
|
49
|
+
fetch-depth: 0 # full history needed for git log
|
|
50
|
+
|
|
51
|
+
- uses: actions/download-artifact@v4
|
|
52
|
+
with:
|
|
53
|
+
name: dist
|
|
54
|
+
path: dist/
|
|
55
|
+
|
|
56
|
+
- name: Get previous release tag
|
|
57
|
+
id: prev-tag
|
|
58
|
+
env:
|
|
59
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
60
|
+
REPO: ${{ github.repository }}
|
|
61
|
+
TAG: ${{ github.ref_name }}
|
|
62
|
+
run: |
|
|
63
|
+
# Exclude drafts, pre-releases, and the current tag to get the true previous release
|
|
64
|
+
PREV_TAG=$(gh release list --repo "$REPO" --limit 20 \
|
|
65
|
+
--json tagName,isDraft,isPrerelease \
|
|
66
|
+
--jq 'map(select((.isDraft | not) and (.isPrerelease | not) and (.tagName != env.TAG))) | .[0].tagName // ""')
|
|
67
|
+
echo "prev_tag=$PREV_TAG" >> "$GITHUB_OUTPUT"
|
|
68
|
+
|
|
69
|
+
- name: Collect commits since previous release
|
|
70
|
+
id: commits
|
|
71
|
+
env:
|
|
72
|
+
TAG: ${{ github.ref_name }}
|
|
73
|
+
PREV_TAG: ${{ steps.prev-tag.outputs.prev_tag }}
|
|
74
|
+
run: |
|
|
75
|
+
set -euo pipefail
|
|
76
|
+
if [ -n "${PREV_TAG}" ]; then
|
|
77
|
+
RANGE="${PREV_TAG}..${TAG}"
|
|
78
|
+
else
|
|
79
|
+
RANGE="${TAG}"
|
|
80
|
+
fi
|
|
81
|
+
# Validate that the range resolves before proceeding
|
|
82
|
+
if ! git rev-parse "${RANGE}" > /dev/null 2>&1; then
|
|
83
|
+
echo "Error: git range '${RANGE}' does not resolve." >&2
|
|
84
|
+
exit 1
|
|
85
|
+
fi
|
|
86
|
+
# Collect up to 100 subject lines; exclude merge commits
|
|
87
|
+
# Use --max-count instead of | head to avoid SIGPIPE under set -euo pipefail
|
|
88
|
+
COMMITS=$(git log "${RANGE}" --no-merges --max-count=100 --pretty=format:"%s")
|
|
89
|
+
# Use a unique delimiter to avoid collision with commit message content
|
|
90
|
+
DELIM=$(uuidgen)
|
|
91
|
+
{
|
|
92
|
+
echo "commits<<${DELIM}"
|
|
93
|
+
echo "${COMMITS}"
|
|
94
|
+
echo "${DELIM}"
|
|
95
|
+
} >> "$GITHUB_OUTPUT"
|
|
96
|
+
|
|
97
|
+
- name: Generate release notes with GitHub Models
|
|
98
|
+
id: ai
|
|
99
|
+
uses: actions/ai-inference@v1
|
|
100
|
+
with:
|
|
101
|
+
model: openai/gpt-4o-mini
|
|
102
|
+
system-prompt: |
|
|
103
|
+
You are a technical writer generating GitHub release notes from git commit messages.
|
|
104
|
+
|
|
105
|
+
Rules:
|
|
106
|
+
- Classify each commit subject into one of three sections:
|
|
107
|
+
## Features — commits starting with "feat:"
|
|
108
|
+
## Bug Fixes — commits starting with "fix:"
|
|
109
|
+
## Maintenance — all other commits (refactor:, ci:, test:, perf:, etc.)
|
|
110
|
+
- IGNORE commits starting with "docs:" or "chore:" — do not include them at all.
|
|
111
|
+
- Rewrite each item as a concise, user-friendly bullet point (do not copy the raw commit subject verbatim).
|
|
112
|
+
- Omit any section that has no entries.
|
|
113
|
+
- Always output sections in this exact order when present: ## Features, then ## Bug Fixes, then ## Maintenance.
|
|
114
|
+
- Output valid Markdown only. No preamble, no explanation outside the Markdown.
|
|
115
|
+
prompt: |
|
|
116
|
+
Generate release notes for ${{ github.ref_name }} from the following commit messages:
|
|
117
|
+
|
|
118
|
+
${{ steps.commits.outputs.commits }}
|
|
119
|
+
|
|
120
|
+
- name: Write release notes to file
|
|
121
|
+
env:
|
|
122
|
+
NOTES: ${{ steps.ai.outputs.response }}
|
|
123
|
+
run: printf '%s' "$NOTES" > release_notes.md
|
|
124
|
+
|
|
125
|
+
- name: Create draft release
|
|
126
|
+
env:
|
|
127
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
128
|
+
REPO: ${{ github.repository }}
|
|
129
|
+
TAG: ${{ github.ref_name }}
|
|
130
|
+
run: |
|
|
131
|
+
gh release create "$TAG" \
|
|
132
|
+
--repo "$REPO" \
|
|
133
|
+
--title "$TAG" \
|
|
134
|
+
--notes-file release_notes.md \
|
|
135
|
+
--draft \
|
|
136
|
+
dist/*
|
|
137
|
+
|
|
138
|
+
pypi-publish:
|
|
139
|
+
name: Publish to PyPI
|
|
140
|
+
needs: github-release
|
|
141
|
+
runs-on: ubuntu-latest
|
|
142
|
+
environment: pypi
|
|
143
|
+
permissions:
|
|
144
|
+
id-token: write
|
|
145
|
+
steps:
|
|
146
|
+
- uses: actions/download-artifact@v4
|
|
147
|
+
with:
|
|
148
|
+
name: dist
|
|
149
|
+
path: dist/
|
|
150
|
+
|
|
151
|
+
- name: Publish to PyPI
|
|
152
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -39,18 +39,22 @@ papycli/
|
|
|
39
39
|
│ ├── main.py # エントリポイント・引数パース
|
|
40
40
|
│ ├── init_cmd.py # config add コマンド(spec の変換・保存)
|
|
41
41
|
│ ├── api_call.py # HTTP リクエスト実行
|
|
42
|
+
│ ├── checker.py # --check / --check-strict のパラメータ検証
|
|
42
43
|
│ ├── completion.py # シェル補完スクリプト生成
|
|
43
44
|
│ ├── config.py # 設定ファイルの読み書き
|
|
44
45
|
│ ├── i18n.py # 日英ヘルプテキストの切り替えユーティリティ
|
|
46
|
+
│ ├── request_filter.py # リクエストフィルタープラグイン機構
|
|
45
47
|
│ ├── spec_loader.py # OpenAPI spec の読み込み・$ref 解決
|
|
46
48
|
│ └── summary.py # summary コマンド・CSV 出力
|
|
47
49
|
├── tests/
|
|
48
50
|
│ ├── test_api_call.py
|
|
51
|
+
│ ├── test_checker.py
|
|
49
52
|
│ ├── test_completion.py
|
|
50
53
|
│ ├── test_config.py
|
|
51
54
|
│ ├── test_i18n.py
|
|
52
55
|
│ ├── test_init_cmd.py
|
|
53
56
|
│ ├── test_main.py
|
|
57
|
+
│ ├── test_request_filter.py
|
|
54
58
|
│ ├── test_spec_loader.py
|
|
55
59
|
│ └── test_summary.py
|
|
56
60
|
├── examples/
|
|
@@ -66,7 +70,7 @@ papycli/
|
|
|
66
70
|
### 主要モジュール
|
|
67
71
|
|
|
68
72
|
**`main.py`** — CLI エントリポイント
|
|
69
|
-
引数をパースし、各コマンド(`config add`/`use`/`remove`/`list`/`completion-script`、`summary`、メソッド呼び出し)に処理を委譲する。シェル補完用の `_complete` 内部コマンドもここで定義する。
|
|
73
|
+
引数をパースし、各コマンド(`config add`/`use`/`remove`/`list`/`completion-script`、`spec`、`summary`、メソッド呼び出し)に処理を委譲する。シェル補完用の `_complete` 内部コマンドもここで定義する。
|
|
70
74
|
|
|
71
75
|
**`init_cmd.py`** — API 初期化(`config add` コマンドの実処理)
|
|
72
76
|
OpenAPI spec ファイルを受け取り、`$ref` を解決した上で papycli 内部の API 定義フォーマットに変換し、`$PAPYCLI_CONF_DIR/apis/<name>.json` に保存する。設定ファイル (`papycli.conf`) も更新する。
|
|
@@ -75,13 +79,19 @@ OpenAPI spec ファイルを受け取り、`$ref` を解決した上で papycli
|
|
|
75
79
|
OpenAPI spec の JSON/YAML を読み込み、`$ref` を再帰的に解決して、papycli の内部フォーマット(後述)に変換する。Python 標準ライブラリのみ(または最小限の依存)で実装する。
|
|
76
80
|
|
|
77
81
|
**`api_call.py`** — HTTP リクエスト実行
|
|
78
|
-
メソッド・リソースパス・オプションを受け取り、`requests` ライブラリで HTTP リクエストを実行する。パステンプレート(`/pet/{petId}
|
|
82
|
+
メソッド・リソースパス・オプションを受け取り、`requests` ライブラリで HTTP リクエストを実行する。パステンプレート(`/pet/{petId}`)のマッチングと値の埋め込みも担当する。リクエストフィルタープラグインの適用と、リクエスト/レスポンスのファイルログ記録も行う。
|
|
83
|
+
|
|
84
|
+
**`checker.py`** — パラメータ検証
|
|
85
|
+
`--check` / `--check-strict` オプション用のリクエスト前バリデーションロジック。必須パラメータの存在確認、型チェック(integer / boolean)、enum 値の検証を行い、警告メッセージのリストを返す。
|
|
79
86
|
|
|
80
87
|
**`completion.py`** — シェル補完
|
|
81
88
|
bash / zsh 向けの補完スクリプトを生成する。補完の候補はメソッド、リソースパス、パラメータ名、enum 値の順にコンテキストに応じて提供する。
|
|
82
89
|
|
|
83
90
|
**`config.py`** — 設定管理
|
|
84
|
-
`papycli.conf` の読み書きと、`PAPYCLI_CONF_DIR`
|
|
91
|
+
`papycli.conf` の読み書きと、`PAPYCLI_CONF_DIR` 環境変数の解決を行う。ログファイルパスの取得・設定・削除も担当する。
|
|
92
|
+
|
|
93
|
+
**`request_filter.py`** — リクエストフィルタープラグイン機構
|
|
94
|
+
エントリポイントグループ `papycli.request_filters` に登録されたフィルター関数をプラグイン名の昇順で呼び出し、リクエスト送信前に URL・クエリパラメータ・ボディ・ヘッダーを変換できるようにする。`RequestContext` データクラスと `load_filters()` / `apply_filters()` 関数を提供する。
|
|
85
95
|
|
|
86
96
|
**`summary.py`** — サマリー表示
|
|
87
97
|
登録済み API のエンドポイント一覧を整形して出力する。`--summary-csv` では CSV 形式で出力する。
|
|
@@ -154,7 +164,9 @@ papycli config add <spec-file>
|
|
|
154
164
|
papycli config use <api-name>
|
|
155
165
|
papycli config remove <api-name>
|
|
156
166
|
papycli config list
|
|
167
|
+
papycli config log [PATH] [--unset]
|
|
157
168
|
papycli config completion-script <bash|zsh>
|
|
169
|
+
papycli spec [resource]
|
|
158
170
|
papycli summary [resource] [--csv]
|
|
159
171
|
papycli --version
|
|
160
172
|
papycli --help / -h
|
|
@@ -171,6 +183,8 @@ papycli --help / -h
|
|
|
171
183
|
- `-p <name.subname> <value>` — ドット記法で 1 レベルのネストオブジェクトを構築する
|
|
172
184
|
- `-d <json>` — 生の JSON 文字列。`-p` オプションを上書きする
|
|
173
185
|
- `-H <header: value>` — カスタム HTTP ヘッダー
|
|
186
|
+
- `--check` — 送信前にパラメータを検証する(警告を stderr に出力、リクエストは送信)
|
|
187
|
+
- `--check-strict` — 送信前にパラメータを検証する(警告を stderr に出力、問題があればリクエスト中止・exit 1)
|
|
174
188
|
|
|
175
189
|
### パステンプレートのマッチング
|
|
176
190
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: papycli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: A CLI tool to call REST APIs defined in OpenAPI 3.0 specs
|
|
5
5
|
Project-URL: Homepage, https://github.com/tmonj1/papycli
|
|
6
6
|
Project-URL: Repository, https://github.com/tmonj1/papycli
|
|
@@ -34,6 +34,11 @@ Description-Content-Type: text/markdown
|
|
|
34
34
|
- Auto-generates a CLI from any OpenAPI 3.0 spec
|
|
35
35
|
- Shell completion for bash and zsh
|
|
36
36
|
- Register and switch between multiple APIs
|
|
37
|
+
- Inspect API specs with `papycli spec`
|
|
38
|
+
- Validate request parameters before sending with `--check` / `--check-strict`
|
|
39
|
+
- Automatically coerces `-p` values to the correct JSON type (integer, number, boolean) based on the API spec
|
|
40
|
+
- Log requests and responses to a file with `papycli config log`
|
|
41
|
+
- Extend request processing with request filter plugins (`papycli.request_filters` entry point)
|
|
37
42
|
|
|
38
43
|
## Requirements
|
|
39
44
|
|
|
@@ -124,13 +129,13 @@ Once shell completion is enabled, tab completion is available:
|
|
|
124
129
|
|
|
125
130
|
```
|
|
126
131
|
$ papycli <TAB>
|
|
127
|
-
get post put patch delete config summary
|
|
132
|
+
get post put patch delete config spec summary
|
|
128
133
|
|
|
129
134
|
$ papycli get <TAB>
|
|
130
135
|
/pet/findByStatus /pet/{petId} /store/inventory ...
|
|
131
136
|
|
|
132
137
|
$ papycli get /pet/findByStatus <TAB>
|
|
133
|
-
-q -p -H -d --summary --verbose
|
|
138
|
+
-q -p -H -d --summary --verbose --check --check-strict
|
|
134
139
|
|
|
135
140
|
$ papycli get /pet/findByStatus -q <TAB>
|
|
136
141
|
status
|
|
@@ -206,12 +211,18 @@ papycli config add <spec-file> Register an API from an OpenAPI spec
|
|
|
206
211
|
papycli config remove <api-name> Remove a registered API
|
|
207
212
|
papycli config use <api-name> Switch the active API
|
|
208
213
|
papycli config list List registered APIs and current configuration
|
|
214
|
+
papycli config log Show the current log file path
|
|
215
|
+
papycli config log <path> Set the log file path
|
|
216
|
+
papycli config log --unset Disable logging
|
|
209
217
|
papycli config completion-script <bash|zsh> Print a shell completion script
|
|
210
218
|
|
|
211
|
-
#
|
|
219
|
+
# Inspection commands
|
|
220
|
+
papycli spec [resource] Show the raw internal API spec (filter by resource path)
|
|
212
221
|
papycli summary [resource] List available endpoints (filter by resource prefix)
|
|
213
222
|
Required params marked with *, array params with []
|
|
214
223
|
papycli summary --csv Output endpoints in CSV format
|
|
224
|
+
|
|
225
|
+
# API call commands
|
|
215
226
|
papycli <method> <resource> [options]
|
|
216
227
|
|
|
217
228
|
Methods:
|
|
@@ -221,6 +232,9 @@ Options:
|
|
|
221
232
|
-H <header: value> Custom HTTP header (repeatable)
|
|
222
233
|
-q <name> <value> Query parameter (repeatable)
|
|
223
234
|
-p <name> <value> Body parameter (repeatable)
|
|
235
|
+
- Values are coerced to the correct JSON type
|
|
236
|
+
(integer, number, boolean) based on the API spec.
|
|
237
|
+
Strings are passed as-is.
|
|
224
238
|
- Repeat the same key to build a JSON array:
|
|
225
239
|
-p tags foo -p tags bar → {"tags":["foo","bar"]}
|
|
226
240
|
- Use dot notation to build a nested object:
|
|
@@ -228,6 +242,9 @@ Options:
|
|
|
228
242
|
→ {"category":{"id":"1","name":"Dogs"}}
|
|
229
243
|
-d <json> Raw JSON body (overrides -p)
|
|
230
244
|
--summary Show endpoint info without sending a request
|
|
245
|
+
--check Validate params before sending (warn on stderr, request is still sent)
|
|
246
|
+
--check-strict Validate params before sending (warn on stderr, abort with exit 1 on failure)
|
|
247
|
+
--verbose / -v Show HTTP status line
|
|
231
248
|
--version Show version
|
|
232
249
|
--help / -h Show help
|
|
233
250
|
|
|
@@ -240,6 +257,42 @@ Environment variables:
|
|
|
240
257
|
|
|
241
258
|
---
|
|
242
259
|
|
|
260
|
+
## Request Filter Plugins
|
|
261
|
+
|
|
262
|
+
You can intercept and transform outgoing requests by writing a request filter plugin.
|
|
263
|
+
|
|
264
|
+
A filter is a callable that receives a `RequestContext` and returns a modified `RequestContext`:
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
# my_plugin.py
|
|
268
|
+
from papycli.request_filter import RequestContext
|
|
269
|
+
|
|
270
|
+
def request_filter(ctx: RequestContext) -> RequestContext:
|
|
271
|
+
ctx.headers["X-Request-ID"] = "my-id"
|
|
272
|
+
return ctx
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Register it in your package's `pyproject.toml`:
|
|
276
|
+
|
|
277
|
+
```toml
|
|
278
|
+
[project.entry-points."papycli.request_filters"]
|
|
279
|
+
my-filter = "my_plugin:request_filter"
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Install the package and filters are applied automatically on every request, sorted by plugin name.
|
|
283
|
+
|
|
284
|
+
`RequestContext` fields:
|
|
285
|
+
|
|
286
|
+
| Field | Type | Description |
|
|
287
|
+
|-------|------|-------------|
|
|
288
|
+
| `method` | `str` | HTTP method (lowercase). Do not modify. |
|
|
289
|
+
| `url` | `str` | Full URL with path parameters expanded. |
|
|
290
|
+
| `query_params` | `list[tuple[str, str]]` | Query parameters. |
|
|
291
|
+
| `body` | `dict \| list \| str \| int \| float \| bool \| None` | JSON request body. |
|
|
292
|
+
| `headers` | `dict[str, str]` | Custom HTTP headers. |
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
243
296
|
## Limitations
|
|
244
297
|
|
|
245
298
|
- Request bodies are `application/json` only
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
- OpenAPI 3.0 仕様から CLI を自動生成
|
|
8
8
|
- シェル補完(bash / zsh)対応
|
|
9
9
|
- 複数 API の登録・切り替え
|
|
10
|
+
- `papycli spec` による API スペックの確認
|
|
11
|
+
- `--check` / `--check-strict` によるリクエスト前のパラメータ検証
|
|
12
|
+
- API 仕様に基づいて `-p` の値を適切な JSON 型(integer / number / boolean)に自動変換
|
|
13
|
+
- `papycli config log` によるリクエスト/レスポンスのファイルログ
|
|
14
|
+
- リクエストフィルタープラグイン(`papycli.request_filters` エントリポイント)によるリクエスト処理の拡張
|
|
10
15
|
|
|
11
16
|
## 必要環境
|
|
12
17
|
|
|
@@ -97,13 +102,13 @@ papycli delete /pet/1
|
|
|
97
102
|
|
|
98
103
|
```
|
|
99
104
|
$ papycli <TAB>
|
|
100
|
-
get post put patch delete config summary
|
|
105
|
+
get post put patch delete config spec summary
|
|
101
106
|
|
|
102
107
|
$ papycli get <TAB>
|
|
103
108
|
/pet/findByStatus /pet/{petId} /store/inventory ...
|
|
104
109
|
|
|
105
110
|
$ papycli get /pet/findByStatus <TAB>
|
|
106
|
-
-q -p -H -d --summary --verbose
|
|
111
|
+
-q -p -H -d --summary --verbose --check --check-strict
|
|
107
112
|
|
|
108
113
|
$ papycli get /pet/findByStatus -q <TAB>
|
|
109
114
|
status
|
|
@@ -179,12 +184,18 @@ papycli config add <spec-file> OpenAPI spec ファイルから API
|
|
|
179
184
|
papycli config remove <api-name> 登録済み API を削除する
|
|
180
185
|
papycli config use <api-name> アクティブな API を切り替える
|
|
181
186
|
papycli config list 登録済み API と現在の設定を一覧表示する
|
|
187
|
+
papycli config log 現在のログファイルパスを表示する
|
|
188
|
+
papycli config log <path> ログファイルパスを設定する
|
|
189
|
+
papycli config log --unset ログを無効化する
|
|
182
190
|
papycli config completion-script <bash|zsh> シェル補完スクリプトを出力する
|
|
183
191
|
|
|
184
|
-
#
|
|
192
|
+
# 確認コマンド
|
|
193
|
+
papycli spec [resource] 内部 API スペックを表示する(リソースパスでフィルタ可能)
|
|
185
194
|
papycli summary [resource] 利用可能なエンドポイントを表示する(リソースでフィルタ可能)
|
|
186
195
|
必須パラメータは * 付き、配列パラメータは [] 付きで表示
|
|
187
196
|
papycli summary --csv CSV フォーマットでエンドポイントを表示する
|
|
197
|
+
|
|
198
|
+
# API 呼び出しコマンド
|
|
188
199
|
papycli <method> <resource> [options]
|
|
189
200
|
|
|
190
201
|
メソッド:
|
|
@@ -194,6 +205,8 @@ papycli <method> <resource> [options]
|
|
|
194
205
|
-H <header: value> カスタム HTTP ヘッダー(繰り返し可)
|
|
195
206
|
-q <name> <value> クエリパラメータ(繰り返し可)
|
|
196
207
|
-p <name> <value> ボディパラメータ(繰り返し可)
|
|
208
|
+
- API 仕様に基づいて値を適切な JSON 型(integer / number /
|
|
209
|
+
boolean)に自動変換する。文字列はそのまま送信される。
|
|
197
210
|
- 同じキーを繰り返すと JSON 配列を構築する:
|
|
198
211
|
-p tags foo -p tags bar → {"tags":["foo","bar"]}
|
|
199
212
|
- ドット記法でネストしたオブジェクトを構築する:
|
|
@@ -201,6 +214,9 @@ papycli <method> <resource> [options]
|
|
|
201
214
|
→ {"category":{"id":"1","name":"Dogs"}}
|
|
202
215
|
-d <json> 生の JSON ボディ(-p を上書きする)
|
|
203
216
|
--summary リクエストを送らずにエンドポイント情報を表示する
|
|
217
|
+
--check 送信前にパラメータを検証する(警告を stderr に出力、リクエストは送信)
|
|
218
|
+
--check-strict 送信前にパラメータを検証する(警告を stderr に出力、問題があればリクエスト中止・exit 1)
|
|
219
|
+
--verbose / -v HTTP ステータス行を表示する
|
|
204
220
|
--version バージョンを表示する
|
|
205
221
|
--help / -h 使い方を表示する
|
|
206
222
|
|
|
@@ -213,6 +229,42 @@ papycli <method> <resource> [options]
|
|
|
213
229
|
|
|
214
230
|
---
|
|
215
231
|
|
|
232
|
+
## リクエストフィルタープラグイン
|
|
233
|
+
|
|
234
|
+
リクエストフィルタープラグインを作成することで、送信前のリクエストを加工できます。
|
|
235
|
+
|
|
236
|
+
フィルターは `RequestContext` を受け取り、変更した `RequestContext` を返す callable です:
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
# my_plugin.py
|
|
240
|
+
from papycli.request_filter import RequestContext
|
|
241
|
+
|
|
242
|
+
def request_filter(ctx: RequestContext) -> RequestContext:
|
|
243
|
+
ctx.headers["X-Request-ID"] = "my-id"
|
|
244
|
+
return ctx
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
パッケージの `pyproject.toml` にエントリポイントを登録します:
|
|
248
|
+
|
|
249
|
+
```toml
|
|
250
|
+
[project.entry-points."papycli.request_filters"]
|
|
251
|
+
my-filter = "my_plugin:request_filter"
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
パッケージをインストールすると、すべてのリクエストに対してフィルターがプラグイン名の昇順で自動適用されます。
|
|
255
|
+
|
|
256
|
+
`RequestContext` のフィールド:
|
|
257
|
+
|
|
258
|
+
| フィールド | 型 | 説明 |
|
|
259
|
+
|-----------|-----|------|
|
|
260
|
+
| `method` | `str` | HTTP メソッド(小文字)。変更不可。 |
|
|
261
|
+
| `url` | `str` | パスパラメータ展開済みの完全 URL。 |
|
|
262
|
+
| `query_params` | `list[tuple[str, str]]` | クエリパラメータ。 |
|
|
263
|
+
| `body` | `dict \| list \| str \| int \| float \| bool \| None` | JSON リクエストボディ。 |
|
|
264
|
+
| `headers` | `dict[str, str]` | カスタム HTTP ヘッダー。 |
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
216
268
|
## 制限事項
|
|
217
269
|
|
|
218
270
|
- リクエストボディは `application/json` のみ対応
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
- Auto-generates a CLI from any OpenAPI 3.0 spec
|
|
8
8
|
- Shell completion for bash and zsh
|
|
9
9
|
- Register and switch between multiple APIs
|
|
10
|
+
- Inspect API specs with `papycli spec`
|
|
11
|
+
- Validate request parameters before sending with `--check` / `--check-strict`
|
|
12
|
+
- Automatically coerces `-p` values to the correct JSON type (integer, number, boolean) based on the API spec
|
|
13
|
+
- Log requests and responses to a file with `papycli config log`
|
|
14
|
+
- Extend request processing with request filter plugins (`papycli.request_filters` entry point)
|
|
10
15
|
|
|
11
16
|
## Requirements
|
|
12
17
|
|
|
@@ -97,13 +102,13 @@ Once shell completion is enabled, tab completion is available:
|
|
|
97
102
|
|
|
98
103
|
```
|
|
99
104
|
$ papycli <TAB>
|
|
100
|
-
get post put patch delete config summary
|
|
105
|
+
get post put patch delete config spec summary
|
|
101
106
|
|
|
102
107
|
$ papycli get <TAB>
|
|
103
108
|
/pet/findByStatus /pet/{petId} /store/inventory ...
|
|
104
109
|
|
|
105
110
|
$ papycli get /pet/findByStatus <TAB>
|
|
106
|
-
-q -p -H -d --summary --verbose
|
|
111
|
+
-q -p -H -d --summary --verbose --check --check-strict
|
|
107
112
|
|
|
108
113
|
$ papycli get /pet/findByStatus -q <TAB>
|
|
109
114
|
status
|
|
@@ -179,12 +184,18 @@ papycli config add <spec-file> Register an API from an OpenAPI spec
|
|
|
179
184
|
papycli config remove <api-name> Remove a registered API
|
|
180
185
|
papycli config use <api-name> Switch the active API
|
|
181
186
|
papycli config list List registered APIs and current configuration
|
|
187
|
+
papycli config log Show the current log file path
|
|
188
|
+
papycli config log <path> Set the log file path
|
|
189
|
+
papycli config log --unset Disable logging
|
|
182
190
|
papycli config completion-script <bash|zsh> Print a shell completion script
|
|
183
191
|
|
|
184
|
-
#
|
|
192
|
+
# Inspection commands
|
|
193
|
+
papycli spec [resource] Show the raw internal API spec (filter by resource path)
|
|
185
194
|
papycli summary [resource] List available endpoints (filter by resource prefix)
|
|
186
195
|
Required params marked with *, array params with []
|
|
187
196
|
papycli summary --csv Output endpoints in CSV format
|
|
197
|
+
|
|
198
|
+
# API call commands
|
|
188
199
|
papycli <method> <resource> [options]
|
|
189
200
|
|
|
190
201
|
Methods:
|
|
@@ -194,6 +205,9 @@ Options:
|
|
|
194
205
|
-H <header: value> Custom HTTP header (repeatable)
|
|
195
206
|
-q <name> <value> Query parameter (repeatable)
|
|
196
207
|
-p <name> <value> Body parameter (repeatable)
|
|
208
|
+
- Values are coerced to the correct JSON type
|
|
209
|
+
(integer, number, boolean) based on the API spec.
|
|
210
|
+
Strings are passed as-is.
|
|
197
211
|
- Repeat the same key to build a JSON array:
|
|
198
212
|
-p tags foo -p tags bar → {"tags":["foo","bar"]}
|
|
199
213
|
- Use dot notation to build a nested object:
|
|
@@ -201,6 +215,9 @@ Options:
|
|
|
201
215
|
→ {"category":{"id":"1","name":"Dogs"}}
|
|
202
216
|
-d <json> Raw JSON body (overrides -p)
|
|
203
217
|
--summary Show endpoint info without sending a request
|
|
218
|
+
--check Validate params before sending (warn on stderr, request is still sent)
|
|
219
|
+
--check-strict Validate params before sending (warn on stderr, abort with exit 1 on failure)
|
|
220
|
+
--verbose / -v Show HTTP status line
|
|
204
221
|
--version Show version
|
|
205
222
|
--help / -h Show help
|
|
206
223
|
|
|
@@ -213,6 +230,42 @@ Environment variables:
|
|
|
213
230
|
|
|
214
231
|
---
|
|
215
232
|
|
|
233
|
+
## Request Filter Plugins
|
|
234
|
+
|
|
235
|
+
You can intercept and transform outgoing requests by writing a request filter plugin.
|
|
236
|
+
|
|
237
|
+
A filter is a callable that receives a `RequestContext` and returns a modified `RequestContext`:
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
# my_plugin.py
|
|
241
|
+
from papycli.request_filter import RequestContext
|
|
242
|
+
|
|
243
|
+
def request_filter(ctx: RequestContext) -> RequestContext:
|
|
244
|
+
ctx.headers["X-Request-ID"] = "my-id"
|
|
245
|
+
return ctx
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Register it in your package's `pyproject.toml`:
|
|
249
|
+
|
|
250
|
+
```toml
|
|
251
|
+
[project.entry-points."papycli.request_filters"]
|
|
252
|
+
my-filter = "my_plugin:request_filter"
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Install the package and filters are applied automatically on every request, sorted by plugin name.
|
|
256
|
+
|
|
257
|
+
`RequestContext` fields:
|
|
258
|
+
|
|
259
|
+
| Field | Type | Description |
|
|
260
|
+
|-------|------|-------------|
|
|
261
|
+
| `method` | `str` | HTTP method (lowercase). Do not modify. |
|
|
262
|
+
| `url` | `str` | Full URL with path parameters expanded. |
|
|
263
|
+
| `query_params` | `list[tuple[str, str]]` | Query parameters. |
|
|
264
|
+
| `body` | `dict \| list \| str \| int \| float \| bool \| None` | JSON request body. |
|
|
265
|
+
| `headers` | `dict[str, str]` | Custom HTTP headers. |
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
216
269
|
## Limitations
|
|
217
270
|
|
|
218
271
|
- Request bodies are `application/json` only
|
|
@@ -37,16 +37,20 @@ papycli/
|
|
|
37
37
|
│ ├── spec_loader.py # OpenAPI spec 読み込み・$ref 解決・内部形式変換
|
|
38
38
|
│ ├── init_cmd.py # config add コマンドの処理
|
|
39
39
|
│ ├── api_call.py # HTTP リクエスト実行・パステンプレートマッチング
|
|
40
|
+
│ ├── checker.py # --check / --check-strict のパラメータ検証
|
|
40
41
|
│ ├── summary.py # summary コマンドの出力
|
|
41
42
|
│ ├── completion.py # bash / zsh 補完スクリプト生成
|
|
43
|
+
│ ├── request_filter.py # リクエストフィルタープラグイン機構
|
|
42
44
|
│ └── i18n.py # 日英ヘルプテキストの切り替えユーティリティ
|
|
43
45
|
├── tests/
|
|
44
46
|
│ ├── test_api_call.py
|
|
47
|
+
│ ├── test_checker.py
|
|
45
48
|
│ ├── test_completion.py
|
|
46
49
|
│ ├── test_config.py
|
|
47
50
|
│ ├── test_i18n.py
|
|
48
51
|
│ ├── test_init_cmd.py
|
|
49
52
|
│ ├── test_main.py
|
|
53
|
+
│ ├── test_request_filter.py
|
|
50
54
|
│ ├── test_spec_loader.py
|
|
51
55
|
│ └── test_summary.py
|
|
52
56
|
├── examples/
|
|
@@ -174,12 +178,19 @@ papycli/
|
|
|
174
178
|
- `requests` による HTTP 実行
|
|
175
179
|
- レスポンスの出力(JSON は整形表示、それ以外はそのまま)
|
|
176
180
|
- `main.py` に `papycli <method> <resource>` コマンド追加
|
|
177
|
-
-
|
|
181
|
+
- `main.py` に `papycli spec [resource]` コマンド追加(内部 API 定義の JSON 表示)
|
|
182
|
+
- `checker.py`
|
|
183
|
+
- `--check` / `--check-strict` オプション用のパラメータ検証ロジック
|
|
184
|
+
- 必須パラメータの存在確認、型チェック(integer / boolean)、enum 値の検証
|
|
185
|
+
- `--check`: 問題があっても警告を出力してリクエストを送信する
|
|
186
|
+
- `--check-strict`: 問題があれば警告を出力してリクエストを中止し exit 1
|
|
187
|
+
- テスト: `test_api_call.py`、`test_checker.py`
|
|
178
188
|
- パステンプレートマッチングの各ケース
|
|
179
189
|
- `-p` による JSON 構築(配列、ネスト)
|
|
180
190
|
- `responses` ライブラリで HTTP をモックしたエンドツーエンドテスト
|
|
191
|
+
- `check_request()` の各検証ケース(必須・型・enum・raw body)
|
|
181
192
|
|
|
182
|
-
**完了条件**: `papycli get /store/inventory`、`papycli post /pet -p name "My Dog" -p status available` 等が Petstore
|
|
193
|
+
**完了条件**: `papycli get /store/inventory`、`papycli post /pet -p name "My Dog" -p status available` 等が Petstore サーバーに対して動作する。`papycli spec` で内部 API 定義が表示される。`--check` / `--check-strict` でパラメータ検証が動作する。テストがパスする。
|
|
183
194
|
|
|
184
195
|
---
|
|
185
196
|
|
|
@@ -217,9 +228,67 @@ papycli/
|
|
|
217
228
|
4. パラメータ名補完: `-q <TAB>` → クエリパラメータ名の候補
|
|
218
229
|
5. enum 値補完: `-q status <TAB>` → `available pending sold` 等
|
|
219
230
|
- `main.py` に `--completion-script <bash|zsh>` コマンド追加
|
|
231
|
+
- 補完候補の強化(`spec` コマンド、`summary --csv`、`--check`/`--check-strict` オプションを追加)
|
|
232
|
+
- Windows 対応: `_complete` の出力を binary stream(LF のみ)に変更
|
|
220
233
|
- 手動テスト手順をドキュメント化
|
|
221
234
|
|
|
222
|
-
**完了条件**: `eval "$(papycli config completion-script zsh)"` 後にタブ補完が動作する。メソッド・リソース・クエリパラメータ名・ボディパラメータ名・enum
|
|
235
|
+
**完了条件**: `eval "$(papycli config completion-script zsh)"` 後にタブ補完が動作する。メソッド・リソース・クエリパラメータ名・ボディパラメータ名・enum 値が補完候補として表示される。`spec`・`summary`・`--check`・`--check-strict` も補完候補に含まれる。
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
### Milestone 6 — リクエストフィルタープラグイン機構
|
|
240
|
+
|
|
241
|
+
**目的**: サードパーティプラグインがリクエスト送信前に URL・クエリ・ボディ・ヘッダーを変換できる拡張ポイントを提供する。
|
|
242
|
+
|
|
243
|
+
**実装内容**:
|
|
244
|
+
- `request_filter.py`
|
|
245
|
+
- `RequestContext` データクラス(`method`, `url`, `query_params`, `body`, `headers`)
|
|
246
|
+
- `load_filters()`: `papycli.request_filters` エントリポイントグループからフィルターをロードし、callable 検証後にプラグイン名の昇順で返す
|
|
247
|
+
- `apply_filters()`: フィルターを順番に適用。各フィルター呼び出し前にスナップショットを作成し(body は deepcopy、他はシャローコピー)、例外・戻り値不正の場合は警告して前の ctx を維持する
|
|
248
|
+
- `api_call.py` の `call_api()` でフィルターを適用するよう更新
|
|
249
|
+
- フィルター適用後の `method` は使用しない(API 定義マッチング時に確定した元の値を使う)
|
|
250
|
+
- テスト: `test_request_filter.py`
|
|
251
|
+
- フィルターの読み込み・適用・例外処理・戻り値型検証など
|
|
252
|
+
|
|
253
|
+
**プラグイン登録例** (`pyproject.toml`):
|
|
254
|
+
|
|
255
|
+
```toml
|
|
256
|
+
[project.entry-points."papycli.request_filters"]
|
|
257
|
+
my-filter = "my_plugin:request_filter"
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**完了条件**: `papycli.request_filters` エントリポイントに登録したフィルターが全リクエストで自動適用される。フィルターが例外を送出してもリクエスト送信に影響しない。テストがパスする。
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
### Milestone 7 — `config log` コマンドとリクエスト/レスポンスログ
|
|
265
|
+
|
|
266
|
+
**目的**: リクエストとレスポンスの内容をファイルに記録できるようにし、デバッグや監査を容易にする。
|
|
267
|
+
|
|
268
|
+
**実装内容**:
|
|
269
|
+
- `config.py`
|
|
270
|
+
- `get_logfile()` / `set_logfile()` / `unset_logfile()`: ログファイルパスの取得・設定・削除
|
|
271
|
+
- `load_current_apidef()` に `conf` オプション引数を追加し、設定ファイルの二重読み込みを防止
|
|
272
|
+
- `api_call.py`
|
|
273
|
+
- `_SENSITIVE_HEADERS`: ログに記録する際にマスクするヘッダー名の集合(Authorization, Cookie 等)
|
|
274
|
+
- `_LOG_BODY_MAX_CHARS`: ログに記録するボディの最大文字数(10,000 文字)。超過時は `...[truncated]` を付与
|
|
275
|
+
- `_write_log()`: フィルター適用前の URL・クエリ・ボディ・ヘッダーをログに記録。エラー時は警告のみでリクエストには影響しない
|
|
276
|
+
- `call_api()` に `logfile` 引数を追加。`~` 展開に `Path.expanduser()` を使用
|
|
277
|
+
- `main.py` に `papycli config log [PATH] [--unset]` コマンド追加
|
|
278
|
+
- テスト: `test_config.py`、`test_api_call.py`、`test_main.py`
|
|
279
|
+
|
|
280
|
+
**ログフォーマット例**:
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
[2026-03-10T12:34:56+00:00] GET https://example.com/pet/99
|
|
284
|
+
Query: (none)
|
|
285
|
+
Body: (none)
|
|
286
|
+
Headers: {"Authorization": "***"}
|
|
287
|
+
Status: 200
|
|
288
|
+
Response: {"id": 99, "name": "My Dog"}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**完了条件**: `papycli config log ~/papycli.log` 設定後、リクエストごとにログファイルへ追記される。機密ヘッダーはマスクされる。大きなボディは切り詰められる。テストがパスする。
|
|
223
292
|
|
|
224
293
|
---
|
|
225
294
|
|