Compare commits

...

83 Commits

Author SHA1 Message Date
Sebastián Ramírez
79e08a2541 🔖 Release version 0.60.2 2020-08-08 20:23:16 +02:00
Sebastián Ramírez
1c9c80ba93 📝 Update release notes 2020-08-08 20:21:03 +02:00
Yağızcan Değirmenci
25cb05c876 ✏ Fix documentation typo in Query Parameters and String Validations (#1832) 2020-08-08 20:19:14 +02:00
Sebastián Ramírez
694fbab074 📝 Update release notes 2020-08-08 20:07:03 +02:00
Felix Böhm
2fd28434dd 📝 Add documentation about async tests (pytest-asyncio and httpx) (#1619)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2020-08-08 20:01:18 +02:00
Sebastián Ramírez
d15556b152 📝 Update release notes 2020-08-08 09:20:37 +02:00
Sebastián Ramírez
38d8bab770 Raise early when using form data without installing python-multipart (#1851)
* Check if Form exists and multipart is in virtual environment

* Remove unused import

* Move BodyFieldInfo check to separate helper function

* Fix type UploadFile to File for BodyFieldInfo check

* Working solution. Kind of nasty though.

* Use better method of determing if correct package imported

* Use better method of determing if correct package imported

* Add raising exceptions, update error messages

* Check if Form exists and multipart is in virtual environment

* Move BodyFieldInfo check to separate helper function

* Fix type UploadFile to File for BodyFieldInfo check

* Use better method of determing if correct package imported

* Add raising exceptions, update error messages
* Removed unused import, added comments

Co-authored-by: Christopher Nguyen <chrisngyn99@gmail.com>

* Updated what kind of exception will be thrown

* Add type annotations

Adds annotations to is_form_data

* Fix import order

* Add basic tests

* Fixed Travis tests

* Replace logging with fastapi logger

* Change AttributeError to ImportError to fix exception handling

* Fixing tests

* Catch ModuleNotFoundError first

Fix code coverage

* Update fastapi/dependencies/utils.py

Remove error spaces when printing

Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>

* Update fastapi/dependencies/utils.py

Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>

* Removed spaces in error printing

* ♻️ Refactor form data detection

*  Update/increase tests for incorrect multipart install

* 🔥 Remove deprecated Travis (moved to GitHub Actions)

Co-authored-by: yk396 <yk396@cornell.edu>
Co-authored-by: Christopher Nguyen <chrisngyn99@gmail.com>
Co-authored-by: Kai Chen <kaichen120@gmail.com>
Co-authored-by: Chris N <hello@chris-nguyen.me>
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
2020-08-08 09:14:10 +02:00
Sebastián Ramírez
52f0f8657e 📝 Update release notes 2020-08-03 19:28:49 +02:00
Sebastián Ramírez
aedf5c895a 👷 Re-enable Gitter releases bot (#1831) 2020-08-03 19:28:02 +02:00
Sebastián Ramírez
117f9e4abe 📝 Update release notes 2020-08-03 18:38:27 +02:00
s2s
604fea9fc1 📝 Add link in sql-databases.md tutorial to async-sql-databases.md in advanced section. (#1813)
* Add link in sql-databases.md tutorial section to async-sql-databases.md in advanced section.

* 🎨 Update note format

Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2020-08-03 18:37:02 +02:00
Sebastián Ramírez
994bfd4591 📝 Update release notes 2020-08-03 18:27:03 +02:00
Eduard Iskandarov
02722923b1 ✏ Fix documentation typo in behind a proxy tutorial (#1807) 2020-08-03 18:25:01 +02:00
Sebastián Ramírez
d63a93429b 📝 Update release notes 2020-08-03 18:14:15 +02:00
Izabela Guerreiro
b93e216dc7 ✏ Fix typo in portuguese docs (#1795) 2020-08-03 18:12:30 +02:00
Sebastián Ramírez
95a29b6e67 📝 Update release notes 2020-08-03 18:05:05 +02:00
Sebastián Ramírez
272f01a153 🌐 Add Ukrainian language setup, without index translation (#1830) 2020-08-03 18:04:05 +02:00
Sebastián Ramírez
1a345ae7fc 📝 Update release notes 2020-08-03 17:41:50 +02:00
Marcelo Trylesinski
c5c138b8eb 📝 Update external links (#1786)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2020-08-03 17:39:48 +02:00
Sebastián Ramírez
da20e33414 📝 Update release notes 2020-08-03 17:26:12 +02:00
Henry Betts
7fbe3737bc 🐛 Fix encoding a Pydantic model that inherits from another with json_encoders (#1769) 2020-08-03 17:24:29 +02:00
Sebastián Ramírez
f63cec9c95 📝 Update release notes 2020-08-03 15:32:03 +02:00
Nima Mashhadi M. Reza
3063ad83ec Simplify and improve jsonable_encoder (#1754)
Co-authored-by: nimashadix <nimashadix@pop-os.localdomain>
2020-08-03 15:30:23 +02:00
Sebastián Ramírez
78680e5bee 📝 Update relase notes 2020-08-03 15:19:33 +02:00
Yurii Karabas
55b9faeb48 ♻ Simplify code syntax in several places (#1753)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2020-08-03 15:16:51 +02:00
Sebastián Ramírez
72645dfeab 📝 Update release notes 2020-08-03 14:30:25 +02:00
Nima Mashhadi M. Reza
3223de5598 🎨 Add typing.Optional to variables that accept None as value (#1731)
Co-authored-by: nimashadix <nimashadix@pop-os.localdomain>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2020-08-03 14:29:07 +02:00
Sebastián Ramírez
1afa4e8e75 📝 Update release notes 2020-08-03 13:50:22 +02:00
नवुले पवन कुमार राव
6fd3736da2 📝 Add article: Deploy FastAPI on Azure App Service (#1726) 2020-08-03 13:48:30 +02:00
Sebastián Ramírez
7e043e5e6f 📝 Update release notes 2020-08-03 10:46:33 +02:00
Smart
d1585c42b9 📝 Add external link to starlette docs for WebSocket testing (#1717)
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2020-08-03 10:45:22 +02:00
Sebastián Ramírez
fc494e3527 📝 Update release notes 2020-08-03 10:34:05 +02:00
Bloodielie
b344cc9415 ♻ Refactor and merge for loops in dependant creation (#1714)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2020-08-03 10:32:06 +02:00
Sebastián Ramírez
38b71a9298 📝 Update release notes 2020-08-03 09:55:21 +02:00
Francesco Frassinelli
769ee73240 📝 Add HTML media type to template docs (#1690)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2020-08-03 09:53:56 +02:00
Sebastián Ramírez
1df2f14c64 📝 Update release notes 2020-08-03 09:28:03 +02:00
Nils Lindemann
eab9a0e139 ✏ Fix typos and rewording in docs for security (#1678)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2020-08-03 09:27:02 +02:00
Sebastián Ramírez
b86ac6739a 📝 Update release notes 2020-08-03 09:13:17 +02:00
Nils Lindemann
9840d9e59d ✏ Fix typos in docs for dependencies (#1675)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2020-08-03 09:12:07 +02:00
Sebastián Ramírez
0ec52157df 📝 Update release notes 2020-08-03 08:46:50 +02:00
Bar Harel
f1c5330b65 🐛 Fix app.extra type annotation (#1659)
Co-authored-by: bar.harel <bar.harel@biocatch.com>
2020-08-03 08:43:04 +02:00
Sebastián Ramírez
306ec8de04 📝 Update release notes 2020-08-03 08:35:05 +02:00
Adrien Cacciaguerra
6d7c9893d4 ⬆️ Bump mkdocs-material (#1789) 2020-08-03 08:33:43 +02:00
Sebastián Ramírez
6264709054 📝 Update release notes 2020-08-03 08:11:00 +02:00
Sebastián Ramírez
76c2077f47 👷 Update docs previews, remove commit comments (#1826)
as previews use the latest commit from master
2020-08-03 08:09:58 +02:00
Sebastián Ramírez
a63b1efc29 📝 Update release notes 2020-07-22 10:29:49 +02:00
Sebastián Ramírez
9863c3fca8 🐛 Update GitHub action context var for Gitter bot (#1766) 2020-07-22 10:28:27 +02:00
Sebastián Ramírez
6fb97f44cf 📝 Update release notes 2020-07-22 08:47:48 +02:00
Sebastián Ramírez
f64c448329 🔖 Release version 0.60.1 2020-07-22 08:44:44 +02:00
Sebastián Ramírez
df6cbc5ec6 📝 Update release notes 2020-07-22 08:44:13 +02:00
Sebastián Ramírez
0f0af751e4 🔊 Add debugging logs for GitHub actions to introspect GitHub hidden context (#1764) 2020-07-22 08:43:26 +02:00
Sebastián Ramírez
6c9dca55bc 📝 Update release notes 2020-07-22 08:32:22 +02:00
Adrien Cacciaguerra
d71e807401 💄 Use OS preference theme for docs (#1760) 2020-07-22 08:30:12 +02:00
Sebastián Ramírez
7df9ddfe4e 📝 Update release notes 2020-07-22 08:28:31 +02:00
James Alford-Golojuch
4170659359 ⬆ Updates Starlette to version 0.13.6 (#1759)
Co-authored-by: jalfordgolojuch <jalfordgolojuch@activecampaign.com>
2020-07-22 08:25:32 +02:00
Sebastián Ramírez
2940a7fdfa 📝 Update release notes 2020-07-22 08:23:36 +02:00
Sebastián Ramírez
dadd6650ed 📌 Pin Swagger UI temporarily 2020-07-22 08:19:26 +02:00
Sebastián Ramírez
c5a21354af 📝 Update release notes 2020-07-21 23:10:52 +02:00
Sebastián Ramírez
8bafe2a482 🚀 GitHub Actions update, use commit from PR, not pre-merge (#1761)
* 🔥 Remove deploy badge that won't show correctly until next release

after the fixes to the Gitter bot

* 🐛 Fix GitHub Action to upload docs artifacts with commit from PR, not pre-merge

* ♻️ Run zip docs and artifact upload only on PRs
2020-07-21 23:08:14 +02:00
Sebastián Ramírez
42f1716b48 📝 Update release notes 2020-07-20 18:57:01 +02:00
Sebastián Ramírez
6ab2841dbb ♻ Update GitHub actions (#1746)
* 🐛 Fix Gitter notification, use development gitter room until next release

* 🔥 Remove trigger docs preview step from build-docs workflow

as it requires a more privileged token, so it's now triggered by the preview docs watcher

* 🔊 Dump context when building to allow debugging how to refactor the Gitter bot
2020-07-20 18:56:13 +02:00
Sebastián Ramírez
0f54657377 🔖 Release version 0.60.0 2020-07-20 18:26:56 +02:00
Sebastián Ramírez
79e5b36551 📝 Update release notes (#1745)
* 📝 Update release notes

* 📝 Update release notes
2020-07-20 18:22:29 +02:00
Sebastián Ramírez
074868d77e Run watch docs previews every hour 2020-07-20 17:55:02 +02:00
Sebastián Ramírez
3dd16a9458 Fetch artifacts only once in preview docs GitHub action 2020-07-20 17:48:43 +02:00
Sebastián Ramírez
62c23ab5fa 🔒 Use personal access token to trigger docs previews 2020-07-20 17:45:28 +02:00
Sebastián Ramírez
11c05beece 🔊 Add more logging to Watch Preview when artifact is not found 2020-07-20 17:13:27 +02:00
Sebastián Ramírez
7b3ef43127 🐛 Fix Watch Preview Docs GitHub Action, strike 2 2020-07-20 16:59:09 +02:00
Sebastián Ramírez
e0080e5f75 🐛 Fix Watch Previews action 2020-07-20 16:47:48 +02:00
Sebastián Ramírez
e1ba54bd12 🔧 Update Watch Docs Previews GitHub action 2020-07-20 16:35:26 +02:00
Sebastián Ramírez
7032dfb4f1 Add GitHub Action to watch for missing preview docs (#1740)
* 📝 Update release notes

* 🔊 Make curl verbose when triggering docs preview

* 🔧 Update GitHub Actions circus to use commit hash

*  Add PR docs preview watcher
2020-07-20 16:33:17 +02:00
Sebastián Ramírez
14e7f7c1f4 ⬆ Upgrade Deploy to Netlify action 2020-07-19 22:27:32 +02:00
Sebastián Ramírez
9ed6f1e419 🐛 Fix custom GitHub action 2020-07-19 22:22:25 +02:00
Sebastián Ramírez
b268c39758 Add internal GitHub action to deploy docs previews (#1739)
* 📝 Update release notes

*  Add internal GitHub action to pull docs artifact

* 🙈 Add archive.zip to gitignore
2020-07-19 22:11:28 +02:00
Sebastián Ramírez
4dd386b807 🚀 Preview docs for external PRs (#1738)
* 🍱 Save docs zip when building docs

* 🙈 Add docs.zip artifact to .gitignore

* 🚀 Update deploy artifact name

* ♻️ Upload artifact directory

*  Add WIP trigger docs preview

* ♻️ Update trigger docs preview

* 👷 Update env vars for docs preview

* 👷 Update PR extraction

* 👷 Try to show GitHub event

* 💚 Try to see if GitHub context templates is causing the problem

* 💚 Try to debug context GitHub event

* 🔊 Debug GitHub event context

* 👷 Update debugging action

* 👷 Update debug

* 👷 Update Action

* ♻️ Update script to trigger docs preview

* ️ Try to use Zip again to improve speed

* 🔧 Update zip scripts

*  Add preview docs on event

* 🚀 Trigger deploy preview on PRs

* 🐛 Fix trigger script env vars
2020-07-19 20:49:52 +02:00
Sebastián Ramírez
b7251f1654 📝 Update release notes 2020-07-19 14:25:28 +02:00
Sebastián Ramírez
780d3e65ad Add XML coverage report for GitHub Actions (#1737) 2020-07-19 14:24:24 +02:00
Sebastián Ramírez
cc8cac200f 📝 Update release notes 2020-07-19 14:10:51 +02:00
Sebastián Ramírez
e7be5c8ac5 💄 Update badges, remove Travis (#1736)
* 💄 Update badges

* 🔥 Remove Travis
2020-07-19 14:09:55 +02:00
Sebastián Ramírez
8f52864899 📝 Update release notes 2020-07-19 14:04:45 +02:00
Sebastián Ramírez
47a630721a 👷 Add GitHub Actions, move from Travis (#1735) 2020-07-19 14:03:38 +02:00
Sebastián Ramírez
10ae6de111 📝 Update release notes 2020-07-19 12:19:39 +02:00
JAYATI SHRIVASTAVA
2b47f3e56b Add support for adding OpenAPI schema for GET requests with a body (#1626)
* add test for get request body's openapi schema

* 📝 Update docs note for GET requests with body

*  Update test for GET request with body, test it receives the body

* 🔇 Temporary type ignore while it's handled in Pydantic

Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2020-07-19 12:17:50 +02:00
82 changed files with 2020 additions and 662 deletions

View File

@@ -0,0 +1,7 @@
FROM python:3.7
RUN pip install httpx "pydantic==1.5.1"
COPY ./app /app
CMD ["python", "/app/main.py"]

16
.github/actions/get-artifact/action.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: "Get Artifact"
description: "Get artifact, possibly uploaded by a PR, useful to deploy docs previews"
author: "Sebastián Ramírez <tiangolo@gmail.com>"
inputs:
token:
description: 'Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }}'
required: true
name:
description: 'Artifact name'
required: true
path:
description: 'Where to store the artifact'
required: true
runs:
using: 'docker'
image: 'Dockerfile'

View File

@@ -0,0 +1,63 @@
import logging
from datetime import datetime
from pathlib import Path
from typing import List, Optional
import httpx
from pydantic import BaseModel, BaseSettings, SecretStr
github_api = "https://api.github.com"
netlify_api = "https://api.netlify.com"
class Settings(BaseSettings):
input_name: str
input_token: SecretStr
input_path: str
github_repository: str
github_event_path: Path
github_event_name: Optional[str] = None
class Artifact(BaseModel):
id: int
node_id: str
name: str
size_in_bytes: int
url: str
archive_download_url: str
expired: bool
created_at: datetime
updated_at: datetime
class ArtifactResponse(BaseModel):
total_count: int
artifacts: List[Artifact]
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
settings = Settings()
logging.info(f"Using config: {settings.json()}")
github_headers = {
"Authorization": f"token {settings.input_token.get_secret_value()}"
}
response = httpx.get(
f"{github_api}/repos/{settings.github_repository}/actions/artifacts",
headers=github_headers,
)
data = response.json()
artifacts_response = ArtifactResponse.parse_obj(data)
use_artifact: Optional[Artifact] = None
for artifact in artifacts_response.artifacts:
if artifact.name == settings.input_name:
use_artifact = artifact
break
assert use_artifact
file_response = httpx.get(
use_artifact.archive_download_url, headers=github_headers, timeout=30
)
zip_file = Path(settings.input_path)
zip_file.write_bytes(file_response.content)
logging.info("Finished")

View File

@@ -0,0 +1,7 @@
FROM python:3.7
RUN pip install httpx PyGithub "pydantic==1.5.1"
COPY ./app /app
CMD ["python", "/app/main.py"]

View File

@@ -0,0 +1,10 @@
name: "Watch docs previews in PRs"
description: "Check PRs and trigger new docs deploys"
author: "Sebastián Ramírez <tiangolo@gmail.com>"
inputs:
token:
description: 'Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }}'
required: true
runs:
using: 'docker'
image: 'Dockerfile'

View File

@@ -0,0 +1,101 @@
import logging
from datetime import datetime
from pathlib import Path
from typing import List, Optional
import httpx
from github import Github
from github.NamedUser import NamedUser
from pydantic import BaseModel, BaseSettings, SecretStr
github_api = "https://api.github.com"
netlify_api = "https://api.netlify.com"
class Settings(BaseSettings):
input_token: SecretStr
github_repository: str
github_event_path: Path
github_event_name: Optional[str] = None
class Artifact(BaseModel):
id: int
node_id: str
name: str
size_in_bytes: int
url: str
archive_download_url: str
expired: bool
created_at: datetime
updated_at: datetime
class ArtifactResponse(BaseModel):
total_count: int
artifacts: List[Artifact]
def get_message(commit: str) -> str:
return f"Docs preview for commit {commit} at"
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
settings = Settings()
logging.info(f"Using config: {settings.json()}")
g = Github(settings.input_token.get_secret_value())
repo = g.get_repo(settings.github_repository)
owner: NamedUser = repo.owner
headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"}
prs = list(repo.get_pulls(state="open"))
response = httpx.get(
f"{github_api}/repos/{settings.github_repository}/actions/artifacts",
headers=headers,
)
data = response.json()
artifacts_response = ArtifactResponse.parse_obj(data)
for pr in prs:
logging.info("-----")
logging.info(f"Processing PR #{pr.number}: {pr.title}")
pr_comments = list(pr.get_issue_comments())
pr_commits = list(pr.get_commits())
last_commit = pr_commits[0]
for pr_commit in pr_commits:
if pr_commit.commit.author.date > last_commit.commit.author.date:
last_commit = pr_commit
commit = last_commit.commit.sha
logging.info(f"Last commit: {commit}")
message = get_message(commit)
notified = False
for pr_comment in pr_comments:
if message in pr_comment.body:
notified = True
logging.info(f"Docs preview was notified: {notified}")
if not notified:
artifact_name = f"docs-zip-{commit}"
use_artifact: Optional[Artifact] = None
for artifact in artifacts_response.artifacts:
if artifact.name == artifact_name:
use_artifact = artifact
break
if not use_artifact:
logging.info("Artifact not available")
else:
logging.info(f"Existing artifact: {use_artifact.name}")
response = httpx.post(
"https://api.github.com/repos/tiangolo/fastapi/actions/workflows/preview-docs.yml/dispatches",
headers=headers,
json={
"ref": "master",
"inputs": {
"pr": f"{pr.number}",
"name": artifact_name,
"commit": commit,
},
},
)
logging.info(
f"Trigger sent, response status: {response.status_code} - content: {response.content}"
)
logging.info("Finished")

View File

@@ -1,4 +1,4 @@
name: Build and Deploy to Netlify
name: Build Docs
on:
push:
pull_request:
@@ -7,6 +7,10 @@ jobs:
build:
runs-on: ubuntu-18.04
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
@@ -18,12 +22,21 @@ jobs:
run: python3.7 -m flit install --extras doc
- name: Build Docs
run: python3.7 ./scripts/docs.py build-all
- name: Zip docs
if: github.event_name == 'pull_request'
run: bash ./scripts/zip-docs.sh
- uses: actions/upload-artifact@v2
if: github.event_name == 'pull_request'
with:
name: docs-zip-${{ github.event.pull_request.head.sha }}
path: ./docs.zip
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@v1.0.3
uses: nwtgck/actions-netlify@v1.1.5
with:
publish-dir: './site'
production-branch: master
github-token: ${{ secrets.GITHUB_TOKEN }}
enable-commit-comment: false
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}

44
.github/workflows/preview-docs.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Preview Docs
on:
workflow_dispatch:
inputs:
pr:
description: Pull Request number
required: true
name:
description: Artifact name for zip file with docs
required: true
commit:
description: Commit SHA hash
required: true
jobs:
deploy:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/get-artifact
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: ${{ github.event.inputs.name }}
path: ./archive.zip
- name: Unzip docs
run: bash ./scripts/unzip-docs.sh
- name: Deploy to Netlify
id: netlify
uses: nwtgck/actions-netlify@v1.1.5
with:
publish-dir: './site'
production-deploy: false
github-token: ${{ secrets.GITHUB_TOKEN }}
enable-commit-comment: false
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
- name: Comment Deploy
env:
PR: "${{ github.event.inputs.pr }}"
DEPLOY_URL: "${{ steps.netlify.outputs.deploy-url }}"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
COMMIT: "${{ github.event.inputs.commit }}"
run: bash ./scripts/docs-comment-deploy.sh

39
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: Publish
on:
release:
types:
- created
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: "3.6"
- name: Install Flit
run: pip install flit
- name: Install Dependencies
run: flit install --symlink
- name: Publish
env:
FLIT_USERNAME: ${{ secrets.FLIT_USERNAME }}
FLIT_PASSWORD: ${{ secrets.FLIT_PASSWORD }}
run: bash scripts/publish.sh
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Notify
env:
GITTER_TOKEN: ${{ secrets.GITTER_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.event.release.name }}
run: bash scripts/notify.sh

29
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Test
on:
push:
pull_request:
types: [opened, synchronize]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8]
fail-fast: false
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install Flit
run: pip install flit
- name: Install Dependencies
run: flit install --symlink
- name: Test
run: bash scripts/test.sh
- name: Upload coverage
uses: codecov/codecov-action@v1

View File

@@ -0,0 +1,13 @@
name: Watch Docs Previews
on:
schedule:
- cron: "0 * * * *"
jobs:
deploy:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/watch-previews
with:
token: ${{ secrets.ACTIONS_TOKEN }}

2
.gitignore vendored
View File

@@ -17,6 +17,8 @@ env3.*
env
docs_build
venv
docs.zip
archive.zip
# vim temporary files
*~

View File

@@ -1,32 +0,0 @@
dist: xenial
language: python
cache: pip
python:
- "3.6"
- "3.7"
- "3.8"
- "nightly"
matrix:
allow_failures:
- python: "nightly"
install:
- pip install flit
- flit install --symlink
script:
- bash scripts/test.sh
after_script:
- bash <(curl -s https://codecov.io/bash)
deploy:
provider: script
script: bash scripts/deploy.sh
on:
tags: true
python: "3.6"

View File

@@ -5,14 +5,14 @@
<em>FastAPI framework, high performance, easy to learn, fast to code, ready for production</em>
</p>
<p align="center">
<a href="https://travis-ci.com/tiangolo/fastapi" target="_blank">
<img src="https://travis-ci.com/tiangolo/fastapi.svg?branch=master" alt="Build Status">
<a href="https://github.com/tiangolo/fastapi/actions?query=workflow%3ATest" target="_blank">
<img src="https://github.com/tiangolo/fastapi/workflows/Test/badge.svg" alt="Test">
</a>
<a href="https://codecov.io/gh/tiangolo/fastapi" target="_blank">
<img src="https://img.shields.io/codecov/c/github/tiangolo/fastapi" alt="Coverage">
<img src="https://img.shields.io/codecov/c/github/tiangolo/fastapi?color=%2334D058" alt="Coverage">
</a>
<a href="https://pypi.org/project/fastapi" target="_blank">
<img src="https://badge.fury.io/py/fastapi.svg" alt="Package version">
<img src="https://img.shields.io/pypi/v/fastapi?color=%2334D058&label=pypi%20package" alt="Package version">
</a>
<a href="https://gitter.im/tiangolo/fastapi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank">
<img src="https://badges.gitter.im/tiangolo/fastapi.svg" alt="Join the chat at https://gitter.im/tiangolo/fastapi">

View File

@@ -128,6 +128,14 @@ articles:
title: Machine learning model serving in Python using FastAPI and streamlit
author_link: https://github.com/davidefiocco
author: Davide Fiocco
- link: https://www.tutlinks.com/deploy-fastapi-on-azure/
title: Deploy FastAPI on Azure App Service
author_link: https://www.linkedin.com/in/navule/
author: Navule Pavan Kumar Rao
- link: https://towardsdatascience.com/build-and-host-fast-data-science-applications-using-fastapi-823be8a1d6a0
title: Build And Host Fast Data Science Applications Using FastAPI
author_link: https://medium.com/@farhadmalik
author: Farhad Malik
japanese:
- link: https://qiita.com/mtitg/items/47770e9a562dd150631d
title: FastAPIDB接続してCRUDするPython製APIサーバーを構築

View File

@@ -0,0 +1,100 @@
# Async Tests
You have already seen how to test your **FastAPI** applications using the provided `TestClient`, but with it, you can't test or run any other `async` function in your (synchronous) pytest functions.
Being able to use asynchronous functions in your tests could be useful, for example, when you're querying your database asynchronously. Imagine you want to test sending requests to your FastAPI application and then verify that your backend successfully wrote the correct data in the database, while using an async database library.
Let's look at how we can make that work.
## pytest-asyncio
If we want to call asynchronous functions in our tests, our test functions have to be asynchronous. Pytest provides a neat library for this, called `pytest-asyncio`, that allows us to specify that some test functions are to be called asynchronously.
You can install it via:
<div class="termy">
```console
$ pip install pytest-asyncio
---> 100%
```
</div>
## HTTPX
Even if your **FastAPI** application uses normal `def` functions instead of `async def`, it is still an `async` application underneath.
The `TestClient` does some magic inside to call the asynchronous FastAPI application in your normal `def` test functions, using standard pytest. But that magic doesn't work anymore when we're using it inside asynchronous functions. By running our tests asynchronously, we can no longer use the `TestClient` inside our test functions.
Luckily there's a nice alternative, called <a href="https://www.python-httpx.org/" class="external-link" target="_blank">HTTPX</a>.
HTTPX is an HTTP client for Python 3 that allows us to query our FastAPI application similarly to how we did it with the `TestClient`.
If you're familiar with the <a href="https://requests.readthedocs.io/en/master/" class="external-link" target="_blank">Requests</a> library, you'll find that the API of HTTPX is almost identical.
The important difference for us is that with HTTPX we are not limited to synchronous, but can also make asynchronous requests.
## Example
For a simple example, let's consider the following `main.py` module:
```Python
{!../../../docs_src/async_tests/main.py!}
```
The `test_main.py` module that contains the tests for `main.py` could look like this now:
```Python
{!../../../docs_src/async_tests/test_main.py!}
```
## Run it
You can run your tests as usual via:
<div class="termy">
```console
$ pytest
---> 100%
```
</div>
## In Detail
The marker `@pytest.mark.asyncio` tells pytest that this test function should be called asynchronously:
```Python hl_lines="7"
{!../../../docs_src/async_tests/test_main.py!}
```
!!! tip
Note that the test function is now `async def` instead of just `def` as before when using the `TestClient`.
Then we can create an `AsyncClient` with the app, and send async requests to it, using `await`.
```Python hl_lines="9 10"
{!../../../docs_src/async_tests/test_main.py!}
```
This is the equivalent to:
```Python
response = client.get('/')
```
that we used to make our requests with the `TestClient`.
!!! tip
Note that we're using async/await with the new `AsyncClient` - the request is asynchronous.
## Other Asynchronous Function Calls
As the testing function is now asynchronous, you can now also call (and `await`) other `async` functions apart from sending requests to your FastAPI application in your tests, exactly as you would call them anywhere else in your code.
!!! tip
If you encounter a `RuntimeError: Task attached to a different loop` when integrating asynchronous function calls in your tests (e.g. when using <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MongoDB's MotorClient</a>) check out <a href="https://github.com/pytest-dev/pytest-asyncio/issues/38#issuecomment-264418154" class="external-link" target="_blank">this issue</a> in the pytest-asyncio repository.

View File

@@ -238,7 +238,7 @@ Now, if you go to the URL with the port for Uvicorn: <a href="http://127.0.0.1:8
!!! tip
Notice that even though you are accessing it at `http://127.0.0.1:8000/app` it shows the `root_path` of `/api/v1`, taken from the option `--root-path`.
And now open the URL with the port for Traefik, including the path prefix: <a href="http://127.0.0.1:9999/api/v1/app" class="external-link" target="_blank">http://127.0.0.1:9999/api/vi/app</a>.
And now open the URL with the port for Traefik, including the path prefix: <a href="http://127.0.0.1:9999/api/v1/app" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/app</a>.
We get the same response:

View File

@@ -39,13 +39,16 @@ $ pip install aiofiles
* Declare a `Request` parameter in the *path operation* that will return a template.
* Use the `templates` you created to render and return a `TemplateResponse`, passing the `request` as one of the key-value pairs in the Jinja2 "context".
```Python hl_lines="3 10 14 15"
```Python hl_lines="4 11 15 16"
{!../../../docs_src/templates/tutorial001.py!}
```
!!! note
Notice that you have to pass the `request` as part of the key-value pairs in the context for Jinja2. So, you also have to declare it in your *path operation*.
!!! tip
By declaring `response_class=HTMLResponse` the docs UI will be able to know that the response will be HTML.
!!! note "Technical Details"
You could also use `from starlette.templating import Jinja2Templates`.

View File

@@ -7,3 +7,6 @@ For this, you use the `TestClient` in a `with` statement, connecting to the WebS
```Python hl_lines="27 28 29 30 31"
{!../../../docs_src/app_testing/tutorial002.py!}
```
!!! note
For more details, check Starlette's documentation for <a href="https://www.starlette.io/testclient/#testing-websocket-sessions" class="external-link" target="_blank">testing WebSockets</a>.

View File

@@ -5,14 +5,14 @@
<em>FastAPI framework, high performance, easy to learn, fast to code, ready for production</em>
</p>
<p align="center">
<a href="https://travis-ci.com/tiangolo/fastapi" target="_blank">
<img src="https://travis-ci.com/tiangolo/fastapi.svg?branch=master" alt="Build Status">
<a href="https://github.com/tiangolo/fastapi/actions?query=workflow%3ATest" target="_blank">
<img src="https://github.com/tiangolo/fastapi/workflows/Test/badge.svg" alt="Test">
</a>
<a href="https://codecov.io/gh/tiangolo/fastapi" target="_blank">
<img src="https://img.shields.io/codecov/c/github/tiangolo/fastapi" alt="Coverage">
<img src="https://img.shields.io/codecov/c/github/tiangolo/fastapi?color=%2334D058" alt="Coverage">
</a>
<a href="https://pypi.org/project/fastapi" target="_blank">
<img src="https://badge.fury.io/py/fastapi.svg" alt="Package version">
<img src="https://img.shields.io/pypi/v/fastapi?color=%2334D058&label=pypi%20package" alt="Package version">
</a>
<a href="https://gitter.im/tiangolo/fastapi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank">
<img src="https://badges.gitter.im/tiangolo/fastapi.svg" alt="Join the chat at https://gitter.im/tiangolo/fastapi">

View File

@@ -2,6 +2,54 @@
## Latest changes
## 0.60.2
* Fix typo in docs for query parameters. PR [#1832](https://github.com/tiangolo/fastapi/pull/1832) by [@ycd](https://github.com/ycd).
* Add docs about [Async Tests](https://fastapi.tiangolo.com/advanced/async-tests/). PR [#1619](https://github.com/tiangolo/fastapi/pull/1619) by [@empicano](https://github.com/empicano).
* Raise an exception when using form data (`Form`, `File`) without having `python-multipart` installed.
* Up to now the application would run, and raise an exception only when receiving a request with form data, the new behavior, raising early, will prevent from deploying applications with broken dependencies.
* It also detects if the correct package `python-multipart` is installed instead of the incorrect `multipart` (both importable as `multipart`).
* PR [#1851](https://github.com/tiangolo/fastapi/pull/1851) based on original PR [#1627](https://github.com/tiangolo/fastapi/pull/1627) by [@chrisngyn](https://github.com/chrisngyn), [@YKo20010](https://github.com/YKo20010), [@kx-chen](https://github.com/kx-chen).
* Re-enable Gitter releases bot. PR [#1831](https://github.com/tiangolo/fastapi/pull/1831).
* Add link to async SQL databases tutorial from main SQL tutorial. PR [#1813](https://github.com/tiangolo/fastapi/pull/1813) by [@short2strings](https://github.com/short2strings).
* Fix typo in tutorial about behind a proxy. PR [#1807](https://github.com/tiangolo/fastapi/pull/1807) by [@toidi](https://github.com/toidi).
* Fix typo in Portuguese docs. PR [#1795](https://github.com/tiangolo/fastapi/pull/1795) by [@izaguerreiro](https://github.com/izaguerreiro).
* Add translations setup for Ukrainian. PR [#1830](https://github.com/tiangolo/fastapi/pull/1830).
* Add external link [Build And Host Fast Data Science Applications Using FastAPI](https://towardsdatascience.com/build-and-host-fast-data-science-applications-using-fastapi-823be8a1d6a0). PR [#1786](https://github.com/tiangolo/fastapi/pull/1786) by [@Kludex](https://github.com/Kludex).
* Fix encoding of Pydantic models that inherit from others models with custom `json_encoders`. PR [#1769](https://github.com/tiangolo/fastapi/pull/1769) by [@henrybetts](https://github.com/henrybetts).
* Simplify and improve `jsonable_encoder`. PR [#1754](https://github.com/tiangolo/fastapi/pull/1754) by [@MashhadiNima](https://github.com/MashhadiNima).
* Simplify internal code syntax in several points. PR [#1753](https://github.com/tiangolo/fastapi/pull/1753) by [@uriyyo](https://github.com/uriyyo).
* Improve internal typing, declare `Optional` parameters. PR [#1731](https://github.com/tiangolo/fastapi/pull/1731) by [@MashhadiNima](https://github.com/MashhadiNima).
* Add external link [Deploy FastAPI on Azure App Service](https://www.tutlinks.com/deploy-fastapi-on-azure/) to docs. PR [#1726](https://github.com/tiangolo/fastapi/pull/1726) by [@windson](https://github.com/windson).
* Add link to Starlette docs about WebSocket testing. PR [#1717](https://github.com/tiangolo/fastapi/pull/1717) by [@hellocoldworld](https://github.com/hellocoldworld).
* Refactor generating dependant, merge for loops. PR [#1714](https://github.com/tiangolo/fastapi/pull/1714) by [@Bloodielie](https://github.com/Bloodielie).
* Update example for templates with Jinja to include HTML media type. PR [#1690](https://github.com/tiangolo/fastapi/pull/1690) by [@frafra](https://github.com/frafra).
* Fix typos in docs for security. PR [#1678](https://github.com/tiangolo/fastapi/pull/1678) by [@nilslindemann](https://github.com/nilslindemann).
* Fix typos in docs for dependencies. PR [#1675](https://github.com/tiangolo/fastapi/pull/1675) by [@nilslindemann](https://github.com/nilslindemann).
* Fix type annotation for `**extra` parameters in `FastAPI`. PR [#1659](https://github.com/tiangolo/fastapi/pull/1659) by [@bharel](https://github.com/bharel).
* Bump MkDocs Material to fix docs in browsers with dark mode. PR [#1789](https://github.com/tiangolo/fastapi/pull/1789) by [@adriencaccia](https://github.com/adriencaccia).
* Remove docs preview comment from each commit. PR [#1826](https://github.com/tiangolo/fastapi/pull/1826).
* Update GitHub context extraction for Gitter notification bot. PR [#1766](https://github.com/tiangolo/fastapi/pull/1766).
## 0.60.1
* Add debugging logs for GitHub actions to introspect GitHub hidden context. PR [#1764](https://github.com/tiangolo/fastapi/pull/1764).
* Use OS preference theme for online docs. PR [#1760](https://github.com/tiangolo/fastapi/pull/1760) by [@adriencaccia](https://github.com/adriencaccia).
* Upgrade Starlette to version `0.13.6` to handle a vulnerability when using static files in Windows. PR [#1759](https://github.com/tiangolo/fastapi/pull/1759) by [@jamesag26](https://github.com/jamesag26).
* Pin Swagger UI temporarily, waiting for a fix for [swagger-api/swagger-ui#6249](https://github.com/swagger-api/swagger-ui/issues/6249). PR [#1763](https://github.com/tiangolo/fastapi/pull/1763).
* Update GitHub Actions, use commit from PR for docs preview, not commit from pre-merge. PR [#1761](https://github.com/tiangolo/fastapi/pull/1761).
* Update GitHub Actions, refactor Gitter bot. PR [#1746](https://github.com/tiangolo/fastapi/pull/1746).
## 0.60.0
* Add GitHub Action to watch for missing preview docs and trigger a preview deploy. PR [#1740](https://github.com/tiangolo/fastapi/pull/1740).
* Add custom GitHub Action to get artifact with docs preview. PR [#1739](https://github.com/tiangolo/fastapi/pull/1739).
* Add new GitHub Actions to preview docs from PRs. PR [#1738](https://github.com/tiangolo/fastapi/pull/1738).
* Add XML test coverage to support GitHub Actions. PR [#1737](https://github.com/tiangolo/fastapi/pull/1737).
* Update badges and remove Travis now that GitHub Actions is the main CI. PR [#1736](https://github.com/tiangolo/fastapi/pull/1736).
* Add GitHub Actions for CI, move from Travis. PR [#1735](https://github.com/tiangolo/fastapi/pull/1735).
* Add support for adding OpenAPI schema for GET requests with a body. PR [#1626](https://github.com/tiangolo/fastapi/pull/1626) by [@victorphoenix3](https://github.com/victorphoenix3).
## 0.59.0
* Fix typo in docstring for OAuth2 utils. PR [#1621](https://github.com/tiangolo/fastapi/pull/1621) by [@tomarv2](https://github.com/tomarv2).

View File

@@ -9,9 +9,11 @@ Your API almost always has to send a **response** body. But clients don't necess
To declare a **request** body, you use <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a> models with all their power and benefits.
!!! info
You cannot send a request body using a `GET` operation (HTTP method).
To send data, you should use one of: `POST` (the more common), `PUT`, `DELETE` or `PATCH`.
To send data, you have to use one of: `POST` (the more common), `PUT`, `DELETE` or `PATCH`.
Sending a body with a `GET` request has an undefined behavior in the specifications, nevertheless, it is supported by FastAPI, only for very complex/extreme use cases.
As it is discouraged, the interactive docs with Swagger UI won't show the documentation for the body when using `GET`, and proxies in the middle might not support it.
## Import Pydantic's `BaseModel`

View File

@@ -69,7 +69,7 @@ If you pass a "callable" as a dependency in **FastAPI**, it will analyze the par
That also applies to callables with no parameters at all. The same as it would be for *path operation functions* with no parameters.
Then, we can change the dependency "dependable" `common_parameters` from above to the class `CommonQueryParameters`:
Then, we can change the dependency "dependable" `common_parameters` from above to the class `CommonQueryParams`:
```Python hl_lines="11 12 13 14 15"
{!../../../docs_src/dependencies/tutorial002.py!}
@@ -101,15 +101,15 @@ In both cases the data will be converted, validated, documented on the OpenAPI s
Now you can declare your dependency using this class.
And as when **FastAPI** calls that class the value that will be passed as `commons` to your function will be an "instance" of the class, you can declare that parameter `commons` to be of type of the class, `CommonQueryParams`.
```Python hl_lines="19"
{!../../../docs_src/dependencies/tutorial002.py!}
```
**FastAPI** calls the `CommonQueryParams` class. This creates an "instance" of that class and the instance will be passed as the parameter `commons` to your function.
## Type annotation vs `Depends`
In the code above, you are declaring `commons` as:
Notice how we write `CommonQueryParams` twice in the above code:
```Python
commons: CommonQueryParams = Depends(CommonQueryParams)
@@ -175,9 +175,9 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
commons: CommonQueryParams = Depends()
```
So, you can declare the dependency as the type of the variable, and use `Depends()` as the "default" value (the value after the `=`) for that function's parameter, without any parameter, instead of having to write the full class *again* inside of `Depends(CommonQueryParams)`.
You declare the dependency as the type of the parameter, and you use `Depends()` as its "default" value (that after the `=`) for that function's parameter, without any parameter in `Depends()`, instead of having to write the full class *again* inside of `Depends(CommonQueryParams)`.
So, the same example would look like:
The same example would then look like:
```Python hl_lines="19"
{!../../../docs_src/dependencies/tutorial004.py!}
@@ -186,6 +186,6 @@ So, the same example would look like:
...and **FastAPI** will know what to do.
!!! tip
If all that seems more confusing than helpful, disregard it, you don't *need* it.
If that seems more confusing than helpful, disregard it, you don't *need* it.
It is just a shortcut. Because **FastAPI** cares about helping you minimize code repetition.

View File

@@ -25,7 +25,7 @@ These dependencies will be executed/solved the same way normal dependencies. But
Using these `dependencies` in the *path operation decorator* you can make sure they are executed while avoiding editor/tooling errors.
It might also help avoiding confusion for new developers that see an un-used parameter in your code and could think it's unnecessary.
It might also help avoid confusion for new developers that see an unused parameter in your code and could think it's unnecessary.
## Dependencies errors and return values

View File

@@ -39,7 +39,7 @@ That's it.
**2 lines**.
And it has the same shape and structure that all your *path operation functions*.
And it has the same shape and structure that all your *path operation functions* have.
You can think of it as a *path operation function* without the "decorator" (without the `@app.get("/some-path")`).
@@ -123,10 +123,9 @@ So, the interactive docs will have all the information from these dependencies t
<img src="/img/tutorial/dependencies/image01.png">
## Simple usage
If you look at it, *path operation functions* are declared to be used whenever a *path* and *operation* matches, and then **FastAPI** takes care of calling the function with the correct parameters and use the response.
If you look at it, *path operation functions* are declared to be used whenever a *path* and *operation* matches, and then **FastAPI** takes care of calling the function with the correct parameters, extracting the data from the request.
Actually, all (or most) of the web frameworks work in this same way.

View File

@@ -31,7 +31,7 @@ Let's focus on the parameters declared:
* Even though this function is a dependency ("dependable") itself, it also declares another dependency (it "depends" on something else).
* It depends on the `query_extractor`, and assigns the value returned by it to the parameter `q`.
* It also declares an optional `last_query` cookie, as a `str`.
* Let's imagine that if the user didn't provide any query `q`, we just use the last query used, that we had saved to a cookie before.
* If the user didn't provide any query `q`, we use the last query used, which we saved to a cookie before.
### Use the dependency

View File

@@ -142,7 +142,7 @@ So, when you need to declare a value as required while using `Query`, you can us
```
!!! info
If you hadn't seen that `...` before: it is a a special single value, it is <a href="https://docs.python.org/3/library/constants.html#Ellipsis" class="external-link" target="_blank">part of Python and is called "Ellipsis"</a>.
If you hadn't seen that `...` before: it is a special single value, it is <a href="https://docs.python.org/3/library/constants.html#Ellipsis" class="external-link" target="_blank">part of Python and is called "Ellipsis"</a>.
This will let **FastAPI** know that this parameter is required.

View File

@@ -90,7 +90,7 @@ So, let's review it from that simplified point of view:
* The API checks that `username` and `password`, and responds with a "token" (we haven't implemented any of this yet).
* A "token" is just a string with some content that we can use later to verify this user.
* Normally, a token is set to expire after some time.
* So, the user will have to login again at some point later.
* So, the user will have to log in again at some point later.
* And if the token is stolen, the risk is less. It is not like a permanent key that will work forever (in most of the cases).
* The frontend stores that token temporarily somewhere.
* The user clicks in the frontend to go to another section of the frontend web app.
@@ -103,7 +103,7 @@ So, let's review it from that simplified point of view:
**FastAPI** provides several tools, at different levels of abstraction, to implement these security features.
In this example we are going to use **OAuth2**, with the **Password** flow, using a **Bearer** token.
In this example we are going to use **OAuth2**, with the **Password** flow, using a **Bearer** token. We do that using the `OAuth2PasswordBearer` class.
!!! info
A "bearer" token is not the only option.
@@ -114,7 +114,7 @@ In this example we are going to use **OAuth2**, with the **Password** flow, usin
In that case, **FastAPI** also provides you with the tools to build it.
`OAuth2PasswordBearer` is a class that we create passing a parameter with the URL the client (the frontend running in the user's browser) can use to send the `username` and `password` and get a token.
When we create an instance of the `OAuth2PasswordBearer` class we pass in the `tokenUrl` parameter. This parameter contains the URL that the client (the frontend running in the user's browser) will use to send the `username` and `password` in order to get a token.
```Python hl_lines="6"
{!../../../docs_src/security/tutorial001.py!}
@@ -127,7 +127,9 @@ In this example we are going to use **OAuth2**, with the **Password** flow, usin
Using a relative URL is important to make sure your application keeps working even in an advanced use case like [Behind a Proxy](../../advanced/behind-a-proxy.md){.internal-link target=_blank}.
It doesn't create that endpoint / *path operation* for `./token`, but declares that that URL `./token` is the one that the client should use to get the token. That information is used in OpenAPI, and then in the interactive API documentation systems.
This parameter doesn't create that endpoint / *path operation*, but declares that the URL `/token` will be the one that the client should use to get the token. That information is used in OpenAPI, and then in the interactive API documentation systems.
We will soon also create the actual path operation.
!!! info
If you are a very strict "Pythonista" you might dislike the style of the parameter name `tokenUrl` instead of `token_url`.

View File

@@ -20,9 +20,9 @@ It is not encrypted, so, anyone could recover the information from the contents.
But it's signed. So, when you receive a token that you emitted, you can verify that you actually emitted it.
That way, you can create a token with an expiration of, let's say, 1 week. And then when the user comes back the next day with the token, you know she/he is still signed into your system.
That way, you can create a token with an expiration of, let's say, 1 week. And then when the user comes back the next day with the token, you know she/he is still logged in to your system.
And after a week, the token will be expired and the user will not be authorized and will have to sign in again to get a new token. And if the user (or a third party) tried to modify the token to change the expiration, you would be able to discover it, because the signatures would not match.
After a week, the token will be expired and the user will not be authorized and will have to sign in again to get a new token. And if the user (or a third party) tried to modify the token to change the expiration, you would be able to discover it, because the signatures would not match.
If you want to play with JWT tokens and see how they work, check <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a>.
@@ -97,7 +97,7 @@ Import the tools we need from `passlib`.
Create a PassLib "context". This is what will be used to hash and verify passwords.
!!! tip
The PassLib context also has functionality to use different hashing algorithms, including deprecate old ones only to allow verifying them, etc.
The PassLib context also has functionality to use different hashing algorithms, including deprecated old ones only to allow verifying them, etc.
For example, you could use it to read and verify passwords generated by another system (like Django) but hash any new passwords with a different algorithm like Bcrypt.

View File

@@ -36,7 +36,7 @@ They are normally used to declare specific security permissions, for example:
In OAuth2 a "scope" is just a string that declares a specific permission required.
It doesn't matter if it has other characters like `:` or if it is a URL.
Those details are implementation specific.
For OAuth2 they are just strings.
@@ -166,7 +166,7 @@ For this simple example, we are going to just be completely insecure and return
This is something that you have to do yourself in your code, and make sure you use those JSON keys.
It's almost the only thing that you have to remember to do correctly yourself, to be compliant with the specifications.
For the rest, **FastAPI** handles it for you.
## Update the dependencies
@@ -177,7 +177,7 @@ We want to get the `current_user` *only* if this user is active.
So, we create an additional dependency `get_current_active_user` that in turn uses `get_current_user` as a dependency.
Both of these dependencies will just return an HTTP error if the user doesn't exists, or if is inactive.
Both of these dependencies will just return an HTTP error if the user doesn't exist, or if is inactive.
So, in our endpoint, we will only get a user if the user exists, was correctly authenticated, and is active:

View File

@@ -539,6 +539,9 @@ def read_user(user_id: int, db: Session = Depends(get_db)):
...
```
!!! info
If you need to connect to your relational database asynchronously, see [Async SQL (Relational) Databases](../advanced/async-sql-databases.md){.internal-link target=_blank}.
!!! note "Very Technical Details"
If you are curious and have a deep technical knowledge, you can check the very technical details of how this `async def` vs `def` is handled in the [Async](../async.md#very-technical-details){.internal-link target=_blank} docs.

View File

@@ -34,6 +34,9 @@ Write simple `assert` statements with the standard Python expressions that you n
**FastAPI** provides the same `starlette.testclient` as `fastapi.testclient` just as a convenience for you, the developer. But it comes directly from Starlette.
!!! tip
If you want to call `async` functions in your tests apart from sending requests to your FastAPI application (e.g. asynchronous database functions), have a look at the [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} in the advanced tutorial.
## Separating tests
In a real application, you probably would have your tests in a different file.

View File

@@ -4,6 +4,7 @@ site_url: https://fastapi.tiangolo.com/
theme:
name: material
palette:
scheme: preference
primary: teal
accent: amber
icon:
@@ -29,6 +30,7 @@ nav:
- it: /it/
- pt: /pt/
- ru: /ru/
- uk: /uk/
- zh: /zh/
- features.md
- python-types.md
@@ -109,6 +111,7 @@ nav:
- advanced/testing-events.md
- advanced/testing-dependencies.md
- advanced/testing-database.md
- advanced/async-tests.md
- advanced/settings.md
- advanced/conditional-openapi.md
- advanced/extending-openapi.md

View File

@@ -5,14 +5,14 @@
<em>FastAPI framework, alto desempeño, fácil de aprender, rápido de programar, listo para producción</em>
</p>
<p align="center">
<a href="https://travis-ci.com/tiangolo/fastapi" target="_blank">
<img src="https://travis-ci.com/tiangolo/fastapi.svg?branch=master" alt="Build Status">
<a href="https://github.com/tiangolo/fastapi/actions?query=workflow%3ATest" target="_blank">
<img src="https://github.com/tiangolo/fastapi/workflows/Test/badge.svg" alt="Test">
</a>
<a href="https://codecov.io/gh/tiangolo/fastapi" target="_blank">
<img src="https://img.shields.io/codecov/c/github/tiangolo/fastapi" alt="Coverage">
<img src="https://img.shields.io/codecov/c/github/tiangolo/fastapi?color=%2334D058" alt="Coverage">
</a>
<a href="https://pypi.org/project/fastapi" target="_blank">
<img src="https://badge.fury.io/py/fastapi.svg" alt="Package version">
<img src="https://img.shields.io/pypi/v/fastapi?color=%2334D058&label=pypi%20package" alt="Package version">
</a>
<a href="https://gitter.im/tiangolo/fastapi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank">
<img src="https://badges.gitter.im/tiangolo/fastapi.svg" alt="Join the chat at https://gitter.im/tiangolo/fastapi">

View File

@@ -4,6 +4,7 @@ site_url: https://fastapi.tiangolo.com/es/
theme:
name: material
palette:
scheme: preference
primary: teal
accent: amber
icon:
@@ -29,6 +30,7 @@ nav:
- it: /it/
- pt: /pt/
- ru: /ru/
- uk: /uk/
- zh: /zh/
- features.md
- python-types.md

View File

@@ -4,6 +4,7 @@ site_url: https://fastapi.tiangolo.com/it/
theme:
name: material
palette:
scheme: preference
primary: teal
accent: amber
icon:
@@ -29,6 +30,7 @@ nav:
- it: /it/
- pt: /pt/
- ru: /ru/
- uk: /uk/
- zh: /zh/
markdown_extensions:
- toc:

View File

@@ -5,14 +5,14 @@
<em>Framework FastAPI, alta performance, fácil de aprender, fácil de codar, pronto para produção</em>
</p>
<p align="center">
<a href="https://travis-ci.com/tiangolo/fastapi" target="_blank">
<img src="https://travis-ci.com/tiangolo/fastapi.svg?branch=master" alt="Build Status">
<a href="https://github.com/tiangolo/fastapi/actions?query=workflow%3ATest" target="_blank">
<img src="https://github.com/tiangolo/fastapi/workflows/Test/badge.svg" alt="Test">
</a>
<a href="https://codecov.io/gh/tiangolo/fastapi" target="_blank">
<img src="https://img.shields.io/codecov/c/github/tiangolo/fastapi" alt="Coverage">
<img src="https://img.shields.io/codecov/c/github/tiangolo/fastapi?color=%2334D058" alt="Coverage">
</a>
<a href="https://pypi.org/project/fastapi" target="_blank">
<img src="https://badge.fury.io/py/fastapi.svg" alt="Package version">
<img src="https://img.shields.io/pypi/v/fastapi?color=%2334D058&label=pypi%20package" alt="Package version">
</a>
<a href="https://gitter.im/tiangolo/fastapi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank">
<img src="https://badges.gitter.im/tiangolo/fastapi.svg" alt="Join the chat at https://gitter.im/tiangolo/fastapi">
@@ -33,7 +33,7 @@ Os recursos chave são:
* **Rápido**: alta performance, equivalente a **NodeJS** e **Go** (graças ao Starlette e Pydantic). [Um dos frameworks mais rápidos disponíveis](#performance).
* **Rápido para codar**: Aumenta a velocidade para desenvolver recursos entre 200% a 300%. *
* **Poucos bugs**: Reduz cerca de 40% de erros iduzidos por humanos (desenvolvedores). *
* **Poucos bugs**: Reduz cerca de 40% de erros induzidos por humanos (desenvolvedores). *
* **Intuitivo**: Grande suporte a _IDEs_. <abbr title="também conhecido como _auto-complete_, _autocompletion_, _IntelliSense_">_Auto-Complete_</abbr> em todos os lugares. Menos tempo debugando.
* **Fácil**: Projetado para ser fácil de aprender e usar. Menos tempo lendo documentação.
* **Enxuto**: Minimize duplicação de código. Múltiplos recursos para cada declaração de parâmetro. Menos bugs.

View File

@@ -4,6 +4,7 @@ site_url: https://fastapi.tiangolo.com/pt/
theme:
name: material
palette:
scheme: preference
primary: teal
accent: amber
icon:
@@ -29,6 +30,7 @@ nav:
- it: /it/
- pt: /pt/
- ru: /ru/
- uk: /uk/
- zh: /zh/
- features.md
- Tutorial - Guia de Usuário:

View File

@@ -4,6 +4,7 @@ site_url: https://fastapi.tiangolo.com/ru/
theme:
name: material
palette:
scheme: preference
primary: teal
accent: amber
icon:
@@ -29,6 +30,7 @@ nav:
- it: /it/
- pt: /pt/
- ru: /ru/
- uk: /uk/
- zh: /zh/
markdown_extensions:
- toc:

453
docs/uk/docs/index.md Normal file
View File

@@ -0,0 +1,453 @@
{!../../../docs/missing-translation.md!}
<p align="center">
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
</p>
<p align="center">
<em>FastAPI framework, high performance, easy to learn, fast to code, ready for production</em>
</p>
<p align="center">
<a href="https://github.com/tiangolo/fastapi/actions?query=workflow%3ATest" target="_blank">
<img src="https://github.com/tiangolo/fastapi/workflows/Test/badge.svg" alt="Test">
</a>
<a href="https://codecov.io/gh/tiangolo/fastapi" target="_blank">
<img src="https://img.shields.io/codecov/c/github/tiangolo/fastapi?color=%2334D058" alt="Coverage">
</a>
<a href="https://pypi.org/project/fastapi" target="_blank">
<img src="https://img.shields.io/pypi/v/fastapi?color=%2334D058&label=pypi%20package" alt="Package version">
</a>
<a href="https://gitter.im/tiangolo/fastapi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank">
<img src="https://badges.gitter.im/tiangolo/fastapi.svg" alt="Join the chat at https://gitter.im/tiangolo/fastapi">
</a>
</p>
---
**Documentation**: <a href="https://fastapi.tiangolo.com" target="_blank">https://fastapi.tiangolo.com</a>
**Source Code**: <a href="https://github.com/tiangolo/fastapi" target="_blank">https://github.com/tiangolo/fastapi</a>
---
FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.
The key features are:
* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance).
* **Fast to code**: Increase the speed to develop features by about 200% to 300%. *
* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. *
* **Intuitive**: Great editor support. <abbr title="also known as auto-complete, autocompletion, IntelliSense">Completion</abbr> everywhere. Less time debugging.
* **Easy**: Designed to be easy to use and learn. Less time reading docs.
* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
* **Robust**: Get production-ready code. With automatic interactive documentation.
* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (previously known as Swagger) and <a href="http://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>.
<small>* estimation based on tests on an internal development team, building production applications.</small>
## Opinions
"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._"
<div style="text-align: right; margin-right: 10%;">Kabir Khan - <strong>Microsoft</strong> <a href="https://github.com/tiangolo/fastapi/pull/26" target="_blank"><small>(ref)</small></a></div>
---
"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_"
<div style="text-align: right; margin-right: 10%;">Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://eng.uber.com/ludwig-v0-2/" target="_blank"><small>(ref)</small></a></div>
---
"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_"
<div style="text-align: right; margin-right: 10%;">Kevin Glisson, Marc Vilanova, Forest Monsen - <strong>Netflix</strong> <a href="https://netflixtechblog.com/introducing-dispatch-da4b8a2a8072" target="_blank"><small>(ref)</small></a></div>
---
"_Im over the moon excited about **FastAPI**. Its so fun!_"
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> podcast host</strong> <a href="https://twitter.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
---
"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._"
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="http://www.hug.rest/" target="_blank">Hug</a> creator</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
---
"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_"
"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_"
<div style="text-align: right; margin-right: 10%;">Ines Montani - Matthew Honnibal - <strong><a href="https://explosion.ai" target="_blank">Explosion AI</a> founders - <a href="https://spacy.io" target="_blank">spaCy</a> creators</strong> <a href="https://twitter.com/_inesmontani/status/1144173225322143744" target="_blank"><small>(ref)</small></a> - <a href="https://twitter.com/honnibal/status/1144031421859655680" target="_blank"><small>(ref)</small></a></div>
---
## **Typer**, the FastAPI of CLIs
<a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a>
If you are building a <abbr title="Command Line Interface">CLI</abbr> app to be used in the terminal instead of a web API, check out <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>.
**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀
## Requirements
Python 3.6+
FastAPI stands on the shoulders of giants:
* <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> for the web parts.
* <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a> for the data parts.
## Installation
<div class="termy">
```console
$ pip install fastapi
---> 100%
```
</div>
You will also need an ASGI server, for production such as <a href="http://www.uvicorn.org" class="external-link" target="_blank">Uvicorn</a> or <a href="https://gitlab.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>.
<div class="termy">
```console
$ pip install uvicorn
---> 100%
```
</div>
## Example
### Create it
* Create a file `main.py` with:
```Python
from typing import Optional
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
return {"item_id": item_id, "q": q}
```
<details markdown="1">
<summary>Or use <code>async def</code>...</summary>
If your code uses `async` / `await`, use `async def`:
```Python hl_lines="9 14"
from typing import Optional
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Optional[str] = None):
return {"item_id": item_id, "q": q}
```
**Note**:
If you don't know, check the _"In a hurry?"_ section about <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank">`async` and `await` in the docs</a>.
</details>
### Run it
Run the server with:
<div class="termy">
```console
$ uvicorn main:app --reload
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [28720]
INFO: Started server process [28722]
INFO: Waiting for application startup.
INFO: Application startup complete.
```
</div>
<details markdown="1">
<summary>About the command <code>uvicorn main:app --reload</code>...</summary>
The command `uvicorn main:app` refers to:
* `main`: the file `main.py` (the Python "module").
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`.
* `--reload`: make the server restart after code changes. Only do this for development.
</details>
### Check it
Open your browser at <a href="http://127.0.0.1:8000/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>.
You will see the JSON response as:
```JSON
{"item_id": 5, "q": "somequery"}
```
You already created an API that:
* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`.
* Both _paths_ take `GET` <em>operations</em> (also known as HTTP _methods_).
* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`.
* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`.
### Interactive API docs
Now go to <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
You will see the automatic interactive API documentation (provided by <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png)
### Alternative API docs
And now, go to <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
You will see the alternative automatic documentation (provided by <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
## Example upgrade
Now modify the file `main.py` to receive a body from a `PUT` request.
Declare the body using standard Python types, thanks to Pydantic.
```Python hl_lines="4 9 10 11 12 25 26 27"
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: Optional[bool] = None
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
return {"item_id": item_id, "q": q}
@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
return {"item_name": item.name, "item_id": item_id}
```
The server should reload automatically (because you added `--reload` to the `uvicorn` command above).
### Interactive API docs upgrade
Now go to <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
* The interactive API documentation will be automatically updated, including the new body:
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png)
* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API:
![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png)
* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen:
![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png)
### Alternative API docs upgrade
And now, go to <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
* The alternative documentation will also reflect the new query parameter and body:
![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png)
### Recap
In summary, you declare **once** the types of parameters, body, etc. as function parameters.
You do that with standard modern Python types.
You don't have to learn a new syntax, the methods or classes of a specific library, etc.
Just standard **Python 3.6+**.
For example, for an `int`:
```Python
item_id: int
```
or for a more complex `Item` model:
```Python
item: Item
```
...and with that single declaration you get:
* Editor support, including:
* Completion.
* Type checks.
* Validation of data:
* Automatic and clear errors when the data is invalid.
* Validation even for deeply nested JSON objects.
* <abbr title="also known as: serialization, parsing, marshalling">Conversion</abbr> of input data: coming from the network to Python data and types. Reading from:
* JSON.
* Path parameters.
* Query parameters.
* Cookies.
* Headers.
* Forms.
* Files.
* <abbr title="also known as: serialization, parsing, marshalling">Conversion</abbr> of output data: converting from Python data and types to network data (as JSON):
* Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc).
* `datetime` objects.
* `UUID` objects.
* Database models.
* ...and many more.
* Automatic interactive API documentation, including 2 alternative user interfaces:
* Swagger UI.
* ReDoc.
---
Coming back to the previous code example, **FastAPI** will:
* Validate that there is an `item_id` in the path for `GET` and `PUT` requests.
* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests.
* If it is not, the client will see a useful, clear error.
* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests.
* As the `q` parameter is declared with `= None`, it is optional.
* Without the `None` it would be required (as is the body in the case with `PUT`).
* For `PUT` requests to `/items/{item_id}`, Read the body as JSON:
* Check that it has a required attribute `name` that should be a `str`.
* Check that it has a required attribute `price` that has to be a `float`.
* Check that it has an optional attribute `is_offer`, that should be a `bool`, if present.
* All this would also work for deeply nested JSON objects.
* Convert from and to JSON automatically.
* Document everything with OpenAPI, that can be used by:
* Interactive documentation systems.
* Automatic client code generation systems, for many languages.
* Provide 2 interactive documentation web interfaces directly.
---
We just scratched the surface, but you already get the idea of how it all works.
Try changing the line with:
```Python
return {"item_name": item.name, "item_id": item_id}
```
...from:
```Python
... "item_name": item.name ...
```
...to:
```Python
... "item_price": item.price ...
```
...and see how your editor will auto-complete the attributes and know their types:
![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png)
For a more complete example including more features, see the <a href="https://fastapi.tiangolo.com/tutorial/">Tutorial - User Guide</a>.
**Spoiler alert**: the tutorial - user guide includes:
* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**.
* How to set **validation constraints** as `maximum_length` or `regex`.
* A very powerful and easy to use **<abbr title="also known as components, resources, providers, services, injectables">Dependency Injection</abbr>** system.
* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth.
* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic).
* Many extra features (thanks to Starlette) as:
* **WebSockets**
* **GraphQL**
* extremely easy tests based on `requests` and `pytest`
* **CORS**
* **Cookie Sessions**
* ...and more.
## Performance
Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">one of the fastest Python frameworks available</a>, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*)
To understand more about it, see the section <a href="https://fastapi.tiangolo.com/benchmarks/" class="internal-link" target="_blank">Benchmarks</a>.
## Optional Dependencies
Used by Pydantic:
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - for faster JSON <abbr title="converting the string that comes from an HTTP request into Python data">"parsing"</abbr>.
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email_validator</code></a> - for email validation.
Used by Starlette:
* <a href="http://docs.python-requests.org" target="_blank"><code>requests</code></a> - Required if you want to use the `TestClient`.
* <a href="https://github.com/Tinche/aiofiles" target="_blank"><code>aiofiles</code></a> - Required if you want to use `FileResponse` or `StaticFiles`.
* <a href="http://jinja.pocoo.org" target="_blank"><code>jinja2</code></a> - Required if you want to use the default template configuration.
* <a href="https://andrew-d.github.io/python-multipart/" target="_blank"><code>python-multipart</code></a> - Required if you want to support form <abbr title="converting the string that comes from an HTTP request into Python data">"parsing"</abbr>, with `request.form()`.
* <a href="https://pythonhosted.org/itsdangerous/" target="_blank"><code>itsdangerous</code></a> - Required for `SessionMiddleware` support.
* <a href="https://pyyaml.org/wiki/PyYAMLDocumentation" target="_blank"><code>pyyaml</code></a> - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI).
* <a href="https://graphene-python.org/" target="_blank"><code>graphene</code></a> - Required for `GraphQLApp` support.
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - Required if you want to use `UJSONResponse`.
Used by FastAPI / Starlette:
* <a href="http://www.uvicorn.org" target="_blank"><code>uvicorn</code></a> - for the server that loads and serves your application.
* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - Required if you want to use `ORJSONResponse`.
You can install all of these with `pip install fastapi[all]`.
## License
This project is licensed under the terms of the MIT license.

73
docs/uk/mkdocs.yml Normal file
View File

@@ -0,0 +1,73 @@
site_name: FastAPI
site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production
site_url: https://fastapi.tiangolo.com/uk/
theme:
name: material
palette:
scheme: preference
primary: teal
accent: amber
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
favicon: https://fastapi.tiangolo.com/img/favicon.png
language: uk
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
google_analytics:
- UA-133183413-1
- auto
plugins:
- search
- markdownextradata:
data: data
nav:
- FastAPI: index.md
- Languages:
- en: /
- es: /es/
- it: /it/
- pt: /pt/
- ru: /ru/
- uk: /uk/
- zh: /zh/
markdown_extensions:
- toc:
permalink: true
- markdown.extensions.codehilite:
guess_lang: false
- markdown_include.include:
base_path: docs
- admonition
- codehilite
- extra
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_div_format ''
- pymdownx.tabbed
extra:
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/typer
- icon: fontawesome/brands/twitter
link: https://twitter.com/tiangolo
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev
link: https://dev.to/tiangolo
- icon: fontawesome/brands/medium
link: https://medium.com/@tiangolo
- icon: fontawesome/solid/globe
link: https://tiangolo.com
extra_css:
- https://fastapi.tiangolo.com/css/termynal.css
- https://fastapi.tiangolo.com/css/custom.css
extra_javascript:
- https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js
- https://fastapi.tiangolo.com/js/termynal.js
- https://fastapi.tiangolo.com/js/custom.js
- https://fastapi.tiangolo.com/js/chat.js
- https://sidecar.gitter.im/dist/sidecar.v1.js

View File

@@ -5,14 +5,14 @@
<em>FastAPI 框架,高性能,易于学习,高效编码,生产可用</em>
</p>
<p align="center">
<a href="https://travis-ci.com/tiangolo/fastapi" target="_blank">
<img src="https://travis-ci.com/tiangolo/fastapi.svg?branch=master" alt="Build Status">
<a href="https://github.com/tiangolo/fastapi/actions?query=workflow%3ATest" target="_blank">
<img src="https://github.com/tiangolo/fastapi/workflows/Test/badge.svg" alt="Test">
</a>
<a href="https://codecov.io/gh/tiangolo/fastapi" target="_blank">
<img src="https://img.shields.io/codecov/c/github/tiangolo/fastapi" alt="Coverage">
<img src="https://img.shields.io/codecov/c/github/tiangolo/fastapi?color=%2334D058" alt="Coverage">
</a>
<a href="https://pypi.org/project/fastapi" target="_blank">
<img src="https://badge.fury.io/py/fastapi.svg" alt="Package version">
<img src="https://img.shields.io/pypi/v/fastapi?color=%2334D058&label=pypi%20package" alt="Package version">
</a>
<a href="https://gitter.im/tiangolo/fastapi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank">
<img src="https://badges.gitter.im/tiangolo/fastapi.svg" alt="Join the chat at https://gitter.im/tiangolo/fastapi">

View File

@@ -4,6 +4,7 @@ site_url: https://fastapi.tiangolo.com/zh/
theme:
name: material
palette:
scheme: preference
primary: teal
accent: amber
icon:
@@ -29,6 +30,7 @@ nav:
- it: /it/
- pt: /pt/
- ru: /ru/
- uk: /uk/
- zh: /zh/
- features.md
- python-types.md

View File

View File

@@ -0,0 +1,8 @@
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Tomato"}

View File

@@ -0,0 +1,12 @@
import pytest
from httpx import AsyncClient
from .main import app
@pytest.mark.asyncio
async def test_root():
async with AsyncClient(app=app, base_url="http://test") as ac:
response = await ac.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Tomato"}

View File

@@ -1,4 +1,5 @@
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
@@ -10,6 +11,6 @@ app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
@app.get("/items/{id}")
@app.get("/items/{id}", response_class=HTMLResponse)
async def read_item(request: Request, id: str):
return templates.TemplateResponse("item.html", {"request": request, "id": id})

View File

@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.59.0"
__version__ = "0.60.2"
from starlette import status

View File

@@ -32,7 +32,7 @@ class FastAPI(Starlette):
self,
*,
debug: bool = False,
routes: List[BaseRoute] = None,
routes: Optional[List[BaseRoute]] = None,
title: str = "FastAPI",
description: str = "",
version: str = "0.1.0",
@@ -44,14 +44,16 @@ class FastAPI(Starlette):
redoc_url: Optional[str] = "/redoc",
swagger_ui_oauth2_redirect_url: Optional[str] = "/docs/oauth2-redirect",
swagger_ui_init_oauth: Optional[dict] = None,
middleware: Sequence[Middleware] = None,
exception_handlers: Dict[Union[int, Type[Exception]], Callable] = None,
on_startup: Sequence[Callable] = None,
on_shutdown: Sequence[Callable] = None,
middleware: Optional[Sequence[Middleware]] = None,
exception_handlers: Optional[
Dict[Union[int, Type[Exception]], Callable]
] = None,
on_startup: Optional[Sequence[Callable]] = None,
on_shutdown: Optional[Sequence[Callable]] = None,
openapi_prefix: str = "",
root_path: str = "",
root_path_in_servers: bool = True,
**extra: Dict[str, Any],
**extra: Any,
) -> None:
self.default_response_class = default_response_class
self._debug = debug
@@ -115,11 +117,8 @@ class FastAPI(Starlette):
def setup(self) -> None:
if self.openapi_url:
server_urls = set()
for server_data in self.servers:
url = server_data.get("url")
if url:
server_urls.add(url)
urls = (server_data.get("url") for server_data in self.servers)
server_urls = {url for url in urls if url}
async def openapi(req: Request) -> JSONResponse:
root_path = req.scope.get("root_path", "").rstrip("/")
@@ -187,27 +186,27 @@ class FastAPI(Starlette):
path: str,
endpoint: Callable,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
methods: List[str] = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
methods: Optional[List[str]] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
) -> None:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -242,27 +241,27 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
methods: List[str] = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
methods: Optional[List[str]] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -299,11 +298,11 @@ class FastAPI(Starlette):
return decorator
def add_api_websocket_route(
self, path: str, endpoint: Callable, name: str = None
self, path: str, endpoint: Callable, name: Optional[str] = None
) -> None:
self.router.add_api_websocket_route(path, endpoint, name=name)
def websocket(self, path: str, name: str = None) -> Callable:
def websocket(self, path: str, name: Optional[str] = None) -> Callable:
def decorator(func: Callable) -> Callable:
self.add_api_websocket_route(path, func, name=name)
return func
@@ -315,9 +314,9 @@ class FastAPI(Starlette):
router: routing.APIRouter,
*,
prefix: str = "",
tags: List[str] = None,
dependencies: Sequence[Depends] = None,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
default_response_class: Optional[Type[Response]] = None,
) -> None:
self.router.include_router(
@@ -334,27 +333,27 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
callbacks: List[routing.APIRoute] = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
callbacks: Optional[List[routing.APIRoute]] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -388,27 +387,27 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
callbacks: List[routing.APIRoute] = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
callbacks: Optional[List[routing.APIRoute]] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -442,27 +441,27 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
callbacks: List[routing.APIRoute] = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
callbacks: Optional[List[routing.APIRoute]] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -496,27 +495,27 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
callbacks: List[routing.APIRoute] = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
callbacks: Optional[List[routing.APIRoute]] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -550,27 +549,27 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
callbacks: List[routing.APIRoute] = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
callbacks: Optional[List[routing.APIRoute]] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -604,27 +603,27 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
callbacks: List[routing.APIRoute] = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
callbacks: Optional[List[routing.APIRoute]] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -658,27 +657,27 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
callbacks: List[routing.APIRoute] = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
callbacks: Optional[List[routing.APIRoute]] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -712,27 +711,27 @@ class FastAPI(Starlette):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
callbacks: List[routing.APIRoute] = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
callbacks: Optional[List[routing.APIRoute]] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover

View File

@@ -1,4 +1,4 @@
from typing import Callable, List, Sequence
from typing import Callable, List, Optional, Sequence
from fastapi.security.base import SecurityBase
@@ -12,7 +12,9 @@ param_supported_types = (str, int, float, bool)
class SecurityRequirement:
def __init__(self, security_scheme: SecurityBase, scopes: Sequence[str] = None):
def __init__(
self, security_scheme: SecurityBase, scopes: Optional[Sequence[str]] = None
):
self.security_scheme = security_scheme
self.scopes = scopes
@@ -21,23 +23,23 @@ class Dependant:
def __init__(
self,
*,
path_params: List[ModelField] = None,
query_params: List[ModelField] = None,
header_params: List[ModelField] = None,
cookie_params: List[ModelField] = None,
body_params: List[ModelField] = None,
dependencies: List["Dependant"] = None,
security_schemes: List[SecurityRequirement] = None,
name: str = None,
call: Callable = None,
request_param_name: str = None,
websocket_param_name: str = None,
response_param_name: str = None,
background_tasks_param_name: str = None,
security_scopes_param_name: str = None,
security_scopes: List[str] = None,
path_params: Optional[List[ModelField]] = None,
query_params: Optional[List[ModelField]] = None,
header_params: Optional[List[ModelField]] = None,
cookie_params: Optional[List[ModelField]] = None,
body_params: Optional[List[ModelField]] = None,
dependencies: Optional[List["Dependant"]] = None,
security_schemes: Optional[List[SecurityRequirement]] = None,
name: Optional[str] = None,
call: Optional[Callable] = None,
request_param_name: Optional[str] = None,
websocket_param_name: Optional[str] = None,
response_param_name: Optional[str] = None,
background_tasks_param_name: Optional[str] = None,
security_scopes_param_name: Optional[str] = None,
security_scopes: Optional[List[str]] = None,
use_cache: bool = True,
path: str = None,
path: Optional[str] = None,
) -> None:
self.path_params = path_params or []
self.query_params = query_params or []

View File

@@ -24,6 +24,7 @@ from fastapi.concurrency import (
contextmanager_in_threadpool,
)
from fastapi.dependencies.models import Dependant, SecurityRequirement
from fastapi.logger import logger
from fastapi.security.base import SecurityBase
from fastapi.security.oauth2 import OAuth2, SecurityScopes
from fastapi.security.open_id_connect_url import OpenIdConnect
@@ -96,8 +97,44 @@ sequence_shape_to_type = {
}
multipart_not_installed_error = (
'Form data requires "python-multipart" to be installed. \n'
'You can install "python-multipart" with: \n\n'
"pip install python-multipart\n"
)
multipart_incorrect_install_error = (
'Form data requires "python-multipart" to be installed. '
'It seems you installed "multipart" instead. \n'
'You can remove "multipart" with: \n\n'
"pip uninstall multipart\n\n"
'And then install "python-multipart" with: \n\n'
"pip install python-multipart\n"
)
def check_file_field(field: ModelField) -> None:
field_info = get_field_info(field)
if isinstance(field_info, params.Form):
try:
# __version__ is available in both multiparts, and can be mocked
from multipart import __version__
assert __version__
try:
# parse_options_header is only available in the right multlipart
from multipart.multipart import parse_options_header
assert parse_options_header
except ImportError:
logger.error(multipart_incorrect_install_error)
raise RuntimeError(multipart_incorrect_install_error)
except ImportError:
logger.error(multipart_not_installed_error)
raise RuntimeError(multipart_not_installed_error)
def get_param_sub_dependant(
*, param: inspect.Parameter, path: str, security_scopes: List[str] = None
*, param: inspect.Parameter, path: str, security_scopes: Optional[List[str]] = None
) -> Dependant:
depends: params.Depends = param.default
if depends.dependency:
@@ -125,8 +162,8 @@ def get_sub_dependant(
depends: params.Depends,
dependency: Callable,
path: str,
name: str = None,
security_scopes: List[str] = None,
name: Optional[str] = None,
security_scopes: Optional[List[str]] = None,
) -> Dependant:
security_requirement = None
security_scopes = security_scopes or []
@@ -157,7 +194,10 @@ CacheKey = Tuple[Optional[Callable], Tuple[str, ...]]
def get_flat_dependant(
dependant: Dependant, *, skip_repeats: bool = False, visited: List[CacheKey] = None
dependant: Dependant,
*,
skip_repeats: bool = False,
visited: Optional[List[CacheKey]] = None,
) -> Dependant:
if visited is None:
visited = []
@@ -246,7 +286,9 @@ def get_typed_signature(call: Callable) -> inspect.Signature:
def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) -> Any:
annotation = param.annotation
if isinstance(annotation, str):
annotation = ForwardRef(annotation)
# Temporary ignore type
# Ref: https://github.com/samuelcolvin/pydantic/issues/1738
annotation = ForwardRef(annotation) # type: ignore
annotation = evaluate_forwardref(annotation, globalns, globalns)
return annotation
@@ -267,8 +309,8 @@ def get_dependant(
*,
path: str,
call: Callable,
name: str = None,
security_scopes: List[str] = None,
name: Optional[str] = None,
security_scopes: Optional[List[str]] = None,
use_cache: bool = True,
) -> Dependant:
path_param_names = get_path_param_names(path)
@@ -283,8 +325,6 @@ def get_dependant(
param=param, path=path, security_scopes=security_scopes
)
dependant.dependencies.append(sub_dependant)
for param_name, param in signature_params.items():
if isinstance(param.default, params.Depends):
continue
if add_non_field_param_to_dependency(param=param, dependant=dependant):
continue
@@ -348,7 +388,7 @@ def get_param_field(
param: inspect.Parameter,
param_name: str,
default_field_info: Type[params.Param] = params.Param,
force_type: params.ParamTypes = None,
force_type: Optional[params.ParamTypes] = None,
ignore_default: bool = False,
) -> ModelField:
default_value = Required
@@ -456,10 +496,10 @@ async def solve_dependencies(
request: Union[Request, WebSocket],
dependant: Dependant,
body: Optional[Union[Dict[str, Any], FormData]] = None,
background_tasks: BackgroundTasks = None,
response: Response = None,
dependency_overrides_provider: Any = None,
dependency_cache: Dict[Tuple[Callable, Tuple[str]], Any] = None,
background_tasks: Optional[BackgroundTasks] = None,
response: Optional[Response] = None,
dependency_overrides_provider: Optional[Any] = None,
dependency_cache: Optional[Dict[Tuple[Callable, Tuple[str]], Any]] = None,
) -> Tuple[
Dict[str, Any],
List[ErrorWrapper],
@@ -653,7 +693,7 @@ async def request_body_to_args(
else:
loc = ("body", field.alias)
value: Any = None
value: Optional[Any] = None
if received_body is not None:
if (
field.shape in sequence_shapes or field.type_ in sequence_types
@@ -730,9 +770,8 @@ def get_schema_compatible_field(*, field: ModelField) -> ModelField:
default=field.default,
required=field.required,
alias=field.alias,
field_info=field.field_info if PYDANTIC_1 else field.schema, # type: ignore
field_info=get_field_info(field),
)
return out_field
@@ -743,9 +782,11 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
first_param = flat_dependant.body_params[0]
field_info = get_field_info(first_param)
embed = getattr(field_info, "embed", None)
body_param_names_set = set([param.name for param in flat_dependant.body_params])
body_param_names_set = {param.name for param in flat_dependant.body_params}
if len(body_param_names_set) == 1 and not embed:
return get_schema_compatible_field(field=first_param)
final_field = get_schema_compatible_field(field=first_param)
check_file_field(final_field)
return final_field
# If one field requires to embed, all have to be embedded
# in case a sub-dependency is evaluated with a single unique body field
# That is combined (embedded) with other body fields
@@ -776,10 +817,12 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
]
if len(set(body_param_media_types)) == 1:
BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0]
return create_response_field(
final_field = create_response_field(
name="body",
type_=BodyModel,
required=required,
alias="body",
field_info=BodyFieldInfo(**BodyFieldInfo_kwargs),
)
check_file_field(final_field)
return final_field

View File

@@ -1,7 +1,8 @@
from collections import defaultdict
from enum import Enum
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Set, Tuple, Union
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
from fastapi.logger import logger
from fastapi.utils import PYDANTIC_1
@@ -15,12 +16,9 @@ DictIntStrAny = Dict[Union[int, str], Any]
def generate_encoders_by_class_tuples(
type_encoder_map: Dict[Any, Callable]
) -> Dict[Callable, Tuple]:
encoders_by_classes: Dict[Callable, List] = {}
encoders_by_class_tuples: Dict[Callable, Tuple] = defaultdict(tuple)
for type_, encoder in type_encoder_map.items():
encoders_by_classes.setdefault(encoder, []).append(type_)
encoders_by_class_tuples: Dict[Callable, Tuple] = {}
for encoder, classes in encoders_by_classes.items():
encoders_by_class_tuples[encoder] = tuple(classes)
encoders_by_class_tuples[encoder] += (type_,)
return encoders_by_class_tuples
@@ -29,10 +27,10 @@ encoders_by_class_tuples = generate_encoders_by_class_tuples(ENCODERS_BY_TYPE)
def jsonable_encoder(
obj: Any,
include: Union[SetIntStr, DictIntStrAny] = None,
include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
exclude: Union[SetIntStr, DictIntStrAny] = set(),
by_alias: bool = True,
skip_defaults: bool = None,
skip_defaults: Optional[bool] = None,
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
@@ -50,7 +48,7 @@ def jsonable_encoder(
if exclude is not None and not isinstance(exclude, set):
exclude = set(exclude)
if isinstance(obj, BaseModel):
encoder = getattr(obj.Config, "json_encoders", {})
encoder = getattr(obj.__config__, "json_encoders", {})
if custom_encoder:
encoder.update(custom_encoder)
if PYDANTIC_1:

View File

@@ -1,4 +1,4 @@
from typing import Any, Sequence
from typing import Any, Dict, Optional, Sequence
from fastapi.utils import PYDANTIC_1
from pydantic import ValidationError, create_model
@@ -10,7 +10,10 @@ from starlette.websockets import WebSocket
class HTTPException(StarletteHTTPException):
def __init__(
self, status_code: int, detail: Any = None, headers: dict = None
self,
status_code: int,
detail: Any = None,
headers: Optional[Dict[str, Any]] = None,
) -> None:
super().__init__(status_code=status_code, detail=detail)
self.headers = headers

View File

@@ -1,3 +1,3 @@
METHODS_WITH_BODY = set(("POST", "PUT", "DELETE", "PATCH"))
STATUS_CODES_WITH_NO_BODY = set((100, 101, 102, 103, 204, 304))
METHODS_WITH_BODY = {"GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"}
STATUS_CODES_WITH_NO_BODY = {100, 101, 102, 103, 204, 304}
REF_PREFIX = "#/components/schemas/"

View File

@@ -9,8 +9,8 @@ def get_swagger_ui_html(
*,
openapi_url: str,
title: str,
swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui-bundle.js",
swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css",
swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3.30.0/swagger-ui-bundle.js",
swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3.30.0/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,

View File

@@ -329,7 +329,7 @@ def get_openapi(
title: str,
version: str,
openapi_version: str = "3.0.2",
description: str = None,
description: Optional[str] = None,
routes: Sequence[BaseRoute],
tags: Optional[List[Dict[str, Any]]] = None,
servers: Optional[List[Dict[str, Union[str, Any]]]] = None,

View File

@@ -1,4 +1,4 @@
from typing import Any, Callable, Sequence
from typing import Any, Callable, Optional, Sequence
from fastapi import params
@@ -6,17 +6,17 @@ from fastapi import params
def Path( # noqa: N802
default: Any,
*,
alias: str = None,
title: str = None,
description: str = None,
gt: float = None,
ge: float = None,
lt: float = None,
le: float = None,
min_length: int = None,
max_length: int = None,
regex: str = None,
deprecated: bool = None,
alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
regex: Optional[str] = None,
deprecated: Optional[bool] = None,
**extra: Any,
) -> Any:
return params.Path(
@@ -39,17 +39,17 @@ def Path( # noqa: N802
def Query( # noqa: N802
default: Any,
*,
alias: str = None,
title: str = None,
description: str = None,
gt: float = None,
ge: float = None,
lt: float = None,
le: float = None,
min_length: int = None,
max_length: int = None,
regex: str = None,
deprecated: bool = None,
alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
regex: Optional[str] = None,
deprecated: Optional[bool] = None,
**extra: Any,
) -> Any:
return params.Query(
@@ -72,18 +72,18 @@ def Query( # noqa: N802
def Header( # noqa: N802
default: Any,
*,
alias: str = None,
alias: Optional[str] = None,
convert_underscores: bool = True,
title: str = None,
description: str = None,
gt: float = None,
ge: float = None,
lt: float = None,
le: float = None,
min_length: int = None,
max_length: int = None,
regex: str = None,
deprecated: bool = None,
title: Optional[str] = None,
description: Optional[str] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
regex: Optional[str] = None,
deprecated: Optional[bool] = None,
**extra: Any,
) -> Any:
return params.Header(
@@ -107,17 +107,17 @@ def Header( # noqa: N802
def Cookie( # noqa: N802
default: Any,
*,
alias: str = None,
title: str = None,
description: str = None,
gt: float = None,
ge: float = None,
lt: float = None,
le: float = None,
min_length: int = None,
max_length: int = None,
regex: str = None,
deprecated: bool = None,
alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
regex: Optional[str] = None,
deprecated: Optional[bool] = None,
**extra: Any,
) -> Any:
return params.Cookie(
@@ -142,16 +142,16 @@ def Body( # noqa: N802
*,
embed: bool = False,
media_type: str = "application/json",
alias: str = None,
title: str = None,
description: str = None,
gt: float = None,
ge: float = None,
lt: float = None,
le: float = None,
min_length: int = None,
max_length: int = None,
regex: str = None,
alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
regex: Optional[str] = None,
**extra: Any,
) -> Any:
return params.Body(
@@ -176,16 +176,16 @@ def Form( # noqa: N802
default: Any,
*,
media_type: str = "application/x-www-form-urlencoded",
alias: str = None,
title: str = None,
description: str = None,
gt: float = None,
ge: float = None,
lt: float = None,
le: float = None,
min_length: int = None,
max_length: int = None,
regex: str = None,
alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
regex: Optional[str] = None,
**extra: Any,
) -> Any:
return params.Form(
@@ -209,16 +209,16 @@ def File( # noqa: N802
default: Any,
*,
media_type: str = "multipart/form-data",
alias: str = None,
title: str = None,
description: str = None,
gt: float = None,
ge: float = None,
lt: float = None,
le: float = None,
min_length: int = None,
max_length: int = None,
regex: str = None,
alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
regex: Optional[str] = None,
**extra: Any,
) -> Any:
return params.File(
@@ -239,12 +239,15 @@ def File( # noqa: N802
def Depends( # noqa: N802
dependency: Callable = None, *, use_cache: bool = True
dependency: Optional[Callable] = None, *, use_cache: bool = True
) -> Any:
return params.Depends(dependency=dependency, use_cache=use_cache)
def Security( # noqa: N802
dependency: Callable = None, *, scopes: Sequence[str] = None, use_cache: bool = True
dependency: Optional[Callable] = None,
*,
scopes: Optional[Sequence[str]] = None,
use_cache: bool = True,
) -> Any:
return params.Security(dependency=dependency, scopes=scopes, use_cache=use_cache)

View File

@@ -1,5 +1,5 @@
from enum import Enum
from typing import Any, Callable, Sequence
from typing import Any, Callable, Optional, Sequence
try:
from pydantic.fields import FieldInfo
@@ -22,17 +22,17 @@ class Param(FieldInfo):
self,
default: Any,
*,
alias: str = None,
title: str = None,
description: str = None,
gt: float = None,
ge: float = None,
lt: float = None,
le: float = None,
min_length: int = None,
max_length: int = None,
regex: str = None,
deprecated: bool = None,
alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
regex: Optional[str] = None,
deprecated: Optional[bool] = None,
**extra: Any,
):
self.deprecated = deprecated
@@ -62,17 +62,17 @@ class Path(Param):
self,
default: Any,
*,
alias: str = None,
title: str = None,
description: str = None,
gt: float = None,
ge: float = None,
lt: float = None,
le: float = None,
min_length: int = None,
max_length: int = None,
regex: str = None,
deprecated: bool = None,
alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
regex: Optional[str] = None,
deprecated: Optional[bool] = None,
**extra: Any,
):
self.in_ = self.in_
@@ -100,17 +100,17 @@ class Query(Param):
self,
default: Any,
*,
alias: str = None,
title: str = None,
description: str = None,
gt: float = None,
ge: float = None,
lt: float = None,
le: float = None,
min_length: int = None,
max_length: int = None,
regex: str = None,
deprecated: bool = None,
alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
regex: Optional[str] = None,
deprecated: Optional[bool] = None,
**extra: Any,
):
super().__init__(
@@ -137,18 +137,18 @@ class Header(Param):
self,
default: Any,
*,
alias: str = None,
alias: Optional[str] = None,
convert_underscores: bool = True,
title: str = None,
description: str = None,
gt: float = None,
ge: float = None,
lt: float = None,
le: float = None,
min_length: int = None,
max_length: int = None,
regex: str = None,
deprecated: bool = None,
title: Optional[str] = None,
description: Optional[str] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
regex: Optional[str] = None,
deprecated: Optional[bool] = None,
**extra: Any,
):
self.convert_underscores = convert_underscores
@@ -176,17 +176,17 @@ class Cookie(Param):
self,
default: Any,
*,
alias: str = None,
title: str = None,
description: str = None,
gt: float = None,
ge: float = None,
lt: float = None,
le: float = None,
min_length: int = None,
max_length: int = None,
regex: str = None,
deprecated: bool = None,
alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
regex: Optional[str] = None,
deprecated: Optional[bool] = None,
**extra: Any,
):
super().__init__(
@@ -213,16 +213,16 @@ class Body(FieldInfo):
*,
embed: bool = False,
media_type: str = "application/json",
alias: str = None,
title: str = None,
description: str = None,
gt: float = None,
ge: float = None,
lt: float = None,
le: float = None,
min_length: int = None,
max_length: int = None,
regex: str = None,
alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
regex: Optional[str] = None,
**extra: Any,
):
self.embed = embed
@@ -252,16 +252,16 @@ class Form(Body):
default: Any,
*,
media_type: str = "application/x-www-form-urlencoded",
alias: str = None,
title: str = None,
description: str = None,
gt: float = None,
ge: float = None,
lt: float = None,
le: float = None,
min_length: int = None,
max_length: int = None,
regex: str = None,
alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
regex: Optional[str] = None,
**extra: Any,
):
super().__init__(
@@ -288,16 +288,16 @@ class File(Form):
default: Any,
*,
media_type: str = "multipart/form-data",
alias: str = None,
title: str = None,
description: str = None,
gt: float = None,
ge: float = None,
lt: float = None,
le: float = None,
min_length: int = None,
max_length: int = None,
regex: str = None,
alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
regex: Optional[str] = None,
**extra: Any,
):
super().__init__(
@@ -318,7 +318,9 @@ class File(Form):
class Depends:
def __init__(self, dependency: Callable = None, *, use_cache: bool = True):
def __init__(
self, dependency: Optional[Callable] = None, *, use_cache: bool = True
):
self.dependency = dependency
self.use_cache = use_cache
@@ -331,9 +333,9 @@ class Depends:
class Security(Depends):
def __init__(
self,
dependency: Callable = None,
dependency: Optional[Callable] = None,
*,
scopes: Sequence[str] = None,
scopes: Optional[Sequence[str]] = None,
use_cache: bool = True,
):
super().__init__(dependency=dependency, use_cache=use_cache)

View File

@@ -93,9 +93,9 @@ def _prepare_response_content(
async def serialize_response(
*,
field: ModelField = None,
field: Optional[ModelField] = None,
response_content: Any,
include: Union[SetIntStr, DictIntStrAny] = None,
include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
exclude: Union[SetIntStr, DictIntStrAny] = set(),
by_alias: bool = True,
exclude_unset: bool = False,
@@ -151,17 +151,17 @@ async def run_endpoint_function(
def get_request_handler(
dependant: Dependant,
body_field: ModelField = None,
body_field: Optional[ModelField] = None,
status_code: int = 200,
response_class: Type[Response] = JSONResponse,
response_field: ModelField = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
response_field: Optional[ModelField] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
dependency_overrides_provider: Any = None,
dependency_overrides_provider: Optional[Any] = None,
) -> Callable:
assert dependant.call is not None, "dependant.call must be a function"
is_coroutine = asyncio.iscoroutinefunction(dependant.call)
@@ -226,7 +226,7 @@ def get_request_handler(
def get_websocket_app(
dependant: Dependant, dependency_overrides_provider: Any = None
dependant: Dependant, dependency_overrides_provider: Optional[Any] = None
) -> Callable:
async def app(websocket: WebSocket) -> None:
solved_result = await solve_dependencies(
@@ -250,8 +250,8 @@ class APIWebSocketRoute(routing.WebSocketRoute):
path: str,
endpoint: Callable,
*,
name: str = None,
dependency_overrides_provider: Any = None,
name: Optional[str] = None,
dependency_overrides_provider: Optional[Any] = None,
) -> None:
self.path = path
self.endpoint = endpoint
@@ -272,19 +272,19 @@ class APIRoute(routing.Route):
path: str,
endpoint: Callable,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
name: str = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
name: Optional[str] = None,
methods: Optional[Union[Set[str], List[str]]] = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_exclude_unset: bool = False,
@@ -292,7 +292,7 @@ class APIRoute(routing.Route):
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Optional[Type[Response]] = None,
dependency_overrides_provider: Any = None,
dependency_overrides_provider: Optional[Any] = None,
callbacks: Optional[List["APIRoute"]] = None,
) -> None:
# normalise enums e.g. http.HTTPStatus
@@ -401,14 +401,14 @@ class APIRoute(routing.Route):
class APIRouter(routing.Router):
def __init__(
self,
routes: List[routing.BaseRoute] = None,
routes: Optional[List[routing.BaseRoute]] = None,
redirect_slashes: bool = True,
default: ASGIApp = None,
dependency_overrides_provider: Any = None,
default: Optional[ASGIApp] = None,
dependency_overrides_provider: Optional[Any] = None,
route_class: Type[APIRoute] = APIRoute,
default_response_class: Type[Response] = None,
on_startup: Sequence[Callable] = None,
on_shutdown: Sequence[Callable] = None,
default_response_class: Optional[Type[Response]] = None,
on_startup: Optional[Sequence[Callable]] = None,
on_shutdown: Optional[Sequence[Callable]] = None,
) -> None:
super().__init__(
routes=routes,
@@ -426,29 +426,29 @@ class APIRouter(routing.Router):
path: str,
endpoint: Callable,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
methods: Optional[Union[Set[str], List[str]]] = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
route_class_override: Optional[Type[APIRoute]] = None,
callbacks: List[APIRoute] = None,
callbacks: Optional[List[APIRoute]] = None,
) -> None:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -487,28 +487,28 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
methods: List[str] = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
methods: Optional[List[str]] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
callbacks: List[APIRoute] = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
callbacks: Optional[List[APIRoute]] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -546,7 +546,7 @@ class APIRouter(routing.Router):
return decorator
def add_api_websocket_route(
self, path: str, endpoint: Callable, name: str = None
self, path: str, endpoint: Callable, name: Optional[str] = None
) -> None:
route = APIWebSocketRoute(
path,
@@ -556,7 +556,7 @@ class APIRouter(routing.Router):
)
self.routes.append(route)
def websocket(self, path: str, name: str = None) -> Callable:
def websocket(self, path: str, name: Optional[str] = None) -> Callable:
def decorator(func: Callable) -> Callable:
self.add_api_websocket_route(path, func, name=name)
return func
@@ -568,9 +568,9 @@ class APIRouter(routing.Router):
router: "APIRouter",
*,
prefix: str = "",
tags: List[str] = None,
dependencies: Sequence[params.Depends] = None,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
default_response_class: Optional[Type[Response]] = None,
) -> None:
if prefix:
@@ -643,27 +643,27 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
callbacks: List[APIRoute] = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
callbacks: Optional[List[APIRoute]] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -698,27 +698,27 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
callbacks: List[APIRoute] = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
callbacks: Optional[List[APIRoute]] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -753,27 +753,27 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
callbacks: List[APIRoute] = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
callbacks: Optional[List[APIRoute]] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -808,27 +808,27 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
callbacks: List[APIRoute] = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
callbacks: Optional[List[APIRoute]] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -863,27 +863,27 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
callbacks: List[APIRoute] = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
callbacks: Optional[List[APIRoute]] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -918,27 +918,27 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
callbacks: List[APIRoute] = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
callbacks: Optional[List[APIRoute]] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -973,27 +973,27 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
callbacks: List[APIRoute] = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
callbacks: Optional[List[APIRoute]] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover
@@ -1028,27 +1028,27 @@ class APIRouter(routing.Router):
self,
path: str,
*,
response_model: Type[Any] = None,
response_model: Optional[Type[Any]] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
tags: Optional[List[str]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
response_model_by_alias: bool = True,
response_model_skip_defaults: bool = None,
response_model_skip_defaults: Optional[bool] = None,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = None,
name: str = None,
callbacks: List[APIRoute] = None,
response_class: Optional[Type[Response]] = None,
name: Optional[str] = None,
callbacks: Optional[List[APIRoute]] = None,
) -> Callable:
if response_model_skip_defaults is not None:
warning_response_model_skip_defaults_deprecated() # pragma: nocover

View File

@@ -12,7 +12,9 @@ class APIKeyBase(SecurityBase):
class APIKeyQuery(APIKeyBase):
def __init__(self, *, name: str, scheme_name: str = None, auto_error: bool = True):
def __init__(
self, *, name: str, scheme_name: Optional[str] = None, auto_error: bool = True
):
self.model: APIKey = APIKey(**{"in": APIKeyIn.query}, name=name)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
@@ -30,7 +32,9 @@ class APIKeyQuery(APIKeyBase):
class APIKeyHeader(APIKeyBase):
def __init__(self, *, name: str, scheme_name: str = None, auto_error: bool = True):
def __init__(
self, *, name: str, scheme_name: Optional[str] = None, auto_error: bool = True
):
self.model: APIKey = APIKey(**{"in": APIKeyIn.header}, name=name)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
@@ -48,7 +52,9 @@ class APIKeyHeader(APIKeyBase):
class APIKeyCookie(APIKeyBase):
def __init__(self, *, name: str, scheme_name: str = None, auto_error: bool = True):
def __init__(
self, *, name: str, scheme_name: Optional[str] = None, auto_error: bool = True
):
self.model: APIKey = APIKey(**{"in": APIKeyIn.cookie}, name=name)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error

View File

@@ -24,7 +24,7 @@ class HTTPAuthorizationCredentials(BaseModel):
class HTTPBase(SecurityBase):
def __init__(
self, *, scheme: str, scheme_name: str = None, auto_error: bool = True
self, *, scheme: str, scheme_name: Optional[str] = None, auto_error: bool = True
):
self.model = HTTPBaseModel(scheme=scheme)
self.scheme_name = scheme_name or self.__class__.__name__
@@ -47,7 +47,11 @@ class HTTPBase(SecurityBase):
class HTTPBasic(HTTPBase):
def __init__(
self, *, scheme_name: str = None, realm: str = None, auto_error: bool = True
self,
*,
scheme_name: Optional[str] = None,
realm: Optional[str] = None,
auto_error: bool = True,
):
self.model = HTTPBaseModel(scheme="basic")
self.scheme_name = scheme_name or self.__class__.__name__
@@ -82,7 +86,7 @@ class HTTPBasic(HTTPBase):
except (ValueError, UnicodeDecodeError, binascii.Error):
raise invalid_user_credentials_exc
username, separator, password = data.partition(":")
if not (separator):
if not separator:
raise invalid_user_credentials_exc
return HTTPBasicCredentials(username=username, password=password)
@@ -91,8 +95,8 @@ class HTTPBearer(HTTPBase):
def __init__(
self,
*,
bearerFormat: str = None,
scheme_name: str = None,
bearerFormat: Optional[str] = None,
scheme_name: Optional[str] = None,
auto_error: bool = True,
):
self.model = HTTPBearerModel(bearerFormat=bearerFormat)
@@ -123,7 +127,7 @@ class HTTPBearer(HTTPBase):
class HTTPDigest(HTTPBase):
def __init__(self, *, scheme_name: str = None, auto_error: bool = True):
def __init__(self, *, scheme_name: Optional[str] = None, auto_error: bool = True):
self.model = HTTPBaseModel(scheme="digest")
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error

View File

@@ -117,8 +117,8 @@ class OAuth2(SecurityBase):
self,
*,
flows: OAuthFlowsModel = OAuthFlowsModel(),
scheme_name: str = None,
auto_error: bool = True
scheme_name: Optional[str] = None,
auto_error: Optional[bool] = True
):
self.model = OAuth2Model(flows=flows)
self.scheme_name = scheme_name or self.__class__.__name__
@@ -140,8 +140,8 @@ class OAuth2PasswordBearer(OAuth2):
def __init__(
self,
tokenUrl: str,
scheme_name: str = None,
scopes: dict = None,
scheme_name: Optional[str] = None,
scopes: Optional[dict] = None,
auto_error: bool = True,
):
if not scopes:
@@ -169,9 +169,9 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
self,
authorizationUrl: str,
tokenUrl: str,
refreshUrl: str = None,
scheme_name: str = None,
scopes: dict = None,
refreshUrl: Optional[str] = None,
scheme_name: Optional[str] = None,
scopes: Optional[dict] = None,
auto_error: bool = True,
):
if not scopes:
@@ -202,6 +202,6 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
class SecurityScopes:
def __init__(self, scopes: List[str] = None):
def __init__(self, scopes: Optional[List[str]] = None):
self.scopes = scopes or []
self.scope_str = " ".join(self.scopes)

View File

@@ -9,7 +9,11 @@ from starlette.status import HTTP_403_FORBIDDEN
class OpenIdConnect(SecurityBase):
def __init__(
self, *, openIdConnectUrl: str, scheme_name: str = None, auto_error: bool = True
self,
*,
openIdConnectUrl: str,
scheme_name: Optional[str] = None,
auto_error: bool = True
):
self.model = OpenIdConnectModel(openIdConnectUrl=openIdConnectUrl)
self.scheme_name = scheme_name or self.__class__.__name__

View File

@@ -109,7 +109,9 @@ def create_response_field(
def create_cloned_field(
field: ModelField, *, cloned_types: Dict[Type[BaseModel], Type[BaseModel]] = None,
field: ModelField,
*,
cloned_types: Optional[Dict[Type[BaseModel], Type[BaseModel]]] = None,
) -> ModelField:
# _cloned_types has already cloned types, to support recursive models
if cloned_types is None:

View File

@@ -32,7 +32,7 @@ classifiers = [
"Topic :: Internet :: WWW/HTTP",
]
requires = [
"starlette ==0.13.4",
"starlette ==0.13.6",
"pydantic >=0.32.2,<2.0.0"
]
description-file = "README.md"
@@ -45,10 +45,12 @@ Documentation = "https://fastapi.tiangolo.com/"
test = [
"pytest ==5.4.3",
"pytest-cov ==2.10.0",
"pytest-asyncio >=0.14.0,<0.15.0",
"mypy ==0.782",
"black ==19.10b0",
"isort >=5.0.6,<6.0.0",
"requests >=2.24.0,<3.0.0",
"httpx >=0.14.0,<0.15.0",
"email_validator >=1.1.1,<2.0.0",
"sqlalchemy >=1.3.18,<2.0.0",
"peewee >=3.13.3,<4.0.0",
@@ -62,7 +64,7 @@ test = [
]
doc = [
"mkdocs >=1.1.2,<2.0.0",
"mkdocs-material >=5.4.0,<6.0.0",
"mkdocs-material >=5.5.0,<6.0.0",
"markdown-include >=0.5.1,<0.6.0",
"mkdocs-markdownextradata-plugin >=0.1.7,<0.2.0",
"typer >=0.3.0,<0.4.0",

View File

@@ -0,0 +1,14 @@
#! /usr/bin/env bash
set -x
set -e
PR=${PR:?Variable not set}
DEPLOY_URL=${DEPLOY_URL:?Variable not set}
GITHUB_TOKEN=${GITHUB_TOKEN:?Variable not set}
COMMIT=${COMMIT:?Variable not set}
curl \
-H "Authorization: token ${GITHUB_TOKEN}" \
https://api.github.com/repos/tiangolo/fastapi/issues/${PR}/comments \
-d '{"body": "📝 Docs preview for commit '"${COMMIT} at: ${DEPLOY_URL}"'"}'

View File

@@ -10,7 +10,7 @@ gitter_token = os.getenv("GITTER_TOKEN")
assert gitter_token
github_token = os.getenv("GITHUB_TOKEN")
assert github_token
tag_name = os.getenv("TRAVIS_TAG")
tag_name = os.getenv("TAG")
assert tag_name

View File

@@ -2,8 +2,4 @@
set -e
bash scripts/publish.sh
bash scripts/trigger-docker.sh
python scripts/gitter_releases_bot.py

View File

@@ -7,4 +7,4 @@ bash ./scripts/lint.sh
# Check README.md is up to date
diff --brief docs/en/docs/index.md README.md
export PYTHONPATH=./docs_src
pytest --cov=fastapi --cov=tests --cov=docs/src --cov-report=term-missing tests ${@}
pytest --cov=fastapi --cov=tests --cov=docs/src --cov-report=term-missing --cov-report=xml tests ${@}

View File

@@ -1,17 +0,0 @@
#!/usr/bin/env bash
set -e
set -x
body='{
"request": {
"branch":"master"
}}'
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Travis-API-Version: 3" \
-H "Authorization: token $TRAVIS_TOKEN" \
-d "$body" \
https://api.travis-ci.org/repo/tiangolo%2Fuvicorn-gunicorn-fastapi-docker/requests

13
scripts/unzip-docs.sh Normal file
View File

@@ -0,0 +1,13 @@
#! /usr/bin/env bash
set -x
set -e
if [ -d ./site/ ]; then
rm -rf ./site/
fi
unzip archive.zip
# Double zipped by GitHub when downlading the archive
unzip docs.zip
rm -rf archive.zip
rm -rf docs.zip

9
scripts/zip-docs.sh Normal file
View File

@@ -0,0 +1,9 @@
#! /usr/bin/env bash
set -x
set -e
if [ -f docs.zip ]; then
rm -rf docs.zip
fi
zip -r docs.zip ./site

View File

@@ -0,0 +1,108 @@
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
app = FastAPI()
class Product(BaseModel):
name: str
description: str = None
price: float
@app.get("/product")
async def create_item(product: Product):
return product
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/product": {
"get": {
"summary": "Create Item",
"operationId": "create_item_product_get",
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Product"}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
}
},
"components": {
"schemas": {
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
"Product": {
"title": "Product",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {"title": "Description", "type": "string"},
"price": {"title": "Price", "type": "number"},
},
},
"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"},
},
},
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_get_with_body():
body = {"name": "Foo", "description": "Some description", "price": 5.5}
response = client.get("/product", json=body)
assert response.json() == body

View File

@@ -55,6 +55,11 @@ class ModelWithCustomEncoder(BaseModel):
}
class ModelWithCustomEncoderSubclass(ModelWithCustomEncoder):
class Config:
pass
class RoleEnum(Enum):
admin = "admin"
normal = "normal"
@@ -117,6 +122,11 @@ def test_encode_custom_json_encoders_model():
assert jsonable_encoder(model) == {"dt_field": "2019-01-01T08:00:00+00:00"}
def test_encode_custom_json_encoders_model_subclass():
model = ModelWithCustomEncoderSubclass(dt_field=datetime(2019, 1, 1, 8))
assert jsonable_encoder(model) == {"dt_field": "2019-01-01T08:00:00+00:00"}
def test_encode_model_with_config():
model = ModelWithConfig(role=RoleEnum.admin)
assert jsonable_encoder(model) == {"role": "admin"}

View File

@@ -0,0 +1,106 @@
import pytest
from fastapi import FastAPI, File, Form, UploadFile
from fastapi.dependencies.utils import (
multipart_incorrect_install_error,
multipart_not_installed_error,
)
def test_incorrect_multipart_installed_form(monkeypatch):
monkeypatch.delattr("multipart.multipart.parse_options_header", raising=False)
with pytest.raises(RuntimeError, match=multipart_incorrect_install_error):
app = FastAPI()
@app.post("/")
async def root(username: str = Form(...)):
return username # pragma: nocover
def test_incorrect_multipart_installed_file_upload(monkeypatch):
monkeypatch.delattr("multipart.multipart.parse_options_header", raising=False)
with pytest.raises(RuntimeError, match=multipart_incorrect_install_error):
app = FastAPI()
@app.post("/")
async def root(f: UploadFile = File(...)):
return f # pragma: nocover
def test_incorrect_multipart_installed_file_bytes(monkeypatch):
monkeypatch.delattr("multipart.multipart.parse_options_header", raising=False)
with pytest.raises(RuntimeError, match=multipart_incorrect_install_error):
app = FastAPI()
@app.post("/")
async def root(f: bytes = File(...)):
return f # pragma: nocover
def test_incorrect_multipart_installed_multi_form(monkeypatch):
monkeypatch.delattr("multipart.multipart.parse_options_header", raising=False)
with pytest.raises(RuntimeError, match=multipart_incorrect_install_error):
app = FastAPI()
@app.post("/")
async def root(username: str = Form(...), pasword: str = Form(...)):
return username # pragma: nocover
def test_incorrect_multipart_installed_form_file(monkeypatch):
monkeypatch.delattr("multipart.multipart.parse_options_header", raising=False)
with pytest.raises(RuntimeError, match=multipart_incorrect_install_error):
app = FastAPI()
@app.post("/")
async def root(username: str = Form(...), f: UploadFile = File(...)):
return username # pragma: nocover
def test_no_multipart_installed(monkeypatch):
monkeypatch.delattr("multipart.__version__", raising=False)
with pytest.raises(RuntimeError, match=multipart_not_installed_error):
app = FastAPI()
@app.post("/")
async def root(username: str = Form(...)):
return username # pragma: nocover
def test_no_multipart_installed_file(monkeypatch):
monkeypatch.delattr("multipart.__version__", raising=False)
with pytest.raises(RuntimeError, match=multipart_not_installed_error):
app = FastAPI()
@app.post("/")
async def root(f: UploadFile = File(...)):
return f # pragma: nocover
def test_no_multipart_installed_file_bytes(monkeypatch):
monkeypatch.delattr("multipart.__version__", raising=False)
with pytest.raises(RuntimeError, match=multipart_not_installed_error):
app = FastAPI()
@app.post("/")
async def root(f: bytes = File(...)):
return f # pragma: nocover
def test_no_multipart_installed_multi_form(monkeypatch):
monkeypatch.delattr("multipart.__version__", raising=False)
with pytest.raises(RuntimeError, match=multipart_not_installed_error):
app = FastAPI()
@app.post("/")
async def root(username: str = Form(...), password: str = Form(...)):
return username # pragma: nocover
def test_no_multipart_installed_form_file(monkeypatch):
monkeypatch.delattr("multipart.__version__", raising=False)
with pytest.raises(RuntimeError, match=multipart_not_installed_error):
app = FastAPI()
@app.post("/")
async def root(username: str = Form(...), f: UploadFile = File(...)):
return username # pragma: nocover

View File

@@ -10,7 +10,7 @@ app = FastAPI()
class Item(BaseModel):
name: str
price: Optional[float] = None
owner_ids: List[int] = None
owner_ids: Optional[List[int]] = None
@app.get("/items/valid", response_model=Item)

View File

@@ -11,7 +11,7 @@ app = FastAPI()
class Item:
name: str
price: Optional[float] = None
owner_ids: List[int] = None
owner_ids: Optional[List[int]] = None
@app.get("/items/valid", response_model=Item)

View File

@@ -10,7 +10,7 @@ app = FastAPI()
class Item(BaseModel):
name: str = Field(..., alias="aliased_name")
price: Optional[float] = None
owner_ids: List[int] = None
owner_ids: Optional[List[int]] = None
@app.get("/items/valid", response_model=Item)

View File

View File

@@ -0,0 +1,8 @@
import pytest
from docs_src.async_tests.test_main import test_root
@pytest.mark.asyncio
async def test_async_testing():
await test_root()

View File

@@ -11,7 +11,7 @@ app = FastAPI()
class Item(BaseModel):
name: str
price: Optional[float] = None
owner_ids: List[int] = None
owner_ids: Optional[List[int]] = None
@app.get("/items/invalid", response_model=Item)

View File

@@ -13,7 +13,7 @@ app = FastAPI()
class Item:
name: str
price: Optional[float] = None
owner_ids: List[int] = None
owner_ids: Optional[List[int]] = None
@app.get("/items/invalid", response_model=Item)