mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-30 17:50:39 -05:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdb6d43e10 | ||
|
|
a7c718e968 | ||
|
|
f4d753620b | ||
|
|
fadfe4c586 | ||
|
|
5fd83c5fa4 | ||
|
|
14daaf409f | ||
|
|
c7dc26b760 | ||
|
|
f5ccb3c35d | ||
|
|
4cea311e6e | ||
|
|
f8718072a0 | ||
|
|
3dbbecdd16 | ||
|
|
6d5530ec1c | ||
|
|
0761f11d1a | ||
|
|
f2e7ef7056 | ||
|
|
d5d9a20937 | ||
|
|
96f092179f | ||
|
|
8505b716af |
@@ -1,5 +1,18 @@
|
||||
## Latest changes
|
||||
|
||||
## 0.40.0
|
||||
|
||||
* Add notes to docs about installing `python-multipart` when using forms. PR [#574](https://github.com/tiangolo/fastapi/pull/574) by [@sliptonic](https://github.com/sliptonic).
|
||||
* Generate OpenAPI schemas in alphabetical order. PR [#554](https://github.com/tiangolo/fastapi/pull/554) by [@dmontagu](https://github.com/dmontagu).
|
||||
* Add support for truncating docstrings from *path operation functions*.
|
||||
* New docs at [Advanced description from docstring](https://fastapi.tiangolo.com/tutorial/path-operation-advanced-configuration/#advanced-description-from-docstring).
|
||||
* PR [#556](https://github.com/tiangolo/fastapi/pull/556) by [@svalouch](https://github.com/svalouch).
|
||||
* Fix `DOCTYPE` in HTML files generated for Swagger UI and ReDoc. PR [#537](https://github.com/tiangolo/fastapi/pull/537) by [@Trim21](https://github.com/Trim21).
|
||||
* Fix handling `4XX` responses overriding default `422` validation error responses. PR [#517](https://github.com/tiangolo/fastapi/pull/517) by [@tsouvarev](https://github.com/tsouvarev).
|
||||
* Fix typo in documentation for [Simple HTTP Basic Auth](https://fastapi.tiangolo.com/tutorial/security/http-basic-auth/#simple-http-basic-auth). PR [#514](https://github.com/tiangolo/fastapi/pull/514) by [@prostomarkeloff](https://github.com/prostomarkeloff).
|
||||
* Fix incorrect documentation example in [first steps](https://fastapi.tiangolo.com/tutorial/first-steps/). PR [#511](https://github.com/tiangolo/fastapi/pull/511) by [@IgnatovFedor](https://github.com/IgnatovFedor).
|
||||
* Add support for Swagger UI [initOauth](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/oauth2.md) settings with the parameter `swagger_ui_init_oauth`. PR [#499](https://github.com/tiangolo/fastapi/pull/499) by [@zamiramir](https://github.com/zamiramir).
|
||||
|
||||
## 0.39.0
|
||||
|
||||
* Allow path parameters to have default values (e.g. `None`) and discard them instead of raising an error.
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
from typing import Set
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: str = None
|
||||
price: float
|
||||
tax: float = None
|
||||
tags: Set[str] = []
|
||||
|
||||
|
||||
@app.post("/items/", response_model=Item, summary="Create an item")
|
||||
async def create_item(*, item: Item):
|
||||
"""
|
||||
Create an item with all the information:
|
||||
|
||||
- **name**: each item must have a name
|
||||
- **description**: a long description
|
||||
- **price**: required
|
||||
- **tax**: if the item doesn't have tax, you can omit this
|
||||
- **tags**: a set of unique tag strings for this item
|
||||
\f
|
||||
:param item: User input.
|
||||
"""
|
||||
return item
|
||||
@@ -37,7 +37,7 @@ Open your browser at <a href="http://127.0.0.1:8000" target="_blank">http://127.
|
||||
You will see the JSON response as:
|
||||
|
||||
```JSON
|
||||
{"hello": "world"}
|
||||
{"message": "Hello World"}
|
||||
```
|
||||
|
||||
### Interactive API docs
|
||||
|
||||
@@ -18,3 +18,15 @@ To exclude a path operation from the generated OpenAPI schema (and thus, from th
|
||||
```Python hl_lines="6"
|
||||
{!./src/path_operation_advanced_configuration/tutorial002.py!}
|
||||
```
|
||||
|
||||
## Advanced description from docstring
|
||||
|
||||
You can limit the lines used from the docstring of a *path operation function* for OpenAPI.
|
||||
|
||||
Adding an `\f` (an escaped "form feed" character) causes **FastAPI** to truncate the output used for OpenAPI at this point.
|
||||
|
||||
It won't show up in the documentation, but other tools (such as Sphinx) will be able to use the rest.
|
||||
|
||||
```Python hl_lines="19 20 21 22 23 24 25 26 27 28 29"
|
||||
{!./src/path_operation_advanced_configuration/tutorial003.py!}
|
||||
```
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
You can define files to be uploaded by the client using `File`.
|
||||
|
||||
!!! info
|
||||
To receive uploaded files, first install [`python-multipart`](https://andrew-d.github.io/python-multipart/).
|
||||
|
||||
E.g. `pip install python-multipart`.
|
||||
|
||||
This is because uploaded files are sent as "form data".
|
||||
|
||||
## Import `File`
|
||||
|
||||
Import `File` and `UploadFile` from `fastapi`:
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
You can define files and form fields at the same time using `File` and `Form`.
|
||||
|
||||
!!! info
|
||||
To receive uploaded files and/or form data, first install [`python-multipart`](https://andrew-d.github.io/python-multipart/).
|
||||
|
||||
E.g. `pip install python-multipart`.
|
||||
|
||||
## Import `File` and `Form`
|
||||
|
||||
```Python hl_lines="1"
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
When you need to receive form fields instead of JSON, you can use `Form`.
|
||||
|
||||
!!! info
|
||||
To use forms, first install [`python-multipart`](https://andrew-d.github.io/python-multipart/).
|
||||
|
||||
E.g. `pip install python-multipart`.
|
||||
|
||||
## Import `Form`
|
||||
|
||||
Import `Form` from `fastapi`:
|
||||
|
||||
@@ -24,6 +24,13 @@ Copy the example in a file `main.py`:
|
||||
|
||||
## Run it
|
||||
|
||||
!!! info
|
||||
First install [`python-multipart`](https://andrew-d.github.io/python-multipart/).
|
||||
|
||||
E.g. `pip install python-multipart`.
|
||||
|
||||
This is because **OAuth2** uses "form data" for sending the `username` and `password`.
|
||||
|
||||
Run the example with:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -12,8 +12,8 @@ Then, when you type that username and password, the browser sends them in the he
|
||||
|
||||
## Simple HTTP Basic Auth
|
||||
|
||||
* Import `HTTPBAsic` and `HTTPBasicCredentials`.
|
||||
* Create a "`security` scheme" using `HTTPBAsic`.
|
||||
* Import `HTTPBasic` and `HTTPBasicCredentials`.
|
||||
* Create a "`security` scheme" using `HTTPBasic`.
|
||||
* Use that `security` with a dependency in your *path operation*.
|
||||
* It returns an object of type `HTTPBasicCredentials`:
|
||||
* It contains the `username` and `password` sent.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.39.0"
|
||||
__version__ = "0.40.0"
|
||||
|
||||
from starlette.background import BackgroundTasks
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ class FastAPI(Starlette):
|
||||
docs_url: Optional[str] = "/docs",
|
||||
redoc_url: Optional[str] = "/redoc",
|
||||
swagger_ui_oauth2_redirect_url: Optional[str] = "/docs/oauth2-redirect",
|
||||
swagger_ui_init_oauth: Optional[dict] = None,
|
||||
**extra: Dict[str, Any],
|
||||
) -> None:
|
||||
self.default_response_class = default_response_class
|
||||
@@ -57,6 +58,7 @@ class FastAPI(Starlette):
|
||||
self.docs_url = docs_url
|
||||
self.redoc_url = redoc_url
|
||||
self.swagger_ui_oauth2_redirect_url = swagger_ui_oauth2_redirect_url
|
||||
self.swagger_ui_init_oauth = swagger_ui_init_oauth
|
||||
self.extra = extra
|
||||
self.dependency_overrides: Dict[Callable, Callable] = {}
|
||||
|
||||
@@ -98,6 +100,7 @@ class FastAPI(Starlette):
|
||||
openapi_url=openapi_url,
|
||||
title=self.title + " - Swagger UI",
|
||||
oauth2_redirect_url=self.swagger_ui_oauth2_redirect_url,
|
||||
init_oauth=self.swagger_ui_init_oauth,
|
||||
)
|
||||
|
||||
self.add_route(self.docs_url, swagger_ui_html, include_in_schema=False)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
|
||||
@@ -11,10 +13,11 @@ def get_swagger_ui_html(
|
||||
swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css",
|
||||
swagger_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
|
||||
oauth2_redirect_url: Optional[str] = None,
|
||||
init_oauth: Optional[dict] = None,
|
||||
) -> HTMLResponse:
|
||||
|
||||
html = f"""
|
||||
<! doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="{swagger_css_url}">
|
||||
@@ -42,7 +45,14 @@ def get_swagger_ui_html(
|
||||
],
|
||||
layout: "BaseLayout",
|
||||
deepLinking: true
|
||||
})
|
||||
})"""
|
||||
|
||||
if init_oauth:
|
||||
html += f"""
|
||||
ui.initOAuth({json.dumps(jsonable_encoder(init_oauth))})
|
||||
"""
|
||||
|
||||
html += """
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -94,7 +104,7 @@ def get_redoc_html(
|
||||
|
||||
def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
|
||||
html = """
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<body onload="run()">
|
||||
</body>
|
||||
|
||||
@@ -225,7 +225,7 @@ def get_openapi_path(
|
||||
if (all_route_params or route.body_field) and not any(
|
||||
[
|
||||
status in operation["responses"]
|
||||
for status in [http422, "4xx", "default"]
|
||||
for status in [http422, "4XX", "default"]
|
||||
]
|
||||
):
|
||||
operation["responses"][http422] = {
|
||||
@@ -283,7 +283,7 @@ def get_openapi(
|
||||
if path_definitions:
|
||||
definitions.update(path_definitions)
|
||||
if definitions:
|
||||
components.setdefault("schemas", {}).update(definitions)
|
||||
components["schemas"] = {k: definitions[k] for k in sorted(definitions)}
|
||||
if components:
|
||||
output["components"] = components
|
||||
output["paths"] = paths
|
||||
|
||||
@@ -246,6 +246,9 @@ class APIRoute(routing.Route):
|
||||
self.dependencies = []
|
||||
self.summary = summary
|
||||
self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
|
||||
# if a "form feed" character (page break) is found in the description text,
|
||||
# truncate description text to the content preceding the first "form feed"
|
||||
self.description = self.description.split("\f")[0]
|
||||
self.response_description = response_description
|
||||
self.responses = responses or {}
|
||||
response_fields = {}
|
||||
|
||||
28
tests/test_swagger_ui_init_oauth.py
Normal file
28
tests/test_swagger_ui_init_oauth.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
swagger_ui_init_oauth = {"clientId": "the-foo-clients", "appName": "The Predendapp"}
|
||||
|
||||
app = FastAPI(swagger_ui_init_oauth=swagger_ui_init_oauth)
|
||||
|
||||
|
||||
@app.get("/items/")
|
||||
async def read_items():
|
||||
return {"id": "foo"}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_swagger_ui():
|
||||
response = client.get("/docs")
|
||||
assert response.status_code == 200
|
||||
print(response.text)
|
||||
assert f"ui.initOAuth" in response.text
|
||||
assert f'"appName": "The Predendapp"' in response.text
|
||||
assert f'"clientId": "the-foo-clients"' in response.text
|
||||
|
||||
|
||||
def test_response():
|
||||
response = client.get("/items/")
|
||||
assert response.json() == {"id": "foo"}
|
||||
@@ -0,0 +1,112 @@
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from path_operation_advanced_configuration.tutorial003 import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "Fast API", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Create an item",
|
||||
"description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item\n",
|
||||
"operationId": "create_item_items__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"required": ["name", "price"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"price": {"title": "Price", "type": "number"},
|
||||
"description": {"title": "Description", "type": "string"},
|
||||
"tax": {"title": "Tax", "type": "number"},
|
||||
"tags": {
|
||||
"title": "Tags",
|
||||
"uniqueItems": True,
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"default": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == openapi_schema
|
||||
|
||||
|
||||
def test_query_params_str_validations():
|
||||
response = client.post("/items/", json={"name": "Foo", "price": 42})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"name": "Foo",
|
||||
"price": 42,
|
||||
"description": None,
|
||||
"tax": None,
|
||||
"tags": [],
|
||||
}
|
||||
Reference in New Issue
Block a user