fastapi-docs-plus 1.0.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.
- fastapi_docs_plus-1.0.0/.github/workflows/publish.yml +89 -0
- fastapi_docs_plus-1.0.0/.gitignore +7 -0
- fastapi_docs_plus-1.0.0/LICENSE +21 -0
- fastapi_docs_plus-1.0.0/PKG-INFO +271 -0
- fastapi_docs_plus-1.0.0/README.md +243 -0
- fastapi_docs_plus-1.0.0/__init__.py +9 -0
- fastapi_docs_plus-1.0.0/ai_fill.py +239 -0
- fastapi_docs_plus-1.0.0/config.py +123 -0
- fastapi_docs_plus-1.0.0/docs_plus.py +171 -0
- fastapi_docs_plus-1.0.0/fastapi_docs_plus.egg-info/PKG-INFO +271 -0
- fastapi_docs_plus-1.0.0/fastapi_docs_plus.egg-info/SOURCES.txt +35 -0
- fastapi_docs_plus-1.0.0/fastapi_docs_plus.egg-info/dependency_links.txt +1 -0
- fastapi_docs_plus-1.0.0/fastapi_docs_plus.egg-info/requires.txt +4 -0
- fastapi_docs_plus-1.0.0/fastapi_docs_plus.egg-info/scm_file_list.json +21 -0
- fastapi_docs_plus-1.0.0/fastapi_docs_plus.egg-info/scm_version.json +8 -0
- fastapi_docs_plus-1.0.0/fastapi_docs_plus.egg-info/top_level.txt +1 -0
- fastapi_docs_plus-1.0.0/i18n.py +61 -0
- fastapi_docs_plus-1.0.0/pre_request.py +145 -0
- fastapi_docs_plus-1.0.0/pyproject.toml +53 -0
- fastapi_docs_plus-1.0.0/schema_utils.py +174 -0
- fastapi_docs_plus-1.0.0/setup.cfg +4 -0
- fastapi_docs_plus-1.0.0/static/adapter.js +197 -0
- fastapi_docs_plus-1.0.0/static/demo.png +0 -0
- fastapi_docs_plus-1.0.0/static/docs-plus.css +122 -0
- fastapi_docs_plus-1.0.0/static/docs-plus.js +628 -0
- fastapi_docs_plus-1.0.0/static/docs.html +19 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
name: Upload Python Package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
release-build:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
with:
|
|
17
|
+
fetch-depth: 0
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.x"
|
|
22
|
+
|
|
23
|
+
- name: Build release distributions
|
|
24
|
+
run: |
|
|
25
|
+
python -m pip install --upgrade build
|
|
26
|
+
python -m build
|
|
27
|
+
|
|
28
|
+
- name: Upload distributions
|
|
29
|
+
uses: actions/upload-artifact@v4
|
|
30
|
+
with:
|
|
31
|
+
name: release-dists
|
|
32
|
+
path: dist/
|
|
33
|
+
|
|
34
|
+
# Pre-release → TestPyPI. Tick "Set as a pre-release" when creating the
|
|
35
|
+
# release to exercise the pipeline without touching the real index.
|
|
36
|
+
testpypi-publish:
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
needs:
|
|
39
|
+
- release-build
|
|
40
|
+
if: ${{ github.event.release.prerelease }}
|
|
41
|
+
|
|
42
|
+
# Trusted Publishing (OIDC): no API token stored. Configure the publisher
|
|
43
|
+
# on TestPyPI (Project -> Publishing) with workflow "publish.yml" and the
|
|
44
|
+
# "testpypi" environment below before the first pre-release.
|
|
45
|
+
permissions:
|
|
46
|
+
id-token: write
|
|
47
|
+
|
|
48
|
+
environment:
|
|
49
|
+
name: testpypi
|
|
50
|
+
url: https://test.pypi.org/p/fastapi-docs-plus
|
|
51
|
+
|
|
52
|
+
steps:
|
|
53
|
+
- name: Retrieve release distributions
|
|
54
|
+
uses: actions/download-artifact@v4
|
|
55
|
+
with:
|
|
56
|
+
name: release-dists
|
|
57
|
+
path: dist/
|
|
58
|
+
|
|
59
|
+
- name: Publish release distributions to TestPyPI
|
|
60
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
61
|
+
with:
|
|
62
|
+
repository-url: https://test.pypi.org/legacy/
|
|
63
|
+
|
|
64
|
+
# Full release → PyPI.
|
|
65
|
+
pypi-publish:
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
needs:
|
|
68
|
+
- release-build
|
|
69
|
+
if: ${{ !github.event.release.prerelease }}
|
|
70
|
+
|
|
71
|
+
# Trusted Publishing (OIDC): no API token stored. Configure the publisher
|
|
72
|
+
# on PyPI (Project -> Publishing) with workflow "publish.yml" and the
|
|
73
|
+
# "pypi" environment below before the first release.
|
|
74
|
+
permissions:
|
|
75
|
+
id-token: write
|
|
76
|
+
|
|
77
|
+
environment:
|
|
78
|
+
name: pypi
|
|
79
|
+
url: https://pypi.org/p/fastapi-docs-plus
|
|
80
|
+
|
|
81
|
+
steps:
|
|
82
|
+
- name: Retrieve release distributions
|
|
83
|
+
uses: actions/download-artifact@v4
|
|
84
|
+
with:
|
|
85
|
+
name: release-dists
|
|
86
|
+
path: dist/
|
|
87
|
+
|
|
88
|
+
- name: Publish release distributions to PyPI
|
|
89
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 left666
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi-docs-plus
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Enhanced interactive API docs for FastAPI: AI parameter filling + Python-side pre-request hooks
|
|
5
|
+
Author: left666
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/left666/fastapi-docs-plus
|
|
8
|
+
Project-URL: Repository, https://github.com/left666/fastapi-docs-plus
|
|
9
|
+
Keywords: fastapi,swagger,openapi,docs,ai,llm
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Framework :: FastAPI
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Topic :: Documentation
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: fastapi>=0.100.0
|
|
24
|
+
Requires-Dist: pydantic>=2.0.0
|
|
25
|
+
Requires-Dist: openai>=1.0.0
|
|
26
|
+
Requires-Dist: jsonschema>=4.0.0
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# fastapi-docs-plus
|
|
30
|
+
|
|
31
|
+
[](https://pypi.org/project/fastapi-docs-plus/)
|
|
32
|
+
[](https://pypi.org/project/fastapi-docs-plus/)
|
|
33
|
+
[](https://github.com/left666/fastapi-docs-plus/blob/main/LICENSE)
|
|
34
|
+
|
|
35
|
+
Enhanced interactive API docs for FastAPI that add two capabilities essential for integration and debugging:
|
|
36
|
+
|
|
37
|
+
- **AI parameter filling** — one click per operation to call an LLM and generate realistic request data that respects the JSON Schema, then write it directly into the Try-it-out form fields.
|
|
38
|
+
- **Python-side pre-request hooks** — server-side Python functions that intercept every Swagger UI request to inject authentication, signatures, or dynamic headers (JWT tokens, tenant IDs, CSRF tokens, etc.) using your project's own code.
|
|
39
|
+
|
|
40
|
+
It replaces FastAPI's built-in `/docs` while keeping Swagger UI as the rendering engine — no fork, no custom frontend build.
|
|
41
|
+
|
|
42
|
+

|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install fastapi-docs-plus
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Requires Python 3.10 or later.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Quick start
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from fastapi import FastAPI
|
|
60
|
+
from fastapi_docs_plus import DocsPlusConfig, PreRequestContext, setup_docs_plus
|
|
61
|
+
|
|
62
|
+
# Turn off the built-in docs, otherwise FastAPI's own /docs route takes
|
|
63
|
+
# precedence and this library can never serve the page.
|
|
64
|
+
app = FastAPI(docs_url=None)
|
|
65
|
+
|
|
66
|
+
# ... your routes ...
|
|
67
|
+
|
|
68
|
+
# Register the enhanced docs. Do this in development only: the pre-request
|
|
69
|
+
# hooks run with server-side privileges and the AI endpoints spend your quota.
|
|
70
|
+
docs = setup_docs_plus(app, DocsPlusConfig(identities=["admin", "shop_owner"]))
|
|
71
|
+
|
|
72
|
+
# Optional — give every request a freshly signed token.
|
|
73
|
+
@docs.pre_request
|
|
74
|
+
async def inject_auth(ctx: PreRequestContext) -> None:
|
|
75
|
+
if ctx.route_path == "/auth/login": # login issues its own token
|
|
76
|
+
return
|
|
77
|
+
username = ctx.identity or "admin"
|
|
78
|
+
token = create_token(username) # reuse your project's own signing code
|
|
79
|
+
ctx.headers["Authorization"] = f"Bearer {token}"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Start the app with an LLM key to enable the AI buttons:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
DOCS_PLUS_LLM_API_KEY=sk-xxx uvicorn app.main:app --reload
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Then open `http://127.0.0.1:8000/docs`. Compared with the stock docs you now get:
|
|
89
|
+
|
|
90
|
+
- a **Generate** button on every operation, which asks the LLM for realistic parameters and caches the validated result;
|
|
91
|
+
- a **Fill** button that writes the next cached result straight into the form;
|
|
92
|
+
- an identity dropdown (populated from `identities`), whose selection reaches your hook as `ctx.identity`.
|
|
93
|
+
|
|
94
|
+
Without `DOCS_PLUS_LLM_API_KEY` everything still works — the AI buttons are simply not rendered.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Pre-request hooks
|
|
99
|
+
|
|
100
|
+
### Registration
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
@docs.pre_request
|
|
104
|
+
def sync_hook(ctx: PreRequestContext) -> None: ...
|
|
105
|
+
|
|
106
|
+
@docs.pre_request
|
|
107
|
+
async def async_hook(ctx: PreRequestContext) -> None: ...
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Both synchronous and asynchronous callables are supported. Multiple hooks run in registration order and share the same `PreRequestContext`.
|
|
111
|
+
|
|
112
|
+
### How it works
|
|
113
|
+
|
|
114
|
+
Before Swagger UI sends any request, the browser POSTs the pending request metadata to `{api_prefix}/api/pre-request`. The hooks execute server-side and return header / query patches. The browser then applies those patches and sends the real request.
|
|
115
|
+
|
|
116
|
+
- Hooks have full access to your project: databases, signing functions, HTTP clients.
|
|
117
|
+
- Protected `openapi.json` URLs work correctly (the pre-request call has no method; the server falls back to `GET`).
|
|
118
|
+
- Injected headers appear in the Swagger UI cURL display.
|
|
119
|
+
|
|
120
|
+
### PreRequestContext
|
|
121
|
+
|
|
122
|
+
| Field | Type | Description |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `method` | `str` | Uppercase HTTP method |
|
|
125
|
+
| `url` | `str` | Full request URL |
|
|
126
|
+
| `path` | `str` | URL path component |
|
|
127
|
+
| `headers` | `dict[str, str]` | **Mutable**. Sent back as the complete set — deleting a key removes the header |
|
|
128
|
+
| `query` | `dict[str, str]` | **Mutable**. URL-decoded; re-encoded on return |
|
|
129
|
+
| `env` | `dict[str, Any]` | Environment from the frontend: identity selection + extra env JSON |
|
|
130
|
+
| `identity` | `str \| None` | Shorthand for `env["identity"]` |
|
|
131
|
+
| `route_path` | `str \| None` | Matched route template, e.g. `/shops/{shop_id}` |
|
|
132
|
+
| `operation_id` | `str \| None` | The route's `operation_id`, or the endpoint function name |
|
|
133
|
+
| `path_params` | `dict[str, Any]` | Path parameters extracted from the URL |
|
|
134
|
+
|
|
135
|
+
`route_path`, `operation_id`, and `path_params` are resolved server-side by matching the method and path against `app.routes`, independent of frontend input.
|
|
136
|
+
|
|
137
|
+
### Headers injected by hooks
|
|
138
|
+
|
|
139
|
+
Declare hook-injected headers with `include_in_schema=False` so Swagger UI does not require them in the form:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from typing import Annotated
|
|
143
|
+
from fastapi import Header
|
|
144
|
+
|
|
145
|
+
tenant_id: Annotated[str | None, Header(alias="X-Tenant-Id", include_in_schema=False)] = None
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Identity switching and extra environment
|
|
149
|
+
|
|
150
|
+
- `DocsPlusConfig(identities=[...])` controls the top-bar dropdown. The selected value is passed to hooks as `env["identity"]`.
|
|
151
|
+
- The **extra environment variables** textarea accepts a JSON object that is merged into `env`, useful for flags like `{"tenant": "...", "debug": true}`.
|
|
152
|
+
- Both are persisted in `localStorage` across page refreshes.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## AI parameter filling
|
|
157
|
+
|
|
158
|
+
### Workflow
|
|
159
|
+
|
|
160
|
+
Split into two separate actions:
|
|
161
|
+
|
|
162
|
+
1. **Generate** — calls the LLM and caches validated results (does not modify the form).
|
|
163
|
+
2. **Fill** — writes the next cached result into the form fields.
|
|
164
|
+
|
|
165
|
+
**Generate** → `POST {api_prefix}/api/ai/generate`:
|
|
166
|
+
|
|
167
|
+
1. Extracts the operation from `app.openapi()`, inlines all `$ref` pointers, truncates circular references and levels beyond `max_schema_depth`.
|
|
168
|
+
2. Preserves `description`, `enum`, `pattern`, `format`, `minimum`/`maximum`, `examples` as the only signals the LLM receives about real-world semantics.
|
|
169
|
+
3. Appends any previously cached results for the same operation so the model avoids duplicates; temperature scales up with history count.
|
|
170
|
+
4. Calls the LLM (OpenAI-compatible API, JSON mode) to produce a fixed envelope:
|
|
171
|
+
```json
|
|
172
|
+
{ "path": {}, "query": {}, "header": {}, "cookie": {}, "body": null }
|
|
173
|
+
```
|
|
174
|
+
5. Strips parameters the model invented that are not declared in the schema; forces `body` to `null` when the operation has no `requestBody`.
|
|
175
|
+
6. Validates `body` against `jsonschema`. On failure, feeds the validation error back to the model for one retry. **Only validated results enter the cache.**
|
|
176
|
+
|
|
177
|
+
The cache is a per-operation in-memory queue (`ai_cache_max_size`, default 5). Oldest entries are evicted first. When the schema changes, the cache for that operation is invalidated automatically.
|
|
178
|
+
|
|
179
|
+
**Fill** → `POST {api_prefix}/api/ai/fill`:
|
|
180
|
+
|
|
181
|
+
Returns the next result in round-robin order. The frontend expands the operation and writes path/query/header/cookie/body values into the form. Returns 409 when the cache is empty.
|
|
182
|
+
|
|
183
|
+
### Business hints
|
|
184
|
+
|
|
185
|
+
Add an `x-ai-hint` extension to your route for domain-specific guidance:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
@router.post(
|
|
189
|
+
"/orders",
|
|
190
|
+
openapi_extra={"x-ai-hint": "E-commerce order; use realistic province/city names; unit price 100-2000 CNY"},
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The hint is sent to the LLM as `businessHint`.
|
|
195
|
+
|
|
196
|
+
### Model configuration
|
|
197
|
+
|
|
198
|
+
Supports any OpenAI-compatible service (OpenAI, DeepSeek, Tongyi Qianwen, Ollama, etc.).
|
|
199
|
+
|
|
200
|
+
| Environment variable | `DocsPlusConfig` field | Default |
|
|
201
|
+
|---|---|---|
|
|
202
|
+
| `DOCS_PLUS_LLM_API_KEY` or `OPENAI_API_KEY` | `llm_api_key` | (none) |
|
|
203
|
+
| `DOCS_PLUS_LLM_BASE_URL` or `OPENAI_BASE_URL` | `llm_base_url` | OpenAI official |
|
|
204
|
+
| `DOCS_PLUS_LLM_MODEL` | `llm_model` | `gpt-4o-mini` |
|
|
205
|
+
|
|
206
|
+
When no API key is configured, the AI buttons are not rendered and a notice is shown in the toolbar. The rest of the documentation works normally.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Configuration
|
|
211
|
+
|
|
212
|
+
### DocsPlusConfig
|
|
213
|
+
|
|
214
|
+
| Field | Default | Description |
|
|
215
|
+
|---|---|---|
|
|
216
|
+
| `docs_url` | `"/docs"` | Documentation page path |
|
|
217
|
+
| `api_prefix` | `"/_docs"` | Prefix for internal endpoints and static assets |
|
|
218
|
+
| `openapi_url` | `"/openapi.json"` | URL for the OpenAPI spec |
|
|
219
|
+
| `swagger_ui_version` | `"5.17.14"` | Swagger UI version for CDN URLs |
|
|
220
|
+
| `swagger_ui_js_url` | `None` | Override the JS bundle URL |
|
|
221
|
+
| `swagger_ui_css_url` | `None` | Override the CSS URL |
|
|
222
|
+
| `swagger_ui_js_integrity` | `None` | SRI hash for the JS bundle |
|
|
223
|
+
| `swagger_ui_css_integrity` | `None` | SRI hash for the CSS |
|
|
224
|
+
| `swagger_ui_parameters` | see below | Extra `SwaggerUIBundle` options; merged key-wise over the defaults, so you only need to pass the keys you want to change |
|
|
225
|
+
| `identities` | `[]` | Identity dropdown options; empty hides the dropdown |
|
|
226
|
+
| `llm_model` | `"gpt-4o-mini"` | LLM model name |
|
|
227
|
+
| `llm_base_url` | `None` | API base URL (reads `DOCS_PLUS_LLM_BASE_URL` / `OPENAI_BASE_URL`) |
|
|
228
|
+
| `llm_api_key` | `None` | API key (reads `DOCS_PLUS_LLM_API_KEY` / `OPENAI_API_KEY`) |
|
|
229
|
+
| `llm_temperature` | `0.3` | LLM temperature; +0.1 per history item, capped at 1.0 |
|
|
230
|
+
| `llm_timeout` | `60.0` | Per-call timeout in seconds |
|
|
231
|
+
| `max_schema_depth` | `4` | Maximum `$ref` inlining depth |
|
|
232
|
+
| `max_schema_chars` | `60000` | Max characters for schema + history; beyond this the request is rejected |
|
|
233
|
+
| `ai_cache_max_size` | `5` | Max cached results per operation; oldest evicted first |
|
|
234
|
+
|
|
235
|
+
Default `swagger_ui_parameters`:
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
{
|
|
239
|
+
"persistAuthorization": True,
|
|
240
|
+
"displayRequestDuration": True,
|
|
241
|
+
"docExpansion": "list",
|
|
242
|
+
"showExtensions": True,
|
|
243
|
+
"showCommonExtensions": True,
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
User-supplied values are merged **key-wise** over these defaults, so you only need to pass the keys you want to change; the remaining keys keep their default values.
|
|
248
|
+
|
|
249
|
+
### HTTP endpoints
|
|
250
|
+
|
|
251
|
+
All are excluded from the OpenAPI document.
|
|
252
|
+
|
|
253
|
+
| Method | Path | Purpose |
|
|
254
|
+
|---|---|---|
|
|
255
|
+
| GET | `{docs_url}` | Documentation HTML page |
|
|
256
|
+
| GET | `{api_prefix}/static/*` | Frontend static assets |
|
|
257
|
+
| POST | `{api_prefix}/api/ai/generate` | Generate and cache validated AI parameters |
|
|
258
|
+
| POST | `{api_prefix}/api/ai/fill` | Retrieve next cached result (409 if empty) |
|
|
259
|
+
| GET | `{api_prefix}/api/ai/cache` | Read cache counts for all operations |
|
|
260
|
+
| POST | `{api_prefix}/api/pre-request` | Execute pre-request hooks and return patches |
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Limitations
|
|
265
|
+
|
|
266
|
+
- **Development use only.** Pre-request hooks issue server-side credentials and AI calls consume tokens. Do not register enhanced docs in production.
|
|
267
|
+
- **`static/adapter.js` is the sole bridge to Swagger UI internals.** Upgrading `swagger_ui_version` requires regression testing of parameter and body filling. Two known pitfalls (already handled in the adapter):
|
|
268
|
+
- Parameter values use `${in}.${name}.hash-${param.hashCode()}` as storage keys; the hash comes from the **raw** parameter object. Objects from `operationWithMeta()` carry `value`/`errors` and produce a different hash, causing writes to land on keys nobody reads.
|
|
269
|
+
- When an operation is collapsed the `RequestBody` component is not mounted; on mount it overwrites values with auto-generated examples. The fill flow expands the operation first.
|
|
270
|
+
- **AI cache is in-process memory.** Per-operation queue, lost on restart. Multi-worker processes have independent caches — badge counts and round-robin order may be inconsistent across workers.
|
|
271
|
+
- **Complex parameter type support.** Scalars, enums, and arrays use native form controls. Deeply nested object-type query parameters are serialised as JSON strings and may not match the target API's deserialisation convention. Request bodies (JSON) are not affected.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# fastapi-docs-plus
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/fastapi-docs-plus/)
|
|
4
|
+
[](https://pypi.org/project/fastapi-docs-plus/)
|
|
5
|
+
[](https://github.com/left666/fastapi-docs-plus/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
Enhanced interactive API docs for FastAPI that add two capabilities essential for integration and debugging:
|
|
8
|
+
|
|
9
|
+
- **AI parameter filling** — one click per operation to call an LLM and generate realistic request data that respects the JSON Schema, then write it directly into the Try-it-out form fields.
|
|
10
|
+
- **Python-side pre-request hooks** — server-side Python functions that intercept every Swagger UI request to inject authentication, signatures, or dynamic headers (JWT tokens, tenant IDs, CSRF tokens, etc.) using your project's own code.
|
|
11
|
+
|
|
12
|
+
It replaces FastAPI's built-in `/docs` while keeping Swagger UI as the rendering engine — no fork, no custom frontend build.
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install fastapi-docs-plus
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Requires Python 3.10 or later.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from fastapi import FastAPI
|
|
32
|
+
from fastapi_docs_plus import DocsPlusConfig, PreRequestContext, setup_docs_plus
|
|
33
|
+
|
|
34
|
+
# Turn off the built-in docs, otherwise FastAPI's own /docs route takes
|
|
35
|
+
# precedence and this library can never serve the page.
|
|
36
|
+
app = FastAPI(docs_url=None)
|
|
37
|
+
|
|
38
|
+
# ... your routes ...
|
|
39
|
+
|
|
40
|
+
# Register the enhanced docs. Do this in development only: the pre-request
|
|
41
|
+
# hooks run with server-side privileges and the AI endpoints spend your quota.
|
|
42
|
+
docs = setup_docs_plus(app, DocsPlusConfig(identities=["admin", "shop_owner"]))
|
|
43
|
+
|
|
44
|
+
# Optional — give every request a freshly signed token.
|
|
45
|
+
@docs.pre_request
|
|
46
|
+
async def inject_auth(ctx: PreRequestContext) -> None:
|
|
47
|
+
if ctx.route_path == "/auth/login": # login issues its own token
|
|
48
|
+
return
|
|
49
|
+
username = ctx.identity or "admin"
|
|
50
|
+
token = create_token(username) # reuse your project's own signing code
|
|
51
|
+
ctx.headers["Authorization"] = f"Bearer {token}"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Start the app with an LLM key to enable the AI buttons:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
DOCS_PLUS_LLM_API_KEY=sk-xxx uvicorn app.main:app --reload
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Then open `http://127.0.0.1:8000/docs`. Compared with the stock docs you now get:
|
|
61
|
+
|
|
62
|
+
- a **Generate** button on every operation, which asks the LLM for realistic parameters and caches the validated result;
|
|
63
|
+
- a **Fill** button that writes the next cached result straight into the form;
|
|
64
|
+
- an identity dropdown (populated from `identities`), whose selection reaches your hook as `ctx.identity`.
|
|
65
|
+
|
|
66
|
+
Without `DOCS_PLUS_LLM_API_KEY` everything still works — the AI buttons are simply not rendered.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Pre-request hooks
|
|
71
|
+
|
|
72
|
+
### Registration
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
@docs.pre_request
|
|
76
|
+
def sync_hook(ctx: PreRequestContext) -> None: ...
|
|
77
|
+
|
|
78
|
+
@docs.pre_request
|
|
79
|
+
async def async_hook(ctx: PreRequestContext) -> None: ...
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Both synchronous and asynchronous callables are supported. Multiple hooks run in registration order and share the same `PreRequestContext`.
|
|
83
|
+
|
|
84
|
+
### How it works
|
|
85
|
+
|
|
86
|
+
Before Swagger UI sends any request, the browser POSTs the pending request metadata to `{api_prefix}/api/pre-request`. The hooks execute server-side and return header / query patches. The browser then applies those patches and sends the real request.
|
|
87
|
+
|
|
88
|
+
- Hooks have full access to your project: databases, signing functions, HTTP clients.
|
|
89
|
+
- Protected `openapi.json` URLs work correctly (the pre-request call has no method; the server falls back to `GET`).
|
|
90
|
+
- Injected headers appear in the Swagger UI cURL display.
|
|
91
|
+
|
|
92
|
+
### PreRequestContext
|
|
93
|
+
|
|
94
|
+
| Field | Type | Description |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `method` | `str` | Uppercase HTTP method |
|
|
97
|
+
| `url` | `str` | Full request URL |
|
|
98
|
+
| `path` | `str` | URL path component |
|
|
99
|
+
| `headers` | `dict[str, str]` | **Mutable**. Sent back as the complete set — deleting a key removes the header |
|
|
100
|
+
| `query` | `dict[str, str]` | **Mutable**. URL-decoded; re-encoded on return |
|
|
101
|
+
| `env` | `dict[str, Any]` | Environment from the frontend: identity selection + extra env JSON |
|
|
102
|
+
| `identity` | `str \| None` | Shorthand for `env["identity"]` |
|
|
103
|
+
| `route_path` | `str \| None` | Matched route template, e.g. `/shops/{shop_id}` |
|
|
104
|
+
| `operation_id` | `str \| None` | The route's `operation_id`, or the endpoint function name |
|
|
105
|
+
| `path_params` | `dict[str, Any]` | Path parameters extracted from the URL |
|
|
106
|
+
|
|
107
|
+
`route_path`, `operation_id`, and `path_params` are resolved server-side by matching the method and path against `app.routes`, independent of frontend input.
|
|
108
|
+
|
|
109
|
+
### Headers injected by hooks
|
|
110
|
+
|
|
111
|
+
Declare hook-injected headers with `include_in_schema=False` so Swagger UI does not require them in the form:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from typing import Annotated
|
|
115
|
+
from fastapi import Header
|
|
116
|
+
|
|
117
|
+
tenant_id: Annotated[str | None, Header(alias="X-Tenant-Id", include_in_schema=False)] = None
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Identity switching and extra environment
|
|
121
|
+
|
|
122
|
+
- `DocsPlusConfig(identities=[...])` controls the top-bar dropdown. The selected value is passed to hooks as `env["identity"]`.
|
|
123
|
+
- The **extra environment variables** textarea accepts a JSON object that is merged into `env`, useful for flags like `{"tenant": "...", "debug": true}`.
|
|
124
|
+
- Both are persisted in `localStorage` across page refreshes.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## AI parameter filling
|
|
129
|
+
|
|
130
|
+
### Workflow
|
|
131
|
+
|
|
132
|
+
Split into two separate actions:
|
|
133
|
+
|
|
134
|
+
1. **Generate** — calls the LLM and caches validated results (does not modify the form).
|
|
135
|
+
2. **Fill** — writes the next cached result into the form fields.
|
|
136
|
+
|
|
137
|
+
**Generate** → `POST {api_prefix}/api/ai/generate`:
|
|
138
|
+
|
|
139
|
+
1. Extracts the operation from `app.openapi()`, inlines all `$ref` pointers, truncates circular references and levels beyond `max_schema_depth`.
|
|
140
|
+
2. Preserves `description`, `enum`, `pattern`, `format`, `minimum`/`maximum`, `examples` as the only signals the LLM receives about real-world semantics.
|
|
141
|
+
3. Appends any previously cached results for the same operation so the model avoids duplicates; temperature scales up with history count.
|
|
142
|
+
4. Calls the LLM (OpenAI-compatible API, JSON mode) to produce a fixed envelope:
|
|
143
|
+
```json
|
|
144
|
+
{ "path": {}, "query": {}, "header": {}, "cookie": {}, "body": null }
|
|
145
|
+
```
|
|
146
|
+
5. Strips parameters the model invented that are not declared in the schema; forces `body` to `null` when the operation has no `requestBody`.
|
|
147
|
+
6. Validates `body` against `jsonschema`. On failure, feeds the validation error back to the model for one retry. **Only validated results enter the cache.**
|
|
148
|
+
|
|
149
|
+
The cache is a per-operation in-memory queue (`ai_cache_max_size`, default 5). Oldest entries are evicted first. When the schema changes, the cache for that operation is invalidated automatically.
|
|
150
|
+
|
|
151
|
+
**Fill** → `POST {api_prefix}/api/ai/fill`:
|
|
152
|
+
|
|
153
|
+
Returns the next result in round-robin order. The frontend expands the operation and writes path/query/header/cookie/body values into the form. Returns 409 when the cache is empty.
|
|
154
|
+
|
|
155
|
+
### Business hints
|
|
156
|
+
|
|
157
|
+
Add an `x-ai-hint` extension to your route for domain-specific guidance:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
@router.post(
|
|
161
|
+
"/orders",
|
|
162
|
+
openapi_extra={"x-ai-hint": "E-commerce order; use realistic province/city names; unit price 100-2000 CNY"},
|
|
163
|
+
)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The hint is sent to the LLM as `businessHint`.
|
|
167
|
+
|
|
168
|
+
### Model configuration
|
|
169
|
+
|
|
170
|
+
Supports any OpenAI-compatible service (OpenAI, DeepSeek, Tongyi Qianwen, Ollama, etc.).
|
|
171
|
+
|
|
172
|
+
| Environment variable | `DocsPlusConfig` field | Default |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| `DOCS_PLUS_LLM_API_KEY` or `OPENAI_API_KEY` | `llm_api_key` | (none) |
|
|
175
|
+
| `DOCS_PLUS_LLM_BASE_URL` or `OPENAI_BASE_URL` | `llm_base_url` | OpenAI official |
|
|
176
|
+
| `DOCS_PLUS_LLM_MODEL` | `llm_model` | `gpt-4o-mini` |
|
|
177
|
+
|
|
178
|
+
When no API key is configured, the AI buttons are not rendered and a notice is shown in the toolbar. The rest of the documentation works normally.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Configuration
|
|
183
|
+
|
|
184
|
+
### DocsPlusConfig
|
|
185
|
+
|
|
186
|
+
| Field | Default | Description |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| `docs_url` | `"/docs"` | Documentation page path |
|
|
189
|
+
| `api_prefix` | `"/_docs"` | Prefix for internal endpoints and static assets |
|
|
190
|
+
| `openapi_url` | `"/openapi.json"` | URL for the OpenAPI spec |
|
|
191
|
+
| `swagger_ui_version` | `"5.17.14"` | Swagger UI version for CDN URLs |
|
|
192
|
+
| `swagger_ui_js_url` | `None` | Override the JS bundle URL |
|
|
193
|
+
| `swagger_ui_css_url` | `None` | Override the CSS URL |
|
|
194
|
+
| `swagger_ui_js_integrity` | `None` | SRI hash for the JS bundle |
|
|
195
|
+
| `swagger_ui_css_integrity` | `None` | SRI hash for the CSS |
|
|
196
|
+
| `swagger_ui_parameters` | see below | Extra `SwaggerUIBundle` options; merged key-wise over the defaults, so you only need to pass the keys you want to change |
|
|
197
|
+
| `identities` | `[]` | Identity dropdown options; empty hides the dropdown |
|
|
198
|
+
| `llm_model` | `"gpt-4o-mini"` | LLM model name |
|
|
199
|
+
| `llm_base_url` | `None` | API base URL (reads `DOCS_PLUS_LLM_BASE_URL` / `OPENAI_BASE_URL`) |
|
|
200
|
+
| `llm_api_key` | `None` | API key (reads `DOCS_PLUS_LLM_API_KEY` / `OPENAI_API_KEY`) |
|
|
201
|
+
| `llm_temperature` | `0.3` | LLM temperature; +0.1 per history item, capped at 1.0 |
|
|
202
|
+
| `llm_timeout` | `60.0` | Per-call timeout in seconds |
|
|
203
|
+
| `max_schema_depth` | `4` | Maximum `$ref` inlining depth |
|
|
204
|
+
| `max_schema_chars` | `60000` | Max characters for schema + history; beyond this the request is rejected |
|
|
205
|
+
| `ai_cache_max_size` | `5` | Max cached results per operation; oldest evicted first |
|
|
206
|
+
|
|
207
|
+
Default `swagger_ui_parameters`:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
{
|
|
211
|
+
"persistAuthorization": True,
|
|
212
|
+
"displayRequestDuration": True,
|
|
213
|
+
"docExpansion": "list",
|
|
214
|
+
"showExtensions": True,
|
|
215
|
+
"showCommonExtensions": True,
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
User-supplied values are merged **key-wise** over these defaults, so you only need to pass the keys you want to change; the remaining keys keep their default values.
|
|
220
|
+
|
|
221
|
+
### HTTP endpoints
|
|
222
|
+
|
|
223
|
+
All are excluded from the OpenAPI document.
|
|
224
|
+
|
|
225
|
+
| Method | Path | Purpose |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| GET | `{docs_url}` | Documentation HTML page |
|
|
228
|
+
| GET | `{api_prefix}/static/*` | Frontend static assets |
|
|
229
|
+
| POST | `{api_prefix}/api/ai/generate` | Generate and cache validated AI parameters |
|
|
230
|
+
| POST | `{api_prefix}/api/ai/fill` | Retrieve next cached result (409 if empty) |
|
|
231
|
+
| GET | `{api_prefix}/api/ai/cache` | Read cache counts for all operations |
|
|
232
|
+
| POST | `{api_prefix}/api/pre-request` | Execute pre-request hooks and return patches |
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Limitations
|
|
237
|
+
|
|
238
|
+
- **Development use only.** Pre-request hooks issue server-side credentials and AI calls consume tokens. Do not register enhanced docs in production.
|
|
239
|
+
- **`static/adapter.js` is the sole bridge to Swagger UI internals.** Upgrading `swagger_ui_version` requires regression testing of parameter and body filling. Two known pitfalls (already handled in the adapter):
|
|
240
|
+
- Parameter values use `${in}.${name}.hash-${param.hashCode()}` as storage keys; the hash comes from the **raw** parameter object. Objects from `operationWithMeta()` carry `value`/`errors` and produce a different hash, causing writes to land on keys nobody reads.
|
|
241
|
+
- When an operation is collapsed the `RequestBody` component is not mounted; on mount it overwrites values with auto-generated examples. The fill flow expands the operation first.
|
|
242
|
+
- **AI cache is in-process memory.** Per-operation queue, lost on restart. Multi-worker processes have independent caches — badge counts and round-robin order may be inconsistent across workers.
|
|
243
|
+
- **Complex parameter type support.** Scalars, enums, and arrays use native form controls. Deeply nested object-type query parameters are serialised as JSON strings and may not match the target API's deserialisation convention. Request bodies (JSON) are not affected.
|