papycli 0.5.1__tar.gz → 0.5.3__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.5.3/.github/workflows/release.yml +151 -0
- {papycli-0.5.1 → papycli-0.5.3}/CLAUDE.md +9 -1
- {papycli-0.5.1 → papycli-0.5.3}/PKG-INFO +13 -9
- {papycli-0.5.1 → papycli-0.5.3}/README.ja.md +12 -6
- {papycli-0.5.1 → papycli-0.5.3}/README.md +12 -8
- {papycli-0.5.1 → papycli-0.5.3}/design_doc.md +14 -3
- {papycli-0.5.1 → papycli-0.5.3}/pyproject.toml +1 -1
- papycli-0.5.3/src/papycli/checker.py +126 -0
- {papycli-0.5.1 → papycli-0.5.3}/src/papycli/completion.py +23 -2
- {papycli-0.5.1 → papycli-0.5.3}/src/papycli/main.py +70 -2
- papycli-0.5.3/tests/test_checker.py +219 -0
- {papycli-0.5.1 → papycli-0.5.3}/tests/test_completion.py +63 -3
- {papycli-0.5.1 → papycli-0.5.3}/tests/test_main.py +183 -0
- {papycli-0.5.1 → papycli-0.5.3}/uv.lock +1 -1
- papycli-0.5.1/.github/workflows/release.yml +0 -77
- {papycli-0.5.1 → papycli-0.5.3}/.gitignore +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/.python-version +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/LICENSE +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/examples/docker-compose.yml +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/examples/petstore-oas3.json +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/src/papycli/__init__.py +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/src/papycli/api_call.py +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/src/papycli/config.py +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/src/papycli/i18n.py +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/src/papycli/init_cmd.py +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/src/papycli/spec_loader.py +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/src/papycli/summary.py +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/tests/conftest.py +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/tests/test_api_call.py +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/tests/test_config.py +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/tests/test_i18n.py +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/tests/test_init_cmd.py +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/tests/test_spec_loader.py +0 -0
- {papycli-0.5.1 → papycli-0.5.3}/tests/test_summary.py +0 -0
|
@@ -0,0 +1,151 @@
|
|
|
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
|
+
- Output valid Markdown only. No preamble, no explanation outside the Markdown.
|
|
114
|
+
prompt: |
|
|
115
|
+
Generate release notes for ${{ github.ref_name }} from the following commit messages:
|
|
116
|
+
|
|
117
|
+
${{ steps.commits.outputs.commits }}
|
|
118
|
+
|
|
119
|
+
- name: Write release notes to file
|
|
120
|
+
env:
|
|
121
|
+
NOTES: ${{ steps.ai.outputs.response }}
|
|
122
|
+
run: printf '%s' "$NOTES" > release_notes.md
|
|
123
|
+
|
|
124
|
+
- name: Create draft release
|
|
125
|
+
env:
|
|
126
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
127
|
+
REPO: ${{ github.repository }}
|
|
128
|
+
TAG: ${{ github.ref_name }}
|
|
129
|
+
run: |
|
|
130
|
+
gh release create "$TAG" \
|
|
131
|
+
--repo "$REPO" \
|
|
132
|
+
--title "$TAG" \
|
|
133
|
+
--notes-file release_notes.md \
|
|
134
|
+
--draft \
|
|
135
|
+
dist/*
|
|
136
|
+
|
|
137
|
+
pypi-publish:
|
|
138
|
+
name: Publish to PyPI
|
|
139
|
+
needs: github-release
|
|
140
|
+
runs-on: ubuntu-latest
|
|
141
|
+
environment: pypi
|
|
142
|
+
permissions:
|
|
143
|
+
id-token: write
|
|
144
|
+
steps:
|
|
145
|
+
- uses: actions/download-artifact@v4
|
|
146
|
+
with:
|
|
147
|
+
name: dist
|
|
148
|
+
path: dist/
|
|
149
|
+
|
|
150
|
+
- name: Publish to PyPI
|
|
151
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -39,6 +39,7 @@ 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,6 +47,7 @@ papycli/
|
|
|
46
47
|
│ └── summary.py # summary コマンド・CSV 出力
|
|
47
48
|
├── tests/
|
|
48
49
|
│ ├── test_api_call.py
|
|
50
|
+
│ ├── test_checker.py
|
|
49
51
|
│ ├── test_completion.py
|
|
50
52
|
│ ├── test_config.py
|
|
51
53
|
│ ├── test_i18n.py
|
|
@@ -66,7 +68,7 @@ papycli/
|
|
|
66
68
|
### 主要モジュール
|
|
67
69
|
|
|
68
70
|
**`main.py`** — CLI エントリポイント
|
|
69
|
-
引数をパースし、各コマンド(`config add`/`use`/`remove`/`list`/`completion-script`、`summary`、メソッド呼び出し)に処理を委譲する。シェル補完用の `_complete` 内部コマンドもここで定義する。
|
|
71
|
+
引数をパースし、各コマンド(`config add`/`use`/`remove`/`list`/`completion-script`、`spec`、`summary`、メソッド呼び出し)に処理を委譲する。シェル補完用の `_complete` 内部コマンドもここで定義する。
|
|
70
72
|
|
|
71
73
|
**`init_cmd.py`** — API 初期化(`config add` コマンドの実処理)
|
|
72
74
|
OpenAPI spec ファイルを受け取り、`$ref` を解決した上で papycli 内部の API 定義フォーマットに変換し、`$PAPYCLI_CONF_DIR/apis/<name>.json` に保存する。設定ファイル (`papycli.conf`) も更新する。
|
|
@@ -77,6 +79,9 @@ OpenAPI spec の JSON/YAML を読み込み、`$ref` を再帰的に解決して
|
|
|
77
79
|
**`api_call.py`** — HTTP リクエスト実行
|
|
78
80
|
メソッド・リソースパス・オプションを受け取り、`requests` ライブラリで HTTP リクエストを実行する。パステンプレート(`/pet/{petId}`)のマッチングと値の埋め込みも担当する。
|
|
79
81
|
|
|
82
|
+
**`checker.py`** — パラメータ検証
|
|
83
|
+
`--check` / `--check-strict` オプション用のリクエスト前バリデーションロジック。必須パラメータの存在確認、型チェック(integer / boolean)、enum 値の検証を行い、警告メッセージのリストを返す。
|
|
84
|
+
|
|
80
85
|
**`completion.py`** — シェル補完
|
|
81
86
|
bash / zsh 向けの補完スクリプトを生成する。補完の候補はメソッド、リソースパス、パラメータ名、enum 値の順にコンテキストに応じて提供する。
|
|
82
87
|
|
|
@@ -155,6 +160,7 @@ papycli config use <api-name>
|
|
|
155
160
|
papycli config remove <api-name>
|
|
156
161
|
papycli config list
|
|
157
162
|
papycli config completion-script <bash|zsh>
|
|
163
|
+
papycli spec [resource]
|
|
158
164
|
papycli summary [resource] [--csv]
|
|
159
165
|
papycli --version
|
|
160
166
|
papycli --help / -h
|
|
@@ -171,6 +177,8 @@ papycli --help / -h
|
|
|
171
177
|
- `-p <name.subname> <value>` — ドット記法で 1 レベルのネストオブジェクトを構築する
|
|
172
178
|
- `-d <json>` — 生の JSON 文字列。`-p` オプションを上書きする
|
|
173
179
|
- `-H <header: value>` — カスタム HTTP ヘッダー
|
|
180
|
+
- `--check` — 送信前にパラメータを検証する(警告を stderr に出力、リクエストは送信)
|
|
181
|
+
- `--check-strict` — 送信前にパラメータを検証する(警告を stderr に出力、問題があればリクエスト中止・exit 1)
|
|
174
182
|
|
|
175
183
|
### パステンプレートのマッチング
|
|
176
184
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: papycli
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
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,8 @@ 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`
|
|
37
39
|
|
|
38
40
|
## Requirements
|
|
39
41
|
|
|
@@ -41,8 +43,6 @@ Description-Content-Type: text/markdown
|
|
|
41
43
|
|------|-------|
|
|
42
44
|
| Python | 3.12 or later |
|
|
43
45
|
|
|
44
|
-
No external tools (e.g. `jq`) required. Works with Python and pip alone.
|
|
45
|
-
|
|
46
46
|
---
|
|
47
47
|
|
|
48
48
|
## Installation
|
|
@@ -126,13 +126,13 @@ Once shell completion is enabled, tab completion is available:
|
|
|
126
126
|
|
|
127
127
|
```
|
|
128
128
|
$ papycli <TAB>
|
|
129
|
-
get post put patch delete config summary
|
|
129
|
+
get post put patch delete config spec summary
|
|
130
130
|
|
|
131
131
|
$ papycli get <TAB>
|
|
132
132
|
/pet/findByStatus /pet/{petId} /store/inventory ...
|
|
133
133
|
|
|
134
134
|
$ papycli get /pet/findByStatus <TAB>
|
|
135
|
-
-q -p -H -d --summary --verbose
|
|
135
|
+
-q -p -H -d --summary --verbose --check --check-strict
|
|
136
136
|
|
|
137
137
|
$ papycli get /pet/findByStatus -q <TAB>
|
|
138
138
|
status
|
|
@@ -210,10 +210,13 @@ papycli config use <api-name> Switch the active API
|
|
|
210
210
|
papycli config list List registered APIs and current configuration
|
|
211
211
|
papycli config completion-script <bash|zsh> Print a shell completion script
|
|
212
212
|
|
|
213
|
-
#
|
|
213
|
+
# Inspection commands
|
|
214
|
+
papycli spec [resource] Show the raw internal API spec (filter by resource path)
|
|
214
215
|
papycli summary [resource] List available endpoints (filter by resource prefix)
|
|
215
216
|
Required params marked with *, array params with []
|
|
216
217
|
papycli summary --csv Output endpoints in CSV format
|
|
218
|
+
|
|
219
|
+
# API call commands
|
|
217
220
|
papycli <method> <resource> [options]
|
|
218
221
|
|
|
219
222
|
Methods:
|
|
@@ -230,6 +233,9 @@ Options:
|
|
|
230
233
|
→ {"category":{"id":"1","name":"Dogs"}}
|
|
231
234
|
-d <json> Raw JSON body (overrides -p)
|
|
232
235
|
--summary Show endpoint info without sending a request
|
|
236
|
+
--check Validate params before sending (warn on stderr, request is still sent)
|
|
237
|
+
--check-strict Validate params before sending (warn on stderr, abort with exit 1 on failure)
|
|
238
|
+
--verbose / -v Show HTTP status line
|
|
233
239
|
--version Show version
|
|
234
240
|
--help / -h Show help
|
|
235
241
|
|
|
@@ -257,6 +263,4 @@ Environment variables:
|
|
|
257
263
|
git clone https://github.com/tmonj1/papycli.git
|
|
258
264
|
cd papycli
|
|
259
265
|
pip install -e ".[dev]"
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
See [CLAUDE.md](CLAUDE.md) for details.
|
|
266
|
+
```
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
- OpenAPI 3.0 仕様から CLI を自動生成
|
|
8
8
|
- シェル補完(bash / zsh)対応
|
|
9
9
|
- 複数 API の登録・切り替え
|
|
10
|
+
- `papycli spec` による API スペックの確認
|
|
11
|
+
- `--check` / `--check-strict` によるリクエスト前のパラメータ検証
|
|
10
12
|
|
|
11
13
|
## 必要環境
|
|
12
14
|
|
|
@@ -97,13 +99,13 @@ papycli delete /pet/1
|
|
|
97
99
|
|
|
98
100
|
```
|
|
99
101
|
$ papycli <TAB>
|
|
100
|
-
get post put patch delete config summary
|
|
102
|
+
get post put patch delete config spec summary
|
|
101
103
|
|
|
102
104
|
$ papycli get <TAB>
|
|
103
105
|
/pet/findByStatus /pet/{petId} /store/inventory ...
|
|
104
106
|
|
|
105
107
|
$ papycli get /pet/findByStatus <TAB>
|
|
106
|
-
-q -p -H -d --summary --verbose
|
|
108
|
+
-q -p -H -d --summary --verbose --check --check-strict
|
|
107
109
|
|
|
108
110
|
$ papycli get /pet/findByStatus -q <TAB>
|
|
109
111
|
status
|
|
@@ -181,10 +183,13 @@ papycli config use <api-name> アクティブな API を切り替
|
|
|
181
183
|
papycli config list 登録済み API と現在の設定を一覧表示する
|
|
182
184
|
papycli config completion-script <bash|zsh> シェル補完スクリプトを出力する
|
|
183
185
|
|
|
184
|
-
#
|
|
186
|
+
# 確認コマンド
|
|
187
|
+
papycli spec [resource] 内部 API スペックを表示する(リソースパスでフィルタ可能)
|
|
185
188
|
papycli summary [resource] 利用可能なエンドポイントを表示する(リソースでフィルタ可能)
|
|
186
189
|
必須パラメータは * 付き、配列パラメータは [] 付きで表示
|
|
187
190
|
papycli summary --csv CSV フォーマットでエンドポイントを表示する
|
|
191
|
+
|
|
192
|
+
# API 呼び出しコマンド
|
|
188
193
|
papycli <method> <resource> [options]
|
|
189
194
|
|
|
190
195
|
メソッド:
|
|
@@ -201,6 +206,9 @@ papycli <method> <resource> [options]
|
|
|
201
206
|
→ {"category":{"id":"1","name":"Dogs"}}
|
|
202
207
|
-d <json> 生の JSON ボディ(-p を上書きする)
|
|
203
208
|
--summary リクエストを送らずにエンドポイント情報を表示する
|
|
209
|
+
--check 送信前にパラメータを検証する(警告を stderr に出力、リクエストは送信)
|
|
210
|
+
--check-strict 送信前にパラメータを検証する(警告を stderr に出力、問題があればリクエスト中止・exit 1)
|
|
211
|
+
--verbose / -v HTTP ステータス行を表示する
|
|
204
212
|
--version バージョンを表示する
|
|
205
213
|
--help / -h 使い方を表示する
|
|
206
214
|
|
|
@@ -228,6 +236,4 @@ papycli <method> <resource> [options]
|
|
|
228
236
|
git clone https://github.com/<your-org>/papycli.git
|
|
229
237
|
cd papycli
|
|
230
238
|
pip install -e ".[dev]"
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
詳細は [CLAUDE.md](CLAUDE.md) を参照してください。
|
|
239
|
+
```
|
|
@@ -7,6 +7,8 @@
|
|
|
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`
|
|
10
12
|
|
|
11
13
|
## Requirements
|
|
12
14
|
|
|
@@ -14,8 +16,6 @@
|
|
|
14
16
|
|------|-------|
|
|
15
17
|
| Python | 3.12 or later |
|
|
16
18
|
|
|
17
|
-
No external tools (e.g. `jq`) required. Works with Python and pip alone.
|
|
18
|
-
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
## Installation
|
|
@@ -99,13 +99,13 @@ Once shell completion is enabled, tab completion is available:
|
|
|
99
99
|
|
|
100
100
|
```
|
|
101
101
|
$ papycli <TAB>
|
|
102
|
-
get post put patch delete config summary
|
|
102
|
+
get post put patch delete config spec summary
|
|
103
103
|
|
|
104
104
|
$ papycli get <TAB>
|
|
105
105
|
/pet/findByStatus /pet/{petId} /store/inventory ...
|
|
106
106
|
|
|
107
107
|
$ papycli get /pet/findByStatus <TAB>
|
|
108
|
-
-q -p -H -d --summary --verbose
|
|
108
|
+
-q -p -H -d --summary --verbose --check --check-strict
|
|
109
109
|
|
|
110
110
|
$ papycli get /pet/findByStatus -q <TAB>
|
|
111
111
|
status
|
|
@@ -183,10 +183,13 @@ papycli config use <api-name> Switch the active API
|
|
|
183
183
|
papycli config list List registered APIs and current configuration
|
|
184
184
|
papycli config completion-script <bash|zsh> Print a shell completion script
|
|
185
185
|
|
|
186
|
-
#
|
|
186
|
+
# Inspection commands
|
|
187
|
+
papycli spec [resource] Show the raw internal API spec (filter by resource path)
|
|
187
188
|
papycli summary [resource] List available endpoints (filter by resource prefix)
|
|
188
189
|
Required params marked with *, array params with []
|
|
189
190
|
papycli summary --csv Output endpoints in CSV format
|
|
191
|
+
|
|
192
|
+
# API call commands
|
|
190
193
|
papycli <method> <resource> [options]
|
|
191
194
|
|
|
192
195
|
Methods:
|
|
@@ -203,6 +206,9 @@ Options:
|
|
|
203
206
|
→ {"category":{"id":"1","name":"Dogs"}}
|
|
204
207
|
-d <json> Raw JSON body (overrides -p)
|
|
205
208
|
--summary Show endpoint info without sending a request
|
|
209
|
+
--check Validate params before sending (warn on stderr, request is still sent)
|
|
210
|
+
--check-strict Validate params before sending (warn on stderr, abort with exit 1 on failure)
|
|
211
|
+
--verbose / -v Show HTTP status line
|
|
206
212
|
--version Show version
|
|
207
213
|
--help / -h Show help
|
|
208
214
|
|
|
@@ -230,6 +236,4 @@ Environment variables:
|
|
|
230
236
|
git clone https://github.com/tmonj1/papycli.git
|
|
231
237
|
cd papycli
|
|
232
238
|
pip install -e ".[dev]"
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
See [CLAUDE.md](CLAUDE.md) for details.
|
|
239
|
+
```
|
|
@@ -37,11 +37,13 @@ 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 補完スクリプト生成
|
|
42
43
|
│ └── i18n.py # 日英ヘルプテキストの切り替えユーティリティ
|
|
43
44
|
├── tests/
|
|
44
45
|
│ ├── test_api_call.py
|
|
46
|
+
│ ├── test_checker.py
|
|
45
47
|
│ ├── test_completion.py
|
|
46
48
|
│ ├── test_config.py
|
|
47
49
|
│ ├── test_i18n.py
|
|
@@ -174,12 +176,19 @@ papycli/
|
|
|
174
176
|
- `requests` による HTTP 実行
|
|
175
177
|
- レスポンスの出力(JSON は整形表示、それ以外はそのまま)
|
|
176
178
|
- `main.py` に `papycli <method> <resource>` コマンド追加
|
|
177
|
-
-
|
|
179
|
+
- `main.py` に `papycli spec [resource]` コマンド追加(内部 API 定義の JSON 表示)
|
|
180
|
+
- `checker.py`
|
|
181
|
+
- `--check` / `--check-strict` オプション用のパラメータ検証ロジック
|
|
182
|
+
- 必須パラメータの存在確認、型チェック(integer / boolean)、enum 値の検証
|
|
183
|
+
- `--check`: 問題があっても警告を出力してリクエストを送信する
|
|
184
|
+
- `--check-strict`: 問題があれば警告を出力してリクエストを中止し exit 1
|
|
185
|
+
- テスト: `test_api_call.py`、`test_checker.py`
|
|
178
186
|
- パステンプレートマッチングの各ケース
|
|
179
187
|
- `-p` による JSON 構築(配列、ネスト)
|
|
180
188
|
- `responses` ライブラリで HTTP をモックしたエンドツーエンドテスト
|
|
189
|
+
- `check_request()` の各検証ケース(必須・型・enum・raw body)
|
|
181
190
|
|
|
182
|
-
**完了条件**: `papycli get /store/inventory`、`papycli post /pet -p name "My Dog" -p status available` 等が Petstore
|
|
191
|
+
**完了条件**: `papycli get /store/inventory`、`papycli post /pet -p name "My Dog" -p status available` 等が Petstore サーバーに対して動作する。`papycli spec` で内部 API 定義が表示される。`--check` / `--check-strict` でパラメータ検証が動作する。テストがパスする。
|
|
183
192
|
|
|
184
193
|
---
|
|
185
194
|
|
|
@@ -217,9 +226,11 @@ papycli/
|
|
|
217
226
|
4. パラメータ名補完: `-q <TAB>` → クエリパラメータ名の候補
|
|
218
227
|
5. enum 値補完: `-q status <TAB>` → `available pending sold` 等
|
|
219
228
|
- `main.py` に `--completion-script <bash|zsh>` コマンド追加
|
|
229
|
+
- 補完候補の強化(`spec` コマンド、`summary --csv`、`--check`/`--check-strict` オプションを追加)
|
|
230
|
+
- Windows 対応: `_complete` の出力を binary stream(LF のみ)に変更
|
|
220
231
|
- 手動テスト手順をドキュメント化
|
|
221
232
|
|
|
222
|
-
**完了条件**: `eval "$(papycli config completion-script zsh)"` 後にタブ補完が動作する。メソッド・リソース・クエリパラメータ名・ボディパラメータ名・enum
|
|
233
|
+
**完了条件**: `eval "$(papycli config completion-script zsh)"` 後にタブ補完が動作する。メソッド・リソース・クエリパラメータ名・ボディパラメータ名・enum 値が補完候補として表示される。`spec`・`summary`・`--check`・`--check-strict` も補完候補に含まれる。
|
|
223
234
|
|
|
224
235
|
---
|
|
225
236
|
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""リクエスト前のパラメータ検証ロジック。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _check_value(param: dict[str, Any], raw_value: Any) -> list[str]:
|
|
11
|
+
"""単一パラメータ値に対して型・enum チェックを行い、警告メッセージのリストを返す。
|
|
12
|
+
|
|
13
|
+
raw_value は文字列(-p/-q 経由)または JSON から解析された任意の値。
|
|
14
|
+
"""
|
|
15
|
+
warnings: list[str] = []
|
|
16
|
+
name = param["name"]
|
|
17
|
+
ptype = param.get("type", "")
|
|
18
|
+
enum = param.get("enum")
|
|
19
|
+
|
|
20
|
+
if ptype == "integer":
|
|
21
|
+
# Note: int(str(x)) rejects float strings like "1.5" (even if mathematically integer-like).
|
|
22
|
+
# For -d JSON where raw_value may already be a float (e.g. 1.5), this is intentional:
|
|
23
|
+
# the spec declares the type as integer, so floating-point values trigger a warning.
|
|
24
|
+
try:
|
|
25
|
+
int(str(raw_value))
|
|
26
|
+
except ValueError:
|
|
27
|
+
warnings.append(
|
|
28
|
+
f"Warning: parameter '{name}' expects integer, got '{raw_value}'"
|
|
29
|
+
)
|
|
30
|
+
elif ptype == "boolean":
|
|
31
|
+
if str(raw_value).lower() not in {"true", "false", "1", "0"}:
|
|
32
|
+
warnings.append(
|
|
33
|
+
f"Warning: parameter '{name}' expects boolean (true/false), got '{raw_value}'"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if enum is not None and str(raw_value) not in [str(e) for e in enum]:
|
|
37
|
+
warnings.append(
|
|
38
|
+
f"Warning: parameter '{name}' value '{raw_value}' is not in enum {enum}"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return warnings
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def check_request(
|
|
45
|
+
apidef: dict[str, Any],
|
|
46
|
+
method: str,
|
|
47
|
+
resource: str,
|
|
48
|
+
query_params: Sequence[tuple[str, str]],
|
|
49
|
+
body_params: Sequence[tuple[str, str]],
|
|
50
|
+
raw_body: str | None,
|
|
51
|
+
) -> list[str]:
|
|
52
|
+
"""リクエスト前に必須パラメータ・型・enum を検証し、警告メッセージのリストを返す。
|
|
53
|
+
|
|
54
|
+
問題がなければ空リストを返す。
|
|
55
|
+
"""
|
|
56
|
+
from papycli.api_call import match_path_template # lazy import to avoid circular dependency
|
|
57
|
+
|
|
58
|
+
warnings: list[str] = []
|
|
59
|
+
|
|
60
|
+
match = match_path_template(resource, list(apidef.keys()))
|
|
61
|
+
if match is None:
|
|
62
|
+
return warnings # パス不明は api_call 側でエラーになるのでここでは無視
|
|
63
|
+
|
|
64
|
+
template, _ = match
|
|
65
|
+
ops: list[dict[str, Any]] = apidef[template]
|
|
66
|
+
op = next((o for o in ops if o["method"] == method), None)
|
|
67
|
+
if op is None:
|
|
68
|
+
return warnings
|
|
69
|
+
|
|
70
|
+
# ---- クエリパラメータのチェック ----
|
|
71
|
+
q_params: list[dict[str, Any]] = op.get("query_parameters", [])
|
|
72
|
+
provided_query = {name for name, _ in query_params}
|
|
73
|
+
|
|
74
|
+
for p in q_params:
|
|
75
|
+
if p.get("required") and p["name"] not in provided_query:
|
|
76
|
+
warnings.append(f"Warning: required query parameter '{p['name']}' is missing")
|
|
77
|
+
|
|
78
|
+
for name, value in query_params:
|
|
79
|
+
param_def = next((p for p in q_params if p["name"] == name), None)
|
|
80
|
+
if param_def:
|
|
81
|
+
warnings.extend(_check_value(param_def, value))
|
|
82
|
+
|
|
83
|
+
# ---- ボディパラメータのチェック ----
|
|
84
|
+
b_params: list[dict[str, Any]] = op.get("post_parameters", [])
|
|
85
|
+
|
|
86
|
+
if raw_body is not None:
|
|
87
|
+
# -d JSON の場合: JSON をパースして各フィールドをチェック
|
|
88
|
+
can_check_body = True
|
|
89
|
+
try:
|
|
90
|
+
parsed = json.loads(raw_body)
|
|
91
|
+
except json.JSONDecodeError:
|
|
92
|
+
warnings.append("Warning: failed to parse raw body as JSON")
|
|
93
|
+
can_check_body = False
|
|
94
|
+
|
|
95
|
+
if can_check_body and not isinstance(parsed, dict):
|
|
96
|
+
warnings.append(
|
|
97
|
+
"Warning: raw body is not a JSON object; parameter checks skipped"
|
|
98
|
+
)
|
|
99
|
+
can_check_body = False
|
|
100
|
+
|
|
101
|
+
if not can_check_body:
|
|
102
|
+
return warnings
|
|
103
|
+
|
|
104
|
+
body_dict: dict[str, Any] = parsed
|
|
105
|
+
provided_body = set(body_dict.keys())
|
|
106
|
+
for p in b_params:
|
|
107
|
+
if p.get("required") and p["name"] not in provided_body:
|
|
108
|
+
warnings.append(f"Warning: required body parameter '{p['name']}' is missing")
|
|
109
|
+
|
|
110
|
+
for p in b_params:
|
|
111
|
+
if p["name"] in body_dict:
|
|
112
|
+
warnings.extend(_check_value(p, body_dict[p["name"]]))
|
|
113
|
+
else:
|
|
114
|
+
# -p ペアの場合
|
|
115
|
+
provided_body = {name for name, _ in body_params}
|
|
116
|
+
|
|
117
|
+
for p in b_params:
|
|
118
|
+
if p.get("required") and p["name"] not in provided_body:
|
|
119
|
+
warnings.append(f"Warning: required body parameter '{p['name']}' is missing")
|
|
120
|
+
|
|
121
|
+
for name, value in body_params:
|
|
122
|
+
param_def = next((p for p in b_params if p["name"] == name), None)
|
|
123
|
+
if param_def:
|
|
124
|
+
warnings.extend(_check_value(param_def, value))
|
|
125
|
+
|
|
126
|
+
return warnings
|
|
@@ -12,7 +12,7 @@ from typing import Any
|
|
|
12
12
|
|
|
13
13
|
METHODS = ["get", "post", "put", "patch", "delete"]
|
|
14
14
|
CONFIG_SUBCOMMANDS = ["add", "completion-script", "list", "remove", "use"]
|
|
15
|
-
TOP_LEVEL_COMMANDS = METHODS + ["config", "summary"]
|
|
15
|
+
TOP_LEVEL_COMMANDS = METHODS + ["config", "spec", "summary"]
|
|
16
16
|
|
|
17
17
|
# ---------------------------------------------------------------------------
|
|
18
18
|
# シェルスクリプトテンプレート
|
|
@@ -140,6 +140,25 @@ def completions_for_context(
|
|
|
140
140
|
return [c for c in CONFIG_SUBCOMMANDS if c.startswith(incomplete)]
|
|
141
141
|
return []
|
|
142
142
|
|
|
143
|
+
# summary コマンドの補完
|
|
144
|
+
if words[1] == "summary":
|
|
145
|
+
if current == 2:
|
|
146
|
+
candidates: list[str] = []
|
|
147
|
+
if apidef is not None:
|
|
148
|
+
candidates = [p for p in sorted(apidef.keys()) if p.startswith(incomplete)]
|
|
149
|
+
if "--csv".startswith(incomplete):
|
|
150
|
+
candidates.append("--csv")
|
|
151
|
+
return candidates
|
|
152
|
+
if current == 3 and (len(words) <= 2 or words[2] != "--csv"):
|
|
153
|
+
return ["--csv"] if "--csv".startswith(incomplete) else []
|
|
154
|
+
return []
|
|
155
|
+
|
|
156
|
+
# spec コマンドの補完
|
|
157
|
+
if words[1] == "spec":
|
|
158
|
+
if current == 2 and apidef is not None:
|
|
159
|
+
return [p for p in sorted(apidef.keys()) if p.startswith(incomplete)]
|
|
160
|
+
return []
|
|
161
|
+
|
|
143
162
|
# words[1] が HTTP メソッドでない場合は補完なし
|
|
144
163
|
if words[1] not in METHODS:
|
|
145
164
|
return []
|
|
@@ -177,7 +196,9 @@ def completions_for_context(
|
|
|
177
196
|
|
|
178
197
|
# オプション名(エンドポイントのパラメータ有無に応じてフィルタリング)
|
|
179
198
|
op = _find_op(apidef, method, resource)
|
|
180
|
-
opts: list[str] = [
|
|
199
|
+
opts: list[str] = [
|
|
200
|
+
"-q", "-p", "-d", "-H", "--summary", "-v", "--verbose", "--check", "--check-strict",
|
|
201
|
+
]
|
|
181
202
|
if op is not None:
|
|
182
203
|
if not op.get("query_parameters"):
|
|
183
204
|
opts = [o for o in opts if o != "-q"]
|