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.
Files changed (37) hide show
  1. papycli-0.6.0/.github/workflows/release.yml +152 -0
  2. {papycli-0.5.2 → papycli-0.6.0}/CLAUDE.md +17 -3
  3. {papycli-0.5.2 → papycli-0.6.0}/PKG-INFO +57 -4
  4. {papycli-0.5.2 → papycli-0.6.0}/README.ja.md +55 -3
  5. {papycli-0.5.2 → papycli-0.6.0}/README.md +56 -3
  6. {papycli-0.5.2 → papycli-0.6.0}/design_doc.md +72 -3
  7. {papycli-0.5.2 → papycli-0.6.0}/pyproject.toml +1 -1
  8. {papycli-0.5.2 → papycli-0.6.0}/src/papycli/api_call.py +145 -12
  9. papycli-0.6.0/src/papycli/checker.py +126 -0
  10. {papycli-0.5.2 → papycli-0.6.0}/src/papycli/completion.py +60 -12
  11. {papycli-0.5.2 → papycli-0.6.0}/src/papycli/config.py +34 -5
  12. {papycli-0.5.2 → papycli-0.6.0}/src/papycli/main.py +134 -2
  13. papycli-0.6.0/src/papycli/request_filter.py +132 -0
  14. papycli-0.6.0/tests/test_api_call.py +567 -0
  15. papycli-0.6.0/tests/test_checker.py +219 -0
  16. {papycli-0.5.2 → papycli-0.6.0}/tests/test_completion.py +107 -7
  17. {papycli-0.5.2 → papycli-0.6.0}/tests/test_config.py +54 -0
  18. {papycli-0.5.2 → papycli-0.6.0}/tests/test_main.py +239 -0
  19. papycli-0.6.0/tests/test_request_filter.py +320 -0
  20. {papycli-0.5.2 → papycli-0.6.0}/uv.lock +1 -1
  21. papycli-0.5.2/.github/workflows/release.yml +0 -77
  22. papycli-0.5.2/tests/test_api_call.py +0 -285
  23. {papycli-0.5.2 → papycli-0.6.0}/.gitignore +0 -0
  24. {papycli-0.5.2 → papycli-0.6.0}/.python-version +0 -0
  25. {papycli-0.5.2 → papycli-0.6.0}/LICENSE +0 -0
  26. {papycli-0.5.2 → papycli-0.6.0}/examples/docker-compose.yml +0 -0
  27. {papycli-0.5.2 → papycli-0.6.0}/examples/petstore-oas3.json +0 -0
  28. {papycli-0.5.2 → papycli-0.6.0}/src/papycli/__init__.py +0 -0
  29. {papycli-0.5.2 → papycli-0.6.0}/src/papycli/i18n.py +0 -0
  30. {papycli-0.5.2 → papycli-0.6.0}/src/papycli/init_cmd.py +0 -0
  31. {papycli-0.5.2 → papycli-0.6.0}/src/papycli/spec_loader.py +0 -0
  32. {papycli-0.5.2 → papycli-0.6.0}/src/papycli/summary.py +0 -0
  33. {papycli-0.5.2 → papycli-0.6.0}/tests/conftest.py +0 -0
  34. {papycli-0.5.2 → papycli-0.6.0}/tests/test_i18n.py +0 -0
  35. {papycli-0.5.2 → papycli-0.6.0}/tests/test_init_cmd.py +0 -0
  36. {papycli-0.5.2 → papycli-0.6.0}/tests/test_spec_loader.py +0 -0
  37. {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.5.2
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
- # API call commands
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
- # API 呼び出しコマンド
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
- # API call commands
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
- - テスト: `test_api_call.py`
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