papycli 0.6.0__tar.gz → 0.8.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 (44) hide show
  1. {papycli-0.6.0 → papycli-0.8.0}/CLAUDE.md +4 -2
  2. {papycli-0.6.0 → papycli-0.8.0}/PKG-INFO +56 -2
  3. {papycli-0.6.0 → papycli-0.8.0}/README.ja.md +54 -0
  4. {papycli-0.6.0 → papycli-0.8.0}/README.md +55 -1
  5. papycli-0.8.0/examples/request_filter/README.md +82 -0
  6. papycli-0.8.0/examples/request_filter/pyproject.toml +18 -0
  7. papycli-0.8.0/examples/request_filter/src/papycli_debug_filter/__init__.py +46 -0
  8. papycli-0.8.0/examples/response_filter/README.md +82 -0
  9. papycli-0.8.0/examples/response_filter/pyproject.toml +18 -0
  10. papycli-0.8.0/examples/response_filter/src/papycli_debug_response_filter/__init__.py +42 -0
  11. {papycli-0.6.0 → papycli-0.8.0}/pyproject.toml +1 -1
  12. {papycli-0.6.0 → papycli-0.8.0}/src/papycli/api_call.py +177 -11
  13. {papycli-0.6.0 → papycli-0.8.0}/src/papycli/completion.py +40 -6
  14. {papycli-0.6.0 → papycli-0.8.0}/src/papycli/config.py +35 -0
  15. {papycli-0.6.0 → papycli-0.8.0}/src/papycli/init_cmd.py +5 -0
  16. {papycli-0.6.0 → papycli-0.8.0}/src/papycli/main.py +70 -4
  17. {papycli-0.6.0 → papycli-0.8.0}/src/papycli/request_filter.py +110 -1
  18. papycli-0.8.0/src/papycli/response_checker.py +276 -0
  19. {papycli-0.6.0 → papycli-0.8.0}/src/papycli/spec_loader.py +52 -1
  20. {papycli-0.6.0 → papycli-0.8.0}/tests/test_api_call.py +164 -5
  21. {papycli-0.6.0 → papycli-0.8.0}/tests/test_completion.py +111 -3
  22. {papycli-0.6.0 → papycli-0.8.0}/tests/test_config.py +53 -0
  23. {papycli-0.6.0 → papycli-0.8.0}/tests/test_init_cmd.py +14 -1
  24. {papycli-0.6.0 → papycli-0.8.0}/tests/test_main.py +257 -1
  25. papycli-0.8.0/tests/test_request_filter.py +755 -0
  26. papycli-0.8.0/tests/test_response_checker.py +599 -0
  27. {papycli-0.6.0 → papycli-0.8.0}/tests/test_spec_loader.py +161 -1
  28. {papycli-0.6.0 → papycli-0.8.0}/uv.lock +1 -1
  29. papycli-0.6.0/tests/test_request_filter.py +0 -320
  30. {papycli-0.6.0 → papycli-0.8.0}/.github/workflows/release.yml +0 -0
  31. {papycli-0.6.0 → papycli-0.8.0}/.gitignore +0 -0
  32. {papycli-0.6.0 → papycli-0.8.0}/.python-version +0 -0
  33. {papycli-0.6.0 → papycli-0.8.0}/LICENSE +0 -0
  34. {papycli-0.6.0 → papycli-0.8.0}/design_doc.md +0 -0
  35. {papycli-0.6.0/examples → papycli-0.8.0/examples/petstore}/docker-compose.yml +0 -0
  36. {papycli-0.6.0/examples → papycli-0.8.0/examples/petstore}/petstore-oas3.json +0 -0
  37. {papycli-0.6.0 → papycli-0.8.0}/src/papycli/__init__.py +0 -0
  38. {papycli-0.6.0 → papycli-0.8.0}/src/papycli/checker.py +0 -0
  39. {papycli-0.6.0 → papycli-0.8.0}/src/papycli/i18n.py +0 -0
  40. {papycli-0.6.0 → papycli-0.8.0}/src/papycli/summary.py +0 -0
  41. {papycli-0.6.0 → papycli-0.8.0}/tests/conftest.py +0 -0
  42. {papycli-0.6.0 → papycli-0.8.0}/tests/test_checker.py +0 -0
  43. {papycli-0.6.0 → papycli-0.8.0}/tests/test_i18n.py +0 -0
  44. {papycli-0.6.0 → papycli-0.8.0}/tests/test_summary.py +0 -0
