Compare commits

..

17 Commits

Author SHA1 Message Date
Sebastián Ramírez
fdb6d43e10 🔖 Release 0.40.0 2019-10-04 15:38:03 -05:00
Sebastián Ramírez
a7c718e968 📝 Update release notes 2019-10-04 15:35:09 -05:00
sliptonic
f4d753620b 📝 Add notes about installing python-multipart for forms (#574) 2019-10-04 15:33:42 -05:00
Sebastián Ramírez
fadfe4c586 📝 Update release notes 2019-10-04 15:11:04 -05:00
dmontagu
5fd83c5fa4 Sort schemas alphabetically (#554)
Modify openapi spec generation to include schemas in alphabetical order.
2019-10-04 15:08:41 -05:00
Sebastián Ramírez
14daaf409f 📝 Update release notes 2019-10-04 15:07:22 -05:00
svalouch
c7dc26b760 Allow docstrings to be truncated before being used for OpenAPI (#556) 2019-10-04 15:02:40 -05:00
Sebastián Ramírez
f5ccb3c35d 📝 Update release notes 2019-10-03 19:37:23 -05:00
Trim21
4cea311e6e 🐛 Fix doctype in docs (#537) 2019-10-03 19:35:44 -05:00
Sebastián Ramírez
f8718072a0 📝 Update release notes 2019-10-03 19:10:34 -05:00
tsouvarev
3dbbecdd16 🐛 Fix setting 4XX overriding default 422 validation errors(#517) 2019-10-03 19:08:29 -05:00
Sebastián Ramírez
6d5530ec1c 📝 Update release notes 2019-10-03 19:04:41 -05:00
prostomarkeloff
0761f11d1a ✏️ Fix typo in HTTP Basic auth tutorial (#514) 2019-10-03 19:01:41 -05:00
Sebastián Ramírez
f2e7ef7056 📝 Update release notes 2019-10-03 19:00:13 -05:00
Fedor Ignatov
d5d9a20937 📝 Fix incorrect example in docs - first steps (#511) 2019-10-03 18:57:49 -05:00
Sebastián Ramírez
96f092179f 📝 Update release notes 2019-10-03 18:43:15 -05:00
Zamir Amir
8505b716af Add support for setting Swagger UI initOAuth configs (clientId, appName) (#499) 2019-10-03 18:41:04 -05:00
16 changed files with 244 additions and 9 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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!}
```

View File

@@ -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`:

View File

@@ -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"

View File

@@ -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`:

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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)

View File

@@ -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>

View File

@@ -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

View File

@@ -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 = {}

View 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"}

View File

@@ -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": [],
}