mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-27 08:10:57 -05:00
Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4721405ef7 | ||
|
|
8066f85b3f | ||
|
|
2ffb08d0bc | ||
|
|
d1805ef466 | ||
|
|
41d774ed6d | ||
|
|
836ac56203 | ||
|
|
a01c2ca3dd | ||
|
|
6553243dbf | ||
|
|
fdc713428e | ||
|
|
60343161ea | ||
|
|
586de94ca1 | ||
|
|
56bc75372f | ||
|
|
4842dfadcf | ||
|
|
cfc06a3a3d | ||
|
|
c812b42293 | ||
|
|
68ce5b37dc | ||
|
|
0dc9a377dc | ||
|
|
d82700c96d | ||
|
|
fafe670db6 | ||
|
|
47342cdd18 | ||
|
|
e76dd3e70d | ||
|
|
e5f3d6a5eb | ||
|
|
7217f167d4 | ||
|
|
762ede2bec | ||
|
|
a3b1478221 | ||
|
|
41ff599d4b | ||
|
|
b1f27c96c4 | ||
|
|
4c401aef0f | ||
|
|
c7dad1bb59 | ||
|
|
0ef164e1ee | ||
|
|
fd6a78cbfe | ||
|
|
fa7474b2e8 | ||
|
|
2f0541f17a | ||
|
|
804a0a90cf | ||
|
|
847befdc1d | ||
|
|
4a7b21483b | ||
|
|
1182b36362 | ||
|
|
e17cacfee4 | ||
|
|
234cecb5bf | ||
|
|
612cbee165 | ||
|
|
223ed67682 | ||
|
|
a2a0119c14 | ||
|
|
09319d6271 | ||
|
|
a92e9c957a | ||
|
|
7505f24f2e | ||
|
|
57727fa4e0 | ||
|
|
a2aede32b4 | ||
|
|
7c66ec8a8b | ||
|
|
d47eea9bb6 | ||
|
|
74de9a7b15 | ||
|
|
3279f0ba63 | ||
|
|
428376d285 | ||
|
|
05c5ce3689 | ||
|
|
b4b39d3359 | ||
|
|
2f048f7199 | ||
|
|
2cef119cd7 | ||
|
|
dd1c2018dc | ||
|
|
e94c13ce74 | ||
|
|
b7ce10079e | ||
|
|
87d5870314 | ||
|
|
49bc3e0873 | ||
|
|
8767634932 | ||
|
|
32935103b1 | ||
|
|
395ece75aa | ||
|
|
e958d30d1d | ||
|
|
34fca99b28 | ||
|
|
3289796286 | ||
|
|
7167c77a18 | ||
|
|
ba882c10fe | ||
|
|
4ac55af283 | ||
|
|
3390a82832 | ||
|
|
f5844e76b5 | ||
|
|
32cefb9bff | ||
|
|
17e49bc9f7 | ||
|
|
df58ecdee2 | ||
|
|
6595658324 | ||
|
|
c8b729aea7 | ||
|
|
d8b8f211e8 | ||
|
|
ee96a099d8 | ||
|
|
ab03f22635 | ||
|
|
f5e2dd8025 | ||
|
|
19347bfc3c | ||
|
|
20d93fad94 | ||
|
|
58e50622de | ||
|
|
4ac8b8e443 | ||
|
|
3d162455a7 | ||
|
|
edc939eb3a | ||
|
|
4c64c15ead | ||
|
|
e3d67a150c | ||
|
|
9b14107695 | ||
|
|
57679e8370 | ||
|
|
6fe26b5689 | ||
|
|
510fa5b7fe | ||
|
|
ca8ddb2893 | ||
|
|
6dd8e567cc | ||
|
|
ae5c51afa6 | ||
|
|
19757d1859 | ||
|
|
e645a2db1b | ||
|
|
52fd0afc94 | ||
|
|
503cec5649 | ||
|
|
2c7a0aca95 | ||
|
|
8d29e494e0 | ||
|
|
4c23c0644b | ||
|
|
d189c38aaf | ||
|
|
010d44ee1b | ||
|
|
4b31beef35 | ||
|
|
61a8d6720c | ||
|
|
155fc5e24e | ||
|
|
2d35651a5a |
6
.github/workflows/build-docs.yml
vendored
6
.github/workflows/build-docs.yml
vendored
@@ -22,10 +22,10 @@ jobs:
|
||||
id: cache
|
||||
with:
|
||||
path: ${{ env.pythonLocation }}
|
||||
key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-v03
|
||||
key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-docs.txt') }}-v03
|
||||
- name: Install docs extras
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: pip install .[doc]
|
||||
run: pip install -r requirements-docs.txt
|
||||
- name: Install Material for MkDocs Insiders
|
||||
if: ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false ) && steps.cache.outputs.cache-hit != 'true'
|
||||
run: pip install git+https://${{ secrets.ACTIONS_TOKEN }}@github.com/squidfunk/mkdocs-material-insiders.git
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
with:
|
||||
publish-dir: './site'
|
||||
production-branch: master
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
github-token: ${{ secrets.FASTAPI_BUILD_DOCS_NETLIFY }}
|
||||
enable-commit-comment: false
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
|
||||
2
.github/workflows/issue-manager.yml
vendored
2
.github/workflows/issue-manager.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
steps:
|
||||
- uses: tiangolo/issue-manager@0.4.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ secrets.FASTAPI_ISSUE_MANAGER }}
|
||||
config: >
|
||||
{
|
||||
"answered": {
|
||||
|
||||
2
.github/workflows/label-approved.yml
vendored
2
.github/workflows/label-approved.yml
vendored
@@ -10,4 +10,4 @@ jobs:
|
||||
steps:
|
||||
- uses: docker://tiangolo/label-approved:0.0.2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ secrets.FASTAPI_LABEL_APPROVED }}
|
||||
|
||||
4
.github/workflows/latest-changes.yml
vendored
4
.github/workflows/latest-changes.yml
vendored
@@ -30,11 +30,9 @@ jobs:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
|
||||
with:
|
||||
limit-access-to-actor: true
|
||||
token: ${{ secrets.ACTIONS_TOKEN }}
|
||||
standard_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: docker://tiangolo/latest-changes:0.0.3
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ secrets.FASTAPI_LATEST_CHANGES }}
|
||||
latest_changes_file: docs/en/docs/release-notes.md
|
||||
latest_changes_header: '## Latest Changes\n\n'
|
||||
debug_logs: true
|
||||
|
||||
2
.github/workflows/notify-translations.yml
vendored
2
.github/workflows/notify-translations.yml
vendored
@@ -19,4 +19,4 @@ jobs:
|
||||
limit-access-to-actor: true
|
||||
- uses: ./.github/actions/notify-translations
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ secrets.FASTAPI_NOTIFY_TRANSLATIONS }}
|
||||
|
||||
4
.github/workflows/people.yml
vendored
4
.github/workflows/people.yml
vendored
@@ -24,9 +24,7 @@ jobs:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
|
||||
with:
|
||||
limit-access-to-actor: true
|
||||
token: ${{ secrets.ACTIONS_TOKEN }}
|
||||
standard_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: ./.github/actions/people
|
||||
with:
|
||||
token: ${{ secrets.ACTIONS_TOKEN }}
|
||||
standard_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
standard_token: ${{ secrets.FASTAPI_PEOPLE }}
|
||||
|
||||
6
.github/workflows/preview-docs.yml
vendored
6
.github/workflows/preview-docs.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Download Artifact Docs
|
||||
uses: dawidd6/action-download-artifact@v2.27.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
github_token: ${{ secrets.FASTAPI_PREVIEW_DOCS_DOWNLOAD_ARTIFACTS }}
|
||||
workflow: build-docs.yml
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
name: docs-zip
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
with:
|
||||
publish-dir: './site'
|
||||
production-deploy: false
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
github-token: ${{ secrets.FASTAPI_PREVIEW_DOCS_NETLIFY }}
|
||||
enable-commit-comment: false
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
@@ -42,5 +42,5 @@ jobs:
|
||||
- name: Comment Deploy
|
||||
uses: ./.github/actions/comment-docs-preview-in-pr
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ secrets.FASTAPI_PREVIEW_DOCS_COMMENT_DEPLOY }}
|
||||
deploy_url: "${{ steps.netlify.outputs.deploy-url }}"
|
||||
|
||||
8
.github/workflows/publish.yml
vendored
8
.github/workflows/publish.yml
vendored
@@ -32,16 +32,10 @@ jobs:
|
||||
- name: Build distribution
|
||||
run: python -m build
|
||||
- name: Publish
|
||||
uses: pypa/gh-action-pypi-publish@v1.8.5
|
||||
uses: pypa/gh-action-pypi-publish@v1.8.6
|
||||
with:
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
- 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
|
||||
|
||||
3
.github/workflows/smokeshow.yml
vendored
3
.github/workflows/smokeshow.yml
vendored
@@ -22,6 +22,7 @@ jobs:
|
||||
|
||||
- uses: dawidd6/action-download-artifact@v2.27.0
|
||||
with:
|
||||
github_token: ${{ secrets.FASTAPI_SMOKESHOW_DOWNLOAD_ARTIFACTS }}
|
||||
workflow: test.yml
|
||||
commit: ${{ github.event.workflow_run.head_sha }}
|
||||
|
||||
@@ -30,6 +31,6 @@ jobs:
|
||||
SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage}
|
||||
SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 100
|
||||
SMOKESHOW_GITHUB_CONTEXT: coverage
|
||||
SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SMOKESHOW_GITHUB_TOKEN: ${{ secrets.FASTAPI_SMOKESHOW_UPLOAD }}
|
||||
SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY }}
|
||||
|
||||
48
.github/workflows/test.yml
vendored
48
.github/workflows/test.yml
vendored
@@ -5,16 +5,39 @@ on:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
# Issue ref: https://github.com/actions/setup-python/issues/436
|
||||
# cache: "pip"
|
||||
# cache-dependency-path: pyproject.toml
|
||||
- uses: actions/cache@v3
|
||||
id: cache
|
||||
with:
|
||||
path: ${{ env.pythonLocation }}
|
||||
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-test-v03
|
||||
- name: Install Dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: pip install -r requirements-tests.txt
|
||||
- name: Lint
|
||||
run: bash scripts/lint.sh
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
@@ -23,17 +46,15 @@ jobs:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
# Issue ref: https://github.com/actions/setup-python/issues/436
|
||||
# cache: "pip"
|
||||
cache-dependency-path: pyproject.toml
|
||||
# cache-dependency-path: pyproject.toml
|
||||
- uses: actions/cache@v3
|
||||
id: cache
|
||||
with:
|
||||
path: ${{ env.pythonLocation }}
|
||||
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-test-v03
|
||||
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-test-v03
|
||||
- name: Install Dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: pip install -e .[all,dev,doc,test]
|
||||
- name: Lint
|
||||
run: bash scripts/lint.sh
|
||||
run: pip install -r requirements-tests.txt
|
||||
- run: mkdir coverage
|
||||
- name: Test
|
||||
run: bash scripts/test.sh
|
||||
@@ -45,33 +66,28 @@ jobs:
|
||||
with:
|
||||
name: coverage
|
||||
path: coverage
|
||||
|
||||
coverage-combine:
|
||||
needs: [test]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.8'
|
||||
# Issue ref: https://github.com/actions/setup-python/issues/436
|
||||
# cache: "pip"
|
||||
cache-dependency-path: pyproject.toml
|
||||
|
||||
# cache-dependency-path: pyproject.toml
|
||||
- name: Get coverage files
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: coverage
|
||||
path: coverage
|
||||
|
||||
- run: pip install coverage[toml]
|
||||
|
||||
- run: ls -la coverage
|
||||
- run: coverage combine coverage
|
||||
- run: coverage report
|
||||
- run: coverage html --show-contexts --title "Coverage for ${{ github.sha }}"
|
||||
|
||||
- name: Store coverage HTML
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -80,14 +96,10 @@ jobs:
|
||||
|
||||
# https://github.com/marketplace/actions/alls-green#why
|
||||
check: # This job does nothing and is only used for the branch protection
|
||||
|
||||
if: always()
|
||||
|
||||
needs:
|
||||
- coverage-combine
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Decide whether the needed jobs succeeded or failed
|
||||
uses: re-actors/alls-green@release/v1
|
||||
|
||||
@@ -21,24 +21,13 @@ repos:
|
||||
- --py3-plus
|
||||
- --keep-runtime-typing
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.254
|
||||
rev: v0.0.272
|
||||
hooks:
|
||||
- id: ruff
|
||||
args:
|
||||
- --fix
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort (python)
|
||||
- id: isort
|
||||
name: isort (cython)
|
||||
types: [cython]
|
||||
- id: isort
|
||||
name: isort (pyi)
|
||||
types: [pyi]
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.1.0
|
||||
rev: 23.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
ci:
|
||||
|
||||
@@ -47,6 +47,7 @@ The key features are:
|
||||
<!-- sponsors -->
|
||||
|
||||
<a href="https://cryptapi.io/" target="_blank" title="CryptAPI: Your easy to use, secure and privacy oriented payment gateway."><img src="https://fastapi.tiangolo.com/img/sponsors/cryptapi.svg"></a>
|
||||
<a href="https://platform.sh/try-it-now/?utm_source=fastapi-signup&utm_medium=banner&utm_campaign=FastAPI-signup-June-2023" target="_blank" title="Build, run and scale your apps on a modern, reliable, and secure PaaS."><img src="https://fastapi.tiangolo.com/img/sponsors/platform-sh.png"></a>
|
||||
<a href="https://www.deta.sh/?ref=fastapi" target="_blank" title="The launchpad for all your (team's) ideas"><img src="https://fastapi.tiangolo.com/img/sponsors/deta.svg"></a>
|
||||
<a href="https://training.talkpython.fm/fastapi-courses" target="_blank" title="FastAPI video courses on demand from people you trust"><img src="https://fastapi.tiangolo.com/img/sponsors/talkpython.png"></a>
|
||||
<a href="https://testdriven.io/courses/tdd-fastapi/" target="_blank" title="Learn to build high-quality web apps with best practices"><img src="https://fastapi.tiangolo.com/img/sponsors/testdriven.svg"></a>
|
||||
@@ -445,7 +446,6 @@ To understand more about it, see the section <a href="https://fastapi.tiangolo.c
|
||||
|
||||
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:
|
||||
|
||||
@@ -441,7 +441,6 @@ To understand more about it, see the section <a href="https://fastapi.tiangolo.c
|
||||
|
||||
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:
|
||||
|
||||
@@ -440,7 +440,6 @@ To understand more about it, see the section <a href="https://fastapi.tiangolo.c
|
||||
|
||||
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:
|
||||
|
||||
@@ -108,7 +108,7 @@ $ python -m pip install --upgrade pip
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install -e ."[dev,doc,test]"
|
||||
$ pip install -r requirements.txt
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
@@ -233,6 +233,10 @@ articles:
|
||||
link: https://medium.com/@krishnardt365/fastapi-docker-and-postgres-91943e71be92
|
||||
title: Fastapi, Docker(Docker compose) and Postgres
|
||||
german:
|
||||
- author: Marcel Sander (actidoo)
|
||||
author_link: https://www.actidoo.com
|
||||
link: https://www.actidoo.com/de/blog/python-fastapi-domain-driven-design
|
||||
title: Domain-driven Design mit Python und FastAPI
|
||||
- author: Nico Axtmann
|
||||
author_link: https://twitter.com/_nicoax
|
||||
link: https://blog.codecentric.de/2019/08/inbetriebnahme-eines-scikit-learn-modells-mit-onnx-und-fastapi/
|
||||
|
||||
@@ -2,6 +2,9 @@ gold:
|
||||
- url: https://cryptapi.io/
|
||||
title: "CryptAPI: Your easy to use, secure and privacy oriented payment gateway."
|
||||
img: https://fastapi.tiangolo.com/img/sponsors/cryptapi.svg
|
||||
- url: https://platform.sh/try-it-now/?utm_source=fastapi-signup&utm_medium=banner&utm_campaign=FastAPI-signup-June-2023
|
||||
title: "Build, run and scale your apps on a modern, reliable, and secure PaaS."
|
||||
img: https://fastapi.tiangolo.com/img/sponsors/platform-sh.png
|
||||
silver:
|
||||
- url: https://www.deta.sh/?ref=fastapi
|
||||
title: The launchpad for all your (team's) ideas
|
||||
@@ -28,3 +31,6 @@ bronze:
|
||||
- url: https://www.exoflare.com/open-source/?utm_source=FastAPI&utm_campaign=open_source
|
||||
title: Biosecurity risk assessments made easy.
|
||||
img: https://fastapi.tiangolo.com/img/sponsors/exoflare.png
|
||||
- url: https://www.flint.sh
|
||||
title: IT expertise, consulting and development by passionate people
|
||||
img: https://fastapi.tiangolo.com/img/sponsors/flint.png
|
||||
|
||||
@@ -15,3 +15,5 @@ logins:
|
||||
- svix
|
||||
- armand-sauzay
|
||||
- databento-bot
|
||||
- nanram22
|
||||
- Flint-company
|
||||
|
||||
@@ -44,7 +44,7 @@ So the new file structure looks like:
|
||||
|
||||
First, we create a new database session with the new database.
|
||||
|
||||
For the tests we'll use a file `test.db` instead of `sql_app.db`.
|
||||
We'll use an in-memory database that persists during the tests instead of the local file `sql_app.db`.
|
||||
|
||||
But the rest of the session code is more or less the same, we just copy it.
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ After activating the environment as described above:
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install -e ".[dev,doc,test]"
|
||||
$ pip install -r requirements.txt
|
||||
|
||||
---> 100%
|
||||
```
|
||||
@@ -121,10 +121,15 @@ It will install all the dependencies and your local FastAPI in your local enviro
|
||||
|
||||
If you create a Python file that imports and uses FastAPI, and run it with the Python from your local environment, it will use your local FastAPI source code.
|
||||
|
||||
And if you update that local FastAPI source code, as it is installed with `-e`, when you run that Python file again, it will use the fresh version of FastAPI you just edited.
|
||||
And if you update that local FastAPI source code when you run that Python file again, it will use the fresh version of FastAPI you just edited.
|
||||
|
||||
That way, you don't have to "install" your local version to be able to test every change.
|
||||
|
||||
!!! note "Technical Details"
|
||||
This only happens when you install using this included `requiements.txt` instead of installing `pip install fastapi` directly.
|
||||
|
||||
That is because inside of the `requirements.txt` file, the local version of FastAPI is marked to be installed in "editable" mode, with the `-e` option.
|
||||
|
||||
### Format
|
||||
|
||||
There is a script that you can run that will format and clean all your code:
|
||||
|
||||
@@ -189,8 +189,6 @@ With **FastAPI** you get all of **Pydantic**'s features (as FastAPI is based on
|
||||
* If you know Python types you know how to use Pydantic.
|
||||
* Plays nicely with your **<abbr title="Integrated Development Environment, similar to a code editor">IDE</abbr>/<abbr title="A program that checks for code errors">linter</abbr>/brain**:
|
||||
* Because pydantic data structures are just instances of classes you define; auto-completion, linting, mypy and your intuition should all work properly with your validated data.
|
||||
* **Fast**:
|
||||
* in <a href="https://pydantic-docs.helpmanual.io/benchmarks/" class="external-link" target="_blank">benchmarks</a> Pydantic is faster than all other tested libraries.
|
||||
* Validate **complex structures**:
|
||||
* Use of hierarchical Pydantic models, Python `typing`’s `List` and `Dict`, etc.
|
||||
* And validators allow complex data schemas to be clearly and easily defined, checked and documented as JSON Schema.
|
||||
|
||||
BIN
docs/en/docs/img/sponsors/flint.png
Normal file
BIN
docs/en/docs/img/sponsors/flint.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/en/docs/img/sponsors/platform-sh-banner.png
Normal file
BIN
docs/en/docs/img/sponsors/platform-sh-banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
BIN
docs/en/docs/img/sponsors/platform-sh.png
Normal file
BIN
docs/en/docs/img/sponsors/platform-sh.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
@@ -445,7 +445,6 @@ To understand more about it, see the section <a href="https://fastapi.tiangolo.c
|
||||
|
||||
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:
|
||||
|
||||
@@ -3,6 +3,105 @@
|
||||
## Latest Changes
|
||||
|
||||
|
||||
## 0.98.0
|
||||
|
||||
### Features
|
||||
|
||||
* ✨ Allow disabling `redirect_slashes` at the FastAPI app level. PR [#3432](https://github.com/tiangolo/fastapi/pull/3432) by [@cyberlis](https://github.com/cyberlis).
|
||||
|
||||
### Docs
|
||||
|
||||
* 📝 Update docs on Pydantic using ujson internally. PR [#5804](https://github.com/tiangolo/fastapi/pull/5804) by [@mvasilkov](https://github.com/mvasilkov).
|
||||
* ✏ Rewording in `docs/en/docs/tutorial/debugging.md`. PR [#9581](https://github.com/tiangolo/fastapi/pull/9581) by [@ivan-abc](https://github.com/ivan-abc).
|
||||
* 📝 Add german blog post (Domain-driven Design mit Python und FastAPI). PR [#9261](https://github.com/tiangolo/fastapi/pull/9261) by [@msander](https://github.com/msander).
|
||||
* ✏️ Tweak wording in `docs/en/docs/tutorial/security/index.md`. PR [#9561](https://github.com/tiangolo/fastapi/pull/9561) by [@jyothish-mohan](https://github.com/jyothish-mohan).
|
||||
* 📝 Update `Annotated` notes in `docs/en/docs/tutorial/schema-extra-example.md`. PR [#9620](https://github.com/tiangolo/fastapi/pull/9620) by [@Alexandrhub](https://github.com/Alexandrhub).
|
||||
* ✏️ Fix typo `Annotation` -> `Annotated` in `docs/en/docs/tutorial/query-params-str-validations.md`. PR [#9625](https://github.com/tiangolo/fastapi/pull/9625) by [@mccricardo](https://github.com/mccricardo).
|
||||
* 📝 Use in memory database for testing SQL in docs. PR [#1223](https://github.com/tiangolo/fastapi/pull/1223) by [@HarshaLaxman](https://github.com/HarshaLaxman).
|
||||
|
||||
### Translations
|
||||
|
||||
* 🌐 Add Russian translation for `docs/ru/docs/tutorial/metadata.md`. PR [#9681](https://github.com/tiangolo/fastapi/pull/9681) by [@TabarakoAkula](https://github.com/TabarakoAkula).
|
||||
* 🌐 Fix typo in Spanish translation for `docs/es/docs/tutorial/first-steps.md`. PR [#9571](https://github.com/tiangolo/fastapi/pull/9571) by [@lilidl-nft](https://github.com/lilidl-nft).
|
||||
* 🌐 Add Russian translation for `docs/tutorial/path-operation-configuration.md`. PR [#9696](https://github.com/tiangolo/fastapi/pull/9696) by [@TabarakoAkula](https://github.com/TabarakoAkula).
|
||||
* 🌐 Add Chinese translation for `docs/zh/docs/advanced/security/index.md`. PR [#9666](https://github.com/tiangolo/fastapi/pull/9666) by [@lordqyxz](https://github.com/lordqyxz).
|
||||
* 🌐 Add Chinese translations for `docs/zh/docs/advanced/settings.md`. PR [#9652](https://github.com/tiangolo/fastapi/pull/9652) by [@ChoyeonChern](https://github.com/ChoyeonChern).
|
||||
* 🌐 Add Chinese translations for `docs/zh/docs/advanced/websockets.md`. PR [#9651](https://github.com/tiangolo/fastapi/pull/9651) by [@ChoyeonChern](https://github.com/ChoyeonChern).
|
||||
* 🌐 Add Chinese translation for `docs/zh/docs/tutorial/testing.md`. PR [#9641](https://github.com/tiangolo/fastapi/pull/9641) by [@wdh99](https://github.com/wdh99).
|
||||
* 🌐 Add Russian translation for `docs/tutorial/extra-models.md`. PR [#9619](https://github.com/tiangolo/fastapi/pull/9619) by [@ivan-abc](https://github.com/ivan-abc).
|
||||
* 🌐 Add Russian translation for `docs/tutorial/cors.md`. PR [#9608](https://github.com/tiangolo/fastapi/pull/9608) by [@ivan-abc](https://github.com/ivan-abc).
|
||||
* 🌐 Add Polish translation for `docs/pl/docs/features.md`. PR [#5348](https://github.com/tiangolo/fastapi/pull/5348) by [@mbroton](https://github.com/mbroton).
|
||||
* 🌐 Add Russian translation for `docs/ru/docs/tutorial/body-nested-models.md`. PR [#9605](https://github.com/tiangolo/fastapi/pull/9605) by [@Alexandrhub](https://github.com/Alexandrhub).
|
||||
|
||||
### Internal
|
||||
|
||||
* ⬆ Bump ruff from 0.0.272 to 0.0.275. PR [#9721](https://github.com/tiangolo/fastapi/pull/9721) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Update uvicorn[standard] requirement from <0.21.0,>=0.12.0 to >=0.12.0,<0.23.0. PR [#9463](https://github.com/tiangolo/fastapi/pull/9463) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump mypy from 1.3.0 to 1.4.0. PR [#9719](https://github.com/tiangolo/fastapi/pull/9719) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Update pre-commit requirement from <3.0.0,>=2.17.0 to >=2.17.0,<4.0.0. PR [#9251](https://github.com/tiangolo/fastapi/pull/9251) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump pypa/gh-action-pypi-publish from 1.8.5 to 1.8.6. PR [#9482](https://github.com/tiangolo/fastapi/pull/9482) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ✏️ Fix tooltips for light/dark theme toggler in docs. PR [#9588](https://github.com/tiangolo/fastapi/pull/9588) by [@pankaj1707k](https://github.com/pankaj1707k).
|
||||
* 🔧 Set minimal hatchling version needed to build the package. PR [#9240](https://github.com/tiangolo/fastapi/pull/9240) by [@mgorny](https://github.com/mgorny).
|
||||
* 📝 Add repo link to PyPI. PR [#9559](https://github.com/tiangolo/fastapi/pull/9559) by [@JacobCoffee](https://github.com/JacobCoffee).
|
||||
* ✏️ Fix typos in data for tests. PR [#4958](https://github.com/tiangolo/fastapi/pull/4958) by [@ryanrussell](https://github.com/ryanrussell).
|
||||
* 🔧 Update sponsors, add Flint. PR [#9699](https://github.com/tiangolo/fastapi/pull/9699) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷 Lint in CI only once, only with one version of Python, run tests with all of them. PR [#9686](https://github.com/tiangolo/fastapi/pull/9686) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.97.0
|
||||
|
||||
### Features
|
||||
|
||||
* ✨ Add support for `dependencies` in WebSocket routes. PR [#4534](https://github.com/tiangolo/fastapi/pull/4534) by [@paulo-raca](https://github.com/paulo-raca).
|
||||
* ✨ Add exception handler for `WebSocketRequestValidationError` (which also allows to override it). PR [#6030](https://github.com/tiangolo/fastapi/pull/6030) by [@kristjanvalur](https://github.com/kristjanvalur).
|
||||
|
||||
### Refactors
|
||||
|
||||
* ⬆️ Upgrade and fully migrate to Ruff, remove isort, includes a couple of tweaks suggested by the new version of Ruff. PR [#9660](https://github.com/tiangolo/fastapi/pull/9660) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ♻️ Update internal type annotations and upgrade mypy. PR [#9658](https://github.com/tiangolo/fastapi/pull/9658) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ♻️ Simplify `AsyncExitStackMiddleware` as without Python 3.6 `AsyncExitStack` is always available. PR [#9657](https://github.com/tiangolo/fastapi/pull/9657) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Upgrades
|
||||
|
||||
* ⬆️ Upgrade Black. PR [#9661](https://github.com/tiangolo/fastapi/pull/9661) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Internal
|
||||
|
||||
* 💚 Update CI cache to fix installs when dependencies change. PR [#9659](https://github.com/tiangolo/fastapi/pull/9659) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ⬇️ Separate requirements for development into their own requirements.txt files, they shouldn't be extras. PR [#9655](https://github.com/tiangolo/fastapi/pull/9655) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.96.1
|
||||
|
||||
### Fixes
|
||||
|
||||
* 🐛 Fix `HTTPException` header type annotations. PR [#9648](https://github.com/tiangolo/fastapi/pull/9648) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 🐛 Fix OpenAPI model fields int validations, `gte` to `ge`. PR [#9635](https://github.com/tiangolo/fastapi/pull/9635) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Upgrades
|
||||
|
||||
* 📌 Update minimum version of Pydantic to >=1.7.4. This fixes an issue when trying to use an old version of Pydantic. PR [#9567](https://github.com/tiangolo/fastapi/pull/9567) by [@Kludex](https://github.com/Kludex).
|
||||
|
||||
### Refactors
|
||||
|
||||
* ♻ Remove `media_type` from `ORJSONResponse` as it's inherited from the parent class. PR [#5805](https://github.com/tiangolo/fastapi/pull/5805) by [@Kludex](https://github.com/Kludex).
|
||||
* ♻ Instantiate `HTTPException` only when needed, optimization refactor. PR [#5356](https://github.com/tiangolo/fastapi/pull/5356) by [@pawamoy](https://github.com/pawamoy).
|
||||
|
||||
### Docs
|
||||
|
||||
* 🔥 Remove link to Pydantic's benchmark, as it was removed there. PR [#5811](https://github.com/tiangolo/fastapi/pull/5811) by [@Kludex](https://github.com/Kludex).
|
||||
|
||||
### Translations
|
||||
|
||||
* 🌐 Fix spelling in Indonesian translation of `docs/id/docs/tutorial/index.md`. PR [#5635](https://github.com/tiangolo/fastapi/pull/5635) by [@purwowd](https://github.com/purwowd).
|
||||
* 🌐 Add Russian translation for `docs/ru/docs/tutorial/index.md`. PR [#5896](https://github.com/tiangolo/fastapi/pull/5896) by [@Wilidon](https://github.com/Wilidon).
|
||||
* 🌐 Add Chinese translations for `docs/zh/docs/advanced/response-change-status-code.md` and `docs/zh/docs/advanced/response-headers.md`. PR [#9544](https://github.com/tiangolo/fastapi/pull/9544) by [@ChoyeonChern](https://github.com/ChoyeonChern).
|
||||
* 🌐 Add Russian translation for `docs/ru/docs/tutorial/schema-extra-example.md`. PR [#9621](https://github.com/tiangolo/fastapi/pull/9621) by [@Alexandrhub](https://github.com/Alexandrhub).
|
||||
|
||||
### Internal
|
||||
|
||||
* 🔧 Add sponsor Platform.sh. PR [#9650](https://github.com/tiangolo/fastapi/pull/9650) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷 Add custom token to Smokeshow and Preview Docs for download-artifact, to prevent API rate limits. PR [#9646](https://github.com/tiangolo/fastapi/pull/9646) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷 Add custom tokens for GitHub Actions to avoid rate limits. PR [#9647](https://github.com/tiangolo/fastapi/pull/9647) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.96.0
|
||||
|
||||
### Features
|
||||
|
||||
@@ -64,7 +64,7 @@ from myapp import app
|
||||
# Some more code
|
||||
```
|
||||
|
||||
in that case, the automatic variable inside of `myapp.py` will not have the variable `__name__` with a value of `"__main__"`.
|
||||
in that case, the automatically created variable inside of `myapp.py` will not have the variable `__name__` with a value of `"__main__"`.
|
||||
|
||||
So, the line:
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ To achieve that, first import:
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
In versions of Python below Python 3.9 you import `Annotation` from `typing_extensions`.
|
||||
In versions of Python below Python 3.9 you import `Annotated` from `typing_extensions`.
|
||||
|
||||
It will already be installed with FastAPI.
|
||||
|
||||
|
||||
@@ -86,6 +86,9 @@ Here we pass an `example` of the data expected in `Body()`:
|
||||
|
||||
=== "Python 3.10+ non-Annotated"
|
||||
|
||||
!!! tip
|
||||
Prefer to use the `Annotated` version if possible.
|
||||
|
||||
```Python hl_lines="18-23"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial003_py310.py!}
|
||||
```
|
||||
@@ -138,6 +141,9 @@ Each specific example `dict` in the `examples` can contain:
|
||||
|
||||
=== "Python 3.10+ non-Annotated"
|
||||
|
||||
!!! tip
|
||||
Prefer to use the `Annotated` version if possible.
|
||||
|
||||
```Python hl_lines="19-45"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial004_py310.py!}
|
||||
```
|
||||
|
||||
@@ -26,7 +26,7 @@ That's what all the systems with "login with Facebook, Google, Twitter, GitHub"
|
||||
|
||||
### OAuth 1
|
||||
|
||||
There was an OAuth 1, which is very different from OAuth2, and more complex, as it included directly specifications on how to encrypt the communication.
|
||||
There was an OAuth 1, which is very different from OAuth2, and more complex, as it included direct specifications on how to encrypt the communication.
|
||||
|
||||
It is not very popular or used nowadays.
|
||||
|
||||
|
||||
@@ -11,14 +11,14 @@ theme:
|
||||
accent: amber
|
||||
toggle:
|
||||
icon: material/lightbulb
|
||||
name: Switch to light mode
|
||||
name: Switch to dark mode
|
||||
- media: '(prefers-color-scheme: dark)'
|
||||
scheme: slate
|
||||
primary: teal
|
||||
accent: amber
|
||||
toggle:
|
||||
icon: material/lightbulb-outline
|
||||
name: Switch to dark mode
|
||||
name: Switch to light mode
|
||||
features:
|
||||
- search.suggest
|
||||
- search.highlight
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
<img class="sponsor-image" src="/img/sponsors/cryptapi-banner.svg" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a title="Build, run and scale your apps on a modern, reliable, and secure PaaS." style="display: block; position: relative;" href="https://platform.sh/try-it-now/?utm_source=fastapi-signup&utm_medium=banner&utm_campaign=FastAPI-signup-June-2023" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/platform-sh-banner.png" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -433,7 +433,6 @@ Para entender más al respecto revisa la sección <a href="https://fastapi.tiang
|
||||
|
||||
Usadas por Pydantic:
|
||||
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - para <abbr title="convertir el string que viene de un HTTP request a datos de Python">"parsing"</abbr> de JSON más rápido.
|
||||
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email_validator</code></a> - para validación de emails.
|
||||
|
||||
Usados por Starlette:
|
||||
|
||||
@@ -181,7 +181,7 @@ $ uvicorn main:my_awesome_api --reload
|
||||
|
||||
</div>
|
||||
|
||||
### Paso 3: crea un *operación de path*
|
||||
### Paso 3: crea una *operación de path*
|
||||
|
||||
#### Path
|
||||
|
||||
|
||||
@@ -436,7 +436,6 @@ item: Item
|
||||
|
||||
استفاده شده توسط Pydantic:
|
||||
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - برای <abbr title="تبدیل دادههای موجود در درخواستهای HTTP به داده پایتونی">"تجزیه (parse)"</abbr> سریعتر JSON .
|
||||
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email_validator</code></a> - برای اعتبارسنجی آدرسهای ایمیل.
|
||||
|
||||
استفاده شده توسط Starlette:
|
||||
|
||||
@@ -445,7 +445,6 @@ Pour en savoir plus, consultez la section <a href="https://fastapi.tiangolo.com/
|
||||
|
||||
Utilisées par Pydantic:
|
||||
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - pour un <abbr title="convertit la chaine de caractère d'une requête HTTP en donnée Python">"décodage" <abbr title="JavaScript Object Notation">JSON</abbr></abbr> plus rapide.
|
||||
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email_validator</code></a> - pour la validation des adresses email.
|
||||
|
||||
Utilisées par Starlette :
|
||||
|
||||
@@ -440,7 +440,6 @@ item: Item
|
||||
|
||||
בשימוש Pydantic:
|
||||
|
||||
- <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - <abbr title="המרת המחרוזת שמגיעה מבקשת HTTP למידע פייתון">"פרסור"</abbr> JSON.
|
||||
- <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email_validator</code></a> - לאימות כתובות אימייל.
|
||||
|
||||
בשימוש Starlette:
|
||||
|
||||
@@ -441,7 +441,6 @@ To understand more about it, see the section <a href="https://fastapi.tiangolo.c
|
||||
|
||||
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:
|
||||
|
||||
@@ -10,9 +10,9 @@ Sehingga kamu dapat kembali lagi dan mencari apa yang kamu butuhkan dengan tepat
|
||||
|
||||
## Jalankan kode
|
||||
|
||||
Semua blok-blok kode dapat dicopy dan digunakan langsung (Mereka semua sebenarnya adalah file python yang sudah teruji).
|
||||
Semua blok-blok kode dapat disalin dan digunakan langsung (Mereka semua sebenarnya adalah file python yang sudah teruji).
|
||||
|
||||
Untuk menjalankan setiap contoh, copy kode ke file `main.py`, dan jalankan `uvicorn` dengan:
|
||||
Untuk menjalankan setiap contoh, salin kode ke file `main.py`, dan jalankan `uvicorn` dengan:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -28,7 +28,7 @@ $ uvicorn main:app --reload
|
||||
|
||||
</div>
|
||||
|
||||
**SANGAT disarankan** agar kamu menulis atau meng-copy kode, meng-editnya dan menjalankannya secara lokal.
|
||||
**SANGAT disarankan** agar kamu menulis atau menyalin kode, mengubahnya dan menjalankannya secara lokal.
|
||||
|
||||
Dengan menggunakannya di dalam editor, benar-benar memperlihatkan manfaat dari FastAPI, melihat bagaimana sedikitnya kode yang harus kamu tulis, semua pengecekan tipe, pelengkapan otomatis, dll.
|
||||
|
||||
@@ -38,7 +38,7 @@ Dengan menggunakannya di dalam editor, benar-benar memperlihatkan manfaat dari F
|
||||
|
||||
Langkah pertama adalah dengan meng-install FastAPI.
|
||||
|
||||
Untuk tutorial, kamu mungkin hendak meng-instalnya dengan semua pilihan fitur dan dependensinya:
|
||||
Untuk tutorial, kamu mungkin hendak meng-installnya dengan semua pilihan fitur dan dependensinya:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -53,15 +53,15 @@ $ pip install "fastapi[all]"
|
||||
...yang juga termasuk `uvicorn`, yang dapat kamu gunakan sebagai server yang menjalankan kodemu.
|
||||
|
||||
!!! catatan
|
||||
Kamu juga dapat meng-instalnya bagian demi bagian.
|
||||
Kamu juga dapat meng-installnya bagian demi bagian.
|
||||
|
||||
Hal ini mungkin yang akan kamu lakukan ketika kamu hendak men-deploy aplikasimu ke tahap produksi:
|
||||
Hal ini mungkin yang akan kamu lakukan ketika kamu hendak menyebarkan (men-deploy) aplikasimu ke tahap produksi:
|
||||
|
||||
```
|
||||
pip install fastapi
|
||||
```
|
||||
|
||||
Juga install `uvicorn` untk menjalankan server"
|
||||
Juga install `uvicorn` untuk menjalankan server"
|
||||
|
||||
```
|
||||
pip install "uvicorn[standard]"
|
||||
@@ -77,4 +77,4 @@ Tersedia juga **Pedoman Pengguna Lanjutan** yang dapat kamu baca nanti setelah *
|
||||
|
||||
Tetapi kamu harus membaca terlebih dahulu **Tutorial - Pedoman Pengguna** (apa yang sedang kamu baca sekarang).
|
||||
|
||||
Hal ini didesain sehingga kamu dapat membangun aplikasi lengkap dengan hanya **Tutorial - Pedoman Pengguna**, dan kemudian mengembangkannya ke banyak cara yang berbeda, tergantung dari kebutuhanmu, menggunakan beberapa ide-ide tambahan dari **Pedoman Pengguna Lanjutan**.
|
||||
Hal ini dirancang supaya kamu dapat membangun aplikasi lengkap dengan hanya **Tutorial - Pedoman Pengguna**, dan kemudian mengembangkannya ke banyak cara yang berbeda, tergantung dari kebutuhanmu, menggunakan beberapa ide-ide tambahan dari **Pedoman Pengguna Lanjutan**.
|
||||
|
||||
@@ -438,7 +438,6 @@ To understand more about it, see the section <a href="https://fastapi.tiangolo.c
|
||||
|
||||
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:
|
||||
|
||||
@@ -97,7 +97,7 @@ $ python -m venv env
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install -e ."[dev,doc,test]"
|
||||
$ pip install -r requirements.txt
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
@@ -431,7 +431,6 @@ item: Item
|
||||
|
||||
Pydantic によって使用されるもの:
|
||||
|
||||
- <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - より速い JSON への<abbr title="converting the string that comes from an HTTP request into Python data">"変換"</abbr>.
|
||||
- <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email_validator</code></a> - E メールの検証
|
||||
|
||||
Starlette によって使用されるもの:
|
||||
|
||||
@@ -437,7 +437,6 @@ item: Item
|
||||
|
||||
Pydantic이 사용하는:
|
||||
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - 더 빠른 JSON <abbr title="HTTP 요청에서 파이썬 데이터로 가는 문자열 변환">"파싱"</abbr>.
|
||||
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email_validator</code></a> - 이메일 유효성 검사.
|
||||
|
||||
Starlette이 사용하는:
|
||||
|
||||
@@ -444,7 +444,6 @@ To understand more about it, see the section <a href="https://fastapi.tiangolo.c
|
||||
|
||||
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:
|
||||
|
||||
200
docs/pl/docs/features.md
Normal file
200
docs/pl/docs/features.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# Cechy
|
||||
|
||||
## Cechy FastAPI
|
||||
|
||||
**FastAPI** zapewnia Ci następujące korzyści:
|
||||
|
||||
### Oparcie o standardy open
|
||||
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank"><strong>OpenAPI</strong></a> do tworzenia API, w tym deklaracji <abbr title="znane również jako: paths, endpoints, routes">ścieżek</abbr> <abbr title="znane również jako metody HTTP, takie jak POST, GET, PUT, DELETE">operacji</abbr>, parametrów, <abbr title="po angielsku: body requests">ciał zapytań</abbr>, bezpieczeństwa, itp.
|
||||
* Automatyczna dokumentacja modelu danych za pomocą <a href="https://json-schema.org/" class="external-link" target="_blank"><strong>JSON Schema</strong></a> (ponieważ OpenAPI bazuje na JSON Schema).
|
||||
* Zaprojektowane z myślą o zgodności z powyższymi standardami zamiast dodawania ich obsługi po fakcie.
|
||||
* Możliwość automatycznego **generowania kodu klienta** w wielu językach.
|
||||
|
||||
### Automatyczna dokumentacja
|
||||
|
||||
Interaktywna dokumentacja i webowe interfejsy do eksploracji API. Z racji tego, że framework bazuje na OpenAPI, istnieje wiele opcji, z czego 2 są domyślnie dołączone.
|
||||
|
||||
* <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank"><strong>Swagger UI</strong></a>, z interaktywnym interfejsem - odpytuj i testuj swoje API bezpośrednio z przeglądarki.
|
||||
|
||||

|
||||
|
||||
* Alternatywna dokumentacja API z <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank"><strong>ReDoc</strong></a>.
|
||||
|
||||

|
||||
|
||||
### Nowoczesny Python
|
||||
|
||||
Wszystko opiera się na standardowych deklaracjach typu **Python 3.6** (dzięki Pydantic). Brak nowej składni do uczenia. Po prostu standardowy, współczesny Python.
|
||||
|
||||
Jeśli potrzebujesz szybkiego przypomnienia jak używać deklaracji typów w Pythonie (nawet jeśli nie używasz FastAPI), sprawdź krótki samouczek: [Python Types](python-types.md){.internal-link target=_blank}.
|
||||
|
||||
Wystarczy, że napiszesz standardowe deklaracje typów Pythona:
|
||||
|
||||
```Python
|
||||
from datetime import date
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Zadeklaruj parametr jako str
|
||||
# i uzyskaj wsparcie edytora wewnątrz funkcji
|
||||
def main(user_id: str):
|
||||
return user_id
|
||||
|
||||
|
||||
# Model Pydantic
|
||||
class User(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
joined: date
|
||||
```
|
||||
|
||||
A one będą mogły zostać później użyte w następujący sposób:
|
||||
|
||||
```Python
|
||||
my_user: User = User(id=3, name="John Doe", joined="2018-07-19")
|
||||
|
||||
second_user_data = {
|
||||
"id": 4,
|
||||
"name": "Mary",
|
||||
"joined": "2018-11-30",
|
||||
}
|
||||
|
||||
my_second_user: User = User(**second_user_data)
|
||||
```
|
||||
|
||||
!!! info
|
||||
`**second_user_data` oznacza:
|
||||
|
||||
Przekaż klucze i wartości słownika `second_user_data` bezpośrednio jako argumenty klucz-wartość, co jest równoznaczne z: `User(id=4, name="Mary", joined="2018-11-30")`
|
||||
|
||||
### Wsparcie edytora
|
||||
|
||||
Cały framework został zaprojektowany tak, aby był łatwy i intuicyjny w użyciu. Wszystkie pomysły zostały przetestowane na wielu edytorach jeszcze przed rozpoczęciem procesu tworzenia, aby zapewnić najlepsze wrażenia programistyczne.
|
||||
|
||||
Ostatnia ankieta <abbr title="coroczna ankieta przeprowadza w środowisku programistów języka Python">Python developer survey</abbr> jasno wskazuje, że <a href="https://www.jetbrains.com/research/python-developers-survey-2017/#tools-and-features" class="external-link" target="_blank">najczęściej używaną funkcjonalnością jest autouzupełnianie w edytorze</a>.
|
||||
|
||||
Cała struktura frameworku **FastAPI** jest na tym oparta. Autouzupełnianie działa wszędzie.
|
||||
|
||||
Rzadko będziesz musiał wracać do dokumentacji.
|
||||
|
||||
Oto, jak twój edytor może Ci pomóc:
|
||||
|
||||
* <a href="https://code.visualstudio.com/" class="external-link" target="_blank">Visual Studio Code</a>:
|
||||
|
||||

|
||||
|
||||
* <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a>:
|
||||
|
||||

|
||||
|
||||
Otrzymasz uzupełnienie nawet w miejscach, w których normalnie uzupełnienia nie ma. Na przykład klucz "price" w treści JSON (który mógł być zagnieżdżony), który pochodzi z zapytania.
|
||||
|
||||
Koniec z wpisywaniem błędnych nazw kluczy, przechodzeniem tam i z powrotem w dokumentacji lub przewijaniem w górę i w dół, aby sprawdzić, czy w końcu użyłeś nazwy `username` czy `user_name`.
|
||||
|
||||
### Zwięzłość
|
||||
|
||||
Wszystko posiada sensowne **domyślne wartości**. Wszędzie znajdziesz opcjonalne konfiguracje. Wszystkie parametry możesz dostroić, aby zrobić to co potrzebujesz do zdefiniowania API.
|
||||
|
||||
Ale domyślnie wszystko **"po prostu działa"**.
|
||||
|
||||
### Walidacja
|
||||
|
||||
* Walidacja większości (lub wszystkich?) **typów danych** Pythona, w tym:
|
||||
* Obiektów JSON (`dict`).
|
||||
* Tablic JSON (`list`) ze zdefiniowanym typem elementów.
|
||||
* Pól tekstowych (`str`) z określeniem minimalnej i maksymalnej długości.
|
||||
* Liczb (`int`, `float`) z wartościami minimalnymi, maksymalnymi, itp.
|
||||
|
||||
* Walidacja bardziej egzotycznych typów danych, takich jak:
|
||||
* URL.
|
||||
* Email.
|
||||
* UUID.
|
||||
* ...i inne.
|
||||
|
||||
Cała walidacja jest obsługiwana przez ugruntowaną i solidną bibliotekę **Pydantic**.
|
||||
|
||||
### Bezpieczeństwo i uwierzytelnianie
|
||||
|
||||
Bezpieczeństwo i uwierzytelnianie jest zintegrowane. Bez żadnych kompromisów z bazami czy modelami danych.
|
||||
|
||||
Wszystkie schematy bezpieczeństwa zdefiniowane w OpenAPI, w tym:
|
||||
|
||||
* Podstawowy protokół HTTP.
|
||||
* **OAuth2** (również z **tokenami JWT**). Sprawdź samouczek [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}.
|
||||
* Klucze API w:
|
||||
* Nagłówkach.
|
||||
* Parametrach zapytań.
|
||||
* Ciasteczkach, itp.
|
||||
|
||||
Plus wszystkie funkcje bezpieczeństwa Starlette (włączając w to **<abbr title="po angielsku: session cookies">ciasteczka sesyjne</abbr>**).
|
||||
|
||||
Wszystko zbudowane jako narzędzia i komponenty wielokrotnego użytku, które można łatwo zintegrować z systemami, magazynami oraz bazami danych - relacyjnymi, NoSQL, itp.
|
||||
|
||||
### Wstrzykiwanie Zależności
|
||||
|
||||
FastAPI zawiera niezwykle łatwy w użyciu, ale niezwykle potężny system <abbr title='Po angielsku: Dependency Injection. Znane również jako "components", "resources", "services", "providers"'><strong>Wstrzykiwania Zależności</strong></abbr>.
|
||||
|
||||
* Nawet zależności mogą mieć zależności, tworząc hierarchię lub **"graf" zależności**.
|
||||
* Wszystko jest **obsługiwane automatycznie** przez framework.
|
||||
* Wszystkie zależności mogą wymagać danych w żądaniach oraz rozszerzać ograniczenia i automatyczną dokumentację **<abbr title="po angielsku: path operations">operacji na ścieżce</abbr>**.
|
||||
* **Automatyczna walidacja** parametrów *operacji na ścieżce* zdefiniowanych w zależnościach.
|
||||
* Obsługa złożonych systemów uwierzytelniania użytkowników, **połączeń z bazami danych**, itp.
|
||||
* Bazy danych, front end, itp. **bez kompromisów**, ale wciąż łatwe do integracji.
|
||||
|
||||
### Nieograniczone "wtyczki"
|
||||
|
||||
Lub ujmując to inaczej - brak potrzeby wtyczek. Importuj i używaj kod, który potrzebujesz.
|
||||
|
||||
Każda integracja została zaprojektowana tak, aby była tak prosta w użyciu (z zależnościami), że możesz utworzyć "wtyczkę" dla swojej aplikacji w 2 liniach kodu, używając tej samej struktury i składni, które są używane w *operacjach na ścieżce*.
|
||||
|
||||
### Testy
|
||||
|
||||
* 100% <abbr title="Ilość kodu, który jest automatycznie testowany">pokrycia kodu testami</abbr>.
|
||||
* 100% <abbr title="Deklaracje typów Python - dzięki nim twój edytor i zewnętrzne narzędzia mogą zapewnić Ci lepsze wsparcie ">adnotacji typów</abbr>.
|
||||
* Używany w aplikacjach produkcyjnych.
|
||||
|
||||
## Cechy Starlette
|
||||
|
||||
**FastAPI** jest w pełni kompatybilny z (oraz bazuje na) <a href="https://www.starlette.io/" class="external-link" target="_blank"><strong>Starlette</strong></a>. Tak więc każdy dodatkowy kod Starlette, który posiadasz, również będzie działał.
|
||||
|
||||
`FastAPI` jest w rzeczywistości podklasą `Starlette`, więc jeśli już znasz lub używasz Starlette, większość funkcji będzie działać w ten sam sposób.
|
||||
|
||||
Dzięki **FastAPI** otrzymujesz wszystkie funkcje **Starlette** (ponieważ FastAPI to po prostu Starlette na sterydach):
|
||||
|
||||
* Bardzo imponująca wydajność. Jest to <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">jeden z najszybszych dostępnych frameworków Pythona, na równi z **NodeJS** i **Go**</a>.
|
||||
* Wsparcie dla **WebSocket**.
|
||||
* <abbr title='Zadania wykonywane w tle, bez zatrzymywania żądań, w tym samym procesie. Po angielsku: In-process background tasks'>Zadania w tle</abbr>.
|
||||
* Eventy startup i shutdown.
|
||||
* Klient testowy zbudowany na bazie biblioteki `requests`.
|
||||
* **CORS**, GZip, pliki statyczne, streamy.
|
||||
* Obsługa **sesji i ciasteczek**.
|
||||
* 100% pokrycie testami.
|
||||
* 100% adnotacji typów.
|
||||
|
||||
## Cechy Pydantic
|
||||
|
||||
**FastAPI** jest w pełni kompatybilny z (oraz bazuje na) <a href="https://pydantic-docs.helpmanual.io" class="external-link" target="_blank"><strong>Pydantic</strong></a>. Tak więc każdy dodatkowy kod Pydantic, który posiadasz, również będzie działał.
|
||||
|
||||
Wliczając w to zewnętrzne biblioteki, również oparte o Pydantic, takie jak <abbr title="Mapowanie obiektowo-relacyjne. Po angielsku: Object-Relational Mapper">ORM</abbr>, <abbr title="Object-Document Mapper">ODM</abbr> dla baz danych.
|
||||
|
||||
Oznacza to, że w wielu przypadkach możesz przekazać ten sam obiekt, który otrzymasz z żądania **bezpośrednio do bazy danych**, ponieważ wszystko jest walidowane automatycznie.
|
||||
|
||||
Działa to również w drugą stronę, w wielu przypadkach możesz po prostu przekazać obiekt otrzymany z bazy danych **bezpośrednio do klienta**.
|
||||
|
||||
Dzięki **FastAPI** otrzymujesz wszystkie funkcje **Pydantic** (ponieważ FastAPI bazuje na Pydantic do obsługi wszystkich danych):
|
||||
|
||||
* **Bez prania mózgu**:
|
||||
* Brak nowego mikrojęzyka do definiowania schematu, którego trzeba się nauczyć.
|
||||
* Jeśli znasz adnotacje typów Pythona to wiesz jak używać Pydantic.
|
||||
* Dobrze współpracuje z Twoim **<abbr title='Skrót od "Integrated Development Environment", podobne do edytora kodu'>IDE</abbr>/<abbr title="Program, który sprawdza Twój kod pod kątem błędów">linterem</abbr>/mózgiem**:
|
||||
* Ponieważ struktury danych Pydantic to po prostu instancje klas, które definiujesz; autouzupełnianie, linting, mypy i twoja intuicja powinny działać poprawnie z Twoimi zwalidowanymi danymi.
|
||||
* **Szybkość**:
|
||||
* w <a href="https://pydantic-docs.helpmanual.io/benchmarks/" class="external-link" target="_blank">benchmarkach</a> Pydantic jest szybszy niż wszystkie inne testowane biblioteki.
|
||||
* Walidacja **złożonych struktur**:
|
||||
* Wykorzystanie hierarchicznych modeli Pydantic, Pythonowego modułu `typing` zawierającego `List`, `Dict`, itp.
|
||||
* Walidatory umożliwiają jasne i łatwe definiowanie, sprawdzanie złożonych struktur danych oraz dokumentowanie ich jako JSON Schema.
|
||||
* Możesz mieć głęboko **zagnieżdżone obiekty JSON** i wszystkie je poddać walidacji i adnotować.
|
||||
* **Rozszerzalność**:
|
||||
* Pydantic umożliwia zdefiniowanie niestandardowych typów danych lub rozszerzenie walidacji o metody na modelu, na których użyty jest dekorator walidatora.
|
||||
* 100% pokrycie testami.
|
||||
@@ -435,7 +435,6 @@ Aby dowiedzieć się o tym więcej, zobacz sekcję <a href="https://fastapi.tian
|
||||
|
||||
Używane przez Pydantic:
|
||||
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - dla szybszego <abbr title="przetwarzania stringa który przychodzi z żądaniem HTTP na dane używane przez Pythona">"parsowania"</abbr> danych JSON.
|
||||
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email_validator</code></a> - dla walidacji adresów email.
|
||||
|
||||
Używane przez Starlette:
|
||||
|
||||
@@ -63,6 +63,7 @@ nav:
|
||||
- tr: /tr/
|
||||
- uk: /uk/
|
||||
- zh: /zh/
|
||||
- features.md
|
||||
- Samouczek:
|
||||
- tutorial/index.md
|
||||
- tutorial/first-steps.md
|
||||
|
||||
@@ -98,7 +98,7 @@ Após ativar o ambiente como descrito acima:
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install -e ."[dev,doc,test]"
|
||||
$ pip install -r requirements.txt
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
@@ -430,7 +430,6 @@ Para entender mais sobre performance, veja a seção <a href="https://fastapi.ti
|
||||
|
||||
Usados por Pydantic:
|
||||
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - para JSON mais rápido <abbr title="converte uma string que chega de uma requisição HTTP para dados Python">"parsing"</abbr>.
|
||||
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email_validator</code></a> - para validação de email.
|
||||
|
||||
Usados por Starlette:
|
||||
|
||||
@@ -108,7 +108,7 @@ $ python -m pip install --upgrade pip
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install -e ."[dev,doc,test]"
|
||||
$ pip install -r requirements.txt
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
@@ -439,7 +439,6 @@ item: Item
|
||||
|
||||
Используется Pydantic:
|
||||
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - для более быстрого JSON <abbr title="преобразования строки, полученной из HTTP-запроса, в данные Python">"парсинга"</abbr>.
|
||||
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email_validator</code></a> - для проверки электронной почты.
|
||||
|
||||
Используется Starlette:
|
||||
|
||||
382
docs/ru/docs/tutorial/body-nested-models.md
Normal file
382
docs/ru/docs/tutorial/body-nested-models.md
Normal file
@@ -0,0 +1,382 @@
|
||||
# Body - Вложенные модели
|
||||
|
||||
С помощью **FastAPI**, вы можете определять, валидировать, документировать и использовать модели произвольной вложенности (благодаря библиотеке Pydantic).
|
||||
|
||||
## Определение полей содержащих списки
|
||||
|
||||
Вы можете определять атрибут как подтип. Например, тип `list` в Python:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="12"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="14"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial001.py!}
|
||||
```
|
||||
|
||||
Это приведёт к тому, что обьект `tags` преобразуется в список, несмотря на то что тип его элементов не объявлен.
|
||||
|
||||
## Определение полей содержащих список с определением типов его элементов
|
||||
|
||||
Однако в Python есть способ объявления списков с указанием типов для вложенных элементов:
|
||||
|
||||
### Импортируйте `List` из модуля typing
|
||||
|
||||
В Python 3.9 и выше вы можете использовать стандартный тип `list` для объявления аннотаций типов, как мы увидим ниже. 💡
|
||||
|
||||
Но в версиях Python до 3.9 (начиная с 3.6) сначала вам необходимо импортировать `List` из стандартного модуля `typing` в Python:
|
||||
|
||||
```Python hl_lines="1"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial002.py!}
|
||||
```
|
||||
|
||||
### Объявление `list` с указанием типов для вложенных элементов
|
||||
|
||||
Объявление типов для элементов (внутренних типов) вложенных в такие типы как `list`, `dict`, `tuple`:
|
||||
|
||||
* Если у вас Python версии ниже чем 3.9, импортируйте их аналог из модуля `typing`
|
||||
* Передайте внутренний(ие) тип(ы) как "параметры типа", используя квадратные скобки: `[` и `]`
|
||||
|
||||
В Python версии 3.9 это будет выглядеть так:
|
||||
|
||||
```Python
|
||||
my_list: list[str]
|
||||
```
|
||||
|
||||
В версиях Python до 3.9 это будет выглядеть так:
|
||||
|
||||
```Python
|
||||
from typing import List
|
||||
|
||||
my_list: List[str]
|
||||
```
|
||||
|
||||
Это всё стандартный синтаксис Python для объявления типов.
|
||||
|
||||
Используйте этот же стандартный синтаксис для атрибутов модели с внутренними типами.
|
||||
|
||||
Таким образом, в нашем примере мы можем явно указать тип данных для поля `tags` как "список строк":
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="12"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial002_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="14"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial002_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="14"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial002.py!}
|
||||
```
|
||||
|
||||
## Типы множеств
|
||||
|
||||
Но затем мы подумали и поняли, что теги не должны повторяться и, вероятно, они должны быть уникальными строками.
|
||||
|
||||
И в Python есть специальный тип данных для множеств уникальных элементов - `set`.
|
||||
|
||||
Тогда мы может обьявить поле `tags` как множество строк:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="12"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial003_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="14"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial003_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="1 14"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial003.py!}
|
||||
```
|
||||
|
||||
С помощью этого, даже если вы получите запрос с повторяющимися данными, они будут преобразованы в множество уникальных элементов.
|
||||
|
||||
И когда вы выводите эти данные, даже если исходный набор содержал дубликаты, они будут выведены в виде множества уникальных элементов.
|
||||
|
||||
И они также будут соответствующим образом аннотированы / задокументированы.
|
||||
|
||||
## Вложенные Модели
|
||||
|
||||
У каждого атрибута Pydantic-модели есть тип.
|
||||
|
||||
Но этот тип может сам быть другой моделью Pydantic.
|
||||
|
||||
Таким образом вы можете объявлять глубоко вложенные JSON "объекты" с определёнными именами атрибутов, типами и валидацией.
|
||||
|
||||
Всё это может быть произвольно вложенным.
|
||||
|
||||
### Определение подмодели
|
||||
|
||||
Например, мы можем определить модель `Image`:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="7-9"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial004_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="9-11"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial004_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="9-11"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial004.py!}
|
||||
```
|
||||
|
||||
### Использование вложенной модели в качестве типа
|
||||
|
||||
Также мы можем использовать эту модель как тип атрибута:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="18"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial004_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="20"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial004_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="20"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial004.py!}
|
||||
```
|
||||
|
||||
Это означает, что **FastAPI** будет ожидать тело запроса, аналогичное этому:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"name": "Foo",
|
||||
"description": "The pretender",
|
||||
"price": 42.0,
|
||||
"tax": 3.2,
|
||||
"tags": ["rock", "metal", "bar"],
|
||||
"image": {
|
||||
"url": "http://example.com/baz.jpg",
|
||||
"name": "The Foo live"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Ещё раз: сделав такое объявление, с помощью **FastAPI** вы получите:
|
||||
|
||||
* Поддержку редакторов IDE (автодополнение и т.д), даже для вложенных моделей
|
||||
* Преобразование данных
|
||||
* Валидацию данных
|
||||
* Автоматическую документацию
|
||||
|
||||
## Особые типы и валидация
|
||||
|
||||
Помимо обычных простых типов, таких как `str`, `int`, `float`, и т.д. Вы можете использовать более сложные базовые типы, которые наследуются от типа `str`.
|
||||
|
||||
Чтобы увидеть все варианты, которые у вас есть, ознакомьтесь с документацией <a href="https://pydantic-docs.helpmanual.io/usage/types/" class="external-link" target="_blank">по необычным типам Pydantic</a>. Вы увидите некоторые примеры в следующей главе.
|
||||
|
||||
Например, так как в модели `Image` у нас есть поле `url`, то мы можем объявить его как тип `HttpUrl` из модуля Pydantic вместо типа `str`:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="2 8"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial005_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="4 10"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial005_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="4 10"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial005.py!}
|
||||
```
|
||||
|
||||
Строка будет проверена на соответствие допустимому URL-адресу и задокументирована в JSON схему / OpenAPI.
|
||||
|
||||
## Атрибуты, содержащие списки подмоделей
|
||||
|
||||
Вы также можете использовать модели Pydantic в качестве типов вложенных в `list`, `set` и т.д:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="18"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial006_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="20"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial006_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="20"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial006.py!}
|
||||
```
|
||||
|
||||
Такая реализация будет ожидать (конвертировать, валидировать, документировать и т.д) JSON-содержимое в следующем формате:
|
||||
|
||||
```JSON hl_lines="11"
|
||||
{
|
||||
"name": "Foo",
|
||||
"description": "The pretender",
|
||||
"price": 42.0,
|
||||
"tax": 3.2,
|
||||
"tags": [
|
||||
"rock",
|
||||
"metal",
|
||||
"bar"
|
||||
],
|
||||
"images": [
|
||||
{
|
||||
"url": "http://example.com/baz.jpg",
|
||||
"name": "The Foo live"
|
||||
},
|
||||
{
|
||||
"url": "http://example.com/dave.jpg",
|
||||
"name": "The Baz"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
!!! info "Информация"
|
||||
Заметьте, что теперь у ключа `images` есть список объектов изображений.
|
||||
|
||||
## Глубоко вложенные модели
|
||||
|
||||
Вы можете определять модели с произвольным уровнем вложенности:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="7 12 18 21 25"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial007_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="9 14 20 23 27"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial007_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="9 14 20 23 27"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial007.py!}
|
||||
```
|
||||
|
||||
!!! info "Информация"
|
||||
Заметьте, что у объекта `Offer` есть список объектов `Item`, которые, в свою очередь, могут содержать необязательный список объектов `Image`
|
||||
|
||||
## Тела с чистыми списками элементов
|
||||
|
||||
Если верхний уровень значения тела JSON-объекта представляет собой JSON `array` (в Python - `list`), вы можете объявить тип в параметре функции, так же, как в моделях Pydantic:
|
||||
|
||||
```Python
|
||||
images: List[Image]
|
||||
```
|
||||
|
||||
в Python 3.9 и выше:
|
||||
|
||||
```Python
|
||||
images: list[Image]
|
||||
```
|
||||
|
||||
например так:
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="13"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial008_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="15"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial008.py!}
|
||||
```
|
||||
|
||||
## Универсальная поддержка редактора
|
||||
|
||||
И вы получаете поддержку редактора везде.
|
||||
|
||||
Даже для элементов внутри списков:
|
||||
|
||||
<img src="/img/tutorial/body-nested-models/image01.png">
|
||||
|
||||
Вы не могли бы получить такую поддержку редактора, если бы работали напрямую с `dict`, а не с моделями Pydantic.
|
||||
|
||||
Но вы также не должны беспокоиться об этом, входящие словари автоматически конвертируются, а ваш вывод также автоматически преобразуется в формат JSON.
|
||||
|
||||
## Тела запросов с произвольными словарями (`dict` )
|
||||
|
||||
Вы также можете объявить тело запроса как `dict` с ключами определенного типа и значениями другого типа данных.
|
||||
|
||||
Без необходимости знать заранее, какие значения являются допустимыми для имён полей/атрибутов (как это было бы в случае с моделями Pydantic).
|
||||
|
||||
Это было бы полезно, если вы хотите получить ключи, которые вы еще не знаете.
|
||||
|
||||
---
|
||||
|
||||
Другой полезный случай - когда вы хотите чтобы ключи были другого типа данных, например, `int`.
|
||||
|
||||
Именно это мы сейчас и увидим здесь.
|
||||
|
||||
В этом случае вы принимаете `dict`, пока у него есть ключи типа `int` со значениями типа `float`:
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="7"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial009_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="9"
|
||||
{!> ../../../docs_src/body_nested_models/tutorial009.py!}
|
||||
```
|
||||
|
||||
!!! tip "Совет"
|
||||
Имейте в виду, что JSON поддерживает только ключи типа `str`.
|
||||
|
||||
Но Pydantic обеспечивает автоматическое преобразование данных.
|
||||
|
||||
Это значит, что даже если пользователи вашего API могут отправлять только строки в качестве ключей, при условии, что эти строки содержат целые числа, Pydantic автоматический преобразует и валидирует эти данные.
|
||||
|
||||
А `dict`, с именем `weights`, который вы получите в качестве ответа Pydantic, действительно будет иметь ключи типа `int` и значения типа `float`.
|
||||
|
||||
## Резюме
|
||||
|
||||
С помощью **FastAPI** вы получаете максимальную гибкость, предоставляемую моделями Pydantic, сохраняя при этом простоту, краткость и элегантность вашего кода.
|
||||
|
||||
И дополнительно вы получаете:
|
||||
|
||||
* Поддержку редактора (автодополнение доступно везде!)
|
||||
* Преобразование данных (также известно как парсинг / сериализация)
|
||||
* Валидацию данных
|
||||
* Документацию схемы данных
|
||||
* Автоматическую генерацию документации
|
||||
84
docs/ru/docs/tutorial/cors.md
Normal file
84
docs/ru/docs/tutorial/cors.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# CORS (Cross-Origin Resource Sharing)
|
||||
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">Понятие CORS или "Cross-Origin Resource Sharing"</a> относится к ситуациям, при которых запущенный в браузере фронтенд содержит JavaScript-код, который взаимодействует с бэкендом, находящимся на другом "источнике" ("origin").
|
||||
|
||||
## Источник
|
||||
|
||||
Источник - это совокупность протокола (`http`, `https`), домена (`myapp.com`, `localhost`, `localhost.tiangolo.com`) и порта (`80`, `443`, `8080`).
|
||||
|
||||
Поэтому это три разных источника:
|
||||
|
||||
* `http://localhost`
|
||||
* `https://localhost`
|
||||
* `http://localhost:8080`
|
||||
|
||||
Даже если они все расположены в `localhost`, они используют разные протоколы и порты, а значит, являются разными источниками.
|
||||
|
||||
## Шаги
|
||||
|
||||
Допустим, у вас есть фронтенд, запущенный в браузере по адресу `http://localhost:8080`, и его JavaScript-код пытается взаимодействовать с бэкендом, запущенным по адресу `http://localhost` (поскольку мы не указали порт, браузер по умолчанию будет использовать порт `80`).
|
||||
|
||||
Затем браузер отправит бэкенду HTTP-запрос `OPTIONS`, и если бэкенд вернёт соответствующие заголовки для авторизации взаимодействия с другим источником (`http://localhost:8080`), то браузер разрешит JavaScript-коду на фронтенде отправить запрос на этот бэкенд.
|
||||
|
||||
Чтобы это работало, у бэкенда должен быть список "разрешённых источников" ("allowed origins").
|
||||
|
||||
В таком случае этот список должен содержать `http://localhost:8080`, чтобы фронтенд работал корректно.
|
||||
|
||||
## Подстановочный символ `"*"`
|
||||
|
||||
В качестве списка источников можно указать подстановочный символ `"*"` ("wildcard"), чтобы разрешить любые источники.
|
||||
|
||||
Но тогда не будут разрешены некоторые виды взаимодействия, включая всё связанное с учётными данными: куки, заголовки Authorization с Bearer-токенами наподобие тех, которые мы использовали ранее и т.п.
|
||||
|
||||
Поэтому, чтобы всё работало корректно, лучше явно указывать список разрешённых источников.
|
||||
|
||||
## Использование `CORSMiddleware`
|
||||
|
||||
Вы можете настроить этот механизм в вашем **FastAPI** приложении, используя `CORSMiddleware`.
|
||||
|
||||
* Импортируйте `CORSMiddleware`.
|
||||
* Создайте список разрешённых источников (в виде строк).
|
||||
* Добавьте его как "middleware" к вашему **FastAPI** приложению.
|
||||
|
||||
Вы также можете указать, разрешает ли ваш бэкенд использование:
|
||||
|
||||
* Учётных данных (включая заголовки Authorization, куки и т.п.).
|
||||
* Отдельных HTTP-методов (`POST`, `PUT`) или всех вместе, используя `"*"`.
|
||||
* Отдельных HTTP-заголовков или всех вместе, используя `"*"`.
|
||||
|
||||
```Python hl_lines="2 6-11 13-19"
|
||||
{!../../../docs_src/cors/tutorial001.py!}
|
||||
```
|
||||
|
||||
`CORSMiddleware` использует для параметров "запрещающие" значения по умолчанию, поэтому вам нужно явным образом разрешить использование отдельных источников, методов или заголовков, чтобы браузеры могли использовать их в кросс-доменном контексте.
|
||||
|
||||
Поддерживаются следующие аргументы:
|
||||
|
||||
* `allow_origins` - Список источников, на которые разрешено выполнять кросс-доменные запросы. Например, `['https://example.org', 'https://www.example.org']`. Можно использовать `['*']`, чтобы разрешить любые источники.
|
||||
* `allow_origin_regex` - Регулярное выражение для определения источников, на которые разрешено выполнять кросс-доменные запросы. Например, `'https://.*\.example\.org'`.
|
||||
* `allow_methods` - Список HTTP-методов, которые разрешены для кросс-доменных запросов. По умолчанию равно `['GET']`. Можно использовать `['*']`, чтобы разрешить все стандартные методы.
|
||||
* `allow_headers` - Список HTTP-заголовков, которые должны поддерживаться при кросс-доменных запросах. По умолчанию равно `[]`. Можно использовать `['*']`, чтобы разрешить все заголовки. Заголовки `Accept`, `Accept-Language`, `Content-Language` и `Content-Type` всегда разрешены для <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests" class="external-link" rel="noopener" target="_blank">простых CORS-запросов</a>.
|
||||
* `allow_credentials` - указывает, что куки разрешены в кросс-доменных запросах. По умолчанию равно `False`. Также, `allow_origins` нельзя присвоить `['*']`, если разрешено использование учётных данных. В таком случае должен быть указан список источников.
|
||||
* `expose_headers` - Указывает любые заголовки ответа, которые должны быть доступны браузеру. По умолчанию равно `[]`.
|
||||
* `max_age` - Устанавливает максимальное время в секундах, в течение которого браузер кэширует CORS-ответы. По умолчанию равно `600`.
|
||||
|
||||
`CORSMiddleware` отвечает на два типа HTTP-запросов...
|
||||
|
||||
### CORS-запросы с предварительной проверкой
|
||||
|
||||
Это любые `OPTIONS` запросы с заголовками `Origin` и `Access-Control-Request-Method`.
|
||||
|
||||
В этом случае middleware перехватит входящий запрос и отправит соответствующие CORS-заголовки в ответе, а также ответ `200` или `400` в информационных целях.
|
||||
|
||||
### Простые запросы
|
||||
|
||||
Любые запросы с заголовком `Origin`. В этом случае middleware передаст запрос дальше как обычно, но добавит соответствующие CORS-заголовки к ответу.
|
||||
|
||||
## Больше информации
|
||||
|
||||
Для получения более подробной информации о <abbr title="Cross-Origin Resource Sharing">CORS</abbr>, обратитесь к <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">Документации CORS от Mozilla</a>.
|
||||
|
||||
!!! note "Технические детали"
|
||||
Вы также можете использовать `from starlette.middleware.cors import CORSMiddleware`.
|
||||
|
||||
**FastAPI** предоставляет несколько middleware в `fastapi.middleware` только для вашего удобства как разработчика. Но большинство доступных middleware взяты напрямую из Starlette.
|
||||
252
docs/ru/docs/tutorial/extra-models.md
Normal file
252
docs/ru/docs/tutorial/extra-models.md
Normal file
@@ -0,0 +1,252 @@
|
||||
# Дополнительные модели
|
||||
|
||||
В продолжение прошлого примера будет уже обычным делом иметь несколько связанных между собой моделей.
|
||||
|
||||
Это особенно применимо в случае моделей пользователя, потому что:
|
||||
|
||||
* **Модель для ввода** должна иметь возможность содержать пароль.
|
||||
* **Модель для вывода** не должна содержать пароль.
|
||||
* **Модель для базы данных**, возможно, должна содержать хэшированный пароль.
|
||||
|
||||
!!! danger "Внимание"
|
||||
Никогда не храните пароли пользователей в чистом виде. Всегда храните "безопасный хэш", который вы затем сможете проверить.
|
||||
|
||||
Если вам это не знакомо, вы можете узнать про "хэш пароля" в [главах о безопасности](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}.
|
||||
|
||||
## Множественные модели
|
||||
|
||||
Ниже изложена основная идея того, как могут выглядеть эти модели с полями для паролей, а также описаны места, где они используются:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="7 9 14 20 22 27-28 31-33 38-39"
|
||||
{!> ../../../docs_src/extra_models/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="9 11 16 22 24 29-30 33-35 40-41"
|
||||
{!> ../../../docs_src/extra_models/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Про `**user_in.dict()`
|
||||
|
||||
#### `.dict()` из Pydantic
|
||||
|
||||
`user_in` - это Pydantic-модель класса `UserIn`.
|
||||
|
||||
У Pydantic-моделей есть метод `.dict()`, который возвращает `dict` с данными модели.
|
||||
|
||||
Поэтому, если мы создадим Pydantic-объект `user_in` таким способом:
|
||||
|
||||
```Python
|
||||
user_in = UserIn(username="john", password="secret", email="john.doe@example.com")
|
||||
```
|
||||
|
||||
и затем вызовем:
|
||||
|
||||
```Python
|
||||
user_dict = user_in.dict()
|
||||
```
|
||||
|
||||
то теперь у нас есть `dict` с данными модели в переменной `user_dict` (это `dict` вместо объекта Pydantic-модели).
|
||||
|
||||
И если мы вызовем:
|
||||
|
||||
```Python
|
||||
print(user_dict)
|
||||
```
|
||||
|
||||
мы можем получить `dict` с такими данными:
|
||||
|
||||
```Python
|
||||
{
|
||||
'username': 'john',
|
||||
'password': 'secret',
|
||||
'email': 'john.doe@example.com',
|
||||
'full_name': None,
|
||||
}
|
||||
```
|
||||
|
||||
#### Распаковка `dict`
|
||||
|
||||
Если мы возьмём `dict` наподобие `user_dict` и передадим его в функцию (или класс), используя `**user_dict`, Python распакует его. Он передаст ключи и значения `user_dict` напрямую как аргументы типа ключ-значение.
|
||||
|
||||
Поэтому, продолжая описанный выше пример с `user_dict`, написание такого кода:
|
||||
|
||||
```Python
|
||||
UserInDB(**user_dict)
|
||||
```
|
||||
|
||||
Будет работать так же, как примерно такой код:
|
||||
|
||||
```Python
|
||||
UserInDB(
|
||||
username="john",
|
||||
password="secret",
|
||||
email="john.doe@example.com",
|
||||
full_name=None,
|
||||
)
|
||||
```
|
||||
|
||||
Или, если для большей точности мы напрямую используем `user_dict` с любым потенциальным содержимым, то этот пример будет выглядеть так:
|
||||
|
||||
```Python
|
||||
UserInDB(
|
||||
username = user_dict["username"],
|
||||
password = user_dict["password"],
|
||||
email = user_dict["email"],
|
||||
full_name = user_dict["full_name"],
|
||||
)
|
||||
```
|
||||
|
||||
#### Pydantic-модель из содержимого другой модели
|
||||
|
||||
Как в примере выше мы получили `user_dict` из `user_in.dict()`, этот код:
|
||||
|
||||
```Python
|
||||
user_dict = user_in.dict()
|
||||
UserInDB(**user_dict)
|
||||
```
|
||||
|
||||
будет равнозначен такому:
|
||||
|
||||
```Python
|
||||
UserInDB(**user_in.dict())
|
||||
```
|
||||
|
||||
...потому что `user_in.dict()` - это `dict`, и затем мы указываем, чтобы Python его "распаковал", когда передаём его в `UserInDB` и ставим перед ним `**`.
|
||||
|
||||
Таким образом мы получаем Pydantic-модель на основе данных из другой Pydantic-модели.
|
||||
|
||||
#### Распаковка `dict` и дополнительные именованные аргументы
|
||||
|
||||
И затем, если мы добавим дополнительный именованный аргумент `hashed_password=hashed_password` как здесь:
|
||||
|
||||
```Python
|
||||
UserInDB(**user_in.dict(), hashed_password=hashed_password)
|
||||
```
|
||||
|
||||
... то мы получим что-то подобное:
|
||||
|
||||
```Python
|
||||
UserInDB(
|
||||
username = user_dict["username"],
|
||||
password = user_dict["password"],
|
||||
email = user_dict["email"],
|
||||
full_name = user_dict["full_name"],
|
||||
hashed_password = hashed_password,
|
||||
)
|
||||
```
|
||||
|
||||
!!! warning "Предупреждение"
|
||||
Цель использованных в примере вспомогательных функций - не более чем демонстрация возможных операций с данными, но, конечно, они не обеспечивают настоящую безопасность.
|
||||
|
||||
## Сократите дублирование
|
||||
|
||||
Сокращение дублирования кода - это одна из главных идей **FastAPI**.
|
||||
|
||||
Поскольку дублирование кода повышает риск появления багов, проблем с безопасностью, проблем десинхронизации кода (когда вы обновляете код в одном месте, но не обновляете в другом), и т.д.
|
||||
|
||||
А все описанные выше модели используют много общих данных и дублируют названия атрибутов и типов.
|
||||
|
||||
Мы можем это улучшить.
|
||||
|
||||
Мы можем определить модель `UserBase`, которая будет базовой для остальных моделей. И затем мы можем создать подклассы этой модели, которые будут наследовать её атрибуты (объявления типов, валидацию, и т.п.).
|
||||
|
||||
Все операции конвертации, валидации, документации, и т.п. будут по-прежнему работать нормально.
|
||||
|
||||
В этом случае мы можем определить только различия между моделями (с `password` в чистом виде, с `hashed_password` и без пароля):
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="7 13-14 17-18 21-22"
|
||||
{!> ../../../docs_src/extra_models/tutorial002_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="9 15-16 19-20 23-24"
|
||||
{!> ../../../docs_src/extra_models/tutorial002.py!}
|
||||
```
|
||||
|
||||
## `Union` или `anyOf`
|
||||
|
||||
Вы можете определить ответ как `Union` из двух типов. Это означает, что ответ должен соответствовать одному из них.
|
||||
|
||||
Он будет определён в OpenAPI как `anyOf`.
|
||||
|
||||
Для этого используйте стандартные аннотации типов в Python <a href="https://docs.python.org/3/library/typing.html#typing.Union" class="external-link" target="_blank">`typing.Union`</a>:
|
||||
|
||||
!!! note "Примечание"
|
||||
При объявлении <a href="https://pydantic-docs.helpmanual.io/usage/types/#unions" class="external-link" target="_blank">`Union`</a>, сначала указывайте наиболее детальные типы, затем менее детальные. В примере ниже более детальный `PlaneItem` стоит перед `CarItem` в `Union[PlaneItem, CarItem]`.
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="1 14-15 18-20 33"
|
||||
{!> ../../../docs_src/extra_models/tutorial003_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="1 14-15 18-20 33"
|
||||
{!> ../../../docs_src/extra_models/tutorial003.py!}
|
||||
```
|
||||
|
||||
### `Union` в Python 3.10
|
||||
|
||||
В этом примере мы передаём `Union[PlaneItem, CarItem]` в качестве значения аргумента `response_model`.
|
||||
|
||||
Поскольку мы передаём его как **значение аргумента** вместо того, чтобы поместить его в **аннотацию типа**, нам придётся использовать `Union` даже в Python 3.10.
|
||||
|
||||
Если оно было бы указано в аннотации типа, то мы могли бы использовать вертикальную черту как в примере:
|
||||
|
||||
```Python
|
||||
some_variable: PlaneItem | CarItem
|
||||
```
|
||||
|
||||
Но если мы помещаем его в `response_model=PlaneItem | CarItem` мы получим ошибку, потому что Python попытается произвести **некорректную операцию** между `PlaneItem` и `CarItem` вместо того, чтобы интерпретировать это как аннотацию типа.
|
||||
|
||||
## Список моделей
|
||||
|
||||
Таким же образом вы можете определять ответы как списки объектов.
|
||||
|
||||
Для этого используйте `typing.List` из стандартной библиотеки Python (или просто `list` в Python 3.9 и выше):
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="18"
|
||||
{!> ../../../docs_src/extra_models/tutorial004_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="1 20"
|
||||
{!> ../../../docs_src/extra_models/tutorial004.py!}
|
||||
```
|
||||
|
||||
## Ответ с произвольным `dict`
|
||||
|
||||
Вы также можете определить ответ, используя произвольный одноуровневый `dict` и определяя только типы ключей и значений без использования Pydantic-моделей.
|
||||
|
||||
Это полезно, если вы заранее не знаете корректных названий полей/атрибутов (которые будут нужны при использовании Pydantic-модели).
|
||||
|
||||
В этом случае вы можете использовать `typing.Dict` (или просто `dict` в Python 3.9 и выше):
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="6"
|
||||
{!> ../../../docs_src/extra_models/tutorial005_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="1 8"
|
||||
{!> ../../../docs_src/extra_models/tutorial005.py!}
|
||||
```
|
||||
|
||||
## Резюме
|
||||
|
||||
Используйте несколько Pydantic-моделей и свободно применяйте наследование для каждой из них.
|
||||
|
||||
Вам не обязательно иметь единственную модель данных для каждой сущности, если эта сущность должна иметь возможность быть в разных "состояниях". Как в случае с "сущностью" пользователя, у которого есть состояния с полями `password`, `password_hash` и без пароля.
|
||||
80
docs/ru/docs/tutorial/index.md
Normal file
80
docs/ru/docs/tutorial/index.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Учебник - Руководство пользователя - Введение
|
||||
|
||||
В этом руководстве шаг за шагом показано, как использовать **FastApi** с большинством его функций.
|
||||
|
||||
Каждый раздел постепенно основывается на предыдущих, но он структурирован по отдельным темам, так что вы можете перейти непосредственно к конкретной теме для решения ваших конкретных потребностей в API.
|
||||
|
||||
Он также создан для использования в качестве будущего справочника.
|
||||
|
||||
Так что вы можете вернуться и посмотреть именно то, что вам нужно.
|
||||
|
||||
## Запустите код
|
||||
|
||||
Все блоки кода можно копировать и использовать напрямую (на самом деле это проверенные файлы Python).
|
||||
|
||||
Чтобы запустить любой из примеров, скопируйте код в файл `main.py` и запустите `uvicorn` с параметрами:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
<span style="color: green;">INFO</span>: Started reloader process [28720]
|
||||
<span style="color: green;">INFO</span>: Started server process [28722]
|
||||
<span style="color: green;">INFO</span>: Waiting for application startup.
|
||||
<span style="color: green;">INFO</span>: Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
**НАСТОЯТЕЛЬНО рекомендуется**, чтобы вы написали или скопировали код, отредактировали его и запустили локально.
|
||||
|
||||
Использование кода в вашем редакторе — это то, что действительно показывает вам преимущества FastAPI, видя, как мало кода вам нужно написать, все проверки типов, автодополнение и т.д.
|
||||
|
||||
---
|
||||
|
||||
## Установка FastAPI
|
||||
|
||||
Первый шаг — установить FastAPI.
|
||||
|
||||
Для руководства вы, возможно, захотите установить его со всеми дополнительными зависимостями и функциями:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "fastapi[all]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
...это также включает `uvicorn`, который вы можете использовать в качестве сервера, который запускает ваш код.
|
||||
|
||||
!!! note "Технические детали"
|
||||
Вы также можете установить его по частям.
|
||||
|
||||
Это то, что вы, вероятно, сделаете, когда захотите развернуть свое приложение в рабочей среде:
|
||||
|
||||
```
|
||||
pip install fastapi
|
||||
```
|
||||
|
||||
Также установите `uvicorn` для работы в качестве сервера:
|
||||
|
||||
```
|
||||
pip install "uvicorn[standard]"
|
||||
```
|
||||
|
||||
И то же самое для каждой из необязательных зависимостей, которые вы хотите использовать.
|
||||
|
||||
## Продвинутое руководство пользователя
|
||||
|
||||
Существует также **Продвинутое руководство пользователя**, которое вы сможете прочитать после руководства **Учебник - Руководство пользователя**.
|
||||
|
||||
**Продвинутое руководство пользователя** основано на этом, использует те же концепции и учит вас некоторым дополнительным функциям.
|
||||
|
||||
Но вы должны сначала прочитать **Учебник - Руководство пользователя** (то, что вы читаете прямо сейчас).
|
||||
|
||||
Он разработан таким образом, что вы можете создать полноценное приложение, используя только **Учебник - Руководство пользователя**, а затем расширить его различными способами, в зависимости от ваших потребностей, используя некоторые дополнительные идеи из **Продвинутого руководства пользователя**.
|
||||
111
docs/ru/docs/tutorial/metadata.md
Normal file
111
docs/ru/docs/tutorial/metadata.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# URL-адреса метаданных и документации
|
||||
|
||||
Вы можете настроить несколько конфигураций метаданных в вашем **FastAPI** приложении.
|
||||
|
||||
## Метаданные для API
|
||||
|
||||
Вы можете задать следующие поля, которые используются в спецификации OpenAPI и в UI автоматической документации API:
|
||||
|
||||
| Параметр | Тип | Описание |
|
||||
|------------|--|-------------|
|
||||
| `title` | `str` | Заголовок API. |
|
||||
| `description` | `str` | Краткое описание API. Может быть использован Markdown. |
|
||||
| `version` | `string` | Версия API. Версия вашего собственного приложения, а не OpenAPI. К примеру `2.5.0`. |
|
||||
| `terms_of_service` | `str` | Ссылка к условиям пользования API. Если указано, то это должен быть URL-адрес. |
|
||||
| `contact` | `dict` | Контактная информация для открытого API. Может содержать несколько полей. <details><summary>поля <code>contact</code></summary><table><thead><tr><th>Параметр</th><th>Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td>Идентификационное имя контактного лица/организации.</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL указывающий на контактную информацию. ДОЛЖЕН быть в формате URL.</td></tr><tr><td><code>email</code></td><td><code>str</code></td><td>Email адрес контактного лица/организации. ДОЛЖЕН быть в формате email адреса.</td></tr></tbody></table></details> |
|
||||
| `license_info` | `dict` | Информация о лицензии открытого API. Может содержать несколько полей. <details><summary>поля <code>license_info</code></summary><table><thead><tr><th>Параметр</th><th>Тип</th><th>Описание</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>ОБЯЗАТЕЛЬНО</strong> (если установлен параметр <code>license_info</code>). Название лицензии, используемой для API</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL, указывающий на лицензию, используемую для API. ДОЛЖЕН быть в формате URL.</td></tr></tbody></table></details> |
|
||||
|
||||
Вы можете задать их следующим образом:
|
||||
|
||||
```Python hl_lines="3-16 19-31"
|
||||
{!../../../docs_src/metadata/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Вы можете использовать Markdown в поле `description`, и оно будет отображено в выводе.
|
||||
|
||||
С этой конфигурацией автоматическая документация API будут выглядеть так:
|
||||
|
||||
<img src="/img/tutorial/metadata/image01.png">
|
||||
|
||||
## Метаданные для тегов
|
||||
|
||||
Вы также можете добавить дополнительные метаданные для различных тегов, используемых для группировки ваших операций пути с помощью параметра `openapi_tags`.
|
||||
|
||||
Он принимает список, содержащий один словарь для каждого тега.
|
||||
|
||||
Каждый словарь может содержать в себе:
|
||||
|
||||
* `name` (**обязательно**): `str`-значение с тем же именем тега, которое вы используете в параметре `tags` в ваших *операциях пути* и `APIRouter`ах.
|
||||
* `description`: `str`-значение с кратким описанием для тега. Может содержать Markdown и будет отображаться в UI документации.
|
||||
* `externalDocs`: `dict`-значение описывающее внешнюю документацию. Включает в себя:
|
||||
* `description`: `str`-значение с кратким описанием для внешней документации.
|
||||
* `url` (**обязательно**): `str`-значение с URL-адресом для внешней документации.
|
||||
|
||||
### Создание метаданных для тегов
|
||||
|
||||
Давайте попробуем сделать это на примере с тегами для `users` и `items`.
|
||||
|
||||
Создайте метаданные для ваших тегов и передайте их в параметре `openapi_tags`:
|
||||
|
||||
```Python hl_lines="3-16 18"
|
||||
{!../../../docs_src/metadata/tutorial004.py!}
|
||||
```
|
||||
|
||||
Помните, что вы можете использовать Markdown внутри описания, к примеру "login" будет отображен жирным шрифтом (**login**) и "fancy" будет отображаться курсивом (_fancy_).
|
||||
|
||||
!!! tip "Подсказка"
|
||||
Вам необязательно добавлять метаданные для всех используемых тегов
|
||||
|
||||
### Используйте собственные теги
|
||||
Используйте параметр `tags` с вашими *операциями пути* (и `APIRouter`ами), чтобы присвоить им различные теги:
|
||||
|
||||
```Python hl_lines="21 26"
|
||||
{!../../../docs_src/metadata/tutorial004.py!}
|
||||
```
|
||||
|
||||
!!! info "Дополнительная информация"
|
||||
Узнайте больше о тегах в [Конфигурации операции пути](../path-operation-configuration/#tags){.internal-link target=_blank}.
|
||||
|
||||
### Проверьте документацию
|
||||
|
||||
Теперь, если вы проверите документацию, вы увидите всю дополнительную информацию:
|
||||
|
||||
<img src="/img/tutorial/metadata/image02.png">
|
||||
|
||||
### Порядок расположения тегов
|
||||
|
||||
Порядок расположения словарей метаданных для каждого тега определяет также порядок, отображаемый в документах UI
|
||||
|
||||
К примеру, несмотря на то, что `users` будут идти после `items` в алфавитном порядке, они отображаются раньше, потому что мы добавляем свои метаданные в качестве первого словаря в списке.
|
||||
|
||||
## URL-адреса OpenAPI
|
||||
|
||||
По умолчанию схема OpenAPI отображена по адресу `/openapi.json`.
|
||||
|
||||
Но вы можете изменить это с помощью параметра `openapi_url`.
|
||||
|
||||
К примеру, чтобы задать её отображение по адресу `/api/v1/openapi.json`:
|
||||
|
||||
```Python hl_lines="3"
|
||||
{!../../../docs_src/metadata/tutorial002.py!}
|
||||
```
|
||||
|
||||
Если вы хотите отключить схему OpenAPI полностью, вы можете задать `openapi_url=None`, это также отключит пользовательские интерфейсы документации, которые его использует.
|
||||
|
||||
## URL-адреса документации
|
||||
|
||||
Вы можете изменить конфигурацию двух пользовательских интерфейсов документации, среди которых
|
||||
|
||||
* **Swagger UI**: отображаемый по адресу `/docs`.
|
||||
* Вы можете задать его URL с помощью параметра `docs_url`.
|
||||
* Вы можете отключить это с помощью настройки `docs_url=None`.
|
||||
* **ReDoc**: отображаемый по адресу `/redoc`.
|
||||
* Вы можете задать его URL с помощью параметра `redoc_url`.
|
||||
* Вы можете отключить это с помощью настройки `redoc_url=None`.
|
||||
|
||||
К примеру, чтобы задать отображение Swagger UI по адресу `/documentation` и отключить ReDoc:
|
||||
|
||||
```Python hl_lines="3"
|
||||
{!../../../docs_src/metadata/tutorial003.py!}
|
||||
```
|
||||
179
docs/ru/docs/tutorial/path-operation-configuration.md
Normal file
179
docs/ru/docs/tutorial/path-operation-configuration.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Конфигурация операций пути
|
||||
|
||||
Существует несколько параметров, которые вы можете передать вашему *декоратору операций пути* для его настройки.
|
||||
|
||||
!!! warning "Внимание"
|
||||
Помните, что эти параметры передаются непосредственно *декоратору операций пути*, а не вашей *функции-обработчику операций пути*.
|
||||
|
||||
## Коды состояния
|
||||
|
||||
Вы можете определить (HTTP) `status_code`, который будет использован в ответах вашей *операции пути*.
|
||||
|
||||
Вы можете передать только `int`-значение кода, например `404`.
|
||||
|
||||
Но если вы не помните, для чего нужен каждый числовой код, вы можете использовать сокращенные константы в параметре `status`:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="1 15"
|
||||
{!> ../../../docs_src/path_operation_configuration/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="3 17"
|
||||
{!> ../../../docs_src/path_operation_configuration/tutorial001_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="3 17"
|
||||
{!> ../../../docs_src/path_operation_configuration/tutorial001.py!}
|
||||
```
|
||||
|
||||
Этот код состояния будет использован в ответе и будет добавлен в схему OpenAPI.
|
||||
|
||||
!!! note "Технические детали"
|
||||
Вы также можете использовать `from starlette import status`.
|
||||
|
||||
**FastAPI** предоставляет тот же `starlette.status` под псевдонимом `fastapi.status` для удобства разработчика. Но его источник - это непосредственно Starlette.
|
||||
|
||||
## Теги
|
||||
|
||||
Вы можете добавлять теги к вашим *операциям пути*, добавив параметр `tags` с `list` заполненным `str`-значениями (обычно в нём только одна строка):
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="15 20 25"
|
||||
{!> ../../../docs_src/path_operation_configuration/tutorial002_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="17 22 27"
|
||||
{!> ../../../docs_src/path_operation_configuration/tutorial002_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="17 22 27"
|
||||
{!> ../../../docs_src/path_operation_configuration/tutorial002.py!}
|
||||
```
|
||||
|
||||
Они будут добавлены в схему OpenAPI и будут использованы в автоматической документации интерфейса:
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image01.png">
|
||||
|
||||
### Теги с перечислениями
|
||||
|
||||
Если у вас большое приложение, вы можете прийти к необходимости добавить **несколько тегов**, и возможно, вы захотите убедиться в том, что всегда используете **один и тот же тег** для связанных *операций пути*.
|
||||
|
||||
В этих случаях, имеет смысл хранить теги в классе `Enum`.
|
||||
|
||||
**FastAPI** поддерживает это так же, как и в случае с обычными строками:
|
||||
|
||||
```Python hl_lines="1 8-10 13 18"
|
||||
{!../../../docs_src/path_operation_configuration/tutorial002b.py!}
|
||||
```
|
||||
|
||||
## Краткое и развёрнутое содержание
|
||||
|
||||
Вы можете добавить параметры `summary` и `description`:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="18-19"
|
||||
{!> ../../../docs_src/path_operation_configuration/tutorial003_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="20-21"
|
||||
{!> ../../../docs_src/path_operation_configuration/tutorial003_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="20-21"
|
||||
{!> ../../../docs_src/path_operation_configuration/tutorial003.py!}
|
||||
```
|
||||
|
||||
## Описание из строк документации
|
||||
|
||||
Так как описания обычно длинные и содержат много строк, вы можете объявить описание *операции пути* в функции <abbr title="многострочный текст, первое выражение внутри функции (не присвоенный какой-либо переменной), используемый для документации">строки документации</abbr> и **FastAPI** прочитает её отсюда.
|
||||
|
||||
Вы можете использовать <a href="https://en.wikipedia.org/wiki/Markdown" class="external-link" target="_blank">Markdown</a> в строке документации, и он будет интерпретирован и отображён корректно (с учетом отступа в строке документации).
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="17-25"
|
||||
{!> ../../../docs_src/path_operation_configuration/tutorial004_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="19-27"
|
||||
{!> ../../../docs_src/path_operation_configuration/tutorial004_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="19-27"
|
||||
{!> ../../../docs_src/path_operation_configuration/tutorial004.py!}
|
||||
```
|
||||
|
||||
Он будет использован в интерактивной документации:
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image02.png">
|
||||
|
||||
## Описание ответа
|
||||
|
||||
Вы можете указать описание ответа с помощью параметра `response_description`:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="19"
|
||||
{!> ../../../docs_src/path_operation_configuration/tutorial005_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="21"
|
||||
{!> ../../../docs_src/path_operation_configuration/tutorial005_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="21"
|
||||
{!> ../../../docs_src/path_operation_configuration/tutorial005.py!}
|
||||
```
|
||||
|
||||
!!! info "Дополнительная информация"
|
||||
Помните, что `response_description` относится конкретно к ответу, а `description` относится к *операции пути* в целом.
|
||||
|
||||
!!! check "Технические детали"
|
||||
OpenAPI указывает, что каждой *операции пути* необходимо описание ответа.
|
||||
|
||||
Если вдруг вы не укажете его, то **FastAPI** автоматически сгенерирует это описание с текстом "Successful response".
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image03.png">
|
||||
|
||||
## Обозначение *операции пути* как устаревшей
|
||||
|
||||
Если вам необходимо пометить *операцию пути* как <abbr title="устаревшее, не рекомендовано к использованию">устаревшую</abbr>, при этом не удаляя её, передайте параметр `deprecated`:
|
||||
|
||||
```Python hl_lines="16"
|
||||
{!../../../docs_src/path_operation_configuration/tutorial006.py!}
|
||||
```
|
||||
|
||||
Он будет четко помечен как устаревший в интерактивной документации:
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image04.png">
|
||||
|
||||
Проверьте, как будут выглядеть устаревшие и не устаревшие *операции пути*:
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image05.png">
|
||||
|
||||
## Резюме
|
||||
|
||||
Вы можете легко конфигурировать и добавлять метаданные в ваши *операции пути*, передавая параметры *декораторам операций пути*.
|
||||
189
docs/ru/docs/tutorial/schema-extra-example.md
Normal file
189
docs/ru/docs/tutorial/schema-extra-example.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# Объявление примера запроса данных
|
||||
|
||||
Вы можете объявлять примеры данных, которые ваше приложение может получать.
|
||||
|
||||
Вот несколько способов, как это можно сделать.
|
||||
|
||||
## Pydantic `schema_extra`
|
||||
|
||||
Вы можете объявить ключ `example` для модели Pydantic, используя класс `Config` и переменную `schema_extra`, как описано в <a href="https://pydantic-docs.helpmanual.io/usage/schema/#schema-customization" class="external-link" target="_blank">Pydantic документации: Настройка схемы</a>:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="13-21"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="15-23"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial001.py!}
|
||||
```
|
||||
|
||||
Эта дополнительная информация будет включена в **JSON Schema** выходных данных для этой модели, и она будет использоваться в документации к API.
|
||||
|
||||
!!! tip Подсказка
|
||||
Вы можете использовать тот же метод для расширения JSON-схемы и добавления своей собственной дополнительной информации.
|
||||
|
||||
Например, вы можете использовать это для добавления дополнительной информации для пользовательского интерфейса в вашем веб-приложении и т.д.
|
||||
|
||||
## Дополнительные аргументы поля `Field`
|
||||
|
||||
При использовании `Field()` с моделями Pydantic, вы также можете объявлять дополнительную информацию для **JSON Schema**, передавая любые другие произвольные аргументы в функцию.
|
||||
|
||||
Вы можете использовать это, чтобы добавить аргумент `example` для каждого поля:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="2 8-11"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial002_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="4 10-13"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial002.py!}
|
||||
```
|
||||
|
||||
!!! warning Внимание
|
||||
Имейте в виду, что эти дополнительные переданные аргументы не добавляют никакой валидации, только дополнительную информацию для документации.
|
||||
|
||||
## Использование `example` и `examples` в OpenAPI
|
||||
|
||||
При использовании любой из этих функций:
|
||||
|
||||
* `Path()`
|
||||
* `Query()`
|
||||
* `Header()`
|
||||
* `Cookie()`
|
||||
* `Body()`
|
||||
* `Form()`
|
||||
* `File()`
|
||||
|
||||
вы также можете добавить аргумент, содержащий `example` или группу `examples` с дополнительной информацией, которая будет добавлена в **OpenAPI**.
|
||||
|
||||
### Параметр `Body` с аргументом `example`
|
||||
|
||||
Здесь мы передаём аргумент `example`, как пример данных ожидаемых в параметре `Body()`:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="22-27"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial003_an_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="22-27"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial003_an_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="23-28"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial003_an.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.10+ non-Annotated"
|
||||
|
||||
!!! tip Заметка
|
||||
Рекомендуется использовать версию с `Annotated`, если это возможно.
|
||||
|
||||
```Python hl_lines="18-23"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial003_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+ non-Annotated"
|
||||
|
||||
!!! tip Заметка
|
||||
Рекомендуется использовать версию с `Annotated`, если это возможно.
|
||||
|
||||
```Python hl_lines="20-25"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial003.py!}
|
||||
```
|
||||
|
||||
### Аргумент "example" в UI документации
|
||||
|
||||
С любым из вышеуказанных методов это будет выглядеть так в `/docs`:
|
||||
|
||||
<img src="/img/tutorial/body-fields/image01.png">
|
||||
|
||||
### `Body` с аргументом `examples`
|
||||
|
||||
В качестве альтернативы одному аргументу `example`, вы можете передавать `examples` используя тип данных `dict` с **несколькими примерами**, каждый из которых содержит дополнительную информацию, которая также будет добавлена в **OpenAPI**.
|
||||
|
||||
Ключи `dict` указывают на каждый пример, а значения для каждого из них - на еще один тип `dict` с дополнительной информацией.
|
||||
|
||||
Каждый конкретный пример типа `dict` в аргументе `examples` может содержать:
|
||||
|
||||
* `summary`: Краткое описание для примера.
|
||||
* `description`: Полное описание, которое может содержать текст в формате Markdown.
|
||||
* `value`: Это конкретный пример, который отображается, например, в виде типа `dict`.
|
||||
* `externalValue`: альтернатива параметру `value`, URL-адрес, указывающий на пример. Хотя это может не поддерживаться таким же количеством инструментов разработки и тестирования API, как параметр `value`.
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="23-49"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial004_an_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="23-49"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial004_an_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="24-50"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial004_an.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.10+ non-Annotated"
|
||||
|
||||
!!! tip Заметка
|
||||
Рекомендуется использовать версию с `Annotated`, если это возможно.
|
||||
|
||||
```Python hl_lines="19-45"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial004_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+ non-Annotated"
|
||||
|
||||
!!! tip Заметка
|
||||
Рекомендуется использовать версию с `Annotated`, если это возможно.
|
||||
|
||||
```Python hl_lines="21-47"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial004.py!}
|
||||
```
|
||||
|
||||
### Аргумент "examples" в UI документации
|
||||
|
||||
С аргументом `examples`, добавленным в `Body()`, страница документации `/docs` будет выглядеть так:
|
||||
|
||||
<img src="/img/tutorial/body-fields/image02.png">
|
||||
|
||||
## Технические Детали
|
||||
|
||||
!!! warning Внимание
|
||||
Эти технические детали относятся к стандартам **JSON Schema** и **OpenAPI**.
|
||||
|
||||
Если предложенные выше идеи уже работают для вас, возможно этого будет достаточно и эти детали вам не потребуются, можете спокойно их пропустить.
|
||||
|
||||
Когда вы добавляете пример внутрь модели Pydantic, используя `schema_extra` или `Field(example="something")`, этот пример добавляется в **JSON Schema** для данной модели Pydantic.
|
||||
|
||||
И эта **JSON Schema** модели Pydantic включается в **OpenAPI** вашего API, а затем используется в UI документации.
|
||||
|
||||
Поля `example` как такового не существует в стандартах **JSON Schema**. В последних версиях JSON-схемы определено поле <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a>, но OpenAPI 3.0.3 основан на более старой версии JSON-схемы, которая не имела поля `examples`.
|
||||
|
||||
Таким образом, OpenAPI 3.0.3 определяет своё собственное поле <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-20" class="external-link" target="_blank">`example`</a> для модифицированной версии **JSON Schema**, которую он использует чтобы достичь той же цели (однако это именно поле `example`, а не `examples`), и именно это используется API в UI документации (с интеграцией Swagger UI).
|
||||
|
||||
Итак, хотя поле `example` не является частью JSON-схемы, оно является частью настраиваемой версии JSON-схемы в OpenAPI, и именно это поле будет использоваться в UI документации.
|
||||
|
||||
Однако, когда вы используете поле `example` или `examples` с любой другой функцией (`Query()`, `Body()`, и т.д.), эти примеры не добавляются в JSON-схему, которая описывает эти данные (даже в собственную версию JSON-схемы OpenAPI), они добавляются непосредственно в объявление *операции пути* в OpenAPI (вне частей OpenAPI, которые используют JSON-схему).
|
||||
|
||||
Для функций `Path()`, `Query()`, `Header()`, и `Cookie()`, аргументы `example` или `examples` добавляются в <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object" class="external-link" target="_blank">определение OpenAPI, к объекту `Parameter Object` (в спецификации)</a>.
|
||||
|
||||
И для функций `Body()`, `File()` и `Form()` аргументы `example` или `examples` аналогично добавляются в <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#mediaTypeObject" class="external-link" target="_blank"> определение OpenAPI, к объекту `Request Body Object`, в поле `content` в объекте `Media Type Object` (в спецификации)</a>.
|
||||
|
||||
С другой стороны, существует более новая версия OpenAPI: **3.1.0**, недавно выпущенная. Она основана на последней версии JSON-схемы и большинство модификаций из OpenAPI JSON-схемы удалены в обмен на новые возможности из последней версии JSON-схемы, так что все эти мелкие отличия устранены. Тем не менее, Swagger UI в настоящее время не поддерживает OpenAPI 3.1.0, поэтому пока лучше продолжать использовать вышеупомянутые методы.
|
||||
@@ -67,6 +67,7 @@ nav:
|
||||
- fastapi-people.md
|
||||
- python-types.md
|
||||
- Учебник - руководство пользователя:
|
||||
- tutorial/index.md
|
||||
- tutorial/first-steps.md
|
||||
- tutorial/path-params.md
|
||||
- tutorial/query-params-str-validations.md
|
||||
@@ -76,11 +77,17 @@ nav:
|
||||
- tutorial/extra-data-types.md
|
||||
- tutorial/cookie-params.md
|
||||
- tutorial/testing.md
|
||||
- tutorial/extra-models.md
|
||||
- tutorial/response-status-code.md
|
||||
- tutorial/query-params.md
|
||||
- tutorial/body-multiple-params.md
|
||||
- tutorial/metadata.md
|
||||
- tutorial/path-operation-configuration.md
|
||||
- tutorial/cors.md
|
||||
- tutorial/static-files.md
|
||||
- tutorial/debugging.md
|
||||
- tutorial/schema-extra-example.md
|
||||
- tutorial/body-nested-models.md
|
||||
- async.md
|
||||
- Развёртывание:
|
||||
- deployment/index.md
|
||||
|
||||
@@ -441,7 +441,6 @@ To understand more about it, see the section <a href="https://fastapi.tiangolo.c
|
||||
|
||||
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:
|
||||
|
||||
@@ -444,7 +444,6 @@ To understand more about it, see the section <a href="https://fastapi.tiangolo.c
|
||||
|
||||
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:
|
||||
|
||||
@@ -449,7 +449,6 @@ Daha fazla bilgi için, bu bölüme bir göz at <a href="https://fastapi.tiangol
|
||||
|
||||
Pydantic tarafında kullanılan:
|
||||
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - daha hızlı JSON <abbr title="HTTP bağlantısından gelen stringi Python objesine çevirmek için">"dönüşümü"</abbr> için.
|
||||
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email_validator</code></a> - email doğrulaması için.
|
||||
|
||||
Starlette tarafında kullanılan:
|
||||
|
||||
@@ -441,7 +441,6 @@ To understand more about it, see the section <a href="https://fastapi.tiangolo.c
|
||||
|
||||
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:
|
||||
|
||||
31
docs/zh/docs/advanced/response-change-status-code.md
Normal file
31
docs/zh/docs/advanced/response-change-status-code.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# 响应 - 更改状态码
|
||||
|
||||
你可能之前已经了解到,你可以设置默认的[响应状态码](../tutorial/response-status-code.md){.internal-link target=_blank}。
|
||||
|
||||
但在某些情况下,你需要返回一个不同于默认值的状态码。
|
||||
|
||||
## 使用场景
|
||||
|
||||
例如,假设你想默认返回一个HTTP状态码为“OK”`200`。
|
||||
|
||||
但如果数据不存在,你想创建它,并返回一个HTTP状态码为“CREATED”`201`。
|
||||
|
||||
但你仍然希望能够使用`response_model`过滤和转换你返回的数据。
|
||||
|
||||
对于这些情况,你可以使用一个`Response`参数。
|
||||
|
||||
## 使用 `Response` 参数
|
||||
|
||||
你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies和头部做的那样)。
|
||||
|
||||
然后你可以在这个*临时*响应对象中设置`status_code`。
|
||||
|
||||
```Python hl_lines="1 9 12"
|
||||
{!../../../docs_src/response_change_status_code/tutorial001.py!}
|
||||
```
|
||||
|
||||
然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。
|
||||
|
||||
**FastAPI**将使用这个临时响应来提取状态码(也包括cookies和头部),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。
|
||||
|
||||
你也可以在依赖项中声明`Response`参数,并在其中设置状态码。但请注意,最后设置的状态码将会生效。
|
||||
39
docs/zh/docs/advanced/response-headers.md
Normal file
39
docs/zh/docs/advanced/response-headers.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 响应头
|
||||
|
||||
## 使用 `Response` 参数
|
||||
|
||||
你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies做的那样)。
|
||||
|
||||
然后你可以在这个*临时*响应对象中设置头部。
|
||||
```Python hl_lines="1 7-8"
|
||||
{!../../../docs_src/response_headers/tutorial002.py!}
|
||||
```
|
||||
|
||||
然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。
|
||||
|
||||
**FastAPI**将使用这个临时响应来提取头部(也包括cookies和状态码),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。
|
||||
|
||||
你也可以在依赖项中声明`Response`参数,并在其中设置头部(和cookies)。
|
||||
|
||||
## 直接返回 `Response`
|
||||
|
||||
你也可以在直接返回`Response`时添加头部。
|
||||
|
||||
按照[直接返回响应](response-directly.md){.internal-link target=_blank}中所述创建响应,并将头部作为附加参数传递:
|
||||
```Python hl_lines="10-12"
|
||||
{!../../../docs_src/response_headers/tutorial001.py!}
|
||||
```
|
||||
|
||||
|
||||
!!! 注意 "技术细节"
|
||||
你也可以使用`from starlette.responses import Response`或`from starlette.responses import JSONResponse`。
|
||||
|
||||
**FastAPI**提供了与`fastapi.responses`相同的`starlette.responses`,只是为了方便开发者。但是,大多数可用的响应都直接来自Starlette。
|
||||
|
||||
由于`Response`经常用于设置头部和cookies,因此**FastAPI**还在`fastapi.Response`中提供了它。
|
||||
|
||||
## 自定义头部
|
||||
|
||||
请注意,可以使用'X-'前缀添加自定义专有头部。
|
||||
|
||||
但是,如果你有自定义头部,你希望浏览器中的客户端能够看到它们,你需要将它们添加到你的CORS配置中(在[CORS(跨源资源共享)](../tutorial/cors.md){.internal-link target=_blank}中阅读更多),使用在<a href="https://www.starlette.io/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette的CORS文档</a>中记录的`expose_headers`参数。
|
||||
16
docs/zh/docs/advanced/security/index.md
Normal file
16
docs/zh/docs/advanced/security/index.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# 高级安全 - 介绍
|
||||
|
||||
## 附加特性
|
||||
|
||||
除 [教程 - 用户指南: 安全性](../../tutorial/security/){.internal-link target=_blank} 中涵盖的功能之外,还有一些额外的功能来处理安全性.
|
||||
|
||||
!!! tip "小贴士"
|
||||
接下来的章节 **并不一定是 "高级的"**.
|
||||
|
||||
而且对于你的使用场景来说,解决方案很可能就在其中。
|
||||
|
||||
## 先阅读教程
|
||||
|
||||
接下来的部分假设你已经阅读了主要的 [教程 - 用户指南: 安全性](../../tutorial/security/){.internal-link target=_blank}.
|
||||
|
||||
它们都基于相同的概念,但支持一些额外的功能.
|
||||
433
docs/zh/docs/advanced/settings.md
Normal file
433
docs/zh/docs/advanced/settings.md
Normal file
@@ -0,0 +1,433 @@
|
||||
# 设置和环境变量
|
||||
|
||||
在许多情况下,您的应用程序可能需要一些外部设置或配置,例如密钥、数据库凭据、电子邮件服务的凭据等等。
|
||||
|
||||
这些设置中的大多数是可变的(可以更改的),比如数据库的 URL。而且许多设置可能是敏感的,比如密钥。
|
||||
|
||||
因此,通常会将它们提供为由应用程序读取的环境变量。
|
||||
|
||||
## 环境变量
|
||||
|
||||
!!! tip
|
||||
如果您已经知道什么是"环境变量"以及如何使用它们,请随意跳到下面的下一节。
|
||||
|
||||
环境变量(也称为"env var")是一种存在于 Python 代码之外、存在于操作系统中的变量,可以被您的 Python 代码(或其他程序)读取。
|
||||
|
||||
您可以在 shell 中创建和使用环境变量,而无需使用 Python:
|
||||
|
||||
=== "Linux、macOS、Windows Bash"
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// 您可以创建一个名为 MY_NAME 的环境变量
|
||||
$ export MY_NAME="Wade Wilson"
|
||||
|
||||
// 然后您可以与其他程序一起使用它,例如
|
||||
$ echo "Hello $MY_NAME"
|
||||
|
||||
Hello Wade Wilson
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
=== "Windows PowerShell"
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// 创建一个名为 MY_NAME 的环境变量
|
||||
$ $Env:MY_NAME = "Wade Wilson"
|
||||
|
||||
// 与其他程序一起使用它,例如
|
||||
$ echo "Hello $Env:MY_NAME"
|
||||
|
||||
Hello Wade Wilson
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### 在 Python 中读取环境变量
|
||||
|
||||
您还可以在 Python 之外的地方(例如终端中或使用任何其他方法)创建环境变量,然后在 Python 中读取它们。
|
||||
|
||||
例如,您可以有一个名为 `main.py` 的文件,其中包含以下内容:
|
||||
|
||||
```Python hl_lines="3"
|
||||
import os
|
||||
|
||||
name = os.getenv("MY_NAME", "World")
|
||||
print(f"Hello {name} from Python")
|
||||
```
|
||||
|
||||
!!! tip
|
||||
<a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> 的第二个参数是要返回的默认值。
|
||||
|
||||
如果没有提供默认值,默认为 `None`,此处我们提供了 `"World"` 作为要使用的默认值。
|
||||
|
||||
然后,您可以调用该 Python 程序:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// 这里我们还没有设置环境变量
|
||||
$ python main.py
|
||||
|
||||
// 因为我们没有设置环境变量,所以我们得到默认值
|
||||
|
||||
Hello World from Python
|
||||
|
||||
// 但是如果我们先创建一个环境变量
|
||||
$ export MY_NAME="Wade Wilson"
|
||||
|
||||
// 然后再次调用程序
|
||||
$ python main.py
|
||||
|
||||
// 现在它可以读取环境变量
|
||||
|
||||
Hello Wade Wilson from Python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
由于环境变量可以在代码之外设置,但可以由代码读取,并且不需要与其他文件一起存储(提交到 `git`),因此通常将它们用于配置或设置。
|
||||
|
||||
|
||||
|
||||
您还可以仅为特定程序调用创建一个环境变量,该环境变量仅对该程序可用,并且仅在其运行期间有效。
|
||||
|
||||
要做到这一点,在程序本身之前的同一行创建它:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// 在此程序调用行中创建一个名为 MY_NAME 的环境变量
|
||||
$ MY_NAME="Wade Wilson" python main.py
|
||||
|
||||
// 现在它可以读取环境变量
|
||||
|
||||
Hello Wade Wilson from Python
|
||||
|
||||
// 之后环境变量不再存在
|
||||
$ python main.py
|
||||
|
||||
Hello World from Python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
!!! tip
|
||||
您可以在 <a href="https://12factor.net/config" class="external-link" target="_blank">Twelve-Factor App: Config</a> 中阅读更多相关信息。
|
||||
|
||||
### 类型和验证
|
||||
|
||||
这些环境变量只能处理文本字符串,因为它们是外部于 Python 的,并且必须与其他程序和整个系统兼容(甚至与不同的操作系统,如 Linux、Windows、macOS)。
|
||||
|
||||
这意味着从环境变量中在 Python 中读取的任何值都将是 `str` 类型,任何类型的转换或验证都必须在代码中完成。
|
||||
|
||||
## Pydantic 的 `Settings`
|
||||
|
||||
幸运的是,Pydantic 提供了一个很好的工具来处理来自环境变量的设置,即<a href="https://pydantic-docs.helpmanual.io/usage/settings/" class="external-link" target="_blank">Pydantic: Settings management</a>。
|
||||
|
||||
### 创建 `Settings` 对象
|
||||
|
||||
从 Pydantic 导入 `BaseSettings` 并创建一个子类,与 Pydantic 模型非常相似。
|
||||
|
||||
与 Pydantic 模型一样,您使用类型注释声明类属性,还可以指定默认值。
|
||||
|
||||
您可以使用与 Pydantic 模型相同的验证功能和工具,比如不同的数据类型和使用 `Field()` 进行附加验证。
|
||||
|
||||
```Python hl_lines="2 5-8 11"
|
||||
{!../../../docs_src/settings/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
如果您需要一个快速的复制粘贴示例,请不要使用此示例,而应使用下面的最后一个示例。
|
||||
|
||||
然后,当您创建该 `Settings` 类的实例(在此示例中是 `settings` 对象)时,Pydantic 将以不区分大小写的方式读取环境变量,因此,大写的变量 `APP_NAME` 仍将为属性 `app_name` 读取。
|
||||
|
||||
然后,它将转换和验证数据。因此,当您使用该 `settings` 对象时,您将获得您声明的类型的数据(例如 `items_per_user` 将为 `int` 类型)。
|
||||
|
||||
### 使用 `settings`
|
||||
|
||||
然后,您可以在应用程序中使用新的 `settings` 对象:
|
||||
|
||||
```Python hl_lines="18-20"
|
||||
{!../../../docs_src/settings/tutorial001.py!}
|
||||
```
|
||||
|
||||
### 运行服务器
|
||||
|
||||
接下来,您将运行服务器,并将配置作为环境变量传递。例如,您可以设置一个 `ADMIN_EMAIL` 和 `APP_NAME`,如下所示:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
!!! tip
|
||||
要为单个命令设置多个环境变量,只需用空格分隔它们,并将它们全部放在命令之前。
|
||||
|
||||
然后,`admin_email` 设置将为 `"deadpool@example.com"`。
|
||||
|
||||
`app_name` 将为 `"ChimichangApp"`。
|
||||
|
||||
而 `items_per_user` 将保持其默认值为 `50`。
|
||||
|
||||
## 在另一个模块中设置
|
||||
|
||||
您可以将这些设置放在另一个模块文件中,就像您在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中所见的那样。
|
||||
|
||||
例如,您可以创建一个名为 `config.py` 的文件,其中包含以下内容:
|
||||
|
||||
```Python
|
||||
{!../../../docs_src/settings/app01/config.py!}
|
||||
```
|
||||
|
||||
然后在一个名为 `main.py` 的文件中使用它:
|
||||
|
||||
```Python hl_lines="3 11-13"
|
||||
{!../../../docs_src/settings/app01/main.py!}
|
||||
```
|
||||
!!! tip
|
||||
您还需要一个名为 `__init__.py` 的文件,就像您在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。
|
||||
|
||||
## 在依赖项中使用设置
|
||||
|
||||
在某些情况下,从依赖项中提供设置可能比在所有地方都使用全局对象 `settings` 更有用。
|
||||
|
||||
这在测试期间尤其有用,因为很容易用自定义设置覆盖依赖项。
|
||||
|
||||
### 配置文件
|
||||
|
||||
根据前面的示例,您的 `config.py` 文件可能如下所示:
|
||||
|
||||
```Python hl_lines="10"
|
||||
{!../../../docs_src/settings/app02/config.py!}
|
||||
```
|
||||
|
||||
请注意,现在我们不创建默认实例 `settings = Settings()`。
|
||||
|
||||
### 主应用程序文件
|
||||
|
||||
现在我们创建一个依赖项,返回一个新的 `config.Settings()`。
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="6 12-13"
|
||||
{!> ../../../docs_src/settings/app02_an_py39/main.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="6 12-13"
|
||||
{!> ../../../docs_src/settings/app02_an/main.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+ 非注解版本"
|
||||
|
||||
!!! tip
|
||||
如果可能,请尽量使用 `Annotated` 版本。
|
||||
|
||||
```Python hl_lines="5 11-12"
|
||||
{!> ../../../docs_src/settings/app02/main.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
我们稍后会讨论 `@lru_cache()`。
|
||||
|
||||
目前,您可以将 `get_settings()` 视为普通函数。
|
||||
|
||||
然后,我们可以将其作为依赖项从“路径操作函数”中引入,并在需要时使用它。
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="17 19-21"
|
||||
{!> ../../../docs_src/settings/app02_an_py39/main.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="17 19-21"
|
||||
{!> ../../../docs_src/settings/app02_an/main.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+ 非注解版本"
|
||||
|
||||
!!! tip
|
||||
如果可能,请尽量使用 `Annotated` 版本。
|
||||
|
||||
```Python hl_lines="16 18-20"
|
||||
{!> ../../../docs_src/settings/app02/main.py!}
|
||||
```
|
||||
|
||||
### 设置和测试
|
||||
|
||||
然后,在测试期间,通过创建 `get_settings` 的依赖项覆盖,很容易提供一个不同的设置对象:
|
||||
|
||||
```Python hl_lines="9-10 13 21"
|
||||
{!../../../docs_src/settings/app02/test_main.py!}
|
||||
```
|
||||
|
||||
在依赖项覆盖中,我们在创建新的 `Settings` 对象时为 `admin_email` 设置了一个新值,然后返回该新对象。
|
||||
|
||||
然后,我们可以测试它是否被使用。
|
||||
|
||||
## 从 `.env` 文件中读取设置
|
||||
|
||||
如果您有许多可能经常更改的设置,可能在不同的环境中,将它们放在一个文件中,然后从该文件中读取它们,就像它们是环境变量一样,可能非常有用。
|
||||
|
||||
这种做法相当常见,有一个名称,这些环境变量通常放在一个名为 `.env` 的文件中,该文件被称为“dotenv”。
|
||||
|
||||
!!! tip
|
||||
以点 (`.`) 开头的文件是 Unix-like 系统(如 Linux 和 macOS)中的隐藏文件。
|
||||
|
||||
但是,dotenv 文件实际上不一定要具有确切的文件名。
|
||||
|
||||
Pydantic 支持使用外部库从这些类型的文件中读取。您可以在<a href="https://pydantic-docs.helpmanual.io/usage/settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic 设置: Dotenv (.env) 支持</a>中阅读更多相关信息。
|
||||
|
||||
!!! tip
|
||||
要使其工作,您需要执行 `pip install python-dotenv`。
|
||||
|
||||
### `.env` 文件
|
||||
|
||||
您可以使用以下内容创建一个名为 `.env` 的文件:
|
||||
|
||||
```bash
|
||||
ADMIN_EMAIL="deadpool@example.com"
|
||||
APP_NAME="ChimichangApp"
|
||||
```
|
||||
|
||||
### 从 `.env` 文件中读取设置
|
||||
|
||||
然后,您可以使用以下方式更新您的 `config.py`:
|
||||
|
||||
```Python hl_lines="9-10"
|
||||
{!../../../docs_src/settings/app03/config.py!}
|
||||
```
|
||||
|
||||
在这里,我们在 Pydantic 的 `Settings` 类中创建了一个名为 `Config` 的类,并将 `env_file` 设置为我们想要使用的 dotenv 文件的文件名。
|
||||
|
||||
!!! tip
|
||||
`Config` 类仅用于 Pydantic 配置。您可以在<a href="https://pydantic-docs.helpmanual.io/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>中阅读更多相关信息。
|
||||
|
||||
### 使用 `lru_cache` 仅创建一次 `Settings`
|
||||
|
||||
从磁盘中读取文件通常是一项耗时的(慢)操作,因此您可能希望仅在首次读取后并重复使用相同的设置对象,而不是为每个请求都读取它。
|
||||
|
||||
但是,每次执行以下操作:
|
||||
|
||||
```Python
|
||||
Settings()
|
||||
```
|
||||
|
||||
都会创建一个新的 `Settings` 对象,并且在创建时会再次读取 `.env` 文件。
|
||||
|
||||
如果依赖项函数只是这样的:
|
||||
|
||||
```Python
|
||||
def get_settings():
|
||||
return Settings()
|
||||
```
|
||||
|
||||
我们将为每个请求创建该对象,并且将在每个请求中读取 `.env` 文件。 ⚠️
|
||||
|
||||
但是,由于我们在顶部使用了 `@lru_cache()` 装饰器,因此只有在第一次调用它时,才会创建 `Settings` 对象一次。 ✔️
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="1 11"
|
||||
{!> ../../../docs_src/settings/app03_an_py39/main.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="1 11"
|
||||
{!> ../../../docs_src/settings/app03_an/main.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+ 非注解版本"
|
||||
|
||||
!!! tip
|
||||
如果可能,请尽量使用 `Annotated` 版本。
|
||||
|
||||
```Python hl_lines="1 10"
|
||||
{!> ../../../docs_src/settings/app03/main.py!}
|
||||
```
|
||||
|
||||
然后,在下一次请求的依赖项中对 `get_settings()` 进行任何后续调用时,它不会执行 `get_settings()` 的内部代码并创建新的 `Settings` 对象,而是返回在第一次调用时返回的相同对象,一次又一次。
|
||||
|
||||
#### `lru_cache` 技术细节
|
||||
|
||||
`@lru_cache()` 修改了它所装饰的函数,以返回第一次返回的相同值,而不是再次计算它,每次都执行函数的代码。
|
||||
|
||||
因此,下面的函数将对每个参数组合执行一次。然后,每个参数组合返回的值将在使用完全相同的参数组合调用函数时再次使用。
|
||||
|
||||
例如,如果您有一个函数:
|
||||
```Python
|
||||
@lru_cache()
|
||||
def say_hi(name: str, salutation: str = "Ms."):
|
||||
return f"Hello {salutation} {name}"
|
||||
```
|
||||
|
||||
您的程序可以像这样执行:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
|
||||
participant code as Code
|
||||
participant function as say_hi()
|
||||
participant execute as Execute function
|
||||
|
||||
rect rgba(0, 255, 0, .1)
|
||||
code ->> function: say_hi(name="Camila")
|
||||
function ->> execute: 执行函数代码
|
||||
execute ->> code: 返回结果
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 255, .1)
|
||||
code ->> function: say_hi(name="Camila")
|
||||
function ->> code: 返回存储的结果
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 0, .1)
|
||||
code ->> function: say_hi(name="Rick")
|
||||
function ->> execute: 执行函数代码
|
||||
execute ->> code: 返回结果
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 0, .1)
|
||||
code ->> function: say_hi(name="Rick", salutation="Mr.")
|
||||
function ->> execute: 执行函数代码
|
||||
execute ->> code: 返回结果
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 255, .1)
|
||||
code ->> function: say_hi(name="Rick")
|
||||
function ->> code: 返回存储的结果
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 255, .1)
|
||||
code ->> function: say_hi(name="Camila")
|
||||
function ->> code: 返回存储的结果
|
||||
end
|
||||
```
|
||||
|
||||
对于我们的依赖项 `get_settings()`,该函数甚至不接受任何参数,因此它始终返回相同的值。
|
||||
|
||||
这样,它的行为几乎就像是一个全局变量。但是由于它使用了依赖项函数,因此我们可以轻松地进行测试时的覆盖。
|
||||
|
||||
`@lru_cache()` 是 `functools` 的一部分,它是 Python 标准库的一部分,您可以在<a href="https://docs.python.org/3/library/functools.html#functools.lru_cache" class="external-link" target="_blank">Python 文档中了解有关 `@lru_cache()` 的更多信息</a>。
|
||||
|
||||
## 小结
|
||||
|
||||
您可以使用 Pydantic 设置处理应用程序的设置或配置,利用 Pydantic 模型的所有功能。
|
||||
|
||||
* 通过使用依赖项,您可以简化测试。
|
||||
* 您可以使用 `.env` 文件。
|
||||
* 使用 `@lru_cache()` 可以避免为每个请求重复读取 dotenv 文件,同时允许您在测试时进行覆盖。
|
||||
214
docs/zh/docs/advanced/websockets.md
Normal file
214
docs/zh/docs/advanced/websockets.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# WebSockets
|
||||
|
||||
您可以在 **FastAPI** 中使用 [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)。
|
||||
|
||||
## 安装 `WebSockets`
|
||||
|
||||
首先,您需要安装 `WebSockets`:
|
||||
|
||||
```console
|
||||
$ pip install websockets
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
## WebSockets 客户端
|
||||
|
||||
### 在生产环境中
|
||||
|
||||
在您的生产系统中,您可能使用现代框架(如React、Vue.js或Angular)创建了一个前端。
|
||||
|
||||
要使用 WebSockets 与后端进行通信,您可能会使用前端的工具。
|
||||
|
||||
或者,您可能有一个原生移动应用程序,直接使用原生代码与 WebSocket 后端通信。
|
||||
|
||||
或者,您可能有其他与 WebSocket 终端通信的方式。
|
||||
|
||||
---
|
||||
|
||||
但是,在本示例中,我们将使用一个非常简单的HTML文档,其中包含一些JavaScript,全部放在一个长字符串中。
|
||||
|
||||
当然,这并不是最优的做法,您不应该在生产环境中使用它。
|
||||
|
||||
在生产环境中,您应该选择上述任一选项。
|
||||
|
||||
但这是一种专注于 WebSockets 的服务器端并提供一个工作示例的最简单方式:
|
||||
|
||||
```Python hl_lines="2 6-38 41-43"
|
||||
{!../../../docs_src/websockets/tutorial001.py!}
|
||||
```
|
||||
|
||||
## 创建 `websocket`
|
||||
|
||||
在您的 **FastAPI** 应用程序中,创建一个 `websocket`:
|
||||
|
||||
```Python hl_lines="1 46-47"
|
||||
{!../../../docs_src/websockets/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! note "技术细节"
|
||||
您也可以使用 `from starlette.websockets import WebSocket`。
|
||||
|
||||
**FastAPI** 直接提供了相同的 `WebSocket`,只是为了方便开发人员。但它直接来自 Starlette。
|
||||
|
||||
## 等待消息并发送消息
|
||||
|
||||
在您的 WebSocket 路由中,您可以使用 `await` 等待消息并发送消息。
|
||||
|
||||
```Python hl_lines="48-52"
|
||||
{!../../../docs_src/websockets/tutorial001.py!}
|
||||
```
|
||||
|
||||
您可以接收和发送二进制、文本和 JSON 数据。
|
||||
|
||||
## 尝试一下
|
||||
|
||||
如果您的文件名为 `main.py`,请使用以下命令运行应用程序:
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
在浏览器中打开 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>。
|
||||
|
||||
您将看到一个简单的页面,如下所示:
|
||||
|
||||
<img src="/img/tutorial/websockets/image01.png">
|
||||
|
||||
您可以在输入框中输入消息并发送:
|
||||
|
||||
<img src="/img/tutorial/websockets/image02.png">
|
||||
|
||||
您的 **FastAPI** 应用程序将回复:
|
||||
|
||||
<img src="/img/tutorial/websockets/image03.png">
|
||||
|
||||
您可以发送(和接收)多条消息:
|
||||
|
||||
<img src="/img/tutorial/websockets/image04.png">
|
||||
|
||||
所有这些消息都将使用同一个 WebSocket 连
|
||||
|
||||
接。
|
||||
|
||||
## 使用 `Depends` 和其他依赖项
|
||||
|
||||
在 WebSocket 端点中,您可以从 `fastapi` 导入并使用以下内容:
|
||||
|
||||
* `Depends`
|
||||
* `Security`
|
||||
* `Cookie`
|
||||
* `Header`
|
||||
* `Path`
|
||||
* `Query`
|
||||
|
||||
它们的工作方式与其他 FastAPI 端点/ *路径操作* 相同:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="68-69 82"
|
||||
{!> ../../../docs_src/websockets/tutorial002_an_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="68-69 82"
|
||||
{!> ../../../docs_src/websockets/tutorial002_an_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="69-70 83"
|
||||
{!> ../../../docs_src/websockets/tutorial002_an.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.10+ 非带注解版本"
|
||||
|
||||
!!! tip
|
||||
如果可能,请尽量使用 `Annotated` 版本。
|
||||
|
||||
```Python hl_lines="66-67 79"
|
||||
{!> ../../../docs_src/websockets/tutorial002_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+ 非带注解版本"
|
||||
|
||||
!!! tip
|
||||
如果可能,请尽量使用 `Annotated` 版本。
|
||||
|
||||
```Python hl_lines="68-69 81"
|
||||
{!> ../../../docs_src/websockets/tutorial002.py!}
|
||||
```
|
||||
|
||||
!!! info
|
||||
由于这是一个 WebSocket,抛出 `HTTPException` 并不是很合理,而是抛出 `WebSocketException`。
|
||||
|
||||
您可以使用<a href="https://tools.ietf.org/html/rfc6455#section-7.4.1" class="external-link" target="_blank">规范中定义的有效代码</a>。
|
||||
|
||||
### 尝试带有依赖项的 WebSockets
|
||||
|
||||
如果您的文件名为 `main.py`,请使用以下命令运行应用程序:
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
在浏览器中打开 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>。
|
||||
|
||||
在页面中,您可以设置:
|
||||
|
||||
* "Item ID",用于路径。
|
||||
* "Token",作为查询参数。
|
||||
|
||||
!!! tip
|
||||
注意,查询参数 `token` 将由依赖项处理。
|
||||
|
||||
通过这样,您可以连接 WebSocket,然后发送和接收消息:
|
||||
|
||||
<img src="/img/tutorial/websockets/image05.png">
|
||||
|
||||
## 处理断开连接和多个客户端
|
||||
|
||||
当 WebSocket 连接关闭时,`await websocket.receive_text()` 将引发 `WebSocketDisconnect` 异常,您可以捕获并处理该异常,就像本示例中的示例一样。
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="79-81"
|
||||
{!> ../../../docs_src/websockets/tutorial003_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="81-83"
|
||||
{!> ../../../docs_src/websockets/tutorial003.py!}
|
||||
```
|
||||
|
||||
尝试以下操作:
|
||||
|
||||
* 使用多个浏览器选项卡打开应用程序。
|
||||
* 从这些选项卡中发送消息。
|
||||
* 然后关闭其中一个选项卡。
|
||||
|
||||
这将引发 `WebSocketDisconnect` 异常,并且所有其他客户端都会收到类似以下的消息:
|
||||
|
||||
```
|
||||
Client #1596980209979 left the chat
|
||||
```
|
||||
|
||||
!!! tip
|
||||
上面的应用程序是一个最小和简单的示例,用于演示如何处理和向多个 WebSocket 连接广播消息。
|
||||
|
||||
但请记住,由于所有内容都在内存中以单个列表的形式处理,因此它只能在进程运行时工作,并且只能使用单个进程。
|
||||
|
||||
如果您需要与 FastAPI 集成更简单但更强大的功能,支持 Redis、PostgreSQL 或其他功能,请查看 [encode/broadcaster](https://github.com/encode/broadcaster)。
|
||||
|
||||
## 更多信息
|
||||
|
||||
要了解更多选项,请查看 Starlette 的文档:
|
||||
|
||||
* [WebSocket 类](https://www.starlette.io/websockets/)
|
||||
* [基于类的 WebSocket 处理](https://www.starlette.io/endpoints/#websocketendpoint)。
|
||||
@@ -97,7 +97,7 @@ $ python -m venv env
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install -e ."[dev,doc,test]"
|
||||
$ pip install -r requirements.txt
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
@@ -437,7 +437,6 @@ item: Item
|
||||
|
||||
用于 Pydantic:
|
||||
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - 更快的 JSON <abbr title="将来自 HTTP 请求中的字符串转换为 Python 数据类型">「解析」</abbr>。
|
||||
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email_validator</code></a> - 用于 email 校验。
|
||||
|
||||
用于 Starlette:
|
||||
|
||||
212
docs/zh/docs/tutorial/testing.md
Normal file
212
docs/zh/docs/tutorial/testing.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# 测试
|
||||
|
||||
感谢 <a href="https://www.starlette.io/testclient/" class="external-link" target="_blank">Starlette</a>,测试**FastAPI** 应用轻松又愉快。
|
||||
|
||||
它基于 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>, 而HTTPX又是基于Requests设计的,所以很相似且易懂。
|
||||
|
||||
有了它,你可以直接与**FastAPI**一起使用 <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a>。
|
||||
|
||||
## 使用 `TestClient`
|
||||
|
||||
!!! 信息
|
||||
要使用 `TestClient`,先要安装 <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a>.
|
||||
|
||||
例:`pip install httpx`.
|
||||
|
||||
导入 `TestClient`.
|
||||
|
||||
通过传入你的**FastAPI**应用创建一个 `TestClient` 。
|
||||
|
||||
创建名字以 `test_` 开头的函数(这是标准的 `pytest` 约定)。
|
||||
|
||||
像使用 `httpx` 那样使用 `TestClient` 对象。
|
||||
|
||||
为你需要检查的地方用标准的Python表达式写个简单的 `assert` 语句(重申,标准的`pytest`)。
|
||||
|
||||
```Python hl_lines="2 12 15-18"
|
||||
{!../../../docs_src/app_testing/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! 提示
|
||||
注意测试函数是普通的 `def`,不是 `async def`。
|
||||
|
||||
还有client的调用也是普通的调用,不是用 `await`。
|
||||
|
||||
这让你可以直接使用 `pytest` 而不会遇到麻烦。
|
||||
|
||||
!!! note "技术细节"
|
||||
你也可以用 `from starlette.testclient import TestClient`。
|
||||
|
||||
**FastAPI** 提供了和 `starlette.testclient` 一样的 `fastapi.testclient`,只是为了方便开发者。但它直接来自Starlette。
|
||||
|
||||
!!! 提示
|
||||
除了发送请求之外,如果你还想测试时在FastAPI应用中调用 `async` 函数(例如异步数据库函数), 可以在高级教程中看下 [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} 。
|
||||
|
||||
## 分离测试
|
||||
|
||||
在实际应用中,你可能会把你的测试放在另一个文件里。
|
||||
|
||||
您的**FastAPI**应用程序也可能由一些文件/模块组成等等。
|
||||
|
||||
### **FastAPI** app 文件
|
||||
|
||||
假设你有一个像 [更大的应用](./bigger-applications.md){.internal-link target=_blank} 中所描述的文件结构:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ └── main.py
|
||||
```
|
||||
|
||||
在 `main.py` 文件中你有一个 **FastAPI** app:
|
||||
|
||||
|
||||
```Python
|
||||
{!../../../docs_src/app_testing/main.py!}
|
||||
```
|
||||
|
||||
### 测试文件
|
||||
|
||||
然后你会有一个包含测试的文件 `test_main.py` 。app可以像Python包那样存在(一样是目录,但有个 `__init__.py` 文件):
|
||||
|
||||
``` hl_lines="5"
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ └── test_main.py
|
||||
```
|
||||
|
||||
因为这文件在同一个包中,所以你可以通过相对导入从 `main` 模块(`main.py`)导入`app`对象:
|
||||
|
||||
```Python hl_lines="3"
|
||||
{!../../../docs_src/app_testing/test_main.py!}
|
||||
```
|
||||
|
||||
...然后测试代码和之前一样的。
|
||||
|
||||
## 测试:扩展示例
|
||||
|
||||
现在让我们扩展这个例子,并添加更多细节,看下如何测试不同部分。
|
||||
|
||||
### 扩展后的 **FastAPI** app 文件
|
||||
|
||||
让我们继续之前的文件结构:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ └── test_main.py
|
||||
```
|
||||
|
||||
假设现在包含**FastAPI** app的文件 `main.py` 有些其他**路径操作**。
|
||||
|
||||
有个 `GET` 操作会返回错误。
|
||||
|
||||
有个 `POST` 操作会返回一些错误。
|
||||
|
||||
所有*路径操作* 都需要一个`X-Token` 头。
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python
|
||||
{!> ../../../docs_src/app_testing/app_b_an_py310/main.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python
|
||||
{!> ../../../docs_src/app_testing/app_b_an_py39/main.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python
|
||||
{!> ../../../docs_src/app_testing/app_b_an/main.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.10+ non-Annotated"
|
||||
|
||||
!!! tip
|
||||
Prefer to use the `Annotated` version if possible.
|
||||
|
||||
```Python
|
||||
{!> ../../../docs_src/app_testing/app_b_py310/main.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+ non-Annotated"
|
||||
|
||||
!!! tip
|
||||
Prefer to use the `Annotated` version if possible.
|
||||
|
||||
```Python
|
||||
{!> ../../../docs_src/app_testing/app_b/main.py!}
|
||||
```
|
||||
|
||||
### 扩展后的测试文件
|
||||
|
||||
然后您可以使用扩展后的测试更新`test_main.py`:
|
||||
|
||||
```Python
|
||||
{!> ../../../docs_src/app_testing/app_b/test_main.py!}
|
||||
```
|
||||
|
||||
每当你需要客户端在请求中传递信息,但你不知道如何传递时,你可以通过搜索(谷歌)如何用 `httpx`做,或者是用 `requests` 做,毕竟HTTPX的设计是基于Requests的设计的。
|
||||
|
||||
接着只需在测试中同样操作。
|
||||
|
||||
示例:
|
||||
|
||||
* 传一个*路径* 或*查询* 参数,添加到URL上。
|
||||
* 传一个JSON体,传一个Python对象(例如一个`dict`)到参数 `json`。
|
||||
* 如果你需要发送 *Form Data* 而不是 JSON,使用 `data` 参数。
|
||||
* 要发送 *headers*,传 `dict` 给 `headers` 参数。
|
||||
* 对于 *cookies*,传 `dict` 给 `cookies` 参数。
|
||||
|
||||
关于如何传数据给后端的更多信息 (使用`httpx` 或 `TestClient`),请查阅 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX 文档</a>.
|
||||
|
||||
!!! 信息
|
||||
注意 `TestClient` 接收可以被转化为JSON的数据,而不是Pydantic模型。
|
||||
|
||||
如果你在测试中有一个Pydantic模型,并且你想在测试时发送它的数据给应用,你可以使用在[JSON Compatible Encoder](encoder.md){.internal-link target=_blank}介绍的`jsonable_encoder` 。
|
||||
|
||||
## 运行起来
|
||||
|
||||
之后,你只需要安装 `pytest`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install pytest
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
他会自动检测文件和测试,执行测试,然后向你报告结果。
|
||||
|
||||
执行测试:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pytest
|
||||
|
||||
================ test session starts ================
|
||||
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
|
||||
rootdir: /home/user/code/superawesome-cli/app
|
||||
plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1
|
||||
collected 6 items
|
||||
|
||||
---> 100%
|
||||
|
||||
test_main.py <span style="color: green; white-space: pre;">...... [100%]</span>
|
||||
|
||||
<span style="color: green;">================= 1 passed in 0.03s =================</span>
|
||||
```
|
||||
|
||||
</div>
|
||||
@@ -109,6 +109,7 @@ nav:
|
||||
- tutorial/bigger-applications.md
|
||||
- tutorial/metadata.md
|
||||
- tutorial/static-files.md
|
||||
- tutorial/testing.md
|
||||
- tutorial/debugging.md
|
||||
- 高级用户指南:
|
||||
- advanced/index.md
|
||||
@@ -117,7 +118,13 @@ nav:
|
||||
- advanced/response-directly.md
|
||||
- advanced/custom-response.md
|
||||
- advanced/response-cookies.md
|
||||
- advanced/response-change-status-code.md
|
||||
- advanced/settings.md
|
||||
- advanced/response-headers.md
|
||||
- advanced/websockets.md
|
||||
- advanced/wsgi.md
|
||||
- 高级安全:
|
||||
- advanced/security/index.md
|
||||
- contributing.md
|
||||
- help-fastapi.md
|
||||
- benchmarks.md
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.pool import StaticPool
|
||||
|
||||
from ..database import Base
|
||||
from ..main import app, get_db
|
||||
|
||||
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
|
||||
SQLALCHEMY_DATABASE_URL = "sqlite://"
|
||||
|
||||
engine = create_engine(
|
||||
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
|
||||
SQLALCHEMY_DATABASE_URL,
|
||||
connect_args={"check_same_thread": False},
|
||||
poolclass=StaticPool,
|
||||
)
|
||||
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.96.0"
|
||||
__version__ = "0.98.0"
|
||||
|
||||
from starlette import status as status
|
||||
|
||||
|
||||
@@ -19,8 +19,9 @@ from fastapi.encoders import DictIntStrAny, SetIntStr
|
||||
from fastapi.exception_handlers import (
|
||||
http_exception_handler,
|
||||
request_validation_exception_handler,
|
||||
websocket_request_validation_exception_handler,
|
||||
)
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError
|
||||
from fastapi.logger import logger
|
||||
from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware
|
||||
from fastapi.openapi.docs import (
|
||||
@@ -61,6 +62,7 @@ class FastAPI(Starlette):
|
||||
servers: Optional[List[Dict[str, Union[str, Any]]]] = None,
|
||||
dependencies: Optional[Sequence[Depends]] = None,
|
||||
default_response_class: Type[Response] = Default(JSONResponse),
|
||||
redirect_slashes: bool = True,
|
||||
docs_url: Optional[str] = "/docs",
|
||||
redoc_url: Optional[str] = "/redoc",
|
||||
swagger_ui_oauth2_redirect_url: Optional[str] = "/docs/oauth2-redirect",
|
||||
@@ -126,6 +128,7 @@ class FastAPI(Starlette):
|
||||
self.dependency_overrides: Dict[Callable[..., Any], Callable[..., Any]] = {}
|
||||
self.router: routing.APIRouter = routing.APIRouter(
|
||||
routes=routes,
|
||||
redirect_slashes=redirect_slashes,
|
||||
dependency_overrides_provider=self,
|
||||
on_startup=on_startup,
|
||||
on_shutdown=on_shutdown,
|
||||
@@ -145,6 +148,11 @@ class FastAPI(Starlette):
|
||||
self.exception_handlers.setdefault(
|
||||
RequestValidationError, request_validation_exception_handler
|
||||
)
|
||||
self.exception_handlers.setdefault(
|
||||
WebSocketRequestValidationError,
|
||||
# Starlette still has incorrect type specification for the handlers
|
||||
websocket_request_validation_exception_handler, # type: ignore
|
||||
)
|
||||
|
||||
self.user_middleware: List[Middleware] = (
|
||||
[] if middleware is None else list(middleware)
|
||||
@@ -395,15 +403,34 @@ class FastAPI(Starlette):
|
||||
return decorator
|
||||
|
||||
def add_api_websocket_route(
|
||||
self, path: str, endpoint: Callable[..., Any], name: Optional[str] = None
|
||||
self,
|
||||
path: str,
|
||||
endpoint: Callable[..., Any],
|
||||
name: Optional[str] = None,
|
||||
*,
|
||||
dependencies: Optional[Sequence[Depends]] = None,
|
||||
) -> None:
|
||||
self.router.add_api_websocket_route(path, endpoint, name=name)
|
||||
self.router.add_api_websocket_route(
|
||||
path,
|
||||
endpoint,
|
||||
name=name,
|
||||
dependencies=dependencies,
|
||||
)
|
||||
|
||||
def websocket(
|
||||
self, path: str, name: Optional[str] = None
|
||||
self,
|
||||
path: str,
|
||||
name: Optional[str] = None,
|
||||
*,
|
||||
dependencies: Optional[Sequence[Depends]] = None,
|
||||
) -> Callable[[DecoratedCallable], DecoratedCallable]:
|
||||
def decorator(func: DecoratedCallable) -> DecoratedCallable:
|
||||
self.add_api_websocket_route(path, func, name=name)
|
||||
self.add_api_websocket_route(
|
||||
path,
|
||||
func,
|
||||
name=name,
|
||||
dependencies=dependencies,
|
||||
)
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError
|
||||
from fastapi.utils import is_body_allowed_for_status_code
|
||||
from fastapi.websockets import WebSocket
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import JSONResponse, Response
|
||||
from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY
|
||||
from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY, WS_1008_POLICY_VIOLATION
|
||||
|
||||
|
||||
async def http_exception_handler(request: Request, exc: HTTPException) -> Response:
|
||||
@@ -23,3 +24,11 @@ async def request_validation_exception_handler(
|
||||
status_code=HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
content={"detail": jsonable_encoder(exc.errors())},
|
||||
)
|
||||
|
||||
|
||||
async def websocket_request_validation_exception_handler(
|
||||
websocket: WebSocket, exc: WebSocketRequestValidationError
|
||||
) -> None:
|
||||
await websocket.close(
|
||||
code=WS_1008_POLICY_VIOLATION, reason=jsonable_encoder(exc.errors())
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ class HTTPException(StarletteHTTPException):
|
||||
self,
|
||||
status_code: int,
|
||||
detail: Any = None,
|
||||
headers: Optional[Dict[str, Any]] = None,
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
) -> None:
|
||||
super().__init__(status_code=status_code, detail=detail, headers=headers)
|
||||
|
||||
|
||||
@@ -10,19 +10,16 @@ class AsyncExitStackMiddleware:
|
||||
self.context_name = context_name
|
||||
|
||||
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
||||
if AsyncExitStack:
|
||||
dependency_exception: Optional[Exception] = None
|
||||
async with AsyncExitStack() as stack:
|
||||
scope[self.context_name] = stack
|
||||
try:
|
||||
await self.app(scope, receive, send)
|
||||
except Exception as e:
|
||||
dependency_exception = e
|
||||
raise e
|
||||
if dependency_exception:
|
||||
# This exception was possibly handled by the dependency but it should
|
||||
# still bubble up so that the ServerErrorMiddleware can return a 500
|
||||
# or the ExceptionMiddleware can catch and handle any other exceptions
|
||||
raise dependency_exception
|
||||
else:
|
||||
await self.app(scope, receive, send) # pragma: no cover
|
||||
dependency_exception: Optional[Exception] = None
|
||||
async with AsyncExitStack() as stack:
|
||||
scope[self.context_name] = stack
|
||||
try:
|
||||
await self.app(scope, receive, send)
|
||||
except Exception as e:
|
||||
dependency_exception = e
|
||||
raise e
|
||||
if dependency_exception:
|
||||
# This exception was possibly handled by the dependency but it should
|
||||
# still bubble up so that the ServerErrorMiddleware can return a 500
|
||||
# or the ExceptionMiddleware can catch and handle any other exceptions
|
||||
raise dependency_exception
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Any, Callable, Dict, Iterable, List, Optional, Union
|
||||
|
||||
from fastapi.logger import logger
|
||||
from pydantic import AnyUrl, BaseModel, Field
|
||||
from typing_extensions import Literal
|
||||
|
||||
try:
|
||||
import email_validator # type: ignore
|
||||
@@ -108,14 +109,14 @@ class Schema(BaseModel):
|
||||
exclusiveMaximum: Optional[float] = None
|
||||
minimum: Optional[float] = None
|
||||
exclusiveMinimum: Optional[float] = None
|
||||
maxLength: Optional[int] = Field(default=None, gte=0)
|
||||
minLength: Optional[int] = Field(default=None, gte=0)
|
||||
maxLength: Optional[int] = Field(default=None, ge=0)
|
||||
minLength: Optional[int] = Field(default=None, ge=0)
|
||||
pattern: Optional[str] = None
|
||||
maxItems: Optional[int] = Field(default=None, gte=0)
|
||||
minItems: Optional[int] = Field(default=None, gte=0)
|
||||
maxItems: Optional[int] = Field(default=None, ge=0)
|
||||
minItems: Optional[int] = Field(default=None, ge=0)
|
||||
uniqueItems: Optional[bool] = None
|
||||
maxProperties: Optional[int] = Field(default=None, gte=0)
|
||||
minProperties: Optional[int] = Field(default=None, gte=0)
|
||||
maxProperties: Optional[int] = Field(default=None, ge=0)
|
||||
minProperties: Optional[int] = Field(default=None, ge=0)
|
||||
required: Optional[List[str]] = None
|
||||
enum: Optional[List[Any]] = None
|
||||
type: Optional[str] = None
|
||||
@@ -298,18 +299,18 @@ class APIKeyIn(Enum):
|
||||
|
||||
|
||||
class APIKey(SecurityBase):
|
||||
type_ = Field(SecuritySchemeType.apiKey, alias="type")
|
||||
type_: SecuritySchemeType = Field(default=SecuritySchemeType.apiKey, alias="type")
|
||||
in_: APIKeyIn = Field(alias="in")
|
||||
name: str
|
||||
|
||||
|
||||
class HTTPBase(SecurityBase):
|
||||
type_ = Field(SecuritySchemeType.http, alias="type")
|
||||
type_: SecuritySchemeType = Field(default=SecuritySchemeType.http, alias="type")
|
||||
scheme: str
|
||||
|
||||
|
||||
class HTTPBearer(HTTPBase):
|
||||
scheme = "bearer"
|
||||
scheme: Literal["bearer"] = "bearer"
|
||||
bearerFormat: Optional[str] = None
|
||||
|
||||
|
||||
@@ -349,12 +350,14 @@ class OAuthFlows(BaseModel):
|
||||
|
||||
|
||||
class OAuth2(SecurityBase):
|
||||
type_ = Field(SecuritySchemeType.oauth2, alias="type")
|
||||
type_: SecuritySchemeType = Field(default=SecuritySchemeType.oauth2, alias="type")
|
||||
flows: OAuthFlows
|
||||
|
||||
|
||||
class OpenIdConnect(SecurityBase):
|
||||
type_ = Field(SecuritySchemeType.openIdConnect, alias="type")
|
||||
type_: SecuritySchemeType = Field(
|
||||
default=SecuritySchemeType.openIdConnect, alias="type"
|
||||
)
|
||||
openIdConnectUrl: str
|
||||
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ def get_openapi_operation_metadata(
|
||||
file_name = getattr(route.endpoint, "__globals__", {}).get("__file__")
|
||||
if file_name:
|
||||
message += f" at {file_name}"
|
||||
warnings.warn(message)
|
||||
warnings.warn(message, stacklevel=1)
|
||||
operation_ids.add(operation_id)
|
||||
operation["operationId"] = operation_id
|
||||
if route.deprecated:
|
||||
@@ -332,10 +332,8 @@ def get_openapi_path(
|
||||
openapi_response["description"] = description
|
||||
http422 = str(HTTP_422_UNPROCESSABLE_ENTITY)
|
||||
if (all_route_params or route.body_field) and not any(
|
||||
[
|
||||
status in operation["responses"]
|
||||
for status in [http422, "4XX", "default"]
|
||||
]
|
||||
status in operation["responses"]
|
||||
for status in [http422, "4XX", "default"]
|
||||
):
|
||||
operation["responses"][http422] = {
|
||||
"description": "Validation Error",
|
||||
|
||||
@@ -27,8 +27,6 @@ class UJSONResponse(JSONResponse):
|
||||
|
||||
|
||||
class ORJSONResponse(JSONResponse):
|
||||
media_type = "application/json"
|
||||
|
||||
def render(self, content: Any) -> bytes:
|
||||
assert orjson is not None, "orjson must be installed to use ORJSONResponse"
|
||||
return orjson.dumps(
|
||||
|
||||
@@ -30,7 +30,11 @@ from fastapi.dependencies.utils import (
|
||||
solve_dependencies,
|
||||
)
|
||||
from fastapi.encoders import DictIntStrAny, SetIntStr, jsonable_encoder
|
||||
from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError
|
||||
from fastapi.exceptions import (
|
||||
FastAPIError,
|
||||
RequestValidationError,
|
||||
WebSocketRequestValidationError,
|
||||
)
|
||||
from fastapi.types import DecoratedCallable
|
||||
from fastapi.utils import (
|
||||
create_cloned_field,
|
||||
@@ -48,15 +52,15 @@ from starlette.concurrency import run_in_threadpool
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import JSONResponse, Response
|
||||
from starlette.routing import BaseRoute, Match
|
||||
from starlette.routing import Mount as Mount # noqa
|
||||
from starlette.routing import (
|
||||
BaseRoute,
|
||||
Match,
|
||||
compile_path,
|
||||
get_name,
|
||||
request_response,
|
||||
websocket_session,
|
||||
)
|
||||
from starlette.status import WS_1008_POLICY_VIOLATION
|
||||
from starlette.routing import Mount as Mount # noqa
|
||||
from starlette.types import ASGIApp, Lifespan, Scope
|
||||
from starlette.websockets import WebSocket
|
||||
|
||||
@@ -283,7 +287,6 @@ def get_websocket_app(
|
||||
)
|
||||
values, errors, _, _2, _3 = solved_result
|
||||
if errors:
|
||||
await websocket.close(code=WS_1008_POLICY_VIOLATION)
|
||||
raise WebSocketRequestValidationError(errors)
|
||||
assert dependant.call is not None, "dependant.call must be a function"
|
||||
await dependant.call(**values)
|
||||
@@ -298,13 +301,21 @@ class APIWebSocketRoute(routing.WebSocketRoute):
|
||||
endpoint: Callable[..., Any],
|
||||
*,
|
||||
name: Optional[str] = None,
|
||||
dependencies: Optional[Sequence[params.Depends]] = None,
|
||||
dependency_overrides_provider: Optional[Any] = None,
|
||||
) -> None:
|
||||
self.path = path
|
||||
self.endpoint = endpoint
|
||||
self.name = get_name(endpoint) if name is None else name
|
||||
self.dependencies = list(dependencies or [])
|
||||
self.path_regex, self.path_format, self.param_convertors = compile_path(path)
|
||||
self.dependant = get_dependant(path=self.path_format, call=self.endpoint)
|
||||
for depends in self.dependencies[::-1]:
|
||||
self.dependant.dependencies.insert(
|
||||
0,
|
||||
get_parameterless_sub_dependant(depends=depends, path=self.path_format),
|
||||
)
|
||||
|
||||
self.app = websocket_session(
|
||||
get_websocket_app(
|
||||
dependant=self.dependant,
|
||||
@@ -418,10 +429,7 @@ class APIRoute(routing.Route):
|
||||
else:
|
||||
self.response_field = None # type: ignore
|
||||
self.secure_cloned_response_field = None
|
||||
if dependencies:
|
||||
self.dependencies = list(dependencies)
|
||||
else:
|
||||
self.dependencies = []
|
||||
self.dependencies = list(dependencies or [])
|
||||
self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
|
||||
# if a "form feed" character (page break) is found in the description text,
|
||||
# truncate description text to the content preceding the first "form feed"
|
||||
@@ -516,7 +524,7 @@ class APIRouter(routing.Router):
|
||||
), "A path prefix must not end with '/', as the routes will start with '/'"
|
||||
self.prefix = prefix
|
||||
self.tags: List[Union[str, Enum]] = tags or []
|
||||
self.dependencies = list(dependencies or []) or []
|
||||
self.dependencies = list(dependencies or [])
|
||||
self.deprecated = deprecated
|
||||
self.include_in_schema = include_in_schema
|
||||
self.responses = responses or {}
|
||||
@@ -690,21 +698,37 @@ class APIRouter(routing.Router):
|
||||
return decorator
|
||||
|
||||
def add_api_websocket_route(
|
||||
self, path: str, endpoint: Callable[..., Any], name: Optional[str] = None
|
||||
self,
|
||||
path: str,
|
||||
endpoint: Callable[..., Any],
|
||||
name: Optional[str] = None,
|
||||
*,
|
||||
dependencies: Optional[Sequence[params.Depends]] = None,
|
||||
) -> None:
|
||||
current_dependencies = self.dependencies.copy()
|
||||
if dependencies:
|
||||
current_dependencies.extend(dependencies)
|
||||
|
||||
route = APIWebSocketRoute(
|
||||
self.prefix + path,
|
||||
endpoint=endpoint,
|
||||
name=name,
|
||||
dependencies=current_dependencies,
|
||||
dependency_overrides_provider=self.dependency_overrides_provider,
|
||||
)
|
||||
self.routes.append(route)
|
||||
|
||||
def websocket(
|
||||
self, path: str, name: Optional[str] = None
|
||||
self,
|
||||
path: str,
|
||||
name: Optional[str] = None,
|
||||
*,
|
||||
dependencies: Optional[Sequence[params.Depends]] = None,
|
||||
) -> Callable[[DecoratedCallable], DecoratedCallable]:
|
||||
def decorator(func: DecoratedCallable) -> DecoratedCallable:
|
||||
self.add_api_websocket_route(path, func, name=name)
|
||||
self.add_api_websocket_route(
|
||||
path, func, name=name, dependencies=dependencies
|
||||
)
|
||||
return func
|
||||
|
||||
return decorator
|
||||
@@ -744,7 +768,7 @@ class APIRouter(routing.Router):
|
||||
path = getattr(r, "path") # noqa: B009
|
||||
name = getattr(r, "name", "unknown")
|
||||
if path is not None and not path:
|
||||
raise Exception(
|
||||
raise FastAPIError(
|
||||
f"Prefix and path cannot be both empty (path operation: {name})"
|
||||
)
|
||||
if responses is None:
|
||||
@@ -819,8 +843,16 @@ class APIRouter(routing.Router):
|
||||
name=route.name,
|
||||
)
|
||||
elif isinstance(route, APIWebSocketRoute):
|
||||
current_dependencies = []
|
||||
if dependencies:
|
||||
current_dependencies.extend(dependencies)
|
||||
if route.dependencies:
|
||||
current_dependencies.extend(route.dependencies)
|
||||
self.add_api_websocket_route(
|
||||
prefix + route.path, route.endpoint, name=route.name
|
||||
prefix + route.path,
|
||||
route.endpoint,
|
||||
dependencies=current_dependencies,
|
||||
name=route.name,
|
||||
)
|
||||
elif isinstance(route, routing.WebSocketRoute):
|
||||
self.add_websocket_route(
|
||||
|
||||
@@ -21,7 +21,9 @@ class APIKeyQuery(APIKeyBase):
|
||||
auto_error: bool = True,
|
||||
):
|
||||
self.model: APIKey = APIKey(
|
||||
**{"in": APIKeyIn.query}, name=name, description=description
|
||||
**{"in": APIKeyIn.query}, # type: ignore[arg-type]
|
||||
name=name,
|
||||
description=description,
|
||||
)
|
||||
self.scheme_name = scheme_name or self.__class__.__name__
|
||||
self.auto_error = auto_error
|
||||
@@ -48,7 +50,9 @@ class APIKeyHeader(APIKeyBase):
|
||||
auto_error: bool = True,
|
||||
):
|
||||
self.model: APIKey = APIKey(
|
||||
**{"in": APIKeyIn.header}, name=name, description=description
|
||||
**{"in": APIKeyIn.header}, # type: ignore[arg-type]
|
||||
name=name,
|
||||
description=description,
|
||||
)
|
||||
self.scheme_name = scheme_name or self.__class__.__name__
|
||||
self.auto_error = auto_error
|
||||
@@ -75,7 +79,9 @@ class APIKeyCookie(APIKeyBase):
|
||||
auto_error: bool = True,
|
||||
):
|
||||
self.model: APIKey = APIKey(
|
||||
**{"in": APIKeyIn.cookie}, name=name, description=description
|
||||
**{"in": APIKeyIn.cookie}, # type: ignore[arg-type]
|
||||
name=name,
|
||||
description=description,
|
||||
)
|
||||
self.scheme_name = scheme_name or self.__class__.__name__
|
||||
self.auto_error = auto_error
|
||||
|
||||
@@ -73,11 +73,6 @@ class HTTPBasic(HTTPBase):
|
||||
unauthorized_headers = {"WWW-Authenticate": f'Basic realm="{self.realm}"'}
|
||||
else:
|
||||
unauthorized_headers = {"WWW-Authenticate": "Basic"}
|
||||
invalid_user_credentials_exc = HTTPException(
|
||||
status_code=HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid authentication credentials",
|
||||
headers=unauthorized_headers,
|
||||
)
|
||||
if not authorization or scheme.lower() != "basic":
|
||||
if self.auto_error:
|
||||
raise HTTPException(
|
||||
@@ -87,6 +82,11 @@ class HTTPBasic(HTTPBase):
|
||||
)
|
||||
else:
|
||||
return None
|
||||
invalid_user_credentials_exc = HTTPException(
|
||||
status_code=HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid authentication credentials",
|
||||
headers=unauthorized_headers,
|
||||
)
|
||||
try:
|
||||
data = b64decode(param).decode("ascii")
|
||||
except (ValueError, UnicodeDecodeError, binascii.Error):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from typing import Any, Dict, List, Optional, Union, cast
|
||||
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi.openapi.models import OAuth2 as OAuth2Model
|
||||
@@ -121,7 +121,9 @@ class OAuth2(SecurityBase):
|
||||
description: Optional[str] = None,
|
||||
auto_error: bool = True,
|
||||
):
|
||||
self.model = OAuth2Model(flows=flows, description=description)
|
||||
self.model = OAuth2Model(
|
||||
flows=cast(OAuthFlowsModel, flows), description=description
|
||||
)
|
||||
self.scheme_name = scheme_name or self.__class__.__name__
|
||||
self.auto_error = auto_error
|
||||
|
||||
@@ -148,7 +150,9 @@ class OAuth2PasswordBearer(OAuth2):
|
||||
):
|
||||
if not scopes:
|
||||
scopes = {}
|
||||
flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes})
|
||||
flows = OAuthFlowsModel(
|
||||
password=cast(Any, {"tokenUrl": tokenUrl, "scopes": scopes})
|
||||
)
|
||||
super().__init__(
|
||||
flows=flows,
|
||||
scheme_name=scheme_name,
|
||||
@@ -185,12 +189,15 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
|
||||
if not scopes:
|
||||
scopes = {}
|
||||
flows = OAuthFlowsModel(
|
||||
authorizationCode={
|
||||
"authorizationUrl": authorizationUrl,
|
||||
"tokenUrl": tokenUrl,
|
||||
"refreshUrl": refreshUrl,
|
||||
"scopes": scopes,
|
||||
}
|
||||
authorizationCode=cast(
|
||||
Any,
|
||||
{
|
||||
"authorizationUrl": authorizationUrl,
|
||||
"tokenUrl": tokenUrl,
|
||||
"refreshUrl": refreshUrl,
|
||||
"scopes": scopes,
|
||||
},
|
||||
)
|
||||
)
|
||||
super().__init__(
|
||||
flows=flows,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
requires = ["hatchling >= 1.13.0"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
@@ -42,56 +42,16 @@ classifiers = [
|
||||
]
|
||||
dependencies = [
|
||||
"starlette>=0.27.0,<0.28.0",
|
||||
"pydantic >=1.6.2,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0",
|
||||
"pydantic>=1.7.4,!=1.8,!=1.8.1,<2.0.0",
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/tiangolo/fastapi"
|
||||
Documentation = "https://fastapi.tiangolo.com/"
|
||||
Repository = "https://github.com/tiangolo/fastapi"
|
||||
|
||||
[project.optional-dependencies]
|
||||
test = [
|
||||
"pytest >=7.1.3,<8.0.0",
|
||||
"coverage[toml] >= 6.5.0,< 8.0",
|
||||
"mypy ==0.982",
|
||||
"ruff ==0.0.138",
|
||||
"black == 23.1.0",
|
||||
"isort >=5.0.6,<6.0.0",
|
||||
"httpx >=0.23.0,<0.24.0",
|
||||
"email_validator >=1.1.1,<2.0.0",
|
||||
# TODO: once removing databases from tutorial, upgrade SQLAlchemy
|
||||
# probably when including SQLModel
|
||||
"sqlalchemy >=1.3.18,<1.4.43",
|
||||
"peewee >=3.13.3,<4.0.0",
|
||||
"databases[sqlite] >=0.3.2,<0.7.0",
|
||||
"orjson >=3.2.1,<4.0.0",
|
||||
"ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0",
|
||||
"python-multipart >=0.0.5,<0.0.7",
|
||||
"flask >=1.1.2,<3.0.0",
|
||||
"anyio[trio] >=3.2.1,<4.0.0",
|
||||
"python-jose[cryptography] >=3.3.0,<4.0.0",
|
||||
"pyyaml >=5.3.1,<7.0.0",
|
||||
"passlib[bcrypt] >=1.7.2,<2.0.0",
|
||||
|
||||
# types
|
||||
"types-ujson ==5.7.0.1",
|
||||
"types-orjson ==3.6.2",
|
||||
]
|
||||
doc = [
|
||||
"mkdocs >=1.1.2,<2.0.0",
|
||||
"mkdocs-material >=8.1.4,<9.0.0",
|
||||
"mdx-include >=1.4.1,<2.0.0",
|
||||
"mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0",
|
||||
"typer-cli >=0.0.13,<0.0.14",
|
||||
"typer[all] >=0.6.1,<0.8.0",
|
||||
"pyyaml >=5.3.1,<7.0.0",
|
||||
]
|
||||
dev = [
|
||||
"ruff ==0.0.138",
|
||||
"uvicorn[standard] >=0.12.0,<0.21.0",
|
||||
"pre-commit >=2.17.0,<3.0.0",
|
||||
]
|
||||
all = [
|
||||
"httpx >=0.23.0",
|
||||
"jinja2 >=2.11.2",
|
||||
@@ -107,10 +67,6 @@ all = [
|
||||
[tool.hatch.version]
|
||||
path = "fastapi/__init__.py"
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
known_third_party = ["fastapi", "pydantic", "starlette"]
|
||||
|
||||
[tool.mypy]
|
||||
strict = true
|
||||
|
||||
@@ -166,7 +122,7 @@ select = [
|
||||
"E", # pycodestyle errors
|
||||
"W", # pycodestyle warnings
|
||||
"F", # pyflakes
|
||||
# "I", # isort
|
||||
"I", # isort
|
||||
"C", # flake8-comprehensions
|
||||
"B", # flake8-bugbear
|
||||
]
|
||||
|
||||
8
requirements-docs.txt
Normal file
8
requirements-docs.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
-e .
|
||||
mkdocs >=1.1.2,<2.0.0
|
||||
mkdocs-material >=8.1.4,<9.0.0
|
||||
mdx-include >=1.4.1,<2.0.0
|
||||
mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0
|
||||
typer-cli >=0.0.13,<0.0.14
|
||||
typer[all] >=0.6.1,<0.8.0
|
||||
pyyaml >=5.3.1,<7.0.0
|
||||
25
requirements-tests.txt
Normal file
25
requirements-tests.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
-e .
|
||||
pytest >=7.1.3,<8.0.0
|
||||
coverage[toml] >= 6.5.0,< 8.0
|
||||
mypy ==1.4.0
|
||||
ruff ==0.0.275
|
||||
black == 23.3.0
|
||||
httpx >=0.23.0,<0.24.0
|
||||
email_validator >=1.1.1,<2.0.0
|
||||
# TODO: once removing databases from tutorial, upgrade SQLAlchemy
|
||||
# probably when including SQLModel
|
||||
sqlalchemy >=1.3.18,<1.4.43
|
||||
peewee >=3.13.3,<4.0.0
|
||||
databases[sqlite] >=0.3.2,<0.7.0
|
||||
orjson >=3.2.1,<4.0.0
|
||||
ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0
|
||||
python-multipart >=0.0.5,<0.0.7
|
||||
flask >=1.1.2,<3.0.0
|
||||
anyio[trio] >=3.2.1,<4.0.0
|
||||
python-jose[cryptography] >=3.3.0,<4.0.0
|
||||
pyyaml >=5.3.1,<7.0.0
|
||||
passlib[bcrypt] >=1.7.2,<2.0.0
|
||||
|
||||
# types
|
||||
types-ujson ==5.7.0.1
|
||||
types-orjson ==3.6.2
|
||||
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
-e .[all]
|
||||
-r requirements-tests.txt
|
||||
-r requirements-docs.txt
|
||||
uvicorn[standard] >=0.12.0,<0.23.0
|
||||
pre-commit >=2.17.0,<4.0.0
|
||||
@@ -3,4 +3,6 @@
|
||||
set -e
|
||||
set -x
|
||||
|
||||
# Check README.md is up to date
|
||||
python ./scripts/docs.py verify-readme
|
||||
python ./scripts/docs.py build-all
|
||||
|
||||
@@ -3,4 +3,3 @@ set -x
|
||||
|
||||
ruff fastapi tests docs_src scripts --fix
|
||||
black fastapi tests docs_src scripts
|
||||
isort fastapi tests docs_src scripts
|
||||
|
||||
@@ -6,4 +6,3 @@ set -x
|
||||
mypy fastapi
|
||||
ruff fastapi tests docs_src scripts
|
||||
black fastapi tests --check
|
||||
isort fastapi tests docs_src scripts --check-only
|
||||
|
||||
@@ -3,7 +3,5 @@
|
||||
set -e
|
||||
set -x
|
||||
|
||||
# Check README.md is up to date
|
||||
python ./scripts/docs.py verify-readme
|
||||
export PYTHONPATH=./docs_src
|
||||
coverage run -m pytest tests ${@}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
from fastapi import APIRouter, FastAPI
|
||||
from fastapi.exceptions import FastAPIError
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
app = FastAPI()
|
||||
@@ -31,5 +32,5 @@ def test_use_empty():
|
||||
|
||||
def test_include_empty():
|
||||
# if both include and router.path are empty - it should raise exception
|
||||
with pytest.raises(Exception):
|
||||
with pytest.raises(FastAPIError):
|
||||
app.include_router(router)
|
||||
|
||||
@@ -33,7 +33,7 @@ async def hidden_query(
|
||||
return {"hidden_query": hidden_query}
|
||||
|
||||
|
||||
openapi_shema = {
|
||||
openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
@@ -162,7 +162,7 @@ def test_openapi_schema():
|
||||
client = TestClient(app)
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == openapi_shema
|
||||
assert response.json() == openapi_schema
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
40
tests/test_router_redirect_slashes.py
Normal file
40
tests/test_router_redirect_slashes.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from fastapi import APIRouter, FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
def test_redirect_slashes_enabled():
|
||||
app = FastAPI()
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/hello/")
|
||||
def hello_page() -> str:
|
||||
return "Hello, World!"
|
||||
|
||||
app.include_router(router)
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get("/hello/", follow_redirects=False)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = client.get("/hello", follow_redirects=False)
|
||||
assert response.status_code == 307
|
||||
|
||||
|
||||
def test_redirect_slashes_disabled():
|
||||
app = FastAPI(redirect_slashes=False)
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/hello/")
|
||||
def hello_page() -> str:
|
||||
return "Hello, World!"
|
||||
|
||||
app.include_router(router)
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get("/hello/", follow_redirects=False)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = client.get("/hello", follow_redirects=False)
|
||||
assert response.status_code == 404
|
||||
@@ -42,7 +42,7 @@ def examples(
|
||||
@app.post("/example_examples/")
|
||||
def example_examples(
|
||||
item: Item = Body(
|
||||
example={"data": "Overriden example"},
|
||||
example={"data": "Overridden example"},
|
||||
examples={
|
||||
"example1": {"value": {"data": "examples example_examples 1"}},
|
||||
"example2": {"value": {"data": "examples example_examples 2"}},
|
||||
@@ -76,7 +76,7 @@ def example_examples(
|
||||
# def form_example_examples(
|
||||
# lastname: str = Form(
|
||||
# ...,
|
||||
# example="Doe overriden",
|
||||
# example="Doe overridden",
|
||||
# examples={
|
||||
# "example1": {"summary": "last name summary", "value": "Doe"},
|
||||
# "example2": {"value": "Doesn't"},
|
||||
@@ -110,7 +110,7 @@ def path_examples(
|
||||
@app.get("/path_example_examples/{item_id}")
|
||||
def path_example_examples(
|
||||
item_id: str = Path(
|
||||
example="item_overriden",
|
||||
example="item_overridden",
|
||||
examples={
|
||||
"example1": {"summary": "item ID summary", "value": "item_1"},
|
||||
"example2": {"value": "item_2"},
|
||||
@@ -147,7 +147,7 @@ def query_examples(
|
||||
def query_example_examples(
|
||||
data: Union[str, None] = Query(
|
||||
default=None,
|
||||
example="query_overriden",
|
||||
example="query_overridden",
|
||||
examples={
|
||||
"example1": {"summary": "Query example 1", "value": "query1"},
|
||||
"example2": {"value": "query2"},
|
||||
@@ -184,7 +184,7 @@ def header_examples(
|
||||
def header_example_examples(
|
||||
data: Union[str, None] = Header(
|
||||
default=None,
|
||||
example="header_overriden",
|
||||
example="header_overridden",
|
||||
examples={
|
||||
"example1": {"summary": "Query example 1", "value": "header1"},
|
||||
"example2": {"value": "header2"},
|
||||
@@ -221,7 +221,7 @@ def cookie_examples(
|
||||
def cookie_example_examples(
|
||||
data: Union[str, None] = Cookie(
|
||||
default=None,
|
||||
example="cookie_overriden",
|
||||
example="cookie_overridden",
|
||||
examples={
|
||||
"example1": {"summary": "Query example 1", "value": "cookie1"},
|
||||
"example2": {"value": "cookie2"},
|
||||
|
||||
73
tests/test_ws_dependencies.py
Normal file
73
tests/test_ws_dependencies.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import json
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter, Depends, FastAPI, WebSocket
|
||||
from fastapi.testclient import TestClient
|
||||
from typing_extensions import Annotated
|
||||
|
||||
|
||||
def dependency_list() -> List[str]:
|
||||
return []
|
||||
|
||||
|
||||
DepList = Annotated[List[str], Depends(dependency_list)]
|
||||
|
||||
|
||||
def create_dependency(name: str):
|
||||
def fun(deps: DepList):
|
||||
deps.append(name)
|
||||
|
||||
return Depends(fun)
|
||||
|
||||
|
||||
router = APIRouter(dependencies=[create_dependency("router")])
|
||||
prefix_router = APIRouter(dependencies=[create_dependency("prefix_router")])
|
||||
app = FastAPI(dependencies=[create_dependency("app")])
|
||||
|
||||
|
||||
@app.websocket("/", dependencies=[create_dependency("index")])
|
||||
async def index(websocket: WebSocket, deps: DepList):
|
||||
await websocket.accept()
|
||||
await websocket.send_text(json.dumps(deps))
|
||||
await websocket.close()
|
||||
|
||||
|
||||
@router.websocket("/router", dependencies=[create_dependency("routerindex")])
|
||||
async def routerindex(websocket: WebSocket, deps: DepList):
|
||||
await websocket.accept()
|
||||
await websocket.send_text(json.dumps(deps))
|
||||
await websocket.close()
|
||||
|
||||
|
||||
@prefix_router.websocket("/", dependencies=[create_dependency("routerprefixindex")])
|
||||
async def routerprefixindex(websocket: WebSocket, deps: DepList):
|
||||
await websocket.accept()
|
||||
await websocket.send_text(json.dumps(deps))
|
||||
await websocket.close()
|
||||
|
||||
|
||||
app.include_router(router, dependencies=[create_dependency("router2")])
|
||||
app.include_router(
|
||||
prefix_router, prefix="/prefix", dependencies=[create_dependency("prefix_router2")]
|
||||
)
|
||||
|
||||
|
||||
def test_index():
|
||||
client = TestClient(app)
|
||||
with client.websocket_connect("/") as websocket:
|
||||
data = json.loads(websocket.receive_text())
|
||||
assert data == ["app", "index"]
|
||||
|
||||
|
||||
def test_routerindex():
|
||||
client = TestClient(app)
|
||||
with client.websocket_connect("/router") as websocket:
|
||||
data = json.loads(websocket.receive_text())
|
||||
assert data == ["app", "router2", "router", "routerindex"]
|
||||
|
||||
|
||||
def test_routerprefixindex():
|
||||
client = TestClient(app)
|
||||
with client.websocket_connect("/prefix/") as websocket:
|
||||
data = json.loads(websocket.receive_text())
|
||||
assert data == ["app", "prefix_router2", "prefix_router", "routerprefixindex"]
|
||||
@@ -1,4 +1,16 @@
|
||||
from fastapi import APIRouter, Depends, FastAPI, WebSocket
|
||||
import functools
|
||||
|
||||
import pytest
|
||||
from fastapi import (
|
||||
APIRouter,
|
||||
Depends,
|
||||
FastAPI,
|
||||
Header,
|
||||
WebSocket,
|
||||
WebSocketDisconnect,
|
||||
status,
|
||||
)
|
||||
from fastapi.middleware import Middleware
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
router = APIRouter()
|
||||
@@ -63,9 +75,44 @@ async def router_native_prefix_ws(websocket: WebSocket):
|
||||
await websocket.close()
|
||||
|
||||
|
||||
app.include_router(router)
|
||||
app.include_router(prefix_router, prefix="/prefix")
|
||||
app.include_router(native_prefix_route)
|
||||
async def ws_dependency_err():
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@router.websocket("/depends-err/")
|
||||
async def router_ws_depends_err(websocket: WebSocket, data=Depends(ws_dependency_err)):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
async def ws_dependency_validate(x_missing: str = Header()):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
@router.websocket("/depends-validate/")
|
||||
async def router_ws_depends_validate(
|
||||
websocket: WebSocket, data=Depends(ws_dependency_validate)
|
||||
):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
class CustomError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@router.websocket("/custom_error/")
|
||||
async def router_ws_custom_error(websocket: WebSocket):
|
||||
raise CustomError()
|
||||
|
||||
|
||||
def make_app(app=None, **kwargs):
|
||||
app = app or FastAPI(**kwargs)
|
||||
app.include_router(router)
|
||||
app.include_router(prefix_router, prefix="/prefix")
|
||||
app.include_router(native_prefix_route)
|
||||
return app
|
||||
|
||||
|
||||
app = make_app(app)
|
||||
|
||||
|
||||
def test_app():
|
||||
@@ -125,3 +172,100 @@ def test_router_with_params():
|
||||
assert data == "path/to/file"
|
||||
data = websocket.receive_text()
|
||||
assert data == "a_query_param"
|
||||
|
||||
|
||||
def test_wrong_uri():
|
||||
"""
|
||||
Verify that a websocket connection to a non-existent endpoing returns in a shutdown
|
||||
"""
|
||||
client = TestClient(app)
|
||||
with pytest.raises(WebSocketDisconnect) as e:
|
||||
with client.websocket_connect("/no-router/"):
|
||||
pass # pragma: no cover
|
||||
assert e.value.code == status.WS_1000_NORMAL_CLOSURE
|
||||
|
||||
|
||||
def websocket_middleware(middleware_func):
|
||||
"""
|
||||
Helper to create a Starlette pure websocket middleware
|
||||
"""
|
||||
|
||||
def middleware_constructor(app):
|
||||
@functools.wraps(app)
|
||||
async def wrapped_app(scope, receive, send):
|
||||
if scope["type"] != "websocket":
|
||||
return await app(scope, receive, send) # pragma: no cover
|
||||
|
||||
async def call_next():
|
||||
return await app(scope, receive, send)
|
||||
|
||||
websocket = WebSocket(scope, receive=receive, send=send)
|
||||
return await middleware_func(websocket, call_next)
|
||||
|
||||
return wrapped_app
|
||||
|
||||
return middleware_constructor
|
||||
|
||||
|
||||
def test_depend_validation():
|
||||
"""
|
||||
Verify that a validation in a dependency invokes the correct exception handler
|
||||
"""
|
||||
caught = []
|
||||
|
||||
@websocket_middleware
|
||||
async def catcher(websocket, call_next):
|
||||
try:
|
||||
return await call_next()
|
||||
except Exception as e: # pragma: no cover
|
||||
caught.append(e)
|
||||
raise
|
||||
|
||||
myapp = make_app(middleware=[Middleware(catcher)])
|
||||
|
||||
client = TestClient(myapp)
|
||||
with pytest.raises(WebSocketDisconnect) as e:
|
||||
with client.websocket_connect("/depends-validate/"):
|
||||
pass # pragma: no cover
|
||||
# the validation error does produce a close message
|
||||
assert e.value.code == status.WS_1008_POLICY_VIOLATION
|
||||
# and no error is leaked
|
||||
assert caught == []
|
||||
|
||||
|
||||
def test_depend_err_middleware():
|
||||
"""
|
||||
Verify that it is possible to write custom WebSocket middleware to catch errors
|
||||
"""
|
||||
|
||||
@websocket_middleware
|
||||
async def errorhandler(websocket: WebSocket, call_next):
|
||||
try:
|
||||
return await call_next()
|
||||
except Exception as e:
|
||||
await websocket.close(code=status.WS_1006_ABNORMAL_CLOSURE, reason=repr(e))
|
||||
|
||||
myapp = make_app(middleware=[Middleware(errorhandler)])
|
||||
client = TestClient(myapp)
|
||||
with pytest.raises(WebSocketDisconnect) as e:
|
||||
with client.websocket_connect("/depends-err/"):
|
||||
pass # pragma: no cover
|
||||
assert e.value.code == status.WS_1006_ABNORMAL_CLOSURE
|
||||
assert "NotImplementedError" in e.value.reason
|
||||
|
||||
|
||||
def test_depend_err_handler():
|
||||
"""
|
||||
Verify that it is possible to write custom WebSocket middleware to catch errors
|
||||
"""
|
||||
|
||||
async def custom_handler(websocket: WebSocket, exc: CustomError) -> None:
|
||||
await websocket.close(1002, "foo")
|
||||
|
||||
myapp = make_app(exception_handlers={CustomError: custom_handler})
|
||||
client = TestClient(myapp)
|
||||
with pytest.raises(WebSocketDisconnect) as e:
|
||||
with client.websocket_connect("/custom_error/"):
|
||||
pass # pragma: no cover
|
||||
assert e.value.code == 1002
|
||||
assert "foo" in e.value.reason
|
||||
|
||||
Reference in New Issue
Block a user