@@ -58,6 +58,8 @@ papycli/
58
58
  │ ├── test_spec_loader.py
59
59
  │ └── test_summary.py
60
60
  ├── examples/
61
+ │ ├── request_filter/ # リクエストフィルタープラグイン実装例
62
+ │ ├── response_filter/ # レスポンスフィルタープラグイン実装例
61
63
  │ ├── docker-compose.yml
62
64
  │ └── petstore-oas3.json
63
65
  ├── pyproject.toml
@@ -90,8 +92,8 @@ bash / zsh 向けの補完スクリプトを生成する。補完の候補はメ
90
92
  **`config.py`** — 設定管理
91
93
  `papycli.conf` の読み書きと、`PAPYCLI_CONF_DIR` 環境変数の解決を行う。ログファイルパスの取得・設定・削除も担当する。
92
94
 
93
- **`request_filter.py`** — リクエストフィルタープラグイン機構
94
- エントリポイントグループ `papycli.request_filters` に登録されたフィルター関数をプラグイン名の昇順で呼び出し、リクエスト送信前に URL・クエリパラメータ・ボディ・ヘッダーを変換できるようにする。`RequestContext` データクラスと `load_filters()` / `apply_filters()` 関数を提供する。
95
+ **`request_filter.py`** — リクエスト・レスポンスフィルタープラグイン機構
96
+ エントリポイントグループ `papycli.request_filters` に登録されたフィルター関数をプラグイン名の昇順で呼び出し、リクエスト送信前に URL・クエリパラメータ・ボディ・ヘッダーを変換できるようにする。同様に `papycli.response_filters` グループのフィルター関数を呼び出し、レスポンス受信後にステータスコード・理由フレーズ(reason)・ボディ・ヘッダーを参照・変更できるようにする。`RequestContext` / `ResponseContext` データクラスと `load_filters()` / `apply_filters()` / `load_response_filters()` / `apply_response_filters()` 関数を提供する。
95
97
 
96
98
  **`summary.py`** — サマリー表示
97
99
  登録済み API のエンドポイント一覧を整形して出力する。`--summary-csv` では CSV 形式で出力する。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: papycli
3
- Version: 0.6.0
3
+ Version: 0.8.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
@@ -39,6 +39,7 @@ Description-Content-Type: text/markdown
39
39
  - Automatically coerces `-p` values to the correct JSON type (integer, number, boolean) based on the API spec
40
40
  - Log requests and responses to a file with `papycli config log`
41
41
  - Extend request processing with request filter plugins (`papycli.request_filters` entry point)
42
+ - Inspect and transform responses with response filter plugins (`papycli.response_filters` entry point)
42
43
 
43
44
  ## Requirements
44
45
 
@@ -70,6 +71,17 @@ eval "$(papycli config completion-script bash)"
70
71
  eval "$(papycli config completion-script zsh)"
