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.
@@ -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,7 @@
1
+ .idea
2
+ __pycache__/
3
+ *.py[cod]
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ example
@@ -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
+ [![PyPI version](https://img.shields.io/pypi/v/fastapi-docs-plus)](https://pypi.org/project/fastapi-docs-plus/)
32
+ [![Python versions](https://img.shields.io/pypi/pyversions/fastapi-docs-plus)](https://pypi.org/project/fastapi-docs-plus/)
33
+ [![License](https://img.shields.io/pypi/l/fastapi-docs-plus)](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
+ ![fastapi-docs-plus demo](https://raw.githubusercontent.com/left666/fastapi-docs-plus/master/static/demo.png)
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
+ [![PyPI version](https://img.shields.io/pypi/v/fastapi-docs-plus)](https://pypi.org/project/fastapi-docs-plus/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/fastapi-docs-plus)](https://pypi.org/project/fastapi-docs-plus/)
5
+ [![License](https://img.shields.io/pypi/l/fastapi-docs-plus)](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
+ ![fastapi-docs-plus demo](https://raw.githubusercontent.com/left666/fastapi-docs-plus/master/static/demo.png)
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.
@@ -0,0 +1,9 @@
1
+ from .config import DocsPlusConfig
2
+ from .docs_plus import setup_docs_plus
3
+ from .pre_request import PreRequestContext
4
+
5
+ __all__ = [
6
+ "DocsPlusConfig",
7
+ "PreRequestContext",
8
+ "setup_docs_plus",
9
+ ]