mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-26 15:51:02 -05:00
Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6897963d5 | ||
|
|
c14803e3fa | ||
|
|
cdba8481c2 | ||
|
|
64ca596a11 | ||
|
|
e1758d107e | ||
|
|
3390182fc9 | ||
|
|
912a43ee3d | ||
|
|
4020304945 | ||
|
|
0a2fc78fea | ||
|
|
b91acfb457 | ||
|
|
b9a0179a03 | ||
|
|
5ed48ccdc8 | ||
|
|
f32d67c8b9 | ||
|
|
0752c7242d | ||
|
|
be855c696b | ||
|
|
da9b5201c4 | ||
|
|
4daa6ef4e4 | ||
|
|
4ad7c55618 | ||
|
|
79e08a2541 | ||
|
|
1c9c80ba93 | ||
|
|
25cb05c876 | ||
|
|
694fbab074 | ||
|
|
2fd28434dd | ||
|
|
d15556b152 | ||
|
|
38d8bab770 | ||
|
|
52f0f8657e | ||
|
|
aedf5c895a | ||
|
|
117f9e4abe | ||
|
|
604fea9fc1 | ||
|
|
994bfd4591 | ||
|
|
02722923b1 | ||
|
|
d63a93429b | ||
|
|
b93e216dc7 | ||
|
|
95a29b6e67 | ||
|
|
272f01a153 | ||
|
|
1a345ae7fc | ||
|
|
c5c138b8eb | ||
|
|
da20e33414 | ||
|
|
7fbe3737bc | ||
|
|
f63cec9c95 | ||
|
|
3063ad83ec | ||
|
|
78680e5bee | ||
|
|
55b9faeb48 | ||
|
|
72645dfeab | ||
|
|
3223de5598 | ||
|
|
1afa4e8e75 | ||
|
|
6fd3736da2 | ||
|
|
7e043e5e6f | ||
|
|
d1585c42b9 | ||
|
|
fc494e3527 | ||
|
|
b344cc9415 | ||
|
|
38b71a9298 | ||
|
|
769ee73240 | ||
|
|
1df2f14c64 | ||
|
|
eab9a0e139 | ||
|
|
b86ac6739a | ||
|
|
9840d9e59d | ||
|
|
0ec52157df | ||
|
|
f1c5330b65 | ||
|
|
306ec8de04 | ||
|
|
6d7c9893d4 | ||
|
|
6264709054 | ||
|
|
76c2077f47 | ||
|
|
a63b1efc29 | ||
|
|
9863c3fca8 | ||
|
|
6fb97f44cf | ||
|
|
f64c448329 | ||
|
|
df6cbc5ec6 | ||
|
|
0f0af751e4 | ||
|
|
6c9dca55bc | ||
|
|
d71e807401 | ||
|
|
7df9ddfe4e | ||
|
|
4170659359 | ||
|
|
2940a7fdfa | ||
|
|
dadd6650ed | ||
|
|
c5a21354af | ||
|
|
8bafe2a482 | ||
|
|
42f1716b48 | ||
|
|
6ab2841dbb | ||
|
|
0f54657377 | ||
|
|
79e5b36551 | ||
|
|
074868d77e | ||
|
|
3dd16a9458 | ||
|
|
62c23ab5fa | ||
|
|
11c05beece | ||
|
|
7b3ef43127 | ||
|
|
e0080e5f75 | ||
|
|
e1ba54bd12 | ||
|
|
7032dfb4f1 | ||
|
|
14e7f7c1f4 | ||
|
|
9ed6f1e419 | ||
|
|
b268c39758 | ||
|
|
4dd386b807 | ||
|
|
b7251f1654 | ||
|
|
780d3e65ad | ||
|
|
cc8cac200f | ||
|
|
e7be5c8ac5 | ||
|
|
8f52864899 | ||
|
|
47a630721a | ||
|
|
10ae6de111 | ||
|
|
2b47f3e56b |
5
.flake8
Normal file
5
.flake8
Normal file
@@ -0,0 +1,5 @@
|
||||
[flake8]
|
||||
max-line-length = 88
|
||||
select = C,E,F,W,B,B9
|
||||
ignore = E203, E501, W503
|
||||
exclude = __init__.py
|
||||
7
.github/actions/get-artifact/Dockerfile
vendored
Normal file
7
.github/actions/get-artifact/Dockerfile
vendored
Normal 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
16
.github/actions/get-artifact/action.yml
vendored
Normal 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'
|
||||
63
.github/actions/get-artifact/app/main.py
vendored
Normal file
63
.github/actions/get-artifact/app/main.py
vendored
Normal 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")
|
||||
7
.github/actions/watch-previews/Dockerfile
vendored
Normal file
7
.github/actions/watch-previews/Dockerfile
vendored
Normal 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"]
|
||||
10
.github/actions/watch-previews/action.yml
vendored
Normal file
10
.github/actions/watch-previews/action.yml
vendored
Normal 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'
|
||||
101
.github/actions/watch-previews/app/main.py
vendored
Normal file
101
.github/actions/watch-previews/app/main.py
vendored
Normal 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")
|
||||
@@ -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
44
.github/workflows/preview-docs.yml
vendored
Normal 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
39
.github/workflows/publish.yml
vendored
Normal 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
29
.github/workflows/test.yml
vendored
Normal 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
|
||||
13
.github/workflows/watch-docs-previews.yml
vendored
Normal file
13
.github/workflows/watch-docs-previews.yml
vendored
Normal 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
2
.gitignore
vendored
@@ -17,6 +17,8 @@ env3.*
|
||||
env
|
||||
docs_build
|
||||
venv
|
||||
docs.zip
|
||||
archive.zip
|
||||
|
||||
# vim temporary files
|
||||
*~
|
||||
|
||||
32
.travis.yml
32
.travis.yml
@@ -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"
|
||||
@@ -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">
|
||||
|
||||
@@ -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: FastAPI|DB接続してCRUDするPython製APIサーバーを構築
|
||||
|
||||
100
docs/en/docs/advanced/async-tests.md
Normal file
100
docs/en/docs/advanced/async-tests.md
Normal 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.
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -16,3 +16,9 @@ In the next sections you will see other options, configurations, and additional
|
||||
You could still use most of the features in **FastAPI** with the knowledge from the main [Tutorial - User Guide](../tutorial/){.internal-link target=_blank}.
|
||||
|
||||
And the next sections assume you already read it, and assume that you know those main ideas.
|
||||
|
||||
## TestDriven.io course
|
||||
|
||||
If you would like to take an advanced-beginner course to complement this section of the docs, you might want to check: <a href="https://testdriven.io/courses/tdd-fastapi/" class="external-link" target="_blank">Test-Driven Development with FastAPI and Docker</a> by **TestDriven.io**.
|
||||
|
||||
They are currently donating 10% of all profits to the development of **FastAPI**. 🎉 😄
|
||||
|
||||
@@ -54,9 +54,9 @@ But by using the `secrets.compare_digest()` it will be secure against a type of
|
||||
|
||||
But what's a "timing attack"?
|
||||
|
||||
Let's imagine an attacker is trying to guess the username and password.
|
||||
Let's imagine some attackers are trying to guess the username and password.
|
||||
|
||||
And that attacker sends a request with a username `johndoe` and a password `love123`.
|
||||
And they send a request with a username `johndoe` and a password `love123`.
|
||||
|
||||
Then the Python code in your application would be equivalent to something like:
|
||||
|
||||
@@ -67,7 +67,7 @@ if "johndoe" == "stanleyjobson" and "love123" == "swordfish":
|
||||
|
||||
But right at the moment Python compares the first `j` in `johndoe` to the first `s` in `stanleyjobson`, it will return `False`, because it already knows that those two strings are not the same, thinking that "there's no need to waste more computation comparing the rest of the letters". And your application will say "incorrect user or password".
|
||||
|
||||
But then the attacker tries with username `stanleyjobsox` and password `love123`.
|
||||
But then the attackers try with username `stanleyjobsox` and password `love123`.
|
||||
|
||||
And your application code does something like:
|
||||
|
||||
@@ -78,17 +78,17 @@ if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
|
||||
|
||||
Python will have to compare the whole `stanleyjobso` in both `stanleyjobsox` and `stanleyjobson` before realizing that both strings are not the same. So it will take some extra microseconds to reply back "incorrect user or password".
|
||||
|
||||
#### The time to answer helps the attacker
|
||||
#### The time to answer helps the attackers
|
||||
|
||||
At that point, by noticing that the server took some microseconds longer to send the "incorrect user or password" response, the attacker will know that she/he got _something_ right, some of the initial letters were right.
|
||||
At that point, by noticing that the server took some microseconds longer to send the "incorrect user or password" response, the attackers will know that they got _something_ right, some of the initial letters were right.
|
||||
|
||||
And then she/he can try again knowing that it's probably something more similar to `stanleyjobsox` than to `johndoe`.
|
||||
And then they can try again knowing that it's probably something more similar to `stanleyjobsox` than to `johndoe`.
|
||||
|
||||
#### A "professional" attack
|
||||
|
||||
Of course, the attacker would not try all this by hand, she/he would write a program to do it, possibly with thousands or millions of tests per second. And would get just one extra correct letter at a time.
|
||||
Of course, the attackers would not try all this by hand, they would write a program to do it, possibly with thousands or millions of tests per second. And would get just one extra correct letter at a time.
|
||||
|
||||
But doing that, in some minutes or hours the attacker would have guessed the correct username and password, with the "help" of our application, just using the time taken to answer.
|
||||
But doing that, in some minutes or hours the attackers would have guessed the correct username and password, with the "help" of our application, just using the time taken to answer.
|
||||
|
||||
#### Fix it with `secrets.compare_digest()`
|
||||
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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>.
|
||||
|
||||
@@ -137,6 +137,33 @@ With that you can connect the WebSocket and then send and receive messages:
|
||||
|
||||
<img src="/img/tutorial/websockets/image05.png">
|
||||
|
||||
## Handling disconnections and multiple clients
|
||||
|
||||
When a WebSocket connection is closed, the `await websocket.receive_text()` will raise a `WebSocketDisconnect` exception, which you can then catch and handle like in this example.
|
||||
|
||||
```Python hl_lines="81-83"
|
||||
{!../../../docs_src/websockets/tutorial003.py!}
|
||||
```
|
||||
|
||||
To try it out:
|
||||
|
||||
* Open the app with several browser tabs.
|
||||
* Write messages from them.
|
||||
* Then close one of the tabs.
|
||||
|
||||
That will raise the `WebSocketDisconnect` exception, and all the other clients will receive a message like:
|
||||
|
||||
```
|
||||
Client #1596980209979 left the chat
|
||||
```
|
||||
|
||||
!!! tip
|
||||
The app above is a minimal and simple example to demonstrate how to handle and broadcast messages to several WebSocket connections.
|
||||
|
||||
But have in mind that, as everything is handled in memory, in a single list, it will only work while the process is running, and will only work with a single process.
|
||||
|
||||
If you need something easy to integrate with FastAPI but that is more robust, supported by Redis, PostgreSQL or others, check <a href="https://github.com/encode/broadcaster" class="external-link" target="_blank">encode/broadcaster</a>.
|
||||
|
||||
## More info
|
||||
|
||||
To learn more about the options, check Starlette's documentation for:
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -2,6 +2,87 @@
|
||||
|
||||
## Latest changes
|
||||
|
||||
## 0.61.0
|
||||
|
||||
### Features
|
||||
|
||||
* Add support for injecting `HTTPConnection` (as `Request` and `WebSocket`). Useful for sharing app state in dependencies. PR [#1827](https://github.com/tiangolo/fastapi/pull/1827) by [@nsidnev](https://github.com/nsidnev).
|
||||
* Export `WebSocketDisconnect` and add example handling WebSocket disconnections to docs. PR [#1822](https://github.com/tiangolo/fastapi/pull/1822) by [@rkbeatss](https://github.com/rkbeatss).
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Require Pydantic > `1.0.0`.
|
||||
* Remove support for deprecated Pydantic `0.32.2`. This improves maintainability and allows new features.
|
||||
* In `FastAPI` and `APIRouter`:
|
||||
* Remove *path operation decorators* related/deprecated parameter `response_model_skip_defaults` (use `response_model_exclude_unset` instead).
|
||||
* Change *path operation decorators* parameter default for `response_model_exclude` from `set()` to `None` (as is in Pydantic).
|
||||
* In `encoders.jsonable_encoder`:
|
||||
* Remove deprecated `skip_defaults`, use instead `exclude_unset`.
|
||||
* Set default of `exclude` from `set()` to `None` (as is in Pydantic).
|
||||
* PR [#1862](https://github.com/tiangolo/fastapi/pull/1862).
|
||||
* In `encoders.jsonable_encoder` remove parameter `sqlalchemy_safe`.
|
||||
* It was an early hack to allow returning SQLAlchemy models, but it was never documented, and the recommended way is using Pydantic's `orm_mode` as described in the tutorial: [SQL (Relational) Databases](https://fastapi.tiangolo.com/tutorial/sql-databases/).
|
||||
* PR [#1864](https://github.com/tiangolo/fastapi/pull/1864).
|
||||
|
||||
### Docs
|
||||
|
||||
* Add link to the course by TestDriven.io: [Test-Driven Development with FastAPI and Docker](https://testdriven.io/courses/tdd-fastapi/). PR [#1860](https://github.com/tiangolo/fastapi/pull/1860).
|
||||
* Fix empty log message in docs example about handling errors. PR [#1815](https://github.com/tiangolo/fastapi/pull/1815) by [@manlix](https://github.com/manlix).
|
||||
* Reword text to reduce ambiguity while not being gender-specific. PR [#1824](https://github.com/tiangolo/fastapi/pull/1824) by [@Mause](https://github.com/Mause).
|
||||
|
||||
### Internal
|
||||
|
||||
* Add Flake8 linting. Original PR [#1774](https://github.com/tiangolo/fastapi/pull/1774) by [@MashhadiNima](https://github.com/MashhadiNima).
|
||||
* Disable Gitter bot, as it's currently broken, and Gitter's response doesn't show the problem. PR [#1853](https://github.com/tiangolo/fastapi/pull/1853).
|
||||
|
||||
## 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).
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
You can define background tasks to be run *after* returning a response.
|
||||
|
||||
This is useful for operations that need to happen after a request, but that the client doesn't really have to be waiting for the operation to complete before receiving his response.
|
||||
This is useful for operations that need to happen after a request, but that the client doesn't really have to be waiting for the operation to complete before receiving the response.
|
||||
|
||||
This includes, for example:
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ In this example, when the client request an item by an ID that doesn't exist, ra
|
||||
|
||||
### The resulting response
|
||||
|
||||
If the client requests `http://example.com/items/foo` (an `item_id` `"foo"`), he will receive an HTTP status code of 200, and a JSON response of:
|
||||
If the client requests `http://example.com/items/foo` (an `item_id` `"foo"`), that client will receive an HTTP status code of 200, and a JSON response of:
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -55,7 +55,7 @@ If the client requests `http://example.com/items/foo` (an `item_id` `"foo"`), he
|
||||
}
|
||||
```
|
||||
|
||||
But if the client requests `http://example.com/items/bar` (a non-existent `item_id` `"bar"`), he will receive an HTTP status code of 404 (the "not found" error), and a JSON response of:
|
||||
But if the client requests `http://example.com/items/bar` (a non-existent `item_id` `"bar"`), that client will receive an HTTP status code of 404 (the "not found" error), and a JSON response of:
|
||||
|
||||
```JSON
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -85,12 +85,12 @@ But in this case, the same **FastAPI** application will handle the API and the a
|
||||
|
||||
So, let's review it from that simplified point of view:
|
||||
|
||||
* The user types his `username` and `password` in the frontend, and hits `Enter`.
|
||||
* The user types the `username` and `password` in the frontend, and hits `Enter`.
|
||||
* The frontend (running in the user's browser) sends that `username` and `password` to a specific URL in our API (declared with `tokenUrl="token"`).
|
||||
* 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`.
|
||||
|
||||
@@ -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 that user 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.
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
453
docs/uk/docs/index.md
Normal 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>
|
||||
|
||||
---
|
||||
|
||||
"_I’m over the moon excited about **FastAPI**. It’s 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>):
|
||||
|
||||

|
||||
|
||||
### 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>):
|
||||
|
||||

|
||||
|
||||
## 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:
|
||||
|
||||

|
||||
|
||||
* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API:
|
||||
|
||||

|
||||
|
||||
* 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:
|
||||
|
||||

|
||||
|
||||
### 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:
|
||||
|
||||

|
||||
|
||||
### 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:
|
||||
|
||||

|
||||
|
||||
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
73
docs/uk/mkdocs.yml
Normal 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
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
0
docs_src/async_tests/__init__.py
Normal file
0
docs_src/async_tests/__init__.py
Normal file
8
docs_src/async_tests/main.py
Normal file
8
docs_src/async_tests/main.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": "Tomato"}
|
||||
12
docs_src/async_tests/test_main.py
Normal file
12
docs_src/async_tests/test_main.py
Normal 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"}
|
||||
@@ -11,7 +11,7 @@ app = FastAPI()
|
||||
|
||||
@app.exception_handler(StarletteHTTPException)
|
||||
async def custom_http_exception_handler(request, exc):
|
||||
print(f"OMG! An HTTP error!: {exc}")
|
||||
print(f"OMG! An HTTP error!: {repr(exc)}")
|
||||
return await http_exception_handler(request, exc)
|
||||
|
||||
|
||||
|
||||
@@ -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})
|
||||
|
||||
83
docs_src/websockets/tutorial003.py
Normal file
83
docs_src/websockets/tutorial003.py
Normal file
@@ -0,0 +1,83 @@
|
||||
from typing import List
|
||||
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Chat</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>WebSocket Chat</h1>
|
||||
<h2>Your ID: <span id="ws-id"></span></h2>
|
||||
<form action="" onsubmit="sendMessage(event)">
|
||||
<input type="text" id="messageText" autocomplete="off"/>
|
||||
<button>Send</button>
|
||||
</form>
|
||||
<ul id='messages'>
|
||||
</ul>
|
||||
<script>
|
||||
var client_id = Date.now()
|
||||
document.querySelector("#ws-id").textContent = client_id;
|
||||
var ws = new WebSocket(`ws://localhost:8000/ws/${client_id}`);
|
||||
ws.onmessage = function(event) {
|
||||
var messages = document.getElementById('messages')
|
||||
var message = document.createElement('li')
|
||||
var content = document.createTextNode(event.data)
|
||||
message.appendChild(content)
|
||||
messages.appendChild(message)
|
||||
};
|
||||
function sendMessage(event) {
|
||||
var input = document.getElementById("messageText")
|
||||
ws.send(input.value)
|
||||
input.value = ''
|
||||
event.preventDefault()
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
class ConnectionManager:
|
||||
def __init__(self):
|
||||
self.active_connections: List[WebSocket] = []
|
||||
|
||||
async def connect(self, websocket: WebSocket):
|
||||
await websocket.accept()
|
||||
self.active_connections.append(websocket)
|
||||
|
||||
def disconnect(self, websocket: WebSocket):
|
||||
self.active_connections.remove(websocket)
|
||||
|
||||
async def send_personal_message(self, message: str, websocket: WebSocket):
|
||||
await websocket.send_text(message)
|
||||
|
||||
async def broadcast(self, message: str):
|
||||
for connection in self.active_connections:
|
||||
await connection.send_text(message)
|
||||
|
||||
|
||||
manager = ConnectionManager()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def get():
|
||||
return HTMLResponse(html)
|
||||
|
||||
|
||||
@app.websocket("/ws/{client_id}")
|
||||
async def websocket_endpoint(websocket: WebSocket, client_id: int):
|
||||
await manager.connect(websocket)
|
||||
try:
|
||||
while True:
|
||||
data = await websocket.receive_text()
|
||||
await manager.send_personal_message(f"You wrote: {data}", websocket)
|
||||
await manager.broadcast(f"Client #{client_id} says: {data}")
|
||||
except WebSocketDisconnect:
|
||||
manager.disconnect(websocket)
|
||||
await manager.broadcast(f"Client #{client_id} left the chat")
|
||||
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.59.0"
|
||||
__version__ = "0.61.0"
|
||||
|
||||
from starlette import status
|
||||
|
||||
@@ -22,4 +22,4 @@ from .param_functions import (
|
||||
from .requests import Request
|
||||
from .responses import Response
|
||||
from .routing import APIRouter
|
||||
from .websockets import WebSocket
|
||||
from .websockets import WebSocket, WebSocketDisconnect
|
||||
|
||||
@@ -16,7 +16,6 @@ from fastapi.openapi.docs import (
|
||||
)
|
||||
from fastapi.openapi.utils import get_openapi
|
||||
from fastapi.params import Depends
|
||||
from fastapi.utils import warning_response_model_skip_defaults_deprecated
|
||||
from starlette.applications import Starlette
|
||||
from starlette.datastructures import State
|
||||
from starlette.exceptions import HTTPException
|
||||
@@ -32,7 +31,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 +43,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 +116,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,30 +185,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
self.router.add_api_route(
|
||||
path,
|
||||
endpoint=endpoint,
|
||||
@@ -228,9 +223,7 @@ class FastAPI(Starlette):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -242,31 +235,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
|
||||
def decorator(func: Callable) -> Callable:
|
||||
self.router.add_api_route(
|
||||
path,
|
||||
@@ -285,9 +274,7 @@ class FastAPI(Starlette):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -299,11 +286,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 +302,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,30 +321,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
return self.router.get(
|
||||
path,
|
||||
response_model=response_model,
|
||||
@@ -373,9 +357,7 @@ class FastAPI(Starlette):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -388,30 +370,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
return self.router.put(
|
||||
path,
|
||||
response_model=response_model,
|
||||
@@ -427,9 +406,7 @@ class FastAPI(Starlette):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -442,30 +419,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
return self.router.post(
|
||||
path,
|
||||
response_model=response_model,
|
||||
@@ -481,9 +455,7 @@ class FastAPI(Starlette):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -496,30 +468,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
return self.router.delete(
|
||||
path,
|
||||
response_model=response_model,
|
||||
@@ -535,9 +504,7 @@ class FastAPI(Starlette):
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
operation_id=operation_id,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -550,30 +517,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
return self.router.options(
|
||||
path,
|
||||
response_model=response_model,
|
||||
@@ -589,9 +553,7 @@ class FastAPI(Starlette):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -604,30 +566,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
return self.router.head(
|
||||
path,
|
||||
response_model=response_model,
|
||||
@@ -643,9 +602,7 @@ class FastAPI(Starlette):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -658,30 +615,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
return self.router.patch(
|
||||
path,
|
||||
response_model=response_model,
|
||||
@@ -697,9 +651,7 @@ class FastAPI(Starlette):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -712,30 +664,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
return self.router.trace(
|
||||
path,
|
||||
response_model=response_model,
|
||||
@@ -751,9 +700,7 @@ class FastAPI(Starlette):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
from typing import Callable, List, Sequence
|
||||
from typing import Callable, List, Optional, Sequence
|
||||
|
||||
from fastapi.security.base import SecurityBase
|
||||
|
||||
try:
|
||||
from pydantic.fields import ModelField
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic.fields import Field as ModelField # type: ignore
|
||||
from pydantic.fields import ModelField
|
||||
|
||||
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 +18,24 @@ 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,
|
||||
http_connection_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 []
|
||||
@@ -48,6 +46,7 @@ class Dependant:
|
||||
self.security_requirements = security_schemes or []
|
||||
self.request_param_name = request_param_name
|
||||
self.websocket_param_name = websocket_param_name
|
||||
self.http_connection_param_name = http_connection_param_name
|
||||
self.response_param_name = response_param_name
|
||||
self.background_tasks_param_name = background_tasks_param_name
|
||||
self.security_scopes = security_scopes
|
||||
|
||||
@@ -24,61 +24,35 @@ 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
|
||||
from fastapi.utils import (
|
||||
PYDANTIC_1,
|
||||
create_response_field,
|
||||
get_field_info,
|
||||
get_path_param_names,
|
||||
)
|
||||
from pydantic import BaseConfig, BaseModel, create_model
|
||||
from fastapi.utils import create_response_field, get_path_param_names
|
||||
from pydantic import BaseModel, create_model
|
||||
from pydantic.error_wrappers import ErrorWrapper
|
||||
from pydantic.errors import MissingError
|
||||
from pydantic.fields import (
|
||||
SHAPE_LIST,
|
||||
SHAPE_SEQUENCE,
|
||||
SHAPE_SET,
|
||||
SHAPE_SINGLETON,
|
||||
SHAPE_TUPLE,
|
||||
SHAPE_TUPLE_ELLIPSIS,
|
||||
FieldInfo,
|
||||
ModelField,
|
||||
Required,
|
||||
)
|
||||
from pydantic.schema import get_annotation_from_field_info
|
||||
from pydantic.typing import ForwardRef, evaluate_forwardref
|
||||
from pydantic.utils import lenient_issubclass
|
||||
from starlette.background import BackgroundTasks
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
from starlette.datastructures import FormData, Headers, QueryParams, UploadFile
|
||||
from starlette.requests import Request
|
||||
from starlette.requests import HTTPConnection, Request
|
||||
from starlette.responses import Response
|
||||
from starlette.websockets import WebSocket
|
||||
|
||||
try:
|
||||
from pydantic.fields import (
|
||||
SHAPE_LIST,
|
||||
SHAPE_SEQUENCE,
|
||||
SHAPE_SET,
|
||||
SHAPE_SINGLETON,
|
||||
SHAPE_TUPLE,
|
||||
SHAPE_TUPLE_ELLIPSIS,
|
||||
FieldInfo,
|
||||
ModelField,
|
||||
Required,
|
||||
)
|
||||
from pydantic.schema import get_annotation_from_field_info
|
||||
from pydantic.typing import ForwardRef, evaluate_forwardref
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic import Schema as FieldInfo # type: ignore
|
||||
from pydantic.fields import Field as ModelField # type: ignore
|
||||
from pydantic.fields import Required, Shape # type: ignore
|
||||
from pydantic.schema import get_annotation_from_schema # type: ignore
|
||||
from pydantic.utils import ForwardRef, evaluate_forwardref # type: ignore
|
||||
|
||||
SHAPE_LIST = Shape.LIST
|
||||
SHAPE_SEQUENCE = Shape.SEQUENCE
|
||||
SHAPE_SET = Shape.SET
|
||||
SHAPE_SINGLETON = Shape.SINGLETON
|
||||
SHAPE_TUPLE = Shape.TUPLE
|
||||
SHAPE_TUPLE_ELLIPSIS = Shape.TUPLE_ELLIPS
|
||||
|
||||
def get_annotation_from_field_info(
|
||||
annotation: Any, field_info: FieldInfo, field_name: str
|
||||
) -> Type[Any]:
|
||||
return get_annotation_from_schema(annotation, field_info)
|
||||
|
||||
|
||||
sequence_shapes = {
|
||||
SHAPE_LIST,
|
||||
SHAPE_SET,
|
||||
@@ -96,8 +70,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 = field.field_info
|
||||
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 +135,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 +167,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 = []
|
||||
@@ -199,7 +212,7 @@ def get_flat_params(dependant: Dependant) -> List[ModelField]:
|
||||
|
||||
|
||||
def is_scalar_field(field: ModelField) -> bool:
|
||||
field_info = get_field_info(field)
|
||||
field_info = field.field_info
|
||||
if not (
|
||||
field.shape == SHAPE_SINGLETON
|
||||
and not lenient_issubclass(field.type_, BaseModel)
|
||||
@@ -246,7 +259,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 +282,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 +298,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
|
||||
@@ -314,7 +327,7 @@ def get_dependant(
|
||||
) and is_scalar_sequence_field(param_field):
|
||||
add_param_to_fields(field=param_field, dependant=dependant)
|
||||
else:
|
||||
field_info = get_field_info(param_field)
|
||||
field_info = param_field.field_info
|
||||
assert isinstance(
|
||||
field_info, params.Body
|
||||
), f"Param: {param_field.name} can only be a request body, using Body(...)"
|
||||
@@ -331,6 +344,9 @@ def add_non_field_param_to_dependency(
|
||||
elif lenient_issubclass(param.annotation, WebSocket):
|
||||
dependant.websocket_param_name = param.name
|
||||
return True
|
||||
elif lenient_issubclass(param.annotation, HTTPConnection):
|
||||
dependant.http_connection_param_name = param.name
|
||||
return True
|
||||
elif lenient_issubclass(param.annotation, Response):
|
||||
dependant.response_param_name = param.name
|
||||
return True
|
||||
@@ -348,7 +364,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
|
||||
@@ -387,16 +403,13 @@ def get_param_field(
|
||||
)
|
||||
field.required = required
|
||||
if not had_schema and not is_scalar_field(field=field):
|
||||
if PYDANTIC_1:
|
||||
field.field_info = params.Body(field_info.default)
|
||||
else:
|
||||
field.schema = params.Body(field_info.default) # type: ignore # pragma: nocover
|
||||
field.field_info = params.Body(field_info.default)
|
||||
|
||||
return field
|
||||
|
||||
|
||||
def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None:
|
||||
field_info = cast(params.Param, get_field_info(field))
|
||||
field_info = cast(params.Param, field.field_info)
|
||||
if field_info.in_ == params.ParamTypes.path:
|
||||
dependant.path_params.append(field)
|
||||
elif field_info.in_ == params.ParamTypes.query:
|
||||
@@ -456,10 +469,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],
|
||||
@@ -567,6 +580,8 @@ async def solve_dependencies(
|
||||
)
|
||||
values.update(body_values)
|
||||
errors.extend(body_errors)
|
||||
if dependant.http_connection_param_name:
|
||||
values[dependant.http_connection_param_name] = request
|
||||
if dependant.request_param_name and isinstance(request, Request):
|
||||
values[dependant.request_param_name] = request
|
||||
elif dependant.websocket_param_name and isinstance(request, WebSocket):
|
||||
@@ -597,26 +612,17 @@ def request_params_to_args(
|
||||
value = received_params.getlist(field.alias) or field.default
|
||||
else:
|
||||
value = received_params.get(field.alias)
|
||||
field_info = get_field_info(field)
|
||||
field_info = field.field_info
|
||||
assert isinstance(
|
||||
field_info, params.Param
|
||||
), "Params must be subclasses of Param"
|
||||
if value is None:
|
||||
if field.required:
|
||||
if PYDANTIC_1:
|
||||
errors.append(
|
||||
ErrorWrapper(
|
||||
MissingError(), loc=(field_info.in_.value, field.alias)
|
||||
)
|
||||
)
|
||||
else: # pragma: nocover
|
||||
errors.append(
|
||||
ErrorWrapper( # type: ignore
|
||||
MissingError(),
|
||||
loc=(field_info.in_.value, field.alias),
|
||||
config=BaseConfig,
|
||||
)
|
||||
errors.append(
|
||||
ErrorWrapper(
|
||||
MissingError(), loc=(field_info.in_.value, field.alias)
|
||||
)
|
||||
)
|
||||
else:
|
||||
values[field.name] = deepcopy(field.default)
|
||||
continue
|
||||
@@ -640,7 +646,7 @@ async def request_body_to_args(
|
||||
errors = []
|
||||
if required_params:
|
||||
field = required_params[0]
|
||||
field_info = get_field_info(field)
|
||||
field_info = field.field_info
|
||||
embed = getattr(field_info, "embed", None)
|
||||
field_alias_omitted = len(required_params) == 1 and not embed
|
||||
if field_alias_omitted:
|
||||
@@ -653,7 +659,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
|
||||
@@ -707,12 +713,7 @@ async def request_body_to_args(
|
||||
|
||||
|
||||
def get_missing_field_error(loc: Tuple[str, ...]) -> ErrorWrapper:
|
||||
if PYDANTIC_1:
|
||||
missing_field_error = ErrorWrapper(MissingError(), loc=loc)
|
||||
else: # pragma: no cover
|
||||
missing_field_error = ErrorWrapper( # type: ignore
|
||||
MissingError(), loc=loc, config=BaseConfig,
|
||||
)
|
||||
missing_field_error = ErrorWrapper(MissingError(), loc=loc)
|
||||
return missing_field_error
|
||||
|
||||
|
||||
@@ -730,9 +731,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=field.field_info,
|
||||
)
|
||||
|
||||
return out_field
|
||||
|
||||
|
||||
@@ -741,16 +741,18 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
|
||||
if not flat_dependant.body_params:
|
||||
return None
|
||||
first_param = flat_dependant.body_params[0]
|
||||
field_info = get_field_info(first_param)
|
||||
field_info = first_param.field_info
|
||||
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
|
||||
for param in flat_dependant.body_params:
|
||||
setattr(get_field_info(param), "embed", True)
|
||||
setattr(param.field_info, "embed", True)
|
||||
model_name = "Body_" + name
|
||||
BodyModel = create_model(model_name)
|
||||
for f in flat_dependant.body_params:
|
||||
@@ -758,28 +760,26 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
|
||||
required = any(True for f in flat_dependant.body_params if f.required)
|
||||
|
||||
BodyFieldInfo_kwargs: Dict[str, Any] = dict(default=None)
|
||||
if any(
|
||||
isinstance(get_field_info(f), params.File) for f in flat_dependant.body_params
|
||||
):
|
||||
if any(isinstance(f.field_info, params.File) for f in flat_dependant.body_params):
|
||||
BodyFieldInfo: Type[params.Body] = params.File
|
||||
elif any(
|
||||
isinstance(get_field_info(f), params.Form) for f in flat_dependant.body_params
|
||||
):
|
||||
elif any(isinstance(f.field_info, params.Form) for f in flat_dependant.body_params):
|
||||
BodyFieldInfo = params.Form
|
||||
else:
|
||||
BodyFieldInfo = params.Body
|
||||
|
||||
body_param_media_types = [
|
||||
getattr(get_field_info(f), "media_type")
|
||||
getattr(f.field_info, "media_type")
|
||||
for f in flat_dependant.body_params
|
||||
if isinstance(get_field_info(f), params.Body)
|
||||
if isinstance(f.field_info, params.Body)
|
||||
]
|
||||
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
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
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
|
||||
from pydantic import BaseModel
|
||||
from pydantic.json import ENCODERS_BY_TYPE
|
||||
|
||||
@@ -15,12 +14,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,48 +25,30 @@ encoders_by_class_tuples = generate_encoders_by_class_tuples(ENCODERS_BY_TYPE)
|
||||
|
||||
def jsonable_encoder(
|
||||
obj: Any,
|
||||
include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
by_alias: bool = True,
|
||||
skip_defaults: bool = None,
|
||||
exclude_unset: bool = False,
|
||||
exclude_defaults: bool = False,
|
||||
exclude_none: bool = False,
|
||||
custom_encoder: dict = {},
|
||||
sqlalchemy_safe: bool = True,
|
||||
) -> Any:
|
||||
if skip_defaults is not None:
|
||||
logger.warning( # pragma: nocover
|
||||
"skip_defaults in jsonable_encoder has been deprecated in favor of "
|
||||
"exclude_unset to keep in line with Pydantic v1, support for it will be "
|
||||
"removed soon."
|
||||
)
|
||||
if include is not None and not isinstance(include, set):
|
||||
include = set(include)
|
||||
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:
|
||||
obj_dict = obj.dict(
|
||||
include=include,
|
||||
exclude=exclude,
|
||||
by_alias=by_alias,
|
||||
exclude_unset=bool(exclude_unset or skip_defaults),
|
||||
exclude_none=exclude_none,
|
||||
exclude_defaults=exclude_defaults,
|
||||
)
|
||||
else: # pragma: nocover
|
||||
if exclude_defaults:
|
||||
raise ValueError("Cannot use exclude_defaults")
|
||||
obj_dict = obj.dict(
|
||||
include=include,
|
||||
exclude=exclude,
|
||||
by_alias=by_alias,
|
||||
skip_defaults=bool(exclude_unset or skip_defaults),
|
||||
)
|
||||
obj_dict = obj.dict(
|
||||
include=include,
|
||||
exclude=exclude,
|
||||
by_alias=by_alias,
|
||||
exclude_unset=exclude_unset,
|
||||
exclude_none=exclude_none,
|
||||
exclude_defaults=exclude_defaults,
|
||||
)
|
||||
if "__root__" in obj_dict:
|
||||
obj_dict = obj_dict["__root__"]
|
||||
return jsonable_encoder(
|
||||
@@ -78,7 +56,6 @@ def jsonable_encoder(
|
||||
exclude_none=exclude_none,
|
||||
exclude_defaults=exclude_defaults,
|
||||
custom_encoder=encoder,
|
||||
sqlalchemy_safe=sqlalchemy_safe,
|
||||
)
|
||||
if isinstance(obj, Enum):
|
||||
return obj.value
|
||||
@@ -89,14 +66,8 @@ def jsonable_encoder(
|
||||
if isinstance(obj, dict):
|
||||
encoded_dict = {}
|
||||
for key, value in obj.items():
|
||||
if (
|
||||
(
|
||||
not sqlalchemy_safe
|
||||
or (not isinstance(key, str))
|
||||
or (not key.startswith("_sa"))
|
||||
)
|
||||
and (value is not None or not exclude_none)
|
||||
and ((include and key in include) or key not in exclude)
|
||||
if (value is not None or not exclude_none) and (
|
||||
(include and key in include) or not exclude or key not in exclude
|
||||
):
|
||||
encoded_key = jsonable_encoder(
|
||||
key,
|
||||
@@ -104,7 +75,6 @@ def jsonable_encoder(
|
||||
exclude_unset=exclude_unset,
|
||||
exclude_none=exclude_none,
|
||||
custom_encoder=custom_encoder,
|
||||
sqlalchemy_safe=sqlalchemy_safe,
|
||||
)
|
||||
encoded_value = jsonable_encoder(
|
||||
value,
|
||||
@@ -112,7 +82,6 @@ def jsonable_encoder(
|
||||
exclude_unset=exclude_unset,
|
||||
exclude_none=exclude_none,
|
||||
custom_encoder=custom_encoder,
|
||||
sqlalchemy_safe=sqlalchemy_safe,
|
||||
)
|
||||
encoded_dict[encoded_key] = encoded_value
|
||||
return encoded_dict
|
||||
@@ -129,7 +98,6 @@ def jsonable_encoder(
|
||||
exclude_defaults=exclude_defaults,
|
||||
exclude_none=exclude_none,
|
||||
custom_encoder=custom_encoder,
|
||||
sqlalchemy_safe=sqlalchemy_safe,
|
||||
)
|
||||
)
|
||||
return encoded_list
|
||||
@@ -165,5 +133,4 @@ def jsonable_encoder(
|
||||
exclude_defaults=exclude_defaults,
|
||||
exclude_none=exclude_none,
|
||||
custom_encoder=custom_encoder,
|
||||
sqlalchemy_safe=sqlalchemy_safe,
|
||||
)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
from typing import Any, Sequence
|
||||
from typing import Any, Dict, Optional, Sequence
|
||||
|
||||
from fastapi.utils import PYDANTIC_1
|
||||
from pydantic import ValidationError, create_model
|
||||
from pydantic.error_wrappers import ErrorList
|
||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
from starlette.requests import Request
|
||||
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
|
||||
@@ -29,15 +29,9 @@ class FastAPIError(RuntimeError):
|
||||
class RequestValidationError(ValidationError):
|
||||
def __init__(self, errors: Sequence[ErrorList], *, body: Any = None) -> None:
|
||||
self.body = body
|
||||
if PYDANTIC_1:
|
||||
super().__init__(errors, RequestErrorModel)
|
||||
else:
|
||||
super().__init__(errors, Request) # type: ignore # pragma: nocover
|
||||
super().__init__(errors, RequestErrorModel)
|
||||
|
||||
|
||||
class WebSocketRequestValidationError(ValidationError):
|
||||
def __init__(self, errors: Sequence[ErrorList]) -> None:
|
||||
if PYDANTIC_1:
|
||||
super().__init__(errors, WebSocketErrorModel)
|
||||
else:
|
||||
super().__init__(errors, WebSocket) # type: ignore # pragma: nocover
|
||||
super().__init__(errors, WebSocketErrorModel)
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -2,24 +2,13 @@ from enum import Enum
|
||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Union
|
||||
|
||||
from fastapi.logger import logger
|
||||
from pydantic import BaseModel
|
||||
|
||||
try:
|
||||
from pydantic import AnyUrl, Field
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic import Schema as Field # type: ignore
|
||||
from pydantic import UrlStr as AnyUrl # type: ignore
|
||||
from pydantic import AnyUrl, BaseModel, Field
|
||||
|
||||
try:
|
||||
import email_validator
|
||||
|
||||
assert email_validator # make autoflake ignore the unused import
|
||||
try:
|
||||
from pydantic import EmailStr
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic.types import EmailStr # type: ignore
|
||||
from pydantic import EmailStr
|
||||
except ImportError: # pragma: no cover
|
||||
|
||||
class EmailStr(str): # type: ignore
|
||||
|
||||
@@ -16,10 +16,10 @@ from fastapi.params import Body, Param
|
||||
from fastapi.utils import (
|
||||
deep_dict_update,
|
||||
generate_operation_id_for_path,
|
||||
get_field_info,
|
||||
get_model_definitions,
|
||||
)
|
||||
from pydantic import BaseModel
|
||||
from pydantic.fields import ModelField
|
||||
from pydantic.schema import (
|
||||
field_schema,
|
||||
get_flat_models_from_fields,
|
||||
@@ -30,12 +30,6 @@ from starlette.responses import JSONResponse
|
||||
from starlette.routing import BaseRoute
|
||||
from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
try:
|
||||
from pydantic.fields import ModelField
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic.fields import Field as ModelField # type: ignore
|
||||
|
||||
validation_error_definition = {
|
||||
"title": "ValidationError",
|
||||
"type": "object",
|
||||
@@ -91,7 +85,7 @@ def get_openapi_operation_parameters(
|
||||
) -> List[Dict[str, Any]]:
|
||||
parameters = []
|
||||
for param in all_route_params:
|
||||
field_info = get_field_info(param)
|
||||
field_info = param.field_info
|
||||
field_info = cast(Param, field_info)
|
||||
# ignore mypy error until enum schemas are released
|
||||
parameter = {
|
||||
@@ -122,7 +116,7 @@ def get_openapi_operation_request_body(
|
||||
body_schema, _, _ = field_schema(
|
||||
body_field, model_name_map=model_name_map, ref_prefix=REF_PREFIX # type: ignore
|
||||
)
|
||||
field_info = cast(Body, get_field_info(body_field))
|
||||
field_info = cast(Body, body_field.field_info)
|
||||
request_media_type = field_info.media_type
|
||||
required = body_field.required
|
||||
request_body_oai: Dict[str, Any] = {}
|
||||
@@ -329,7 +323,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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
from enum import Enum
|
||||
from typing import Any, Callable, Sequence
|
||||
from typing import Any, Callable, Optional, Sequence
|
||||
|
||||
try:
|
||||
from pydantic.fields import FieldInfo
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic import Schema as FieldInfo # type: ignore
|
||||
from pydantic.fields import FieldInfo
|
||||
|
||||
|
||||
class ParamTypes(Enum):
|
||||
@@ -22,17 +18,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 +58,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 +96,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 +133,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 +172,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 +209,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 +248,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 +284,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 +314,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 +329,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)
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
from starlette.requests import Request # noqa
|
||||
from starlette.requests import HTTPConnection as HTTPConnection # noqa: F401
|
||||
from starlette.requests import Request as Request # noqa: F401
|
||||
|
||||
@@ -16,15 +16,13 @@ from fastapi.encoders import DictIntStrAny, SetIntStr, jsonable_encoder
|
||||
from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError
|
||||
from fastapi.openapi.constants import STATUS_CODES_WITH_NO_BODY
|
||||
from fastapi.utils import (
|
||||
PYDANTIC_1,
|
||||
create_cloned_field,
|
||||
create_response_field,
|
||||
generate_operation_id_for_path,
|
||||
get_field_info,
|
||||
warning_response_model_skip_defaults_deprecated,
|
||||
)
|
||||
from pydantic import BaseModel
|
||||
from pydantic.error_wrappers import ErrorWrapper, ValidationError
|
||||
from pydantic.fields import ModelField
|
||||
from starlette import routing
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
from starlette.exceptions import HTTPException
|
||||
@@ -41,13 +39,6 @@ from starlette.status import WS_1008_POLICY_VIOLATION
|
||||
from starlette.types import ASGIApp
|
||||
from starlette.websockets import WebSocket
|
||||
|
||||
try:
|
||||
from pydantic.fields import FieldInfo, ModelField
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic import Schema as FieldInfo # type: ignore
|
||||
from pydantic.fields import Field as ModelField # type: ignore
|
||||
|
||||
|
||||
def _prepare_response_content(
|
||||
res: Any,
|
||||
@@ -57,17 +48,12 @@ def _prepare_response_content(
|
||||
exclude_none: bool = False,
|
||||
) -> Any:
|
||||
if isinstance(res, BaseModel):
|
||||
if PYDANTIC_1:
|
||||
return res.dict(
|
||||
by_alias=True,
|
||||
exclude_unset=exclude_unset,
|
||||
exclude_defaults=exclude_defaults,
|
||||
exclude_none=exclude_none,
|
||||
)
|
||||
else:
|
||||
return res.dict(
|
||||
by_alias=True, skip_defaults=exclude_unset,
|
||||
) # pragma: nocover
|
||||
return res.dict(
|
||||
by_alias=True,
|
||||
exclude_unset=exclude_unset,
|
||||
exclude_defaults=exclude_defaults,
|
||||
exclude_none=exclude_none,
|
||||
)
|
||||
elif isinstance(res, list):
|
||||
return [
|
||||
_prepare_response_content(
|
||||
@@ -93,10 +79,10 @@ def _prepare_response_content(
|
||||
|
||||
async def serialize_response(
|
||||
*,
|
||||
field: ModelField = None,
|
||||
field: Optional[ModelField] = None,
|
||||
response_content: Any,
|
||||
include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
by_alias: bool = True,
|
||||
exclude_unset: bool = False,
|
||||
exclude_defaults: bool = False,
|
||||
@@ -151,21 +137,21 @@ 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_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_field: Optional[ModelField] = None,
|
||||
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
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)
|
||||
is_body_form = body_field and isinstance(get_field_info(body_field), params.Form)
|
||||
is_body_form = body_field and isinstance(body_field.field_info, params.Form)
|
||||
|
||||
async def app(request: Request) -> Response:
|
||||
try:
|
||||
@@ -226,7 +212,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 +236,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,27 +258,27 @@ 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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
operation_id: Optional[str] = None,
|
||||
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
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: 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 +387,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,32 +412,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
operation_id: Optional[str] = None,
|
||||
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
route_class = route_class_override or self.route_class
|
||||
route = route_class(
|
||||
path,
|
||||
@@ -470,9 +453,7 @@ class APIRouter(routing.Router):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -487,32 +468,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
|
||||
def decorator(func: Callable) -> Callable:
|
||||
self.add_api_route(
|
||||
path,
|
||||
@@ -531,9 +508,7 @@ class APIRouter(routing.Router):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -546,7 +521,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 +531,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 +543,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,30 +618,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
return self.api_route(
|
||||
path=path,
|
||||
response_model=response_model,
|
||||
@@ -683,9 +655,7 @@ class APIRouter(routing.Router):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -698,30 +668,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
return self.api_route(
|
||||
path=path,
|
||||
response_model=response_model,
|
||||
@@ -738,9 +705,7 @@ class APIRouter(routing.Router):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -753,30 +718,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
return self.api_route(
|
||||
path=path,
|
||||
response_model=response_model,
|
||||
@@ -793,9 +755,7 @@ class APIRouter(routing.Router):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -808,30 +768,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
return self.api_route(
|
||||
path=path,
|
||||
response_model=response_model,
|
||||
@@ -848,9 +805,7 @@ class APIRouter(routing.Router):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -863,30 +818,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
return self.api_route(
|
||||
path=path,
|
||||
response_model=response_model,
|
||||
@@ -903,9 +855,7 @@ class APIRouter(routing.Router):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -918,30 +868,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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
return self.api_route(
|
||||
path=path,
|
||||
response_model=response_model,
|
||||
@@ -958,9 +905,7 @@ class APIRouter(routing.Router):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -973,30 +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,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
return self.api_route(
|
||||
path=path,
|
||||
response_model=response_model,
|
||||
@@ -1013,9 +955,7 @@ class APIRouter(routing.Router):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -1028,30 +968,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,
|
||||
operation_id: str = None,
|
||||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
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: Optional[Union[SetIntStr, DictIntStrAny]] = None,
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: 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
|
||||
|
||||
return self.api_route(
|
||||
path=path,
|
||||
response_model=response_model,
|
||||
@@ -1068,9 +1006,7 @@ class APIRouter(routing.Router):
|
||||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
response_model_exclude_unset=response_model_exclude_unset,
|
||||
response_model_exclude_defaults=response_model_exclude_defaults,
|
||||
response_model_exclude_none=response_model_exclude_none,
|
||||
include_in_schema=include_in_schema,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,7 +27,7 @@ class OAuth2PasswordRequestForm:
|
||||
print(data.client_secret)
|
||||
return data
|
||||
|
||||
|
||||
|
||||
It creates the following Form request parameters in your endpoint:
|
||||
|
||||
grant_type: the OAuth2 spec says it is required and MUST be the fixed string "password".
|
||||
@@ -77,7 +77,7 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
|
||||
print(data.client_secret)
|
||||
return data
|
||||
|
||||
|
||||
|
||||
It creates the following Form request parameters in your endpoint:
|
||||
|
||||
grant_type: the OAuth2 spec says it is required and MUST be the fixed string "password".
|
||||
@@ -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)
|
||||
|
||||
@@ -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__
|
||||
|
||||
@@ -5,49 +5,13 @@ from enum import Enum
|
||||
from typing import Any, Dict, Optional, Set, Type, Union, cast
|
||||
|
||||
import fastapi
|
||||
from fastapi.logger import logger
|
||||
from fastapi.openapi.constants import REF_PREFIX
|
||||
from pydantic import BaseConfig, BaseModel, create_model
|
||||
from pydantic.class_validators import Validator
|
||||
from pydantic.fields import FieldInfo, ModelField, UndefinedType
|
||||
from pydantic.schema import model_process_schema
|
||||
from pydantic.utils import lenient_issubclass
|
||||
|
||||
try:
|
||||
from pydantic.fields import FieldInfo, ModelField, UndefinedType
|
||||
|
||||
PYDANTIC_1 = True
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic import Schema as FieldInfo # type: ignore
|
||||
from pydantic.fields import Field as ModelField # type: ignore
|
||||
|
||||
class UndefinedType: # type: ignore
|
||||
def __repr__(self) -> str:
|
||||
return "PydanticUndefined"
|
||||
|
||||
logger.warning(
|
||||
"Pydantic versions < 1.0.0 are deprecated in FastAPI and support will be "
|
||||
"removed soon."
|
||||
)
|
||||
PYDANTIC_1 = False
|
||||
|
||||
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
def get_field_info(field: ModelField) -> FieldInfo:
|
||||
if PYDANTIC_1:
|
||||
return field.field_info # type: ignore
|
||||
else:
|
||||
return field.schema # type: ignore # pragma: nocover
|
||||
|
||||
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
def warning_response_model_skip_defaults_deprecated() -> None:
|
||||
logger.warning( # pragma: nocover
|
||||
"response_model_skip_defaults has been deprecated in favor of "
|
||||
"response_model_exclude_unset to keep in line with Pydantic v1, support for "
|
||||
"it will be removed soon."
|
||||
)
|
||||
|
||||
|
||||
def get_model_definitions(
|
||||
*,
|
||||
@@ -98,10 +62,7 @@ def create_response_field(
|
||||
)
|
||||
|
||||
try:
|
||||
if PYDANTIC_1:
|
||||
return response_field(field_info=field_info)
|
||||
else: # pragma: nocover
|
||||
return response_field(schema=field_info)
|
||||
return response_field(field_info=field_info)
|
||||
except RuntimeError:
|
||||
raise fastapi.exceptions.FastAPIError(
|
||||
f"Invalid args for response field! Hint: check that {type_} is a valid pydantic field type"
|
||||
@@ -109,7 +70,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:
|
||||
@@ -135,10 +98,7 @@ def create_cloned_field(
|
||||
new_field.default = field.default
|
||||
new_field.required = field.required
|
||||
new_field.model_config = field.model_config
|
||||
if PYDANTIC_1:
|
||||
new_field.field_info = field.field_info
|
||||
else: # pragma: nocover
|
||||
new_field.schema = field.schema # type: ignore
|
||||
new_field.field_info = field.field_info
|
||||
new_field.allow_none = field.allow_none
|
||||
new_field.validate_always = field.validate_always
|
||||
if field.sub_fields:
|
||||
@@ -151,19 +111,11 @@ def create_cloned_field(
|
||||
field.key_field, cloned_types=cloned_types
|
||||
)
|
||||
new_field.validators = field.validators
|
||||
if PYDANTIC_1:
|
||||
new_field.pre_validators = field.pre_validators
|
||||
new_field.post_validators = field.post_validators
|
||||
else: # pragma: nocover
|
||||
new_field.whole_pre_validators = field.whole_pre_validators # type: ignore
|
||||
new_field.whole_post_validators = field.whole_post_validators # type: ignore
|
||||
new_field.pre_validators = field.pre_validators
|
||||
new_field.post_validators = field.post_validators
|
||||
new_field.parse_json = field.parse_json
|
||||
new_field.shape = field.shape
|
||||
try:
|
||||
new_field.populate_validators()
|
||||
except AttributeError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
new_field._populate_validators() # type: ignore
|
||||
new_field.populate_validators()
|
||||
return new_field
|
||||
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ classifiers = [
|
||||
"Topic :: Internet :: WWW/HTTP",
|
||||
]
|
||||
requires = [
|
||||
"starlette ==0.13.4",
|
||||
"pydantic >=0.32.2,<2.0.0"
|
||||
"starlette ==0.13.6",
|
||||
"pydantic >=1.0.0,<2.0.0"
|
||||
]
|
||||
description-file = "README.md"
|
||||
requires-python = ">=3.6"
|
||||
@@ -45,10 +45,13 @@ 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",
|
||||
"flake8 >=3.8.3,<4.0.0",
|
||||
"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 +65,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",
|
||||
|
||||
14
scripts/docs-comment-deploy.sh
Normal file
14
scripts/docs-comment-deploy.sh
Normal 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}"'"}'
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -4,5 +4,6 @@ set -e
|
||||
set -x
|
||||
|
||||
mypy fastapi
|
||||
flake8 fastapi tests
|
||||
black fastapi tests --check
|
||||
isort fastapi tests docs_src scripts --check-only
|
||||
|
||||
@@ -2,8 +2,4 @@
|
||||
|
||||
set -e
|
||||
|
||||
bash scripts/publish.sh
|
||||
|
||||
bash scripts/trigger-docker.sh
|
||||
|
||||
python scripts/gitter_releases_bot.py
|
||||
@@ -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 ${@}
|
||||
|
||||
@@ -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
13
scripts/unzip-docs.sh
Normal 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
9
scripts/zip-docs.sh
Normal 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
|
||||
@@ -168,7 +168,7 @@ def get_query_type_optional(query: Optional[int] = None):
|
||||
|
||||
|
||||
@app.get("/query/int/default")
|
||||
def get_query_type_optional(query: int = 10):
|
||||
def get_query_type_int_default(query: int = 10):
|
||||
return f"foo bar {query}"
|
||||
|
||||
|
||||
|
||||
@@ -976,8 +976,8 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Query Type Optional",
|
||||
"operationId": "get_query_type_optional_query_int_default_get",
|
||||
"summary": "Get Query Type Int Default",
|
||||
"operationId": "get_query_type_int_default_query_int_default_get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
@@ -1144,7 +1144,7 @@ def test_swagger_ui():
|
||||
assert response.headers["content-type"] == "text/html; charset=utf-8"
|
||||
assert "swagger-ui-dist" in response.text
|
||||
assert (
|
||||
f"oauth2RedirectUrl: window.location.origin + '/docs/oauth2-redirect'"
|
||||
"oauth2RedirectUrl: window.location.origin + '/docs/oauth2-redirect'"
|
||||
in response.text
|
||||
)
|
||||
|
||||
|
||||
@@ -59,12 +59,14 @@ async def get_callable_gen_dependency(value: str = Depends(callable_gen_dependen
|
||||
|
||||
|
||||
@app.get("/async-callable-dependency")
|
||||
async def get_callable_dependency(value: str = Depends(async_callable_dependency)):
|
||||
async def get_async_callable_dependency(
|
||||
value: str = Depends(async_callable_dependency),
|
||||
):
|
||||
return value
|
||||
|
||||
|
||||
@app.get("/async-callable-gen-dependency")
|
||||
async def get_callable_gen_dependency(
|
||||
async def get_async_callable_gen_dependency(
|
||||
value: str = Depends(async_callable_gen_dependency),
|
||||
):
|
||||
return value
|
||||
|
||||
@@ -204,19 +204,19 @@ client = TestClient(app)
|
||||
|
||||
|
||||
def test_async_state():
|
||||
assert state["/async"] == f"asyncgen not started"
|
||||
assert state["/async"] == "asyncgen not started"
|
||||
response = client.get("/async")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == f"asyncgen started"
|
||||
assert state["/async"] == f"asyncgen completed"
|
||||
assert response.json() == "asyncgen started"
|
||||
assert state["/async"] == "asyncgen completed"
|
||||
|
||||
|
||||
def test_sync_state():
|
||||
assert state["/sync"] == f"generator not started"
|
||||
assert state["/sync"] == "generator not started"
|
||||
response = client.get("/sync")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == f"generator started"
|
||||
assert state["/sync"] == f"generator completed"
|
||||
assert response.json() == "generator started"
|
||||
assert state["/sync"] == "generator completed"
|
||||
|
||||
|
||||
def test_async_raise_other():
|
||||
@@ -281,15 +281,15 @@ def test_sync_raise():
|
||||
def test_sync_async_state():
|
||||
response = client.get("/sync_async")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == f"asyncgen started"
|
||||
assert state["/async"] == f"asyncgen completed"
|
||||
assert response.json() == "asyncgen started"
|
||||
assert state["/async"] == "asyncgen completed"
|
||||
|
||||
|
||||
def test_sync_sync_state():
|
||||
response = client.get("/sync_sync")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == f"generator started"
|
||||
assert state["/sync"] == f"generator completed"
|
||||
assert response.json() == "generator started"
|
||||
assert state["/sync"] == "generator completed"
|
||||
|
||||
|
||||
def test_sync_async_raise_other():
|
||||
|
||||
108
tests/test_get_request_body.py
Normal file
108
tests/test_get_request_body.py
Normal 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
|
||||
39
tests/test_http_connection_injection.py
Normal file
39
tests/test_http_connection_injection.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from fastapi import Depends, FastAPI
|
||||
from fastapi.requests import HTTPConnection
|
||||
from fastapi.testclient import TestClient
|
||||
from starlette.websockets import WebSocket
|
||||
|
||||
app = FastAPI()
|
||||
app.state.value = 42
|
||||
|
||||
|
||||
async def extract_value_from_http_connection(conn: HTTPConnection):
|
||||
return conn.app.state.value
|
||||
|
||||
|
||||
@app.get("/http")
|
||||
async def get_value_by_http(value: int = Depends(extract_value_from_http_connection)):
|
||||
return value
|
||||
|
||||
|
||||
@app.websocket("/ws")
|
||||
async def get_value_by_ws(
|
||||
websocket: WebSocket, value: int = Depends(extract_value_from_http_connection)
|
||||
):
|
||||
await websocket.accept()
|
||||
await websocket.send_json(value)
|
||||
await websocket.close()
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_value_extracting_by_http():
|
||||
response = client.get("/http")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == 42
|
||||
|
||||
|
||||
def test_value_extracting_by_ws():
|
||||
with client.websocket_connect("/ws") as websocket:
|
||||
assert websocket.receive_json() == 42
|
||||
@@ -5,13 +5,7 @@ from typing import Optional
|
||||
|
||||
import pytest
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from pydantic import BaseModel, ValidationError, create_model
|
||||
|
||||
try:
|
||||
from pydantic import Field
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic import Schema as Field
|
||||
from pydantic import BaseModel, Field, ValidationError, create_model
|
||||
|
||||
|
||||
class Person:
|
||||
@@ -55,6 +49,11 @@ class ModelWithCustomEncoder(BaseModel):
|
||||
}
|
||||
|
||||
|
||||
class ModelWithCustomEncoderSubclass(ModelWithCustomEncoder):
|
||||
class Config:
|
||||
pass
|
||||
|
||||
|
||||
class RoleEnum(Enum):
|
||||
admin = "admin"
|
||||
normal = "normal"
|
||||
@@ -117,6 +116,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"}
|
||||
@@ -124,7 +128,7 @@ def test_encode_model_with_config():
|
||||
|
||||
def test_encode_model_with_alias_raises():
|
||||
with pytest.raises(ValidationError):
|
||||
model = ModelWithAlias(foo="Bar")
|
||||
ModelWithAlias(foo="Bar")
|
||||
|
||||
|
||||
def test_encode_model_with_alias():
|
||||
|
||||
106
tests/test_multipart_installation.py
Normal file
106
tests/test_multipart_installation.py
Normal 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
|
||||
@@ -8,6 +8,7 @@ app = FastAPI()
|
||||
|
||||
media_type = "application/vnd.api+json"
|
||||
|
||||
|
||||
# NOTE: These are not valid JSON:API resources
|
||||
# but they are fine for testing requestBody with custom media_type
|
||||
class Product(BaseModel):
|
||||
|
||||
@@ -54,17 +54,17 @@ def by_alias_list():
|
||||
|
||||
|
||||
@app.get("/no-alias/dict", response_model=ModelNoAlias)
|
||||
def by_alias_dict():
|
||||
def no_alias_dict():
|
||||
return {"name": "Foo"}
|
||||
|
||||
|
||||
@app.get("/no-alias/model", response_model=ModelNoAlias)
|
||||
def by_alias_model():
|
||||
def no_alias_model():
|
||||
return ModelNoAlias(name="Foo")
|
||||
|
||||
|
||||
@app.get("/no-alias/list", response_model=List[ModelNoAlias])
|
||||
def by_alias_list():
|
||||
def no_alias_list():
|
||||
return [{"name": "Foo"}, {"name": "Bar"}]
|
||||
|
||||
|
||||
@@ -178,8 +178,8 @@ openapi_schema = {
|
||||
},
|
||||
"/no-alias/dict": {
|
||||
"get": {
|
||||
"summary": "By Alias Dict",
|
||||
"operationId": "by_alias_dict_no_alias_dict_get",
|
||||
"summary": "No Alias Dict",
|
||||
"operationId": "no_alias_dict_no_alias_dict_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
@@ -194,8 +194,8 @@ openapi_schema = {
|
||||
},
|
||||
"/no-alias/model": {
|
||||
"get": {
|
||||
"summary": "By Alias Model",
|
||||
"operationId": "by_alias_model_no_alias_model_get",
|
||||
"summary": "No Alias Model",
|
||||
"operationId": "no_alias_model_no_alias_model_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
@@ -210,15 +210,15 @@ openapi_schema = {
|
||||
},
|
||||
"/no-alias/list": {
|
||||
"get": {
|
||||
"summary": "By Alias List",
|
||||
"operationId": "by_alias_list_no_alias_list_get",
|
||||
"summary": "No Alias List",
|
||||
"operationId": "no_alias_list_no_alias_list_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response By Alias List No Alias List Get",
|
||||
"title": "Response No Alias List No Alias List Get",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ModelNoAlias"
|
||||
|
||||
@@ -28,7 +28,7 @@ def get_current_user(oauth_header: "str" = Security(reusable_oauth2)):
|
||||
|
||||
@app.post("/login")
|
||||
# Here we use string annotations to test them
|
||||
def read_current_user(form_data: "OAuth2PasswordRequestFormStrict" = Depends()):
|
||||
def login(form_data: "OAuth2PasswordRequestFormStrict" = Depends()):
|
||||
return form_data
|
||||
|
||||
|
||||
@@ -62,13 +62,13 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_login_post",
|
||||
"summary": "Login",
|
||||
"operationId": "login_login_post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/x-www-form-urlencoded": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Body_read_current_user_login_post"
|
||||
"$ref": "#/components/schemas/Body_login_login_post"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -92,8 +92,8 @@ openapi_schema = {
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Body_read_current_user_login_post": {
|
||||
"title": "Body_read_current_user_login_post",
|
||||
"Body_login_login_post": {
|
||||
"title": "Body_login_login_post",
|
||||
"required": ["grant_type", "username", "password"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -31,12 +31,12 @@ def get_current_user(oauth_header: Optional[str] = Security(reusable_oauth2)):
|
||||
|
||||
|
||||
@app.post("/login")
|
||||
def read_current_user(form_data: OAuth2PasswordRequestFormStrict = Depends()):
|
||||
def login(form_data: OAuth2PasswordRequestFormStrict = Depends()):
|
||||
return form_data
|
||||
|
||||
|
||||
@app.get("/users/me")
|
||||
def read_current_user(current_user: Optional[User] = Depends(get_current_user)):
|
||||
def read_users_me(current_user: Optional[User] = Depends(get_current_user)):
|
||||
if current_user is None:
|
||||
return {"msg": "Create an account first"}
|
||||
return current_user
|
||||
@@ -66,13 +66,13 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_login_post",
|
||||
"summary": "Login",
|
||||
"operationId": "login_login_post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/x-www-form-urlencoded": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Body_read_current_user_login_post"
|
||||
"$ref": "#/components/schemas/Body_login_login_post"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -88,16 +88,16 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"summary": "Read Users Me",
|
||||
"operationId": "read_users_me_users_me_get",
|
||||
"security": [{"OAuth2": []}],
|
||||
}
|
||||
},
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Body_read_current_user_login_post": {
|
||||
"title": "Body_read_current_user_login_post",
|
||||
"Body_login_login_post": {
|
||||
"title": "Body_login_login_post",
|
||||
"required": ["grant_type", "username", "password"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -30,14 +30,14 @@ class ModelDefaults(BaseModel):
|
||||
|
||||
|
||||
@app.get("/", response_model=Model, response_model_exclude_unset=True)
|
||||
def get() -> ModelSubclass:
|
||||
def get_root() -> ModelSubclass:
|
||||
return ModelSubclass(sub={}, y=1, z=0)
|
||||
|
||||
|
||||
@app.get(
|
||||
"/exclude_unset", response_model=ModelDefaults, response_model_exclude_unset=True
|
||||
)
|
||||
def get() -> ModelDefaults:
|
||||
def get_exclude_unset() -> ModelDefaults:
|
||||
return ModelDefaults(x=None, y="y")
|
||||
|
||||
|
||||
@@ -46,14 +46,14 @@ def get() -> ModelDefaults:
|
||||
response_model=ModelDefaults,
|
||||
response_model_exclude_defaults=True,
|
||||
)
|
||||
def get() -> ModelDefaults:
|
||||
def get_exclude_defaults() -> ModelDefaults:
|
||||
return ModelDefaults(x=None, y="y")
|
||||
|
||||
|
||||
@app.get(
|
||||
"/exclude_none", response_model=ModelDefaults, response_model_exclude_none=True
|
||||
)
|
||||
def get() -> ModelDefaults:
|
||||
def get_exclude_none() -> ModelDefaults:
|
||||
return ModelDefaults(x=None, y="y")
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ def get() -> ModelDefaults:
|
||||
response_model_exclude_unset=True,
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
def get() -> ModelDefaults:
|
||||
def get_exclude_unset_none() -> ModelDefaults:
|
||||
return ModelDefaults(x=None, y="y")
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ items = {"foo": "The Foo Wrestlers"}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
async def create_item(item_id: str):
|
||||
async def read_item(item_id: str):
|
||||
if item_id not in items:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
@@ -19,7 +19,7 @@ async def create_item(item_id: str):
|
||||
|
||||
|
||||
@app.get("/starlette-items/{item_id}")
|
||||
async def create_item(item_id: str):
|
||||
async def read_starlette_item(item_id: str):
|
||||
if item_id not in items:
|
||||
raise StarletteHTTPException(status_code=404, detail="Item not found")
|
||||
return {"item": items[item_id]}
|
||||
@@ -49,8 +49,8 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Create Item",
|
||||
"operationId": "create_item_items__item_id__get",
|
||||
"summary": "Read Item",
|
||||
"operationId": "read_item_items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
@@ -79,8 +79,8 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Create Item",
|
||||
"operationId": "create_item_starlette_items__item_id__get",
|
||||
"summary": "Read Starlette Item",
|
||||
"operationId": "read_starlette_item_starlette_items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user