71
72
  ```
72
73
 
74
+ **Git Bash (Windows):**
75
+
76
+ Git Bash uses MSYS path conversion, which can mangle the output of `$()` command substitution.
77
+ Disable it before running the `eval` command:
78
+
79
+ ```bash
80
+ # Add to ~/.bashrc or ~/.bash_profile
81
+ export MSYS_NO_PATHCONV=1
82
+ eval "$(papycli config completion-script bash)"
83
+ ```
84
+
73
85
  Restart your shell or run `source ~/.bashrc` / `source ~/.zshrc` to apply.
74
86
 
75
87
  ---
@@ -218,6 +230,7 @@ papycli config completion-script <bash|zsh> Print a shell completion script
218
230
 
219
231
  # Inspection commands
220
232
  papycli spec [resource] Show the raw internal API spec (filter by resource path)
233
+ papycli spec --full [resource] Output the stored OpenAPI spec (filter by resource path if given)
221
234
  papycli summary [resource] List available endpoints (filter by resource prefix)
222
235
  Required params marked with *, array params with []
223
236
  papycli summary --csv Output endpoints in CSV format
@@ -230,7 +243,10 @@ Methods:
230
243
 
231
244
  Options:
232
245
  -H <header: value> Custom HTTP header (repeatable)
233
- -q <name> <value> Query parameter (repeatable)
246
+ -q <name> <value> Query parameter (repeatable).
247
+ You can also embed query parameters directly in the
248
+ resource path: "/pet/findByStatus?status=available"
249
+ Inline parameters are sent before any -q parameters.
234
250
  -p <name> <value> Body parameter (repeatable)
235
251
  - Values are coerced to the correct JSON type
236
252
  (integer, number, boolean) based on the API spec.
@@ -293,6 +309,44 @@ Install the package and filters are applied automatically on every request, sort
293
309
 
294
310
  ---
295
311
 
312
+ ## Response Filter Plugins
313
+
314
+ You can inspect and transform incoming responses by writing a response filter plugin.
315
+
316
+ A filter is a callable that receives a `ResponseContext` and returns a modified `ResponseContext`:
317
+
318
+ ```python
319
+ # my_plugin.py
320
+ from papycli.request_filter import ResponseContext
321
+
322
+ def response_filter(ctx: ResponseContext) -> ResponseContext:
323
+ if isinstance(ctx.body, dict):
324
+ ctx.body["_status"] = ctx.status_code
325
+ return ctx
326
+ ```
327
+
328
+ Register it in your package's `pyproject.toml`:
329
+
330
+ ```toml
331
+ [project.entry-points."papycli.response_filters"]
332
+ my-filter = "my_plugin:response_filter"
333
+ ```
334
+
335
+ Install the package and the filters are applied automatically after every response, sorted by plugin name.
336
+
337
+ `ResponseContext` fields:
338
+
339
+ | Field | Type | Description |
340
+ |-------|------|-------------|
341
+ | `method` | `str` | HTTP method used for the request (lowercase). |
342
+ | `url` | `str` | Full URL of the request. |
343
+ | `status_code` | `int` | HTTP response status code. |
344
+ | `reason` | `str` | HTTP response reason phrase (e.g. `"OK"`, `"Not Found"`). |
345
+ | `headers` | `dict[str, str]` | Response headers. |
346
+ | `body` | `dict \| list \| str \| int \| float \| bool \| None` | Parsed response body. Modify this field to replace the response body. |
347
+
348
+ ---
349
+
296
350
  ## Limitations
297
351
 
298
352
  - Request bodies are `application/json` only
@@ -12,6 +12,7 @@
12
12
  - API 仕様に基づいて `-p` の値を適切な JSON 型(integer / number / boolean)に自動変換
13
13
  - `papycli config log` によるリクエスト/レスポンスのファイルログ
14
14
  - リクエストフィルタープラグイン(`papycli.request_filters` エントリポイント)によるリクエスト処理の拡張
15
+ - レスポンスフィルタープラグイン(`papycli.response_filters` エントリポイント)によるレスポンスの参照・変換
15
16
 
16
17
  ## 必要環境
17
18
 
@@ -43,6 +44,17 @@ eval "$(papycli config completion-script bash)"
43
44
  eval "$(papycli config completion-script zsh)"
44
45
  ```
45
46
 
47
+ **Git Bash(Windows)の場合:**
48
+
49
+ Git Bash は MSYS のパス変換機能を持つため、`$()` コマンド置換の出力が変換されて `eval` が正しく動作しないことがあります。
50
+ `eval` を実行する前にパス変換を無効にしてください:
51
+
52
+ ```bash
53
+ # ~/.bashrc または ~/.bash_profile に追加
54
+ export MSYS_NO_PATHCONV=1
55
+ eval "$(papycli config completion-script bash)"
56
+ ```
57
+
46
58
  設定を反映するためにシェルを再起動するか `source ~/.bashrc` / `source ~/.zshrc` を実行してください。
47
59
 
48
60
  ---
@@ -191,6 +203,7 @@ papycli config completion-script <bash|zsh> シェル補完スクリプトを
191
203
 
192
204
  # 確認コマンド
193
205
  papycli spec [resource] 内部 API スペックを表示する(リソースパスでフィルタ可能)
206
+ papycli spec --full [resource] 内部に保存された OpenAPI spec を出力する(RESOURCE 指定で絞り込み可能)
194
207
  papycli summary [resource] 利用可能なエンドポイントを表示する(リソースでフィルタ可能)
195
208
  必須パラメータは * 付き、配列パラメータは [] 付きで表示
196
209
  papycli summary --csv CSV フォーマットでエンドポイントを表示する
@@ -204,6 +217,9 @@ papycli <method> <resource> [options]
204
217
  オプション:
205
218
  -H <header: value> カスタム HTTP ヘッダー(繰り返し可)
206
219
  -q <name> <value> クエリパラメータ(繰り返し可)
220
+ リソースパスにクエリ文字列を直接埋め込むことも可能:
221
+ '/pet/findByStatus?status=available'
222
+ インラインパラメータは -q より先に送信される
207
223
  -p <name> <value> ボディパラメータ(繰り返し可)
208
224
  - API 仕様に基づいて値を適切な JSON 型(integer / number /
209
225
  boolean)に自動変換する。文字列はそのまま送信される。
@@ -265,6 +281,44 @@ my-filter = "my_plugin:request_filter"
265
281
 
266
282
  ---
267
283
 
284
+ ## レスポンスフィルタープラグイン
285
+
286
+ レスポンスフィルタープラグインを作成することで、受信後のレスポンスを参照・変換できます。
287
+
288
+ フィルターは `ResponseContext` を受け取り、変更した `ResponseContext` を返す callable です:
289
+
290
+ ```python
291
+ # my_plugin.py
292
+ from papycli.request_filter import ResponseContext
293
+
294
+ def response_filter(ctx: ResponseContext) -> ResponseContext:
295
+ if isinstance(ctx.body, dict):
296
+ ctx.body["_status"] = ctx.status_code
297
+ return ctx
298
+ ```
299
+
300
+ パッケージの `pyproject.toml` にエントリポイントを登録します:
301
+
302
+ ```toml
303
+ [project.entry-points."papycli.response_filters"]
304
+ my-filter = "my_plugin:response_filter"
305
+ ```
306
+
307
+ パッケージをインストールすると、すべてのレスポンスに対してフィルターがプラグイン名の昇順で自動適用されます。
308
+
309
+ `ResponseContext` のフィールド:
310
+
311
+ | フィールド | 型 | 説明 |
312
+ |-----------|-----|------|
313
+ | `method` | `str` | リクエストに使用した HTTP メソッド(小文字)。 |
314
+ | `url` | `str` | リクエストに使用した完全 URL。 |
315
+ | `status_code` | `int` | HTTP レスポンスステータスコード。 |
316
+ | `reason` | `str` | HTTP レスポンスの理由フレーズ(例:`"OK"`、`"Not Found"`)。 |
317
+ | `headers` | `dict[str, str]` | レスポンスヘッダー。 |
318
+ | `body` | `dict \| list \| str \| int \| float \| bool \| None` | パース済みレスポンスボディ。このフィールドを変更するとレスポンスボディを差し替えられる。 |
319
+
320
+ ---
321
+
268
322
  ## 制限事項
269
323
 
270
324
  - リクエストボディは `application/json` のみ対応
@@ -12,6 +12,7 @@
12
12
  - Automatically coerces `-p` values to the correct JSON type (integer, number, boolean) based on the API spec
13
13
  - Log requests and responses to a file with `papycli config log`
14
14
  - Extend request processing with request filter plugins (`papycli.request_filters` entry point)
15
+ - Inspect and transform responses with response filter plugins (`papycli.response_filters` entry point)
15
16
 
16
17
  ## Requirements
17
18
 
@@ -43,6 +44,17 @@ eval "$(papycli config completion-script bash)"
43
44
  eval "$(papycli config completion-script zsh)"
44
45
  ```
45
46
 
47
+ **Git Bash (Windows):**
48
+
49
+ Git Bash uses MSYS path conversion, which can mangle the output of `$()` command substitution.
50
+ Disable it before running the `eval` command:
51
+
52
+ ```bash
53
+ # Add to ~/.bashrc or ~/.bash_profile
54
+ export MSYS_NO_PATHCONV=1
55
+ eval "$(papycli config completion-script bash)"
56
+ ```
57
+
46
58
  Restart your shell or run `source ~/.bashrc` / `source ~/.zshrc` to apply.
47
59
 
48
60
  ---
@@ -191,6 +203,7 @@ papycli config completion-script <bash|zsh> Print a shell completion script
191
203
 
192
204
  # Inspection commands
193
205
  papycli spec [resource] Show the raw internal API spec (filter by resource path)
206
+ papycli spec --full [resource] Output the stored OpenAPI spec (filter by resource path if given)
194
207
  papycli summary [resource] List available endpoints (filter by resource prefix)
195
208
  Required params marked with *, array params with []
196
209
  papycli summary --csv Output endpoints in CSV format
@@ -203,7 +216,10 @@ Methods:
203
216
 
204
217
  Options:
205
218
  -H <header: value> Custom HTTP header (repeatable)
206
- -q <name> <value> Query parameter (repeatable)
219
+ -q <name> <value> Query parameter (repeatable).
220
+ You can also embed query parameters directly in the
221
+ resource path: "/pet/findByStatus?status=available"
222
+ Inline parameters are sent before any -q parameters.
207
223
  -p <name> <value> Body parameter (repeatable)
208
224
  - Values are coerced to the correct JSON type
209
225
  (integer, number, boolean) based on the API spec.
@@ -266,6 +282,44 @@ Install the package and filters are applied automatically on every request, sort
266
282
 
267
283
  ---
268
284
 
285
+ ## Response Filter Plugins
286
+
287
+ You can inspect and transform incoming responses by writing a response filter plugin.
288
+
289
+ A filter is a callable that receives a `ResponseContext` and returns a modified `ResponseContext`:
290
+
291
+ ```python
292
+ # my_plugin.py
293
+ from papycli.request_filter import ResponseContext
294
+
295
+ def response_filter(ctx: ResponseContext) -> ResponseContext:
296
+ if isinstance(ctx.body, dict):
297
+ ctx.body["_status"] = ctx.status_code
298
+ return ctx
299
+ ```
300
+
301
+ Register it in your package's `pyproject.toml`:
302
+
303
+ ```toml
304
+ [project.entry-points."papycli.response_filters"]
305
+ my-filter = "my_plugin:response_filter"
306
+ ```
307
+
308
+ Install the package and the filters are applied automatically after every response, sorted by plugin name.
309
+
310
+ `ResponseContext` fields:
311
+
312
+ | Field | Type | Description |
313
+ |-------|------|-------------|
314
+ | `method` | `str` | HTTP method used for the request (lowercase). |
315
+ | `url` | `str` | Full URL of the request. |
316
+ | `status_code` | `int` | HTTP response status code. |
317
+ | `reason` | `str` | HTTP response reason phrase (e.g. `"OK"`, `"Not Found"`). |
318
+ | `headers` | `dict[str, str]` | Response headers. |
319
+ | `body` | `dict \| list \| str \| int \| float \| bool \| None` | Parsed response body. Modify this field to replace the response body. |
320
+
321
+ ---
322
+
269
323
  ## Limitations
270
324
 
271
325
  - Request bodies are `application/json` only
@@ -0,0 +1,82 @@
1
+ # papycli-debug-filter
2
+
3
+ A minimal example of a [papycli](https://github.com/tmonj1/papycli) request filter plugin.
4
+
5
+ This plugin prints the outgoing `RequestContext` to **stderr** before each request — useful as a starting point for writing your own filter.
6
+
7
+ > **Warning:** This plugin is intended for **debugging and development only**.
8
+ > It prints request headers without redaction, which may expose sensitive values such as
9
+ > Authorization tokens or API keys. Do NOT use this plugin in production environments.
10
+
11
+ ## What it does
12
+
13
+ Before every `papycli` API call, it writes to stderr:
14
+
15
+ ```
16
+ [papycli-debug-filter]
17
+ Method : GET
18
+ URL : http://localhost:8080/api/v3/store/inventory
19
+ Query : (none)
20
+ Body : (none)
21
+ Headers: (none)
22
+ ```
23
+
24
+ ## Installation
25
+
26
+ Install papycli and this plugin into the same Python environment:
27
+
28
+ ```bash
29
+ pip install papycli
30
+ pip install -e /path/to/papycli/examples/request_filter
31
+ ```
32
+
33
+ Once installed, the filter is picked up automatically — no additional configuration needed.
34
+
35
+ ## Usage
36
+
37
+ Run any papycli command. The filter output appears on **stderr**, while the API response is printed to **stdout** as usual:
38
+
39
+ ```bash
40
+ papycli get /store/inventory
41
+ ```
42
+
43
+ ```
44
+ # stderr:
45
+ [papycli-debug-filter]
46
+ Method : GET
47
+ URL : http://localhost:8080/api/v3/store/inventory
48
+ Query : (none)
49
+ Body : (none)
50
+ Headers: (none)
51
+
52
+ # stdout:
53
+ {
54
+ "approved": 1,
55
+ ...
56
+ }
57
+ ```
58
+
59
+ Because debug output goes to stderr, stdout remains machine-readable and piping still works:
60
+
61
+ ```bash
62
+ papycli get /store/inventory | jq '.approved'
63
+ ```
64
+
65
+ ## How it works
66
+
67
+ The plugin is registered via the `papycli.request_filters` entry point in `pyproject.toml`:
68
+
69
+ ```toml
70
+ [project.entry-points."papycli.request_filters"]
71
+ debug = "papycli_debug_filter:request_filter"
72
+ ```
73
+
74
+ papycli discovers all installed plugins in this group, sorts them by name, and calls each one before sending the request. The filter receives a `RequestContext` and must return a (possibly modified) `RequestContext`.
75
+
76
+ ## Writing your own filter
77
+
78
+ 1. Copy this directory as a starting point.
79
+ 2. Edit `src/papycli_debug_filter/__init__.py` — modify `ctx` fields as needed and return it.
80
+ 3. Change the package name in `pyproject.toml` and re-install with `pip install -e .`.
81
+
82
+ See the [papycli README](../../README.md#request-filter-plugins) for the full `RequestContext` field reference.
@@ -0,0 +1,18 @@
1
+ [project]
2
+ name = "papycli-debug-filter"
3
+ version = "0.1.0"
4
+ description = "A papycli request filter plugin that prints request context to stderr"
5
+ requires-python = ">=3.12"
6
+ dependencies = [
7
+ "papycli>=0.6.0",
8
+ ]
9
+
10
+ [project.entry-points."papycli.request_filters"]
11
+ debug = "papycli_debug_filter:request_filter"
12
+
13
+ [build-system]
14
+ requires = ["hatchling"]
15
+ build-backend = "hatchling.build"
16
+
17
+ [tool.hatch.build.targets.wheel]
18
+ packages = ["src/papycli_debug_filter"]
@@ -0,0 +1,46 @@
1
+ """papycli request filter plugin — debug filter.
2
+
3
+ Prints the outgoing RequestContext to stderr before each request.
4
+
5
+ WARNING: This plugin is intended for debugging and development only.
6
+ It prints request headers without redaction, which may expose
7
+ sensitive values such as Authorization tokens or API keys.
8
+ Do NOT use this plugin in production environments.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import sys
15
+
16
+ from papycli.request_filter import RequestContext
17
+
18
+
19
+ def _to_json(value: object) -> str:
20
+ """Serialize value to a JSON string, falling back to repr() on TypeError."""
21
+ try:
22
+ return json.dumps(value, ensure_ascii=False)
23
+ except TypeError:
24
+ return repr(value)
25
+
26
+
27
+ def request_filter(ctx: RequestContext) -> RequestContext:
28
+ """Print request context fields to stderr and return it unchanged."""
29
+ q_dict: dict[str, object] = {}
30
+ for k, v in ctx.query_params:
31
+ existing = q_dict.get(k)
32
+ if existing is None:
33
+ q_dict[k] = v
34
+ elif isinstance(existing, list):
35
+ existing.append(v)
36
+ else:
37
+ q_dict[k] = [existing, v]
38
+
39
+ print("[papycli-debug-filter]", file=sys.stderr)
40
+ print(f" Method : {ctx.method.upper()}", file=sys.stderr)
41
+ print(f" URL : {ctx.url}", file=sys.stderr)
42
+ print(f" Query : {_to_json(q_dict) if q_dict else '(none)'}", file=sys.stderr)
43
+ print(f" Body : {_to_json(ctx.body) if ctx.body is not None else '(none)'}", file=sys.stderr)
44
+ print(f" Headers: {_to_json(ctx.headers) if ctx.headers else '(none)'}", file=sys.stderr)
45
+
46
+ return ctx
@@ -0,0 +1,82 @@
1
+ # papycli-debug-response-filter
2
+
3
+ A minimal example of a [papycli](https://github.com/tmonj1/papycli) response filter plugin.
4
+
5
+ This plugin prints the received `ResponseContext` to **stderr** after each request — useful as a starting point for writing your own filter.
6
+
7
+ > **Warning:** This plugin is intended for **debugging and development only**.
8
+ > It prints response headers without redaction, which may expose sensitive values such as
9
+ > Set-Cookie headers or authentication tokens. Do NOT use this plugin in production environments.
10
+
11
+ ## What it does
12
+
13
+ After every `papycli` API call, it writes to stderr:
14
+
15
+ ```
16
+ [papycli-debug-response-filter]
17
+ Method : GET
18
+ URL : http://localhost:8080/api/v3/store/inventory
19
+ Status : 200 OK
20
+ Headers : {"Content-Type": "application/json", ...}
21
+ Body : {"approved": 1, ...}
22
+ ```
23
+
24
+ ## Installation
25
+
26
+ Install papycli and this plugin into the same Python environment:
27
+
28
+ ```bash
29
+ pip install papycli
30
+ pip install -e /path/to/papycli/examples/response_filter
31
+ ```
32
+
33
+ Once installed, the filter is picked up automatically — no additional configuration needed.
34
+
35
+ ## Usage
36
+
37
+ Run any papycli command. The filter output appears on **stderr**, while the API response is printed to **stdout** as usual:
38
+
39
+ ```bash
40
+ papycli get /store/inventory
41
+ ```
42
+
43
+ ```
44
+ # stderr:
45
+ [papycli-debug-response-filter]
46
+ Method : GET
47
+ URL : http://localhost:8080/api/v3/store/inventory
48
+ Status : 200 OK
49
+ Headers : {"Content-Type": "application/json; charset=utf-8"}
50
+ Body : {"approved": 1, "sold": 2}
51
+
52
+ # stdout:
53
+ {
54
+ "approved": 1,
55
+ "sold": 2
56
+ }
57
+ ```
58
+
59
+ Because debug output goes to stderr, stdout remains machine-readable and piping still works:
60
+
61
+ ```bash
62
+ papycli get /store/inventory | jq '.approved'
63
+ ```
64
+
65
+ ## How it works
66
+
67
+ The plugin is registered via the `papycli.response_filters` entry point in `pyproject.toml`:
68
+
69
+ ```toml
70
+ [project.entry-points."papycli.response_filters"]
71
+ debug = "papycli_debug_response_filter:response_filter"
72
+ ```
73
+
74
+ papycli discovers all installed plugins in this group, sorts them by name, and calls each one after receiving the response. The filter receives a `ResponseContext` and must return a (possibly modified) `ResponseContext`.
75
+
76
+ ## Writing your own filter
77
+
78
+ 1. Copy this directory as a starting point.
79
+ 2. Edit `src/papycli_debug_response_filter/__init__.py` — inspect or modify `ctx` fields as needed and return it.
80
+ 3. Change the package name in `pyproject.toml` and re-install with `pip install -e .`.
81
+
82
+ See the [papycli README](../../README.md#response-filter-plugins) for the full `ResponseContext` field reference.
@@ -0,0 +1,18 @@
1
+ [project]
2
+ name = "papycli-debug-response-filter"
3
+ version = "0.1.0"
4
+ description = "A papycli response filter plugin that prints response context to stderr"
5
+ requires-python = ">=3.12"
6
+ dependencies = [
7
+ "papycli>=0.7.0",
8
+ ]
9
+
10
+ [project.entry-points."papycli.response_filters"]
11
+ debug = "papycli_debug_response_filter:response_filter"
12
+
13
+ [build-system]
14
+ requires = ["hatchling"]
15
+ build-backend = "hatchling.build"
16
+
17
+ [tool.hatch.build.targets.wheel]
18
+ packages = ["src/papycli_debug_response_filter"]
@@ -0,0 +1,42 @@
1
+ """papycli response filter plugin — debug filter.
2
+
3
+ Prints the received ResponseContext to stderr after each request.
4
+
5
+ WARNING: This plugin is intended for debugging and development only.
6
+ It prints response headers without redaction, which may expose
7
+ sensitive values such as Set-Cookie or authentication tokens.
8
+ Do NOT use this plugin in production environments.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import sys
15
+
16
+ from papycli.request_filter import ResponseContext
17
+
18
+
19
+ def _to_json(value: object) -> str:
20
+ """Serialize value to a JSON string, falling back to repr() on TypeError."""
21
+ try:
22
+ return json.dumps(value, ensure_ascii=False)
23
+ except TypeError:
24
+ return repr(value)
25
+
26
+
27
+ def response_filter(ctx: ResponseContext) -> ResponseContext:
28
+ """Print response context fields to stderr and return it unchanged."""
29
+ print("[papycli-debug-response-filter]", file=sys.stderr)
30
+ print(f" Method : {ctx.method.upper()}", file=sys.stderr)
31
+ print(f" URL : {ctx.url}", file=sys.stderr)
32
+ print(f" Status : {ctx.status_code} {ctx.reason}", file=sys.stderr)
33
+ print(
34
+ f" Headers : {_to_json(ctx.headers) if ctx.headers else '(none)'}",
35
+ file=sys.stderr,
36
+ )
37
+ print(
38
+ f" Body : {_to_json(ctx.body) if ctx.body is not None else '(none)'}",
39
+ file=sys.stderr,
40
+ )
41
+
42
+ return ctx
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "papycli"
3
- version = "0.6.0"
3
+ version = "0.8.0"
4
4
  description = "A CLI tool to call REST APIs defined in OpenAPI 3.0 specs"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}