mirror of https://github.com/tiangolo/fastapi.git
Merge branch 'master' into deferred-init
This commit is contained in:
commit
5a6c8b5e3b
|
|
@ -53,7 +53,7 @@ jobs:
|
|||
with:
|
||||
python-version: "3.11"
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@v3
|
||||
uses: astral-sh/setup-uv@v4
|
||||
with:
|
||||
version: "0.4.15"
|
||||
enable-cache: true
|
||||
|
|
@ -95,7 +95,7 @@ jobs:
|
|||
with:
|
||||
python-version: "3.11"
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@v3
|
||||
uses: astral-sh/setup-uv@v4
|
||||
with:
|
||||
version: "0.4.15"
|
||||
enable-cache: true
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ jobs:
|
|||
with:
|
||||
python-version: "3.11"
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@v3
|
||||
uses: astral-sh/setup-uv@v4
|
||||
with:
|
||||
version: "0.4.15"
|
||||
enable-cache: true
|
||||
|
|
@ -64,7 +64,7 @@ jobs:
|
|||
BRANCH: ${{ ( github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'master' && 'main' ) || ( github.event.workflow_run.head_sha ) }}
|
||||
# TODO: Use v3 when it's fixed, probably in v3.11
|
||||
# https://github.com/cloudflare/wrangler-action/issues/307
|
||||
uses: cloudflare/wrangler-action@v3.12
|
||||
uses: cloudflare/wrangler-action@v3.13
|
||||
# uses: cloudflare/wrangler-action@v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ jobs:
|
|||
with:
|
||||
python-version: "3.11"
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@v3
|
||||
uses: astral-sh/setup-uv@v4
|
||||
with:
|
||||
version: "0.4.15"
|
||||
enable-cache: true
|
||||
|
|
|
|||
|
|
@ -30,13 +30,12 @@ jobs:
|
|||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@v3
|
||||
uses: astral-sh/setup-uv@v4
|
||||
with:
|
||||
version: "0.4.15"
|
||||
enable-cache: true
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ jobs:
|
|||
TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }}
|
||||
run: python -m build
|
||||
- name: Publish
|
||||
uses: pypa/gh-action-pypi-publish@v1.12.2
|
||||
uses: pypa/gh-action-pypi-publish@v1.12.3
|
||||
- name: Dump GitHub context
|
||||
env:
|
||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ jobs:
|
|||
with:
|
||||
python-version: '3.9'
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@v3
|
||||
uses: astral-sh/setup-uv@v4
|
||||
with:
|
||||
version: "0.4.15"
|
||||
enable-cache: true
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ jobs:
|
|||
with:
|
||||
python-version: "3.11"
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@v3
|
||||
uses: astral-sh/setup-uv@v4
|
||||
with:
|
||||
version: "0.4.15"
|
||||
enable-cache: true
|
||||
|
|
@ -66,7 +66,7 @@ jobs:
|
|||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@v3
|
||||
uses: astral-sh/setup-uv@v4
|
||||
with:
|
||||
version: "0.4.15"
|
||||
enable-cache: true
|
||||
|
|
@ -107,7 +107,7 @@ jobs:
|
|||
with:
|
||||
python-version: '3.8'
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@v3
|
||||
uses: astral-sh/setup-uv@v4
|
||||
with:
|
||||
version: "0.4.15"
|
||||
enable-cache: true
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ The key features are:
|
|||
<a href="https://databento.com/" target="_blank" title="Pay as you go for market data"><img src="https://fastapi.tiangolo.com/img/sponsors/databento.svg"></a>
|
||||
<a href="https://speakeasy.com?utm_source=fastapi+repo&utm_medium=github+sponsorship" target="_blank" title="SDKs for your API | Speakeasy"><img src="https://fastapi.tiangolo.com/img/sponsors/speakeasy.png"></a>
|
||||
<a href="https://www.svix.com/" target="_blank" title="Svix - Webhooks as a service"><img src="https://fastapi.tiangolo.com/img/sponsors/svix.svg"></a>
|
||||
<a href="https://www.codacy.com/?utm_source=github&utm_medium=sponsors&utm_id=pioneers" target="_blank" title="Take code reviews from hours to minutes"><img src="https://fastapi.tiangolo.com/img/sponsors/codacy.png"></a>
|
||||
<a href="https://www.stainlessapi.com/?utm_source=fastapi&utm_medium=referral" target="_blank" title="Stainless | Generate best-in-class SDKs"><img src="https://fastapi.tiangolo.com/img/sponsors/stainless.png"></a>
|
||||
|
||||
<!-- /sponsors -->
|
||||
|
|
@ -459,7 +458,7 @@ FastAPI depends on Pydantic and Starlette.
|
|||
|
||||
### `standard` Dependencies
|
||||
|
||||
When you install FastAPI with `pip install "fastapi[standard]"` it comes the `standard` group of optional dependencies:
|
||||
When you install FastAPI with `pip install "fastapi[standard]"` it comes with the `standard` group of optional dependencies:
|
||||
|
||||
Used by Pydantic:
|
||||
|
||||
|
|
|
|||
|
|
@ -14,9 +14,6 @@ members:
|
|||
- login: YuriiMotov
|
||||
avatar_url: https://avatars.githubusercontent.com/u/109919500
|
||||
url: https://github.com/YuriiMotov
|
||||
- login: estebanx64
|
||||
avatar_url: https://avatars.githubusercontent.com/u/10840422
|
||||
url: https://github.com/estebanx64
|
||||
- login: patrick91
|
||||
avatar_url: https://avatars.githubusercontent.com/u/667029
|
||||
url: https://github.com/patrick91
|
||||
|
|
|
|||
|
|
@ -45,9 +45,6 @@ silver:
|
|||
- url: https://www.svix.com/
|
||||
title: Svix - Webhooks as a service
|
||||
img: https://fastapi.tiangolo.com/img/sponsors/svix.svg
|
||||
- url: https://www.codacy.com/?utm_source=github&utm_medium=sponsors&utm_id=pioneers
|
||||
title: Take code reviews from hours to minutes
|
||||
img: https://fastapi.tiangolo.com/img/sponsors/codacy.png
|
||||
- url: https://www.stainlessapi.com/?utm_source=fastapi&utm_medium=referral
|
||||
title: Stainless | Generate best-in-class SDKs
|
||||
img: https://fastapi.tiangolo.com/img/sponsors/stainless.png
|
||||
|
|
|
|||
|
|
@ -9,47 +9,39 @@ To run your FastAPI app for development, you can use the `fastapi dev` command:
|
|||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:single">main.py</u>
|
||||
<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font>
|
||||
<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font>
|
||||
<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files
|
||||
<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
|
||||
|
||||
╭─ <font color="#8AE234"><b>Python module file</b></font> ─╮
|
||||
│ │
|
||||
│ 🐍 main.py │
|
||||
│ │
|
||||
╰──────────────────────╯
|
||||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
|
||||
|
||||
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font>
|
||||
<font color="#3465A4">INFO </font> Found importable FastAPI app
|
||||
Searching for package file structure from directories with
|
||||
<font color="#3465A4">__init__.py</font> files
|
||||
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
|
||||
╭─ <font color="#8AE234"><b>Importable FastAPI app</b></font> ─╮
|
||||
│ │
|
||||
│ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> │
|
||||
│ │
|
||||
╰──────────────────────────╯
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
|
||||
|
||||
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with the
|
||||
following code:
|
||||
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">╭────────── FastAPI CLI - Development mode ───────────╮</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ Serving at: http://127.0.0.1:8000 │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ API docs: http://127.0.0.1:8000/docs │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ Running in development mode, for production use: │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ </font></span><span style="background-color:#C4A000"><font color="#555753"><b>fastapi run</b></font></span><span style="background-color:#C4A000"><font color="#2E3436"> │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">╰─────────────────────────────────────────────────────╯</font></span>
|
||||
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
|
||||
|
||||
<font color="#4E9A06">INFO</font>: Will watch for changes in these directories: ['/home/user/code/awesomeapp']
|
||||
<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://127.0.0.1:8000</b> (Press CTRL+C to quit)
|
||||
<font color="#4E9A06">INFO</font>: Started reloader process [<font color="#34E2E2"><b>2265862</b></font>] using <font color="#34E2E2"><b>WatchFiles</b></font>
|
||||
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">2265873</font>]
|
||||
<font color="#4E9A06">INFO</font>: Waiting for application startup.
|
||||
<font color="#4E9A06">INFO</font>: Application startup complete.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
|
||||
<b>fastapi run</b>
|
||||
|
||||
Logs:
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
|
||||
<b>[</b><font color="#4E9A06">'/home/user/code/awesomeapp'</font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C to
|
||||
quit<b>)</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -456,7 +456,7 @@ FastAPI depends on Pydantic and Starlette.
|
|||
|
||||
### `standard` Dependencies
|
||||
|
||||
When you install FastAPI with `pip install "fastapi[standard]"` it comes the `standard` group of optional dependencies:
|
||||
When you install FastAPI with `pip install "fastapi[standard]"` it comes with the `standard` group of optional dependencies:
|
||||
|
||||
Used by Pydantic:
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,51 @@ hide:
|
|||
|
||||
## Latest Changes
|
||||
|
||||
### Docs
|
||||
|
||||
* 📝 Update includes in `docs/ru/docs/tutorial/query-param-models.md`. PR [#12994](https://github.com/fastapi/fastapi/pull/12994) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✏️ Fix typo in README installation instructions. PR [#13011](https://github.com/fastapi/fastapi/pull/13011) by [@dave-hay](https://github.com/dave-hay).
|
||||
* 📝 Update docs for `fastapi-cli`. PR [#13031](https://github.com/fastapi/fastapi/pull/13031) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Translations
|
||||
|
||||
* 🌐 Update Russian translation for `docs/ru/docs/deployment/docker.md`. PR [#13048](https://github.com/fastapi/fastapi/pull/13048) by [@anklav24](https://github.com/anklav24).
|
||||
* 🌐 Add Portuguese translation for `docs/pt/docs/advanced/generate-clients.md`. PR [#13030](https://github.com/fastapi/fastapi/pull/13030) by [@vitumenezes](https://github.com/vitumenezes).
|
||||
* 🌐 Add Indonesian translation for `docs/id/docs/tutorial/first-steps.md`. PR [#13042](https://github.com/fastapi/fastapi/pull/13042) by [@gerry-sabar](https://github.com/gerry-sabar).
|
||||
* 🌐 Add Chinese translation for `docs/zh/docs/tutorial/cookie-param-models.md`. PR [#13038](https://github.com/fastapi/fastapi/pull/13038) by [@Zhongheng-Cheng](https://github.com/Zhongheng-Cheng).
|
||||
* 🌐 Add Chinese translation for `docs/zh/docs/tutorial/request-form-models.md`. PR [#13045](https://github.com/fastapi/fastapi/pull/13045) by [@Zhongheng-Cheng](https://github.com/Zhongheng-Cheng).
|
||||
* 🌐 Add Russian translation for `docs/ru/docs/virtual-environments.md`. PR [#13026](https://github.com/fastapi/fastapi/pull/13026) by [@alv2017](https://github.com/alv2017).
|
||||
* 🌐 Add Korean translation for `docs/ko/docs/tutorial/testing.md`. PR [#12968](https://github.com/fastapi/fastapi/pull/12968) by [@jts8257](https://github.com/jts8257).
|
||||
* 🌐 Add Korean translation for `docs/ko/docs/advanced/async-test.md`. PR [#12918](https://github.com/fastapi/fastapi/pull/12918) by [@icehongssii](https://github.com/icehongssii).
|
||||
* 🌐 Add Russian translation for `docs/ru/docs/tutorial/security/oauth2-jwt.md`. PR [#10601](https://github.com/fastapi/fastapi/pull/10601) by [@AlertRED](https://github.com/AlertRED).
|
||||
* 🌐 Add Russian translation for `docs/ru/docs/tutorial/security/simple-oauth2.md`. PR [#10599](https://github.com/fastapi/fastapi/pull/10599) by [@AlertRED](https://github.com/AlertRED).
|
||||
* 🌐 Add Russian translation for `docs/ru/docs/tutorial/security/get-current-user.md`. PR [#10594](https://github.com/fastapi/fastapi/pull/10594) by [@AlertRED](https://github.com/AlertRED).
|
||||
* 🌐 Add Traditional Chinese translation for `docs/zh-hant/docs/features.md`. PR [#12441](https://github.com/fastapi/fastapi/pull/12441) by [@codingjenny](https://github.com/codingjenny).
|
||||
* 🌐 Add Traditional Chinese translation for `docs/zh-hant/docs/virtual-environments.md`. PR [#12791](https://github.com/fastapi/fastapi/pull/12791) by [@Vincy1230](https://github.com/Vincy1230).
|
||||
* 🌐 Add Korean translation for `docs/ko/docs/advanced/templates.md`. PR [#12726](https://github.com/fastapi/fastapi/pull/12726) by [@Heumhub](https://github.com/Heumhub).
|
||||
* 🌐 Add Russian translation for `docs/ru/docs/fastapi-cli.md`. PR [#13041](https://github.com/fastapi/fastapi/pull/13041) by [@alv2017](https://github.com/alv2017).
|
||||
* 🌐 Add Korean translation for `docs/ko/docs/tutorial/cookie-param-models.md`. PR [#13000](https://github.com/fastapi/fastapi/pull/13000) by [@hard-coders](https://github.com/hard-coders).
|
||||
* 🌐 Add Korean translation for `docs/ko/docs/tutorial/header-param-models.md`. PR [#13001](https://github.com/fastapi/fastapi/pull/13001) by [@hard-coders](https://github.com/hard-coders).
|
||||
* 🌐 Add Korean translation for `docs/ko/docs/tutorial/request-form-models.md`. PR [#13002](https://github.com/fastapi/fastapi/pull/13002) by [@hard-coders](https://github.com/hard-coders).
|
||||
* 🌐 Add Korean translation for `docs/ko/docs/tutorial/request-forms.md`. PR [#13003](https://github.com/fastapi/fastapi/pull/13003) by [@hard-coders](https://github.com/hard-coders).
|
||||
* 🌐 Add Korean translation for `docs/ko/docs/resources/index.md`. PR [#13004](https://github.com/fastapi/fastapi/pull/13004) by [@hard-coders](https://github.com/hard-coders).
|
||||
* 🌐 Add Korean translation for `docs/ko/docs/how-to/configure-swagger-ui.md`. PR [#12898](https://github.com/fastapi/fastapi/pull/12898) by [@nahyunkeem](https://github.com/nahyunkeem).
|
||||
* 🌐 Add Korean translation to `docs/ko/docs/advanced/additional-status-codes.md`. PR [#12715](https://github.com/fastapi/fastapi/pull/12715) by [@nahyunkeem](https://github.com/nahyunkeem).
|
||||
* 🌐 Add Traditional Chinese translation for `docs/zh-hant/docs/tutorial/first-steps.md`. PR [#12467](https://github.com/fastapi/fastapi/pull/12467) by [@codingjenny](https://github.com/codingjenny).
|
||||
|
||||
### Internal
|
||||
|
||||
* ⬆ Bump pypa/gh-action-pypi-publish from 1.12.2 to 1.12.3. PR [#13055](https://github.com/fastapi/fastapi/pull/13055) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump types-ujson from 5.7.0.1 to 5.10.0.20240515. PR [#13018](https://github.com/fastapi/fastapi/pull/13018) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump black from 24.3.0 to 24.10.0. PR [#13014](https://github.com/fastapi/fastapi/pull/13014) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump inline-snapshot from 0.13.0 to 0.14.0. PR [#13017](https://github.com/fastapi/fastapi/pull/13017) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump dirty-equals from 0.6.0 to 0.8.0. PR [#13015](https://github.com/fastapi/fastapi/pull/13015) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump cloudflare/wrangler-action from 3.12 to 3.13. PR [#12996](https://github.com/fastapi/fastapi/pull/12996) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump astral-sh/setup-uv from 3 to 4. PR [#12982](https://github.com/fastapi/fastapi/pull/12982) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* 🔧 Remove duplicate actions/checkout in `notify-translations.yml`. PR [#12915](https://github.com/fastapi/fastapi/pull/12915) by [@tinyboxvk](https://github.com/tinyboxvk).
|
||||
* 🔧 Update team members. PR [#13033](https://github.com/fastapi/fastapi/pull/13033) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 📝 Update sponsors: remove Codacy. PR [#13032](https://github.com/fastapi/fastapi/pull/13032) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.115.6
|
||||
|
||||
### Fixes
|
||||
|
|
|
|||
|
|
@ -0,0 +1,332 @@
|
|||
# Langkah Pertama
|
||||
|
||||
File FastAPI yang paling sederhana bisa seperti berikut:
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py *}
|
||||
|
||||
Salin file tersebut ke `main.py`.
|
||||
|
||||
Jalankan di server:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:single">main.py</u>
|
||||
<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font>
|
||||
<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font>
|
||||
<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files
|
||||
<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
|
||||
╭─ <font color="#8AE234"><b>Python module file</b></font> ─╮
|
||||
│ │
|
||||
│ 🐍 main.py │
|
||||
│ │
|
||||
╰──────────────────────╯
|
||||
|
||||
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font>
|
||||
<font color="#3465A4">INFO </font> Found importable FastAPI app
|
||||
|
||||
╭─ <font color="#8AE234"><b>Importable FastAPI app</b></font> ─╮
|
||||
│ │
|
||||
│ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> │
|
||||
│ │
|
||||
╰──────────────────────────╯
|
||||
|
||||
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font>
|
||||
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">╭────────── FastAPI CLI - Development mode ───────────╮</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ Serving at: http://127.0.0.1:8000 │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ API docs: http://127.0.0.1:8000/docs │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ Running in development mode, for production use: │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ </font></span><span style="background-color:#C4A000"><font color="#555753"><b>fastapi run</b></font></span><span style="background-color:#C4A000"><font color="#2E3436"> │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">╰─────────────────────────────────────────────────────╯</font></span>
|
||||
|
||||
<font color="#4E9A06">INFO</font>: Will watch for changes in these directories: ['/home/user/code/awesomeapp']
|
||||
<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://127.0.0.1:8000</b> (Press CTRL+C to quit)
|
||||
<font color="#4E9A06">INFO</font>: Started reloader process [<font color="#34E2E2"><b>2265862</b></font>] using <font color="#34E2E2"><b>WatchFiles</b></font>
|
||||
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">2265873</font>]
|
||||
<font color="#4E9A06">INFO</font>: Waiting for application startup.
|
||||
<font color="#4E9A06">INFO</font>: Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Di output, terdapat sebaris pesan:
|
||||
|
||||
```hl_lines="4"
|
||||
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
Baris tersebut menunjukan URL dimana app aktif di komputer anda.
|
||||
|
||||
|
||||
### Mencoba aplikasi
|
||||
|
||||
Buka browser di <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>.
|
||||
|
||||
Anda akan melihat response JSON sebagai berikut:
|
||||
|
||||
```JSON
|
||||
{"message": "Hello World"}
|
||||
```
|
||||
|
||||
### Dokumen API interaktif
|
||||
|
||||
Sekarang kunjungi <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
Anda akan melihat dokumentasi API interaktif otomatis (dibuat oleh <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
|
||||
|
||||

|
||||
|
||||
### Dokumen API alternatif
|
||||
|
||||
Dan sekarang, kunjungi <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
|
||||
|
||||
Anda akan melihat dokumentasi alternatif otomatis (dibuat oleh <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
|
||||
|
||||

|
||||
|
||||
### OpenAPI
|
||||
|
||||
**FastAPI** membuat sebuah "schema" dimana semua API anda menggunakan standar **OpenAPI** untuk mendefinisikan API.
|
||||
|
||||
#### "Schema"
|
||||
|
||||
"schema" adalah suatu definisi atau deskripsi dari sesuatu. Bukan kode yang mengimplementasi definisi tersebut. Ini hanyalah sebuah deskripsi abstrak.
|
||||
|
||||
#### "schema" API
|
||||
|
||||
Dalam hal ini, <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> adalah spesifikasi yang menunjukan bagaimana untuk mendefinisikan sebuah skema di API anda.
|
||||
|
||||
Definisi skema ini termasuk jalur API anda, parameter yang bisa diterima, dll.
|
||||
|
||||
#### "schema" Data
|
||||
|
||||
Istilah "schema" bisa juga merujuk ke struktur data, seperti konten JSON.
|
||||
|
||||
Dalam kondisi ini, ini berarti attribut JSON dan tipe data yang dimiliki, dll.
|
||||
|
||||
#### Schema OpenAPI and JSON
|
||||
|
||||
"schema" OpenAPI mendefinisikan skema API dari API yang anda buat. Skema tersebut termasuk definisi (atau "schema") dari data yang dikirim atau diterima oleh API dari **JSON Schema**, skema data standar JSON.
|
||||
|
||||
#### Lihat `openapi.json`
|
||||
|
||||
Jika anda penasaran bagaimana skema OpenAPI polos seperti apa, FastAPI secara otomatis membuat JSON (schema) dengan deksripsi API anda.
|
||||
|
||||
anda bisa melihatnya di: <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>.
|
||||
|
||||
Anda akan melihat JSON yang dimulai seperti:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "FastAPI",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
|
||||
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
#### Kegunaan OpenAPI
|
||||
|
||||
Skema OpenAPI adalah tulang punggung dua sistem dokumentasi API interaktif yang ada di FastAPI.
|
||||
|
||||
Ada banyak alternatif sistem dokumentasi lainnya yang semuanya berdasarkan OpenAPI. Anda bisa menambahkannya ke aplikasi **FastAPI** anda.
|
||||
|
||||
Anda juga bisa menggunakan OpenAPI untuk membuat kode secara otomatis, untuk klien yang menggunakan API anda. Sebagai contoh, frontend, aplikasi mobile atau IoT.
|
||||
|
||||
## Ringkasan, secara bertahap
|
||||
|
||||
### Langkah 1: impor `FastAPI`
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[1] *}
|
||||
|
||||
`FastAPI` adalah class Python yang menyediakan semua fungsionalitas API anda.
|
||||
|
||||
/// note | Detail Teknis
|
||||
|
||||
`FastAPI` adalah class turunan langsung dari `Starlette`.
|
||||
|
||||
Anda bisa menggunakan semua fungsionalitas <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> dengan `FastAPI` juga.
|
||||
|
||||
///
|
||||
|
||||
### Langkah 2: buat "instance" dari `FastAPI`
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[3] *}
|
||||
|
||||
Di sini variabel `app` akan menjadi sebuah "instance" dari class `FastAPI`.
|
||||
|
||||
Ini akan menjadi gerbang utama untuk membuat semua API anda.
|
||||
|
||||
### Langkah 3: Buat *operasi path*
|
||||
|
||||
#### Path
|
||||
|
||||
"Path" atau jalur di sini merujuk ke bagian URL terakhir dimulai dari `/` pertama.
|
||||
|
||||
Sehingga, URL seperti:
|
||||
|
||||
```
|
||||
https://example.com/items/foo
|
||||
```
|
||||
|
||||
...path-nya adalah:
|
||||
|
||||
```
|
||||
/items/foo
|
||||
```
|
||||
|
||||
/// info
|
||||
|
||||
"path" juga biasa disebut "endpoint" atau "route".
|
||||
|
||||
///
|
||||
|
||||
ketika membuat API, "path" adalah jalan utama untuk memisahkan "concern" dan "resources".
|
||||
|
||||
#### Operasi
|
||||
|
||||
"Operasi" di sini merujuk ke salah satu dari metode HTTP berikut.
|
||||
|
||||
Salah satu dari:
|
||||
|
||||
* `POST`
|
||||
* `GET`
|
||||
* `PUT`
|
||||
* `DELETE`
|
||||
|
||||
...dan operasi lainnya yang unik:
|
||||
|
||||
* `OPTIONS`
|
||||
* `HEAD`
|
||||
* `PATCH`
|
||||
* `TRACE`
|
||||
|
||||
Dalam protokol HTTP, anda bisa berkomunikasi ke setiap path menggunakan satu (atau lebih) metode di atas.
|
||||
|
||||
---
|
||||
|
||||
Ketika membuat API, anda umumnya menggunakan metode HTTP tertentu untuk proses tertentu.
|
||||
|
||||
Umumnya menggunakan:
|
||||
|
||||
* `POST`: untuk membuat data.
|
||||
* `GET`: untuk membaca data.
|
||||
* `PUT`: untuk memperbarui data.
|
||||
* `DELETE`: untuk menghapus data.
|
||||
|
||||
Sehingga, di OpanAPI, setiap metode HTTP ini disebut sebuah "operasi".
|
||||
|
||||
Kita akan menyebut mereka juga "**operasi**".
|
||||
|
||||
#### Mendefinisikan *dekorator operasi path*
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[6] *}
|
||||
|
||||
`@app.get("/")` memberitahu **FastAPI** bahwa fungsi di bawahnya mengurusi request yang menuju ke:
|
||||
|
||||
* path `/`
|
||||
* menggunakan <abbr title="an HTTP GET method">operasi <code>get</code></abbr>
|
||||
|
||||
/// info | `@decorator` Info
|
||||
|
||||
Sintaksis `@sesuatu` di Python disebut "dekorator".
|
||||
|
||||
Dekorator ditempatkan di atas fungsi. Seperti sebuah topi cantik (Saya pikir istilah ini berasal dari situ).
|
||||
|
||||
"dekorator" memanggil dan bekerja dengan fungsi yang ada di bawahnya
|
||||
|
||||
Pada kondisi ini, dekorator ini memberi tahu **FastAPI** bahwa fungsi di bawah nya berhubungan dengan **path** `/` dengan **operasi** `get`.
|
||||
|
||||
Sehingga disebut **dekorator operasi path**.
|
||||
|
||||
///
|
||||
|
||||
Operasi lainnya yang bisa digunakan:
|
||||
|
||||
* `@app.post()`
|
||||
* `@app.put()`
|
||||
* `@app.delete()`
|
||||
|
||||
Dan operasi unik lainnya:
|
||||
|
||||
* `@app.options()`
|
||||
* `@app.head()`
|
||||
* `@app.patch()`
|
||||
* `@app.trace()`
|
||||
|
||||
/// tip | Tips
|
||||
|
||||
Jika anda bisa menggunakan operasi apa saja (metode HTTP).
|
||||
|
||||
**FastAPI** tidak mengharuskan anda menggunakan operasi tertentu.
|
||||
|
||||
Informasi di sini hanyalah sebagai panduan, bukan keharusan.
|
||||
|
||||
Sebagai contoh, ketika menggunakan GraphQL, semua operasi umumnya hanya menggunakan `POST`.
|
||||
|
||||
///
|
||||
|
||||
### Langkah 4: mendefinisikan **fungsi operasi path**
|
||||
|
||||
Ini "**fungsi operasi path**" kita:
|
||||
|
||||
* **path**: adalah `/`.
|
||||
* **operasi**: adalah `get`.
|
||||
* **fungsi**: adalah fungsi yang ada di bawah dekorator (di bawah `@app.get("/")`).
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[7] *}
|
||||
|
||||
Ini adalah fungsi Python.
|
||||
|
||||
Fungsi ini dipanggil **FastAPI** setiap kali menerima request ke URL "`/`" dengan operasi `GET`.
|
||||
|
||||
Di kondisi ini, ini adalah sebuah fungsi `async`.
|
||||
|
||||
---
|
||||
|
||||
Anda bisa mendefinisikan fungsi ini sebagai fungsi normal daripada `async def`:
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial003.py hl[7] *}
|
||||
|
||||
/// note | Catatan
|
||||
|
||||
Jika anda tidak tahu perbedaannya, kunjungi [Async: *"Panduan cepat"*](../async.md#in-a-hurry){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
### Langkah 5: hasilkan konten
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[8] *}
|
||||
|
||||
Anda bisa menghasilkan `dict`, `list`, nilai singular seperti `str`, `int`, dll.
|
||||
|
||||
Anda juga bisa menghasilkan model Pydantic (anda akan belajar mengenai ini nanti).
|
||||
|
||||
Ada banyak objek dan model yang secara otomatis dikonversi ke JSON (termasuk ORM, dll). Anda bisa menggunakan yang anda suka, kemungkinan sudah didukung.
|
||||
|
||||
## Ringkasan
|
||||
|
||||
* Impor `FastAPI`.
|
||||
* Buat sebuah instance `app`.
|
||||
* Tulis **dekorator operasi path** menggunakan dekorator seperti `@app.get("/")`.
|
||||
* Definisikan **fungsi operasi path**; sebagai contoh, `def root(): ...`.
|
||||
* Jalankan server development dengan perintah `fastapi dev`.
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
# 추가 상태 코드
|
||||
|
||||
기본적으로 **FastAPI**는 응답을 `JSONResponse`를 사용하여 반환하며, *경로 작업(path operation)*에서 반환한 내용을 해당 `JSONResponse` 안에 넣어 반환합니다.
|
||||
|
||||
기본 상태 코드 또는 *경로 작업*에서 설정한 상태 코드를 사용합니다.
|
||||
|
||||
## 추가 상태 코드
|
||||
|
||||
기본 상태 코드와 별도로 추가 상태 코드를 반환하려면 `JSONResponse`와 같이 `Response`를 직접 반환하고 추가 상태 코드를 직접 설정할 수 있습니다.
|
||||
|
||||
예를 들어 항목을 업데이트할 수 있는 *경로 작업*이 있고 성공 시 200 “OK”의 HTTP 상태 코드를 반환한다고 가정해 보겠습니다.
|
||||
|
||||
하지만 새로운 항목을 허용하기를 원할 것입니다. 항목이 이전에 존재하지 않았다면 이를 생성하고 HTTP 상태 코드 201 "Created"를 반환합니다.
|
||||
|
||||
이를 위해서는 `JSONResponse`를 가져와서 원하는 `status_code`를 설정하여 콘텐츠를 직접 반환합니다:
|
||||
|
||||
{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *}
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
위의 예제처럼 `Response`를 직접 반환하면 바로 반환됩니다.
|
||||
|
||||
모델 등과 함께 직렬화되지 않습니다.
|
||||
|
||||
원하는 데이터가 있는지, 값이 유효한 JSON인지 확인합니다(`JSONResponse`를 사용하는 경우).
|
||||
|
||||
///
|
||||
|
||||
/// note | 기술적 세부 정보
|
||||
|
||||
`from starlette.responses import JSONResponse`를 사용할 수도 있습니다.
|
||||
|
||||
**FastAPI**는 개발자 여러분을 위한 편의성으로 `fastapi.responses`와 동일한 `starlette.responses`를 제공합니다. 그러나 사용 가능한 응답의 대부분은 Starlette에서 직접 제공됩니다. `status` 또한 마찬가지입니다.
|
||||
|
||||
///
|
||||
|
||||
## OpenAPI 및 API 문서
|
||||
|
||||
추가 상태 코드와 응답을 직접 반환하는 경우, FastAPI는 반환할 내용을 미리 알 수 있는 방법이 없기 때문에 OpenAPI 스키마(API 문서)에 포함되지 않습니다.
|
||||
|
||||
하지만 다음을 사용하여 코드에 이를 문서화할 수 있습니다: [추가 응답](additional-responses.md){.internal-link target=_blank}.
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
# 비동기 테스트 코드 작성
|
||||
|
||||
이전 장에서 `TestClient` 를 이용해 **FastAPI** 어플리케이션 테스트를 작성하는 법을 배우셨을텐데요.
|
||||
지금까지는 `async` 키워드 사용없이 동기 함수의 테스트 코드를 작성하는 법만 익혔습니다.
|
||||
|
||||
하지만 비동기 함수를 사용하여 테스트 코드를 작성하는 것은 매우 유용할 수 있습니다.
|
||||
예를 들면 데이터베이스에 비동기로 쿼리하는 경우를 생각해봅시다.
|
||||
FastAPI 애플리케이션에 요청을 보내고, 비동기 데이터베이스 라이브러리를 사용하여 백엔드가 데이터베이스에 올바르게 데이터를 기록했는지 확인하고 싶을 때가 있을 겁니다.
|
||||
|
||||
이런 경우의 테스트 코드를 어떻게 비동기로 작성하는지 알아봅시다.
|
||||
|
||||
## pytest.mark.anyio
|
||||
|
||||
앞에서 작성한 테스트 함수에서 비동기 함수를 호출하고 싶다면, 테스트 코드도 비동기 함수여야합니다.
|
||||
AnyIO는 특정 테스트 함수를 비동기 함수로 호출 할 수 있는 깔끔한 플러그인을 제공합니다.
|
||||
|
||||
|
||||
## HTTPX
|
||||
|
||||
**FastAPI** 애플리케이션이 `async def` 대신 `def` 키워드로 선언된 함수를 사용하더라도, 내부적으로는 여전히 `비동기` 애플리케이션입니다.
|
||||
|
||||
`TestClient`는 pytest 표준을 사용하여 비동기 FastAPI 애플리케이션을 일반적인 `def` 테스트 함수 내에서 호출할 수 있도록 내부에서 마술을 부립니다. 하지만 이 마술은 비동기 함수 내부에서 사용할 때는 더 이상 작동하지 않습니다. 테스트를 비동기로 실행하면, 더 이상 테스트 함수 내부에서 `TestClient`를 사용할 수 없습니다.
|
||||
|
||||
`TestClient`는 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>를 기반으로 하고 있으며, 다행히 이를 직접 사용하여 API를 테스트할 수 있습니다.
|
||||
|
||||
## 예시
|
||||
|
||||
간단한 예시를 위해 [더 큰 어플리케이션 만들기](../ko/tutorial/bigger-applications.md){.internal-link target=_blank} 와 [테스트](../ko/tutorial/testing.md){.internal-link target=_blank}:에서 다룬 파일 구조와 비슷한 형태를 확인해봅시다:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ └── test_main.py
|
||||
```
|
||||
|
||||
`main.py`는 아래와 같아야 합니다:
|
||||
|
||||
{* ../../docs_src/async_tests/main.py *}
|
||||
|
||||
`test_main.py` 파일은 `main.py`에 대한 테스트가 있을 텐데, 다음과 같을 수 있습니다:
|
||||
|
||||
{* ../../docs_src/async_tests/test_main.py *}
|
||||
|
||||
## 실행하기
|
||||
|
||||
아래의 명령어로 테스트 코드를 실행합니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pytest
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## 자세히 보기
|
||||
|
||||
`@pytest.mark.anyio` 마커는 pytest에게 이 테스트 함수가 비동기로 호출되어야 함을 알려줍니다:
|
||||
|
||||
{* ../../docs_src/async_tests/test_main.py hl[7] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
테스트 함수가 이제 `TestClient`를 사용할 때처럼 단순히 `def`가 아니라 `async def`로 작성된 점에 주목해주세요.
|
||||
|
||||
///
|
||||
|
||||
그 다음에 `AsyncClient` 로 앱을 만들고 비동기 요청을 `await` 키워드로 보낼 수 있습니다:
|
||||
|
||||
{* ../../docs_src/async_tests/test_main.py hl[9:12] *}
|
||||
|
||||
위의 코드는:
|
||||
|
||||
```Python
|
||||
response = client.get('/')
|
||||
```
|
||||
|
||||
`TestClient` 에 요청을 보내던 것과 동일합니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
새로운 `AsyncClient`를 사용할 때 async/await를 사용하고 있다는 점에 주목하세요. 이 요청은 비동기적으로 처리됩니다.
|
||||
|
||||
///
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
만약의 어플리케이션이 Lifespan 이벤트에 의존성을 갖고 있다면 `AsyncClient` 가 이러한 이벤트를 실행시키지 않습니다.
|
||||
`AsyncClient` 가 테스트를 실행시켰다는 것을 확인하기 위해
|
||||
`LifespanManager` from <a href="https://github.com/florimondmanca/asgi-lifespan#usage" class="external-link" target="_blank">florimondmanca/asgi-lifespan</a>.확인해주세요.
|
||||
|
||||
|
||||
///
|
||||
|
||||
## 그 외의 비동기 함수 호출
|
||||
|
||||
테스트 함수가 이제 비동기 함수이므로, FastAPI 애플리케이션에 요청을 보내는 것 외에도 다른 `async` 함수를 호출하고 `await` 키워드를 사용 할 수 있습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
테스트에 비동기 함수 호출을 통합할 때 (예: <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MongoDB의 MotorClient</a>를 사용할 때) `RuntimeError: Task attached to a different loop` 오류가 발생한다면, 이벤트 루프가 필요한 객체는 반드시 비동기 함수 내에서만 인스턴스화해야 한다는 점을 주의하세요!
|
||||
예를 들어 `@app.on_event("startup")` 콜백 내에서 인스턴스화하는 것이 좋습니다.
|
||||
|
||||
///
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
# 템플릿
|
||||
|
||||
**FastAPI**와 함께 원하는 어떤 템플릿 엔진도 사용할 수 있습니다.
|
||||
|
||||
일반적인 선택은 Jinja2로, Flask와 다른 도구에서도 사용됩니다.
|
||||
|
||||
설정을 쉽게 할 수 있는 유틸리티가 있으며, 이를 **FastAPI** 애플리케이션에서 직접 사용할 수 있습니다(Starlette 제공).
|
||||
|
||||
## 의존성 설치
|
||||
|
||||
가상 환경을 생성하고(virtual environment{.internal-link target=_blank}), 활성화한 후 jinja2를 설치해야 합니다:
|
||||
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install jinja2
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## 사용하기 `Jinja2Templates`
|
||||
|
||||
* `Jinja2Templates`를 가져옵니다.
|
||||
* 나중에 재사용할 수 있는 `templates` 객체를 생성합니다.
|
||||
* 템플릿을 반환할 경로 작업에 `Request` 매개변수를 선언합니다.
|
||||
* 생성한 `templates`를 사용하여 `TemplateResponse`를 렌더링하고 반환합니다. 템플릿의 이름, 요청 객체 및 Jinja2 템플릿 내에서 사용될 키-값 쌍이 포함된 "컨텍스트" 딕셔너리도 전달합니다.
|
||||
|
||||
|
||||
```Python hl_lines="4 11 15-18"
|
||||
{!../../docs_src/templates/tutorial001.py!}
|
||||
```
|
||||
|
||||
/// note | 참고
|
||||
|
||||
FastAPI 0.108.0 이전과 Starlette 0.29.0에서는 `name`이 첫 번째 매개변수였습니다.
|
||||
|
||||
또한 이전 버전에서는 `request` 객체가 Jinja2의 컨텍스트에서 키-값 쌍의 일부로 전달되었습니다.
|
||||
|
||||
///
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
`response_class=HTMLResponse`를 선언하면 문서 UI 응답이 HTML임을 알 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
/// note | 기술 세부 사항
|
||||
`from starlette.templating import Jinja2Templates`를 사용할 수도 있습니다.
|
||||
|
||||
**FastAPI**는 개발자를 위한 편리함으로 `fastapi.templating` 대신 `starlette.templating`을 제공합니다. 하지만 대부분의 사용 가능한 응답은 Starlette에서 직접 옵니다. `Request` 및 `StaticFiles`도 마찬가지입니다.
|
||||
///
|
||||
|
||||
## 템플릿 작성하기
|
||||
|
||||
그런 다음 `templates/item.html`에 템플릿을 작성할 수 있습니다. 예를 들면:
|
||||
|
||||
```jinja hl_lines="7"
|
||||
{!../../docs_src/templates/templates/item.html!}
|
||||
```
|
||||
|
||||
### 템플릿 컨텍스트 값
|
||||
|
||||
다음과 같은 HTML에서:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jinja
|
||||
Item ID: {{ id }}
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
...이는 전달한 "컨텍스트" `dict`에서 가져온 `id`를 표시합니다:
|
||||
|
||||
```Python
|
||||
{"id": id}
|
||||
```
|
||||
|
||||
예를 들어, ID가 `42`일 경우, 이는 다음과 같이 렌더링됩니다:
|
||||
|
||||
```html
|
||||
Item ID: 42
|
||||
```
|
||||
|
||||
### 템플릿 `url_for` 인수
|
||||
|
||||
템플릿 내에서 `url_for()`를 사용할 수도 있으며, 이는 *경로 작업 함수*에서 사용될 인수와 동일한 인수를 받습니다.
|
||||
|
||||
따라서 다음과 같은 부분에서:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jinja
|
||||
<a href="{{ url_for('read_item', id=id) }}">
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
...이는 *경로 작업 함수* `read_item(id=id)`가 처리할 동일한 URL로 링크를 생성합니다.
|
||||
|
||||
예를 들어, ID가 `42`일 경우, 이는 다음과 같이 렌더링됩니다:
|
||||
```html
|
||||
<a href="/items/42">
|
||||
```
|
||||
|
||||
## 템플릿과 정적 파일
|
||||
|
||||
템플릿 내에서 `url_for()`를 사용할 수 있으며, 예를 들어 `name="static"`으로 마운트한 `StaticFiles`와 함께 사용할 수 있습니다.
|
||||
|
||||
```jinja hl_lines="4"
|
||||
{!../../docs_src/templates/templates/item.html!}
|
||||
```
|
||||
|
||||
이 예제에서는 `static/styles.css`에 있는 CSS 파일에 연결될 것입니다:
|
||||
|
||||
```CSS hl_lines="4"
|
||||
{!../../docs_src/templates/static/styles.css!}
|
||||
```
|
||||
|
||||
그리고 `StaticFiles`를 사용하고 있으므로, 해당 CSS 파일은 **FastAPI** 애플리케이션에서 `/static/styles.css` URL로 자동 제공됩니다.
|
||||
|
||||
## 더 많은 세부 사항
|
||||
|
||||
템플릿 테스트를 포함한 더 많은 세부 사항은 <a href="https://www.starlette.io/templates/" class="external-link" target="_blank">Starlette의 템플릿 문서</a>를 확인하세요.
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
# Swagger UI 구성
|
||||
|
||||
추가적인 <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">Swagger UI 매개변수</a>를 구성할 수 있습니다.
|
||||
|
||||
구성을 하려면, `FastAPI()` 앱 객체를 생성할 때 또는 `get_swagger_ui_html()` 함수에 `swagger_ui_parameters` 인수를 전달하십시오.
|
||||
|
||||
`swagger_ui_parameters`는 Swagger UI에 직접 전달된 구성을 포함하는 딕셔너리를 받습니다.
|
||||
|
||||
FastAPI는 이 구성을 **JSON** 형식으로 변환하여 JavaScript와 호환되도록 합니다. 이는 Swagger UI에서 필요로 하는 형식입니다.
|
||||
|
||||
## 구문 강조 비활성화
|
||||
|
||||
예를 들어, Swagger UI에서 구문 강조 기능을 비활성화할 수 있습니다.
|
||||
|
||||
설정을 변경하지 않으면, 기본적으로 구문 강조 기능이 활성화되어 있습니다:
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image02.png">
|
||||
|
||||
그러나 `syntaxHighlight`를 `False`로 설정하여 구문 강조 기능을 비활성화할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *}
|
||||
|
||||
...그럼 Swagger UI에서 더 이상 구문 강조 기능이 표시되지 않습니다:
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image03.png">
|
||||
|
||||
## 테마 변경
|
||||
|
||||
동일한 방식으로 `"syntaxHighlight.theme"` 키를 사용하여 구문 강조 테마를 설정할 수 있습니다 (중간에 점이 포함된 것을 참고하십시오).
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *}
|
||||
|
||||
이 설정은 구문 강조 색상 테마를 변경합니다:
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image04.png">
|
||||
|
||||
## 기본 Swagger UI 매개변수 변경
|
||||
|
||||
FastAPI는 대부분의 사용 사례에 적합한 몇 가지 기본 구성 매개변수를 포함하고 있습니다.
|
||||
|
||||
기본 구성에는 다음이 포함됩니다:
|
||||
|
||||
{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *}
|
||||
|
||||
`swagger_ui_parameters` 인수에 다른 값을 설정하여 이러한 기본값 중 일부를 재정의할 수 있습니다.
|
||||
|
||||
예를 들어, `deepLinking`을 비활성화하려면 `swagger_ui_parameters`에 다음 설정을 전달할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *}
|
||||
|
||||
## 기타 Swagger UI 매개변수
|
||||
|
||||
사용할 수 있는 다른 모든 구성 옵션을 확인하려면, Swagger UI 매개변수에 대한 공식 <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">문서</a>를 참조하십시오.
|
||||
|
||||
## JavaScript 전용 설정
|
||||
|
||||
Swagger UI는 **JavaScript 전용** 객체(예: JavaScript 함수)로 다른 구성을 허용하기도 합니다.
|
||||
|
||||
FastAPI는 이러한 JavaScript 전용 `presets` 설정을 포함하고 있습니다:
|
||||
|
||||
```JavaScript
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIBundle.SwaggerUIStandalonePreset
|
||||
]
|
||||
```
|
||||
|
||||
이들은 문자열이 아닌 **JavaScript** 객체이므로 Python 코드에서 직접 전달할 수 없습니다.
|
||||
|
||||
이와 같은 JavaScript 전용 구성을 사용해야 하는 경우, 위의 방법 중 하나를 사용하여 모든 Swagger UI 경로 작업을 재정의하고 필요한 JavaScript를 수동으로 작성할 수 있습니다.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# 리소스
|
||||
|
||||
추가 리소스, 외부 링크, 기사 등. ✈️
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
# 쿠키 매개변수 모델
|
||||
|
||||
관련있는 **쿠키**들의 그룹이 있는 경우, **Pydantic 모델**을 생성하여 선언할 수 있습니다. 🍪
|
||||
|
||||
이를 통해 **여러 위치**에서 **모델을 재사용** 할 수 있고 모든 매개변수에 대한 유효성 검사 및 메타데이터를 한 번에 선언할 수도 있습니다. 😍
|
||||
|
||||
/// note | 참고
|
||||
|
||||
이 기능은 FastAPI 버전 `0.115.0` 이후부터 지원됩니다. 🤓
|
||||
|
||||
///
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
동일한 기술이 `Query`, `Cookie`, 그리고 `Header`에 적용됩니다. 😎
|
||||
|
||||
///
|
||||
|
||||
## Pydantic 모델을 사용한 쿠키
|
||||
|
||||
**Pydantic 모델**에 필요한 **쿠키** 매개변수를 선언한 다음, 해당 매개변수를 `Cookie`로 선언합니다:
|
||||
|
||||
{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *}
|
||||
|
||||
**FastAPI**는 요청에서 받은 **쿠키**에서 **각 필드**에 대한 데이터를 **추출**하고 정의한 Pydantic 모델을 줍니다.
|
||||
|
||||
## 문서 확인하기
|
||||
|
||||
문서 UI `/docs`에서 정의한 쿠키를 볼 수 있습니다:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/cookie-param-models/image01.png">
|
||||
</div>
|
||||
|
||||
/// info | 정보
|
||||
|
||||
명심하세요, 내부적으로 **브라우저는 쿠키를 특별한 방식으로 처리**하기 때문에 **자바스크립트**가 쉽게 쿠키를 건드릴 수 **없습니다**.
|
||||
|
||||
`/docs`에서 **API 문서 UI**로 이동하면 *경로 작업*에 대한 쿠키의 **문서**를 볼 수 있습니다.
|
||||
|
||||
하지만 아무리 **데이터를 입력**하고 "실행(Execute)"을 클릭해도, 문서 UI는 **자바스크립트**로 작동하기 때문에 쿠키는 전송되지 않고, 아무 값도 쓰지 않은 것처럼 **오류** 메시지를 보게 됩니다.
|
||||
|
||||
///
|
||||
|
||||
## 추가 쿠키 금지하기
|
||||
|
||||
일부 특별한 사용 사례(흔하지는 않겠지만)에서는 수신하려는 쿠키를 **제한**할 수 있습니다.
|
||||
|
||||
이제 API는 자신의 <abbr title="농담입니다, 혹시나 해서요. 쿠키 동의와 관련해서 할 수 있는 것은 없지만, 이제 API조차도 잘못된 쿠키를 거부할 수 있다는 점이 재밌습니다. 쿠키 드세요. 🍪">쿠키 동의</abbr>를 제어할 수 있는 권한을 갖게 되었습니다. 🤪🍪
|
||||
|
||||
Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forbid`)할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *}
|
||||
|
||||
클라이언트가 **추가 쿠키**를 보내려고 시도하면, **오류** 응답을 받게 됩니다.
|
||||
|
||||
<abbr title="이건 또 다른 농담입니다. 제 말에 귀 기울이지 마세요. 커피랑 쿠키 좀 드세요. ☕">API가 거부</abbr>하는데도 동의를 얻기 위해 애쓰는 불쌍한 쿠키 배너(팝업)들. 🍪
|
||||
|
||||
예를 들어, 클라이언트가 `good-list-please` 값으로 `santa_tracker` 쿠키를 보내려고 하면 클라이언트는 `santa_tracker` <abbr title="산타는 쿠키가 부족한 것을 못마땅해합니다. 🎅 알겠습니다, 쿠키 농담은 이제 없습니다.">쿠키가 허용되지 않는다</abbr>는 **오류** 응답을 받게 됩니다:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "extra_forbidden",
|
||||
"loc": ["cookie", "santa_tracker"],
|
||||
"msg": "Extra inputs are not permitted",
|
||||
"input": "good-list-please",
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 요약
|
||||
|
||||
**Pydantic 모델**을 사용하여 **FastAPI**에서 <abbr title="가기 전에 마지막 쿠키를 드세요. 🍪">**쿠키**</abbr>를 선언할 수 있습니다. 😍
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# 헤더 매개변수 모델
|
||||
|
||||
관련 있는 **헤더 매개변수** 그룹이 있는 경우, **Pydantic 모델**을 생성하여 선언할 수 있습니다.
|
||||
|
||||
이를 통해 **여러 위치**에서 **모델을 재사용** 할 수 있고 모든 매개변수에 대한 유효성 검사 및 메타데이터를 한 번에 선언할 수도 있습니다. 😎
|
||||
|
||||
/// note | 참고
|
||||
|
||||
이 기능은 FastAPI 버전 `0.115.0` 이후부터 지원됩니다. 🤓
|
||||
|
||||
///
|
||||
|
||||
## Pydantic 모델을 사용한 헤더 매개변수
|
||||
|
||||
**Pydantic 모델**에 필요한 **헤더 매개변수**를 선언한 다음, 해당 매개변수를 `Header`로 선언합니다:
|
||||
|
||||
{* ../../docs_src/header_param_models/tutorial001_an_py310.py hl[9:14,18] *}
|
||||
|
||||
**FastAPI**는 요청에서 받은 **헤더**에서 **각 필드**에 대한 데이터를 **추출**하고 정의한 Pydantic 모델을 줍니다.
|
||||
|
||||
## 문서 확인하기
|
||||
|
||||
문서 UI `/docs`에서 필요한 헤더를 볼 수 있습니다:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/header-param-models/image01.png">
|
||||
</div>
|
||||
|
||||
## 추가 헤더 금지하기
|
||||
|
||||
일부 특별한 사용 사례(흔하지는 않겠지만)에서는 수신하려는 헤더를 **제한**할 수 있습니다.
|
||||
|
||||
Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forbid`)할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/header_param_models/tutorial002_an_py310.py hl[10] *}
|
||||
|
||||
클라이언트가 **추가 헤더**를 보내려고 시도하면, **오류** 응답을 받게 됩니다.
|
||||
|
||||
예를 들어, 클라이언트가 `plumbus` 값으로 `tool` 헤더를 보내려고 하면, 클라이언트는 헤더 매개변수 `tool`이 허용 되지 않는다는 **오류** 응답을 받게 됩니다:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "extra_forbidden",
|
||||
"loc": ["header", "tool"],
|
||||
"msg": "Extra inputs are not permitted",
|
||||
"input": "plumbus",
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 요약
|
||||
|
||||
**Pydantic 모델**을 사용하여 **FastAPI**에서 **헤더**를 선언할 수 있습니다. 😎
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# 폼 모델
|
||||
|
||||
FastAPI에서 **Pydantic 모델**을 이용하여 **폼 필드**를 선언할 수 있습니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
폼(Form)을 사용하려면, 먼저 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>를 설치하세요.
|
||||
|
||||
[가상 환경](../virtual-environments.md){.internal-link target=_blank}을 생성하고 활성화한 다음, 아래와 같이 설치할 수 있습니다:
|
||||
|
||||
```console
|
||||
$ pip install python-multipart
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
/// note | 참고
|
||||
|
||||
이 기능은 FastAPI 버전 `0.113.0` 이후부터 지원됩니다. 🤓
|
||||
|
||||
///
|
||||
|
||||
## Pydantic 모델을 사용한 폼
|
||||
|
||||
**폼 필드**로 받고 싶은 필드를 **Pydantic 모델**로 선언한 다음, 매개변수를 `Form`으로 선언하면 됩니다:
|
||||
|
||||
{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *}
|
||||
|
||||
**FastAPI**는 요청에서 받은 **폼 데이터**에서 **각 필드**에 대한 데이터를 **추출**하고 정의한 Pydantic 모델을 줍니다.
|
||||
|
||||
## 문서 확인하기
|
||||
|
||||
문서 UI `/docs`에서 확인할 수 있습니다:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/request-form-models/image01.png">
|
||||
</div>
|
||||
|
||||
## 추가 폼 필드 금지하기
|
||||
|
||||
일부 특별한 사용 사례(흔하지는 않겠지만)에서는 Pydantic 모델에서 정의한 폼 필드를 **제한**하길 원할 수도 있습니다. 그리고 **추가** 필드를 **금지**할 수도 있습니다.
|
||||
|
||||
/// note | 참고
|
||||
|
||||
이 기능은 FastAPI 버전 `0.114.0` 이후부터 지원됩니다. 🤓
|
||||
|
||||
///
|
||||
|
||||
Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forbid`)할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *}
|
||||
|
||||
클라이언트가 추가 데이터를 보내려고 하면 **오류** 응답을 받게 됩니다.
|
||||
|
||||
예를 들어, 클라이언트가 폼 필드를 보내려고 하면:
|
||||
|
||||
* `username`: `Rick`
|
||||
* `password`: `Portal Gun`
|
||||
* `extra`: `Mr. Poopybutthole`
|
||||
|
||||
`extra` 필드가 허용되지 않는다는 오류 응답을 받게 됩니다:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "extra_forbidden",
|
||||
"loc": ["body", "extra"],
|
||||
"msg": "Extra inputs are not permitted",
|
||||
"input": "Mr. Poopybutthole"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 요약
|
||||
|
||||
Pydantic 모델을 사용하여 FastAPI에서 폼 필드를 선언할 수 있습니다. 😎
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
# 폼 데이터
|
||||
|
||||
JSON 대신 폼 필드를 받아야 하는 경우 `Form`을 사용할 수 있습니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
폼을 사용하려면, 먼저 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>를 설치하세요.
|
||||
|
||||
[가상 환경](../virtual-environments.md){.internal-link target=_blank}을 생성하고 활성화한 다음, 아래와 같이 설치할 수 있습니다:
|
||||
|
||||
```console
|
||||
$ pip install python-multipart
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
## `Form` 임포트하기
|
||||
|
||||
`fastapi`에서 `Form`을 임포트합니다:
|
||||
|
||||
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *}
|
||||
|
||||
## `Form` 매개변수 정의하기
|
||||
|
||||
`Body` 또는 `Query`와 동일한 방식으로 폼 매개변수를 만듭니다:
|
||||
|
||||
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *}
|
||||
|
||||
예를 들어, OAuth2 사양을 사용할 수 있는 방법 중 하나("패스워드 플로우"라고 함)로 `username`과 `password`를 폼 필드로 보내야 합니다.
|
||||
|
||||
<abbr title="specification">사양</abbr>에서는 필드 이름이 `username` 및 `password`로 정확하게 명명되어야 하고, JSON이 아닌 폼 필드로 전송해야 합니다.
|
||||
|
||||
`Form`을 사용하면 유효성 검사, 예제, 별칭(예: `username` 대신 `user-name`) 등을 포함하여 `Body`(및 `Query`, `Path`, `Cookie`)와 동일한 구성을 선언할 수 있습니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`Form`은 `Body`에서 직접 상속되는 클래스입니다.
|
||||
|
||||
///
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
폼 본문을 선언할 때, 폼이 없으면 매개변수가 쿼리 매개변수나 본문(JSON) 매개변수로 해석(interpret)되기 때문에 `Form`을 명시적으로 사용해야 합니다.
|
||||
|
||||
///
|
||||
|
||||
## "폼 필드"에 대해
|
||||
|
||||
HTML 폼(`<form></form>`)이 데이터를 서버로 보내는 방식은 일반적으로 해당 데이터에 대해 "특수" 인코딩을 사용하며, 이는 JSON과 다릅니다.
|
||||
|
||||
**FastAPI**는 JSON 대신 올바른 위치에서 해당 데이터를 읽습니다.
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
폼의 데이터는 일반적으로 "미디어 유형(media type)" `application/x-www-form-urlencoded`를 사용하여 인코딩합니다.
|
||||
|
||||
그러나 폼에 파일이 포함된 경우, `multipart/form-data`로 인코딩합니다. 다음 장에서 파일 처리에 대해 읽을 겁니다.
|
||||
|
||||
|
||||
이러한 인코딩 및 폼 필드에 대해 더 읽고 싶다면, <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><code>POST</code>에 대한 <abbr title="Mozilla Developer Network">MDN</a> 웹 문서를 참조하세요.
|
||||
|
||||
///
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
*경로 작업*에서 여러 `Form` 매개변수를 선언할 수 있지만, JSON으로 수신할 것으로 예상되는 `Body` 필드와 함께 선언할 수 없습니다. 요청 본문은 `application/json` 대신에 `application/x-www-form-urlencoded`를 사용하여 인코딩되기 때문입니다.
|
||||
|
||||
이는 **FastAPI**의 제한 사항이 아니며 HTTP 프로토콜의 일부입니다.
|
||||
|
||||
///
|
||||
|
||||
## 요약
|
||||
|
||||
폼 데이터 입력 매개변수를 선언하려면 `Form`을 사용하세요.
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
# 테스팅
|
||||
|
||||
<a href="https://www.starlette.io/testclient/" class="external-link" target="_blank">Starlette</a> 덕분에 **FastAPI** 를 테스트하는 일은 쉽고 즐거운 일이 되었습니다.
|
||||
|
||||
Starlette는 <a href="https://www.python-httpx.org\" class="external-link" target="_blank">HTTPX</a>를 기반으로 하며, 이는 Requests를 기반으로 설계되었기 때문에 매우 친숙하고 직관적입니다.
|
||||
|
||||
이를 사용하면 FastAPI에서 <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a>를 직접 사용할 수 있습니다.
|
||||
|
||||
## `TestClient` 사용하기
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`TestClient` 사용하려면, 우선 <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a> 를 설치해야 합니다.
|
||||
|
||||
[virtual environment](../virtual-environments.md){.internal-link target=_blank} 를 만들고, 활성화 시킨 뒤에 설치하세요. 예시:
|
||||
|
||||
```console
|
||||
$ pip install httpx
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
`TestClient` 를 임포트하세요.
|
||||
|
||||
**FastAPI** 어플리케이션을 전달하여 `TestClient` 를 만드세요.
|
||||
|
||||
이름이 `test_` 로 시작하는 함수를 만드세요(`pytest` 의 표준적인 관례입니다).
|
||||
|
||||
`httpx` 를 사용하는 것과 같은 방식으로 `TestClient` 객체를 사용하세요.
|
||||
|
||||
표준적인 파이썬 문법을 이용하여 확인이 필요한 곳에 간단한 `assert` 문장을 작성하세요(역시 표준적인 `pytest` 관례입니다).
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
테스트를 위한 함수는 `async def` 가 아니라 `def` 로 작성됨에 주의하세요.
|
||||
|
||||
그리고 클라이언트에 대한 호출도 `await` 를 사용하지 않는 일반 호출입니다.
|
||||
|
||||
이렇게 하여 복잡한 과정 없이 `pytest` 를 직접적으로 사용할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
`from starlette.testclient import TestClient` 역시 사용할 수 있습니다.
|
||||
|
||||
**FastAPI** 는 개발자의 편의를 위해 `starlette.testclient` 를 `fastapi.testclient` 로도 제공할 뿐입니다. 이는 단지 `Starlette` 에서 직접 가져오는지의 차이일 뿐입니다.
|
||||
|
||||
///
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
FastAPI 애플리케이션에 요청을 보내는 것 외에도 테스트에서 `async` 함수를 호출하고 싶다면 (예: 비동기 데이터베이스 함수), 심화 튜토리얼의 [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} 를 참조하세요.
|
||||
|
||||
///
|
||||
|
||||
## 테스트 분리하기
|
||||
|
||||
실제 애플리케이션에서는 테스트를 별도의 파일로 나누는 경우가 많습니다.
|
||||
|
||||
|
||||
그리고 **FastAPI** 애플리케이션도 여러 파일이나 모듈 등으로 구성될 수 있습니다.
|
||||
|
||||
### **FastAPI** app 파일
|
||||
|
||||
[Bigger Applications](bigger-applications.md){.internal-link target=_blank} 에 묘사된 파일 구조를 가지고 있는 것으로 가정해봅시다.
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ └── main.py
|
||||
```
|
||||
|
||||
`main.py` 파일 안에 **FastAPI** app 을 만들었습니다:
|
||||
|
||||
{* ../../docs_src/app_testing/main.py *}
|
||||
|
||||
### 테스트 파일
|
||||
|
||||
테스트를 위해 `test_main.py` 라는 파일을 생성할 수 있습니다. 이 파일은 동일한 Python 패키지(즉, `__init__.py` 파일이 있는 동일한 디렉터리)에 위치할 수 있습니다.
|
||||
|
||||
``` hl_lines="5"
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ └── test_main.py
|
||||
```
|
||||
|
||||
파일들이 동일한 패키지에 위치해 있으므로, 상대 참조를 사용하여 `main` 에서 `app` 객체를 임포트 해올 수 있습니다.
|
||||
|
||||
{* ../../docs_src/app_testing/test_main.py hl[3] *}
|
||||
|
||||
|
||||
...그리고 이전에 작성했던 것과 같은 테스트 코드를 작성할 수 있습니다.
|
||||
|
||||
## 테스트: 확장된 예시
|
||||
|
||||
이제 위의 예시를 확장하고 더 많은 세부 사항을 추가하여 다양한 부분을 어떻게 테스트하는지 살펴보겠습니다.
|
||||
|
||||
### 확장된 FastAPI 애플리케이션 파일
|
||||
|
||||
이전과 같은 파일 구조를 계속 사용해 보겠습니다.
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ └── test_main.py
|
||||
```
|
||||
|
||||
이제 **FastAPI** 앱이 있는 `main.py` 파일에 몇 가지 다른 **경로 작업** 이 추가된 경우를 생각해봅시다.
|
||||
|
||||
단일 오류를 반환할 수 있는 `GET` 작업이 있습니다.
|
||||
|
||||
여러 다른 오류를 반환할 수 있는 `POST` 작업이 있습니다.
|
||||
|
||||
두 *경로 작업* 모두 `X-Token` 헤더를 요구합니다.
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_an_py310/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_an_py39/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_an/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.10+ non-Annotated
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
될 수 있으면 `Annotated` 버전 사용을 권장합나다.
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_py310/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+ non-Annotated
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
될 수 있으면 `Annotated` 버전 사용을 권장합나다.
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
### 확장된 테스트 파일
|
||||
|
||||
이제는 `test_main.py` 를 확장된 테스트들로 수정할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/app_testing/app_b/test_main.py *}
|
||||
|
||||
|
||||
클라이언트가 요청에 정보를 전달해야 하는데 방법을 모르겠다면, `httpx`에서 해당 작업을 수행하는 방법을 검색(Google)하거나, `requests`에서의 방법을 검색해보세요. HTTPX는 Requests의 디자인을 기반으로 설계되었습니다.
|
||||
|
||||
그 후, 테스트에서도 동일하게 적용하면 됩니다.
|
||||
|
||||
예시:
|
||||
|
||||
* *경로* 혹은 *쿼리* 매개변수를 전달하려면, URL 자체에 추가한다.
|
||||
* JSON 본문을 전달하려면, 파이썬 객체 (예를들면 `dict`) 를 `json` 파라미터로 전달한다.
|
||||
* JSON 대신 *폼 데이터* 를 보내야한다면, `data` 파라미터를 대신 전달한다.
|
||||
* *헤더* 를 전달하려면, `headers` 파라미터에 `dict` 를 전달한다.
|
||||
* *쿠키* 를 전달하려면, `cookies` 파라미터에 `dict` 를 전달한다.
|
||||
|
||||
백엔드로 데이터를 어떻게 보내는지 정보를 더 얻으려면 (`httpx` 혹은 `TestClient` 를 이용해서) <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX documentation</a> 를 확인하세요.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`TestClient` 는 Pydantic 모델이 아니라 JSON 으로 변환될 수 있는 데이터를 받습니다.
|
||||
|
||||
만약 테스트중 Pydantic 모델을 어플리케이션으로에 보내고 싶다면, [JSON 호환 가능 인코더](encoder.md){.internal-link target=_blank} 에 설명되어 있는 `jsonable_encoder` 를 사용할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## 실행하기
|
||||
|
||||
테스트 코드를 작성하고, `pytest` 를 설치해야합니다.
|
||||
|
||||
[virtual environment](../virtual-environments.md){.internal-link target=_blank} 를 만들고, 활성화 시킨 뒤에 설치하세요. 예시:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install pytest
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
`pytest` 파일과 테스트를 자동으로 감지하고 실행한 다음, 결과를 보고할 것입니다.
|
||||
|
||||
테스트를 다음 명령어로 실행하세요.
|
||||
|
||||
<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>
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
# Generate Clients
|
||||
|
||||
Como o **FastAPI** é baseado na especificação **OpenAPI**, você obtém compatibilidade automática com muitas ferramentas, incluindo a documentação automática da API (fornecida pelo Swagger UI).
|
||||
|
||||
Uma vantagem particular que nem sempre é óbvia é que você pode **gerar clientes** (às vezes chamados de <abbr title="Software Development Kits">**SDKs**</abbr>) para a sua API, para muitas **linguagens de programação** diferentes.
|
||||
|
||||
## Geradores de Clientes OpenAPI
|
||||
|
||||
Existem muitas ferramentas para gerar clientes a partir do **OpenAPI**.
|
||||
|
||||
Uma ferramenta comum é o <a href="https://openapi-generator.tech/" class="external-link" target="_blank">OpenAPI Generator</a>.
|
||||
|
||||
Se voce está construindo um **frontend**, uma alternativa muito interessante é o <a href="https://github.com/hey-api/openapi-ts" class="external-link" target="_blank">openapi-ts</a>.
|
||||
|
||||
## Geradores de Clientes e SDKs - Patrocinadores
|
||||
|
||||
Existem também alguns geradores de clientes e SDKs baseados na OpenAPI (FastAPI) **patrocinados por empresas**, em alguns casos eles podem oferecer **recursos adicionais** além de SDKs/clientes gerados de alta qualidade.
|
||||
|
||||
Alguns deles também ✨ [**patrocinam o FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, isso garante o **desenvolvimento** contínuo e saudável do FastAPI e seu **ecossistema**.
|
||||
|
||||
E isso mostra o verdadeiro compromisso deles com o FastAPI e sua **comunidade** (você), pois eles não apenas querem fornecer um **bom serviço**, mas também querem garantir que você tenha um **framework bom e saudável**, o FastAPI. 🙇
|
||||
|
||||
Por exemplo, você pode querer experimentar:
|
||||
|
||||
* <a href="https://speakeasy.com/?utm_source=fastapi+repo&utm_medium=github+sponsorship" class="external-link" target="_blank">Speakeasy</a>
|
||||
* <a href="https://www.stainlessapi.com/?utm_source=fastapi&utm_medium=referral" class="external-link" target="_blank">Stainless</a>
|
||||
* <a href="https://developers.liblab.com/tutorials/sdk-for-fastapi/?utm_source=fastapi" class="external-link" target="_blank">liblab</a>
|
||||
|
||||
Existem também várias outras empresas que oferecem serviços semelhantes que você pode pesquisar e encontrar online. 🤓
|
||||
|
||||
## Gerar um Cliente Frontend TypeScript
|
||||
|
||||
Vamos começar com um aplicativo **FastAPI** simples:
|
||||
|
||||
{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *}
|
||||
|
||||
Note que as *operações de rota* definem os modelos que usam para o corpo da requisição e o corpo da resposta, usando os modelos `Item` e `ResponseMessage`.
|
||||
|
||||
### Documentação da API
|
||||
|
||||
Se você acessar a documentação da API, verá que ela tem os **schemas** para os dados a serem enviados nas requisições e recebidos nas respostas:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image01.png">
|
||||
|
||||
Você pode ver esses schemas porque eles foram declarados com os modelos no app.
|
||||
|
||||
Essas informações estão disponíveis no **OpenAPI schema** do app e são mostradas na documentação da API (pelo Swagger UI).
|
||||
|
||||
E essas mesmas informações dos modelos que estão incluídas no OpenAPI são o que pode ser usado para **gerar o código do cliente**.
|
||||
|
||||
### Gerar um Cliente TypeScript
|
||||
|
||||
Agora que temos o app com os modelos, podemos gerar o código do cliente para o frontend.
|
||||
|
||||
#### Instalar o `openapi-ts`
|
||||
|
||||
Você pode instalar o `openapi-ts` no seu código frontend com:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ npm install @hey-api/openapi-ts --save-dev
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
#### Gerar o Código do Cliente
|
||||
|
||||
Para gerar o código do cliente, você pode usar a aplicação de linha de comando `openapi-ts` que agora está instalada.
|
||||
|
||||
Como ela está instalada no projeto local, você provavelmente não conseguiria chamar esse comando diretamente, mas você o colocaria no seu arquivo `package.json`.
|
||||
|
||||
Poderia ser assim:
|
||||
|
||||
```JSON hl_lines="7"
|
||||
{
|
||||
"name": "frontend-app",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"generate-client": "openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios"
|
||||
},
|
||||
"author": "",
|
||||
"license": "",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "^0.27.38",
|
||||
"typescript": "^4.6.2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Depois de ter esse script NPM `generate-client` lá, você pode executá-lo com:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ npm run generate-client
|
||||
|
||||
frontend-app@1.0.0 generate-client /home/user/code/frontend-app
|
||||
> openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Esse comando gerará o código em `./src/client` e usará o `axios` (a biblioteca HTTP frontend) internamente.
|
||||
|
||||
### Experimente o Código do Cliente
|
||||
|
||||
Agora você pode importar e usar o código do cliente, ele poderia ser assim, observe que você obtém preenchimento automático para os métodos:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image02.png">
|
||||
|
||||
Você também obterá preenchimento automático para o corpo a ser enviado:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image03.png">
|
||||
|
||||
/// tip | Dica
|
||||
|
||||
Observe o preenchimento automático para `name` e `price`, que foi definido no aplicativo FastAPI, no modelo `Item`.
|
||||
|
||||
///
|
||||
|
||||
Você terá erros em linha para os dados que você envia:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image04.png">
|
||||
|
||||
O objeto de resposta também terá preenchimento automático:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image05.png">
|
||||
|
||||
## App FastAPI com Tags
|
||||
|
||||
Em muitos casos seu app FastAPI será maior, e você provavelmente usará tags para separar diferentes grupos de *operações de rota*.
|
||||
|
||||
Por exemplo, você poderia ter uma seção para **items** e outra seção para **users**, e elas poderiam ser separadas por tags:
|
||||
|
||||
{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *}
|
||||
|
||||
### Gerar um Cliente TypeScript com Tags
|
||||
|
||||
Se você gerar um cliente para um app FastAPI usando tags, normalmente também separará o código do cliente com base nas tags.
|
||||
|
||||
Dessa forma, você poderá ter as coisas ordenadas e agrupadas corretamente para o código do cliente:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image06.png">
|
||||
|
||||
Nesse caso você tem:
|
||||
|
||||
* `ItemsService`
|
||||
* `UsersService`
|
||||
|
||||
### Nomes dos Métodos do Cliente
|
||||
|
||||
Agora os nomes dos métodos gerados como `createItemItemsPost` não parecem muito "limpos":
|
||||
|
||||
```TypeScript
|
||||
ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
|
||||
```
|
||||
|
||||
...isto ocorre porque o gerador de clientes usa o **operation ID** interno do OpenAPI para cada *operação de rota*.
|
||||
|
||||
O OpenAPI exige que cada operation ID seja único em todas as *operações de rota*, então o FastAPI usa o **nome da função**, o **caminho** e o **método/operacao HTTP** para gerar esse operation ID, porque dessa forma ele pode garantir que os operation IDs sejam únicos.
|
||||
|
||||
Mas eu vou te mostrar como melhorar isso a seguir. 🤓
|
||||
|
||||
### IDs de Operação Personalizados e Melhores Nomes de Método
|
||||
|
||||
Você pode **modificar** a maneira como esses IDs de operação são **gerados** para torná-los mais simples e ter **nomes de método mais simples** nos clientes.
|
||||
|
||||
Neste caso, você terá que garantir que cada ID de operação seja **único** de alguma outra maneira.
|
||||
|
||||
Por exemplo, você poderia garantir que cada *operação de rota* tenha uma tag, e então gerar o ID da operação com base na **tag** e no **nome** da *operação de rota* (o nome da função).
|
||||
|
||||
### Função Personalizada para Gerar IDs de Operação Únicos
|
||||
|
||||
O FastAPI usa um **ID único** para cada *operação de rota*, ele é usado para o **ID da operação** e também para os nomes de quaisquer modelos personalizados necessários, para requisições ou respostas.
|
||||
|
||||
Você pode personalizar essa função. Ela recebe uma `APIRoute` e gera uma string.
|
||||
|
||||
Por exemplo, aqui está usando a primeira tag (você provavelmente terá apenas uma tag) e o nome da *operação de rota* (o nome da função).
|
||||
|
||||
Você pode então passar essa função personalizada para o **FastAPI** como o parâmetro `generate_unique_id_function`:
|
||||
|
||||
{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *}
|
||||
|
||||
### Gerar um Cliente TypeScript com IDs de Operação Personalizados
|
||||
|
||||
Agora, se você gerar o cliente novamente, verá que ele tem os nomes dos métodos melhorados:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image07.png">
|
||||
|
||||
Como você pode ver, os nomes dos métodos agora têm a tag e, em seguida, o nome da função. Agora eles não incluem informações do caminho da URL e da operação HTTP.
|
||||
|
||||
### Pré-processar a Especificação OpenAPI para o Gerador de Clientes
|
||||
|
||||
O código gerado ainda tem algumas **informações duplicadas**.
|
||||
|
||||
Nós já sabemos que esse método está relacionado aos **items** porque essa palavra está no `ItemsService` (retirada da tag), mas ainda temos o nome da tag prefixado no nome do método também. 😕
|
||||
|
||||
Provavelmente ainda queremos mantê-lo para o OpenAPI em geral, pois isso garantirá que os IDs de operação sejam **únicos**.
|
||||
|
||||
Mas para o cliente gerado, poderíamos **modificar** os IDs de operação do OpenAPI logo antes de gerar os clientes, apenas para tornar esses nomes de método mais **simples**.
|
||||
|
||||
Poderíamos baixar o JSON do OpenAPI para um arquivo `openapi.json` e então poderíamos **remover essa tag prefixada** com um script como este:
|
||||
|
||||
{* ../../docs_src/generate_clients/tutorial004.py *}
|
||||
|
||||
//// tab | Node.js
|
||||
|
||||
```Javascript
|
||||
{!> ../../docs_src/generate_clients/tutorial004.js!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
Com isso, os IDs de operação seriam renomeados de coisas como `items-get_items` para apenas `get_items`, dessa forma o gerador de clientes pode gerar nomes de métodos mais simples.
|
||||
|
||||
### Gerar um Cliente TypeScript com o OpenAPI Pré-processado
|
||||
|
||||
Agora, como o resultado final está em um arquivo `openapi.json`, você modificaria o `package.json` para usar esse arquivo local, por exemplo:
|
||||
|
||||
```JSON hl_lines="7"
|
||||
{
|
||||
"name": "frontend-app",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios"
|
||||
},
|
||||
"author": "",
|
||||
"license": "",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "^0.27.38",
|
||||
"typescript": "^4.6.2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Depois de gerar o novo cliente, você teria agora **nomes de métodos "limpos"**, com todo o **preenchimento automático**, **erros em linha**, etc:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image08.png">
|
||||
|
||||
## Benefícios
|
||||
|
||||
Ao usar os clientes gerados automaticamente, você teria **preenchimento automático** para:
|
||||
|
||||
* Métodos.
|
||||
* Corpo de requisições, parâmetros da query, etc.
|
||||
* Corpo de respostas.
|
||||
|
||||
Você também teria **erros em linha** para tudo.
|
||||
|
||||
E sempre que você atualizar o código do backend, e **regenerar** o frontend, ele teria quaisquer novas *operações de rota* disponíveis como métodos, as antigas removidas, e qualquer outra alteração seria refletida no código gerado. 🤓
|
||||
|
||||
Isso também significa que se algo mudar, será **refletido** no código do cliente automaticamente. E se você **construir** o cliente, ele dará erro se houver alguma **incompatibilidade** nos dados usados.
|
||||
|
||||
Então, você **detectaria vários erros** muito cedo no ciclo de desenvolvimento, em vez de ter que esperar que os erros apareçam para seus usuários finais em produção e então tentar depurar onde está o problema. ✨
|
||||
|
|
@ -58,7 +58,7 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
|||
|
||||
## Образы контейнеров
|
||||
|
||||
Docker является одним оз основных инструментов для создания **образов** и **контейнеров** и управления ими.
|
||||
Docker является одним из основных инструментов для создания **образов** и **контейнеров** и управления ими.
|
||||
|
||||
Существует общедоступный <a href="https://hub.docker.com/" class="external-link" target="_blank">Docker Hub</a> с подготовленными **официальными образами** многих инструментов, окружений, баз данных и приложений.
|
||||
|
||||
|
|
@ -87,7 +87,7 @@ Docker является одним оз основных инструменто
|
|||
|
||||
Когда **контейнер** запущен, он будет выполнять прописанные в нём команды и программы. Но вы можете изменить его так, чтоб он выполнял другие команды и программы.
|
||||
|
||||
Контейнер буде работать до тех пор, пока выполняется его **главный процесс** (команда или программа).
|
||||
Контейнер будет работать до тех пор, пока выполняется его **главный процесс** (команда или программа).
|
||||
|
||||
В контейнере обычно выполняется **только один процесс**, но от его имени можно запустить другие процессы, тогда в этом же в контейнере будет выполняться **множество процессов**.
|
||||
|
||||
|
|
@ -215,7 +215,7 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
|||
|
||||
Опция `--upgrade` указывает `pip` обновить библиотеки, емли они уже установлены.
|
||||
|
||||
Ка и в предыдущем шаге с копированием файла, этот шаг также будет использовать **кэш Docker** в случае отсутствия изменений.
|
||||
Как и в предыдущем шаге с копированием файла, этот шаг также будет использовать **кэш Docker** в случае отсутствия изменений.
|
||||
|
||||
Использование кэша, особенно на этом шаге, позволит вам **сэкономить** кучу времени при повторной сборке образа, так как зависимости будут сохранены в кеше, а не **загружаться и устанавливаться каждый раз**.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
# FastAPI CLI
|
||||
|
||||
**FastAPI CLI** это программа командной строки, которую вы можете использовать для запуска вашего FastAPI приложения, для управления FastAPI-проектом, а также для многих других вещей.
|
||||
|
||||
`fastapi-cli` устанавливается вместе со стандартным пакетом FastAPI (при запуске команды `pip install "fastapi[standard]"`). Данный пакет предоставляет доступ к программе `fastapi` через терминал.
|
||||
|
||||
Чтобы запустить приложение FastAPI в режиме разработки, вы можете использовать команду `fastapi dev`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
|
||||
|
||||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
|
||||
|
||||
Searching for package file structure from directories with
|
||||
<font color="#3465A4">__init__.py</font> files
|
||||
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with the
|
||||
following code:
|
||||
|
||||
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
|
||||
<b>fastapi run</b>
|
||||
|
||||
Logs:
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
|
||||
<b>[</b><font color="#4E9A06">'/home/user/code/awesomeapp'</font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C to
|
||||
quit<b>)</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Приложение командной строки `fastapi` это и есть **FastAPI CLI**.
|
||||
|
||||
FastAPI CLI берет путь к вашей Python-программе (напр. `main.py`) и автоматически находит объект `FastAPI` (обычно это `app`), затем определяет правильный процесс импорта и запускает сервер приложения.
|
||||
|
||||
Для работы в production окружении вместо `fastapi dev` нужно использовать `fastapi run`. 🚀
|
||||
|
||||
Внутри **FastAPI CLI** используется <a href="https://www.uvicorn.org" class="external-link" target="_blank">Uvicorn</a>, высокопроизводительный, готовый к работе в production сервер ASGI. 😎
|
||||
|
||||
## `fastapi dev`
|
||||
|
||||
Вызов `fastapi dev` запускает режим разработки.
|
||||
|
||||
По умолчанию включена автоматическая перезагрузка (**auto-reload**), благодаря этому при изменении кода происходит перезагрузка сервера приложения. Эта установка требует значительных ресурсов и делает систему менее стабильной. Используйте её только при разработке. Приложение слушает входящие подключения на IP `127.0.0.1`. Это IP адрес вашей машины, предназначенный для внутренних коммуникаций (`localhost`).
|
||||
|
||||
## `fastapi run`
|
||||
|
||||
Вызов `fastapi run` по умолчанию запускает FastAPI в режиме production.
|
||||
|
||||
По умолчанию функция перезагрузки **auto-reload** отключена. Приложение слушает входящие подключения на IP `0.0.0.0`, т.е. на всех доступных адресах компьютера. Таким образом, приложение будет находиться в публичном доступе для любого, кто может подсоединиться к вашей машине. Продуктовые приложения запускаются именно так, например, с помощью контейнеров.
|
||||
|
||||
В большинстве случаев вы будете (и должны) использовать прокси-сервер ("termination proxy"), который будет поддерживать HTTPS поверх вашего приложения. Всё будет зависеть от того, как вы развертываете приложение: за вас это либо сделает ваш провайдер, либо вам придется сделать настройки самостоятельно.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Вы можете больше узнать об этом в документации по развертыванию приложений [deployment documentation](deployment/index.md){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
|
@ -14,71 +14,7 @@
|
|||
|
||||
Объявите нужные **query-параметры** в **Pydantic-модели**, а после аннотируйте параметр как `Query`:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="9-13 17"
|
||||
{!> ../../docs_src/query_param_models/tutorial001_an_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python hl_lines="8-12 16"
|
||||
{!> ../../docs_src/query_param_models/tutorial001_an_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python hl_lines="10-14 18"
|
||||
{!> ../../docs_src/query_param_models/tutorial001_an.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.10+ без Annotated
|
||||
|
||||
/// tip | Совет
|
||||
|
||||
При возможности используйте версию с `Annotated`.
|
||||
|
||||
///
|
||||
|
||||
```Python hl_lines="9-13 17"
|
||||
{!> ../../docs_src/query_param_models/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+ без Annotated
|
||||
|
||||
/// tip | Совет
|
||||
|
||||
При возможности используйте версию с `Annotated`.
|
||||
|
||||
///
|
||||
|
||||
```Python hl_lines="8-12 16"
|
||||
{!> ../../docs_src/query_param_models/tutorial001_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+ без Annotated
|
||||
|
||||
/// tip | Совет
|
||||
|
||||
При возможности используйте версию с `Annotated`.
|
||||
|
||||
///
|
||||
|
||||
```Python hl_lines="9-13 17"
|
||||
{!> ../../docs_src/query_param_models/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
{* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *}
|
||||
|
||||
**FastAPI извлечёт** данные соответствующие **каждому полю модели** из **query-параметров** запроса и выдаст вам объявленную Pydantic-модель заполненную ими.
|
||||
|
||||
|
|
@ -96,71 +32,7 @@
|
|||
|
||||
Вы можете сконфигурировать Pydantic-модель так, чтобы запретить (`forbid`) все дополнительные (`extra`) поля.
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="10"
|
||||
{!> ../../docs_src/query_param_models/tutorial002_an_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python hl_lines="9"
|
||||
{!> ../../docs_src/query_param_models/tutorial002_an_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python hl_lines="11"
|
||||
{!> ../../docs_src/query_param_models/tutorial002_an.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.10+ без Annotated
|
||||
|
||||
/// tip | Совет
|
||||
|
||||
При возможности используйте версию с `Annotated`.
|
||||
|
||||
///
|
||||
|
||||
```Python hl_lines="10"
|
||||
{!> ../../docs_src/query_param_models/tutorial002_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+ без Annotated
|
||||
|
||||
/// tip | Совет
|
||||
|
||||
При возможности используйте версию с `Annotated`.
|
||||
|
||||
///
|
||||
|
||||
```Python hl_lines="9"
|
||||
{!> ../../docs_src/query_param_models/tutorial002_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+ без Annotated
|
||||
|
||||
/// tip | Совет
|
||||
|
||||
При возможности используйте версию с `Annotated`.
|
||||
|
||||
///
|
||||
|
||||
```Python hl_lines="11"
|
||||
{!> ../../docs_src/query_param_models/tutorial002.py!}
|
||||
```
|
||||
|
||||
////
|
||||
{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *}
|
||||
|
||||
Если клиент попробует отправить **дополнительные** данные в **query-параметрах**, то в ответ он получит **ошибку**.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
# Данные текущего пользователя
|
||||
|
||||
В предыдущей главе система безопасности (основанная на системе внедрения зависимостей) передавала *функции, обрабатывающей эндпоинт,* `токен` в виде `строки`:
|
||||
|
||||
{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
|
||||
|
||||
Это пока что не слишком нам полезно. Давайте изменим код так, чтобы он возвращал нам данные пользователя, отправившего запрос.
|
||||
|
||||
## Создание модели пользователя
|
||||
|
||||
Сначала создадим Pydantic-модель пользователя.
|
||||
|
||||
Точно так же, как мы использовали Pydantic для объявления тел запросов, мы можем использовать его где угодно:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[5,12:6] *}
|
||||
|
||||
## Создание зависимости `get_current_user`
|
||||
|
||||
Давайте создадим зависимость `get_current_user`.
|
||||
|
||||
Помните, что у зависимостей могут быть подзависимости?
|
||||
|
||||
`get_current_user` как раз будет иметь подзависимость `oauth2_scheme`, которую мы создали ранее.
|
||||
|
||||
Аналогично тому, как мы делали это ранее в *обработчике эндпоинта* наша новая зависимость `get_current_user` будет получать `token` в виде `строки` от подзависимости `oauth2_scheme`:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *}
|
||||
|
||||
## Получение данных пользователя
|
||||
|
||||
`get_current_user` будет использовать созданную нами (ненастоящую) служебную функцию, которая принимает токен в виде `строки` и возвращает нашу Pydantic-модель `User`:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *}
|
||||
|
||||
## Внедрение зависимости текущего пользователя
|
||||
|
||||
Теперь мы можем использовать тот же `Depends` с нашей зависимостью `get_current_user` в *функции обрабатывающей эндпоинт*:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *}
|
||||
|
||||
Обратите внимание, что мы объявляем тип переменной `current_user` как Pydantic-модель `User`.
|
||||
|
||||
Это поможет выполнить внутри функции все проверки автозаполнения и типа.
|
||||
|
||||
/// tip | Подсказка
|
||||
Возможно, вы помните, что тело запроса также объявляется с помощью Pydantic-моделей.
|
||||
|
||||
В этом месте у **FastAPI** не возникнет проблем, потому что вы используете `Depends`.
|
||||
///
|
||||
|
||||
/// check | Заметка
|
||||
То, как устроена эта система зависимостей, позволяет нам иметь различные зависимости, которые возвращают модель `User`.
|
||||
|
||||
Мы не ограничены наличием только одной зависимости, которая может возвращать данные такого типа.
|
||||
///
|
||||
|
||||
## Другие модели
|
||||
|
||||
Теперь вы можете получать информацию о текущем пользователе непосредственно в *функции обрабатывающей эндпоинт* и работать с механизмами безопасности на уровне **Внедрения Зависимостей**, используя `Depends`.
|
||||
|
||||
Причем для обеспечения требований безопасности можно использовать любую модель или данные (в данном случае - Pydantic-модель `User`).
|
||||
|
||||
Но вы не ограничены использованием какой-то конкретной моделью данных, классом или типом.
|
||||
|
||||
Вы хотите использовать в модели `id` и `email`, а `username` вам не нужен? Ну разумеется. Воспользуйтесь тем же инструментарием.
|
||||
|
||||
Вам нужны только `строки`? Или только `словари`? Или непосредственно экземпляр модели класса базы данных? Все это работает точно также.
|
||||
|
||||
У вас нет пользователей, которые входят в ваше приложение, а только роботы, боты или другие системы, у которых есть только токен доступа? Опять же, все работает одинаково.
|
||||
|
||||
Просто используйте любую модель, любой класс, любую базу данных, которые нужны для вашего приложения. Система внедрения зависимостей **FastAPI** поможет вам в этом.
|
||||
|
||||
## Размер кода
|
||||
|
||||
Этот пример может показаться многословным. Следует иметь в виду, что в одном файле мы смешиваем безопасность, модели данных, служебные функции и *эндпоинты*.
|
||||
|
||||
Но вот ключевой момент:
|
||||
|
||||
Все, что касается безопасности и внедрения зависимостей, пишется один раз.
|
||||
|
||||
И вы можете сделать его настолько сложным, насколько захотите. И все это будет написано только один раз, в одном месте, со всей своей гибкостью.
|
||||
|
||||
И у вас могут быть тысячи конечных точек (*эндпоинтов*), использующих одну и ту же систему безопасности.
|
||||
|
||||
И все они (или любая их часть по вашему желанию) могут воспользоваться преимуществами повторного использования этих зависимостей или любых других зависимостей, которые вы создадите.
|
||||
|
||||
И все эти тысячи *эндпоинтов* могут составлять всего 3 строки:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *}
|
||||
|
||||
## Резюме
|
||||
|
||||
Теперь вы можете получать данные о текущем пользователе непосредственно в своей *функции обработчике эндпоинта*.
|
||||
|
||||
Мы уже на полпути к этому.
|
||||
|
||||
Осталось лишь добавить *эндпоинт* для отправки пользователем/клиентом своих `имени пользователя` и `пароля`.
|
||||
|
||||
Это будет рассмотрено в следующем разделе.
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
# OAuth2 с паролем (и хешированием), Bearer с JWT-токенами
|
||||
|
||||
Теперь, когда у нас определен процесс обеспечения безопасности, давайте сделаем приложение действительно безопасным, используя токены <abbr title="JSON Web Tokens">JWT</abbr> и безопасное хеширование паролей.
|
||||
|
||||
Этот код можно реально использовать в своем приложении, сохранять хэши паролей в базе данных и т.д.
|
||||
|
||||
Мы продолжим разбираться, начиная с того места, на котором остановились в предыдущей главе.
|
||||
|
||||
## Про JWT
|
||||
|
||||
JWT означает "JSON Web Tokens".
|
||||
|
||||
Это стандарт для кодирования JSON-объекта в виде длинной строки без пробелов. Выглядит это следующим образом:
|
||||
|
||||
```
|
||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|
||||
```
|
||||
|
||||
Он не зашифрован, поэтому любой человек может восстановить информацию из его содержимого.
|
||||
|
||||
Но он подписан. Следовательно, когда вы получаете токен, который вы эмитировали (выдавали), вы можете убедиться, что это именно вы его эмитировали.
|
||||
|
||||
Таким образом, можно создать токен со сроком действия, скажем, 1 неделя. А когда пользователь вернется на следующий день с тем же токеном, вы будете знать, что он все еще авторизирован в вашей системе.
|
||||
|
||||
Через неделю срок действия токена истечет, пользователь не будет авторизован и ему придется заново входить в систему, чтобы получить новый токен. А если пользователь (или третье лицо) попытается модифицировать токен, чтобы изменить срок действия, вы сможете это обнаружить, поскольку подписи не будут совпадать.
|
||||
|
||||
Если вы хотите поиграть с JWT-токенами и посмотреть, как они работают, посмотрите <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a>.
|
||||
|
||||
## Установка `PyJWT`
|
||||
|
||||
Нам необходимо установить `pyjwt` для генерации и проверки JWT-токенов на языке Python.
|
||||
|
||||
Убедитесь, что вы создали [виртуальное окружение](../../virtual-environments.md){.internal-link target=_blank}, активируйте его, а затем установите `pyjwt`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install pyjwt
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// info | Дополнительная информация
|
||||
Если вы планируете использовать алгоритмы цифровой подписи, такие как RSA или ECDSA, вам следует установить зависимость библиотеки криптографии `pyjwt[crypto]`.
|
||||
|
||||
Подробнее об этом можно прочитать в <a href=«https://pyjwt.readthedocs.io/en/latest/installation.html» class=«external-link» target=«_blank»>документации по установке PyJWT</a>.
|
||||
///
|
||||
|
||||
## Хеширование паролей
|
||||
|
||||
"Хеширование" означает преобразование некоторого содержимого (в данном случае пароля) в последовательность байтов (просто строку), которая выглядит как тарабарщина.
|
||||
|
||||
Каждый раз, когда вы передаете точно такое же содержимое (точно такой же пароль), вы получаете точно такую же тарабарщину.
|
||||
|
||||
Но преобразовать тарабарщину обратно в пароль невозможно.
|
||||
|
||||
### Для чего нужно хеширование паролей
|
||||
|
||||
Если ваша база данных будет украдена, то вор не получит пароли пользователей в открытом виде, а только их хэши.
|
||||
|
||||
Таким образом, вор не сможет использовать этот пароль в другой системе (поскольку многие пользователи везде используют один и тот же пароль, это было бы опасно).
|
||||
|
||||
## Установка `passlib`
|
||||
|
||||
PassLib - это отличный пакет Python для работы с хэшами паролей.
|
||||
|
||||
Он поддерживает множество безопасных алгоритмов хеширования и утилит для работы с ними.
|
||||
|
||||
Рекомендуемый алгоритм - "Bcrypt".
|
||||
|
||||
Убедитесь, что вы создали и активировали виртуальное окружение, и затем установите PassLib вместе с Bcrypt:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "passlib[bcrypt]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// tip | Подсказка
|
||||
С помощью `passlib` можно даже настроить его на чтение паролей, созданных **Django**, плагином безопасности **Flask** или многими другими библиотеками.
|
||||
|
||||
Таким образом, вы сможете, например, совместно использовать одни и те же данные из приложения Django в базе данных с приложением FastAPI. Или постепенно мигрировать Django-приложение, используя ту же базу данных.
|
||||
|
||||
При этом пользователи смогут одновременно входить в систему как из приложения Django, так и из приложения **FastAPI**.
|
||||
///
|
||||
|
||||
## Хеширование и проверка паролей
|
||||
|
||||
Импортируйте необходимые инструменты из `passlib`.
|
||||
|
||||
Создайте "контекст" PassLib. Именно он будет использоваться для хэширования и проверки паролей.
|
||||
|
||||
/// tip | Подсказка
|
||||
Контекст PassLib также имеет функциональность для использования различных алгоритмов хеширования, в том числе и устаревших, только для возможности их проверки и т.д.
|
||||
|
||||
Например, вы можете использовать его для чтения и проверки паролей, сгенерированных другой системой (например, Django), но хэшировать все новые пароли другим алгоритмом, например Bcrypt.
|
||||
|
||||
И при этом быть совместимым со всеми этими системами.
|
||||
///
|
||||
|
||||
Создайте служебную функцию для хэширования пароля, поступающего от пользователя.
|
||||
|
||||
А затем создайте другую - для проверки соответствия полученного пароля и хранимого хэша.
|
||||
|
||||
И еще одну - для аутентификации и возврата пользователя.
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *}
|
||||
|
||||
/// note | Технические детали
|
||||
Если проверить новую (фальшивую) базу данных `fake_users_db`, то можно увидеть, как теперь выглядит хэшированный пароль: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`.
|
||||
///
|
||||
|
||||
## Работа с JWT токенами
|
||||
|
||||
Импортируйте установленные модули.
|
||||
|
||||
Создайте случайный секретный ключ, который будет использоваться для подписи JWT-токенов.
|
||||
|
||||
Для генерации безопасного случайного секретного ключа используйте команду:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ openssl rand -hex 32
|
||||
|
||||
09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
И скопируйте полученный результат в переменную `SECRET_KEY` (не используйте тот, что в примере).
|
||||
|
||||
Создайте переменную `ALGORITHM` с алгоритмом, используемым для подписи JWT-токена, и установите для нее значение `"HS256"`.
|
||||
|
||||
Создайте переменную для срока действия токена.
|
||||
|
||||
Определите Pydantic Model, которая будет использоваться для формирования ответа на запрос на получение токена.
|
||||
|
||||
Создайте служебную функцию для генерации нового токена доступа.
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *}
|
||||
|
||||
## Обновление зависимостей
|
||||
|
||||
Обновите `get_current_user` для получения того же токена, что и раньше, но на этот раз с использованием JWT-токенов.
|
||||
|
||||
Декодируйте полученный токен, проверьте его и верните текущего пользователя.
|
||||
|
||||
Если токен недействителен, то сразу же верните HTTP-ошибку.
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *}
|
||||
|
||||
## Обновление *операции пути* `/token`
|
||||
|
||||
Создайте `timedelta` со временем истечения срока действия токена.
|
||||
|
||||
Создайте реальный токен доступа JWT и верните его
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *}
|
||||
|
||||
### Технические подробности о JWT ключе `sub`
|
||||
|
||||
В спецификации JWT говорится, что существует ключ `sub`, содержащий субъект токена.
|
||||
|
||||
Его использование необязательно, но это именно то место, куда вы должны поместить идентификатор пользователя, и поэтому мы здесь его и используем.
|
||||
|
||||
JWT может использоваться и для других целей, помимо идентификации пользователя и предоставления ему возможности выполнять операции непосредственно в вашем API.
|
||||
|
||||
Например, вы могли бы определить "автомобиль" или "запись в блоге".
|
||||
|
||||
Затем вы могли бы добавить права доступа к этой сущности, например "управлять" (для автомобиля) или "редактировать" (для блога).
|
||||
|
||||
Затем вы могли бы передать этот JWT-токен пользователю (или боту), и они использовали бы его для выполнения определенных действий (управление автомобилем или редактирование запись в блоге), даже не имея учетной записи, просто используя JWT-токен, сгенерированный вашим API.
|
||||
|
||||
Используя эти идеи, JWT можно применять для гораздо более сложных сценариев.
|
||||
|
||||
В отдельных случаях несколько сущностей могут иметь один и тот же идентификатор, скажем, `foo` (пользователь `foo`, автомобиль `foo` и запись в блоге `foo`).
|
||||
|
||||
Поэтому, чтобы избежать коллизий идентификаторов, при создании JWT-токена для пользователя можно добавить префикс `username` к значению ключа `sub`. Таким образом, в данном примере значение `sub` было бы `username:johndoe`.
|
||||
|
||||
Важно помнить, что ключ `sub` должен иметь уникальный идентификатор для всего приложения и представлять собой строку.
|
||||
|
||||
## Проверка в действии
|
||||
|
||||
Запустите сервер и перейдите к документации: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
Вы увидите пользовательский интерфейс вида:
|
||||
|
||||
<img src="/img/tutorial/security/image07.png">
|
||||
|
||||
Пройдите авторизацию так же, как делали раньше.
|
||||
|
||||
Используя учетные данные пользователя:
|
||||
|
||||
Username: `johndoe`
|
||||
Password: `secret`
|
||||
|
||||
/// check | Заметка
|
||||
Обратите внимание, что нигде в коде не используется открытый текст пароля "`secret`", мы используем только его хэшированную версию.
|
||||
///
|
||||
|
||||
<img src="/img/tutorial/security/image08.png">
|
||||
|
||||
Вызвав эндпоинт `/users/me/`, вы получите ответ в виде:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"username": "johndoe",
|
||||
"email": "johndoe@example.com",
|
||||
"full_name": "John Doe",
|
||||
"disabled": false
|
||||
}
|
||||
```
|
||||
|
||||
<img src="/img/tutorial/security/image09.png">
|
||||
|
||||
Если открыть инструменты разработчика, то можно увидеть, что передаваемые данные включают только токен, пароль передается только в первом запросе для аутентификации пользователя и получения токена доступа, но не в последующих:
|
||||
|
||||
<img src="/img/tutorial/security/image10.png">
|
||||
|
||||
/// note | Техническая информация
|
||||
Обратите внимание на заголовок `Authorization`, значение которого начинается с `Bearer`.
|
||||
///
|
||||
|
||||
## Продвинутое использование `scopes`
|
||||
|
||||
В OAuth2 существует понятие "диапазоны" ("`scopes`").
|
||||
|
||||
С их помощью можно добавить определенный набор разрешений к JWT-токену.
|
||||
|
||||
Затем вы можете передать этот токен непосредственно пользователю или третьей стороне для взаимодействия с вашим API с определенным набором ограничений.
|
||||
|
||||
О том, как их использовать и как они интегрированы в **FastAPI**, читайте далее в **Руководстве пользователя**.
|
||||
|
||||
## Резюме
|
||||
|
||||
С учетом того, что вы видели до сих пор, вы можете создать безопасное приложение **FastAPI**, используя такие стандарты, как OAuth2 и JWT.
|
||||
|
||||
Практически в любом фреймворке работа с безопасностью довольно быстро превращается в сложную тему.
|
||||
|
||||
Многие пакеты, сильно упрощающие эту задачу, вынуждены идти на многочисленные компромиссы с моделью данных, с базой данных и с доступным функционалом. Некоторые из этих пакетов, которые пытаются уж слишком все упростить, имеют даже "дыры" в системе безопасности.
|
||||
|
||||
---
|
||||
|
||||
**FastAPI** не делает уступок ни одной базе данных, модели данных или инструментарию.
|
||||
|
||||
Он предоставляет вам полную свободу действий, позволяя выбирать то, что лучше всего подходит для вашего проекта.
|
||||
|
||||
Вы можете напрямую использовать многие хорошо поддерживаемые и широко распространенные пакеты, такие как `passlib` и `PyJWT`, поскольку **FastAPI** не требует сложных механизмов для интеграции внешних пакетов.
|
||||
|
||||
Напротив, он предоставляет инструменты, позволяющие максимально упростить этот процесс без ущерба для гибкости, надежности и безопасности.
|
||||
|
||||
При этом вы можете использовать и реализовывать безопасные стандартные протоколы, такие как OAuth2, относительно простым способом.
|
||||
|
||||
В **Руководстве пользователя** вы можете узнать больше о том, как использовать "диапазоны" ("`scopes`") OAuth2 для создания более точно настроенной системы разрешений в соответствии с теми же стандартами. OAuth2 с диапазонами - это механизм, используемый многими крупными провайдерами сервиса аутентификации, такими как Facebook, Google, GitHub, Microsoft, Twitter и др., для авторизации сторонних приложений на взаимодействие с их API от имени их пользователей.
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
# Простая авторизация по протоколу OAuth2 с токеном типа Bearer
|
||||
|
||||
Теперь, отталкиваясь от предыдущей главы, добавим недостающие части, чтобы получить безопасную систему.
|
||||
|
||||
## Получение `имени пользователя` и `пароля`
|
||||
|
||||
Для получения `имени пользователя` и `пароля` мы будем использовать утилиты безопасности **FastAPI**.
|
||||
|
||||
Протокол OAuth2 определяет, что при использовании "аутентификации по паролю" (которую мы и используем) клиент/пользователь должен передавать поля `username` и `password` в полях формы.
|
||||
|
||||
В спецификации сказано, что поля должны быть названы именно так. Поэтому `user-name` или `email` работать не будут.
|
||||
|
||||
Но не волнуйтесь, вы можете показать его конечным пользователям во фронтенде в том виде, в котором хотите.
|
||||
|
||||
А ваши модели баз данных могут использовать любые другие имена.
|
||||
|
||||
Но при авторизации согласно спецификации, требуется использовать именно эти имена, что даст нам возможность воспользоваться встроенной системой документации API.
|
||||
|
||||
В спецификации также указано, что `username` и `password` должны передаваться в виде данных формы (так что никакого JSON здесь нет).
|
||||
|
||||
### Oбласть видимости (scope)
|
||||
|
||||
В спецификации также говорится, что клиент может передать еще одно поле формы "`scope`".
|
||||
|
||||
Имя поля формы - `scope` (в единственном числе), но на самом деле это длинная строка, состоящая из отдельных областей видимости (scopes), разделенных пробелами.
|
||||
|
||||
Каждая "область видимости" (scope) - это просто строка (без пробелов).
|
||||
|
||||
Обычно они используются для указания уровней доступа, например:
|
||||
|
||||
* `users:read` или `users:write` являются распространенными примерами.
|
||||
* `instagram_basic` используется Facebook / Instagram.
|
||||
* `https://www.googleapis.com/auth/drive` используется компанией Google.
|
||||
|
||||
/// info | Дополнительнаяя информация
|
||||
В OAuth2 "scope" - это просто строка, которая уточняет уровень доступа.
|
||||
|
||||
Не имеет значения, содержит ли он другие символы, например `:`, или является ли он URL.
|
||||
|
||||
Эти детали зависят от конкретной реализации.
|
||||
|
||||
Для OAuth2 это просто строки.
|
||||
///
|
||||
|
||||
## Код получения `имени пользователя` и `пароля`
|
||||
|
||||
Для решения задачи давайте воспользуемся утилитами, предоставляемыми **FastAPI**.
|
||||
|
||||
### `OAuth2PasswordRequestForm`
|
||||
|
||||
Сначала импортируйте `OAuth2PasswordRequestForm` и затем используйте ее как зависимость с `Depends` в *эндпоинте* `/token`:
|
||||
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *}
|
||||
|
||||
`OAuth2PasswordRequestForm` - это класс для использования в качестве зависимости для *функции обрабатывающей эндпоинт*, который определяет тело формы со следующими полями:
|
||||
|
||||
* `username`.
|
||||
* `password`.
|
||||
* Необязательное поле `scope` в виде большой строки, состоящей из строк, разделенных пробелами.
|
||||
* Необязательное поле `grant_type`.
|
||||
|
||||
/// tip | Подсказка
|
||||
По спецификации OAuth2 поле `grant_type` является обязательным и содержит фиксированное значение `password`, но `OAuth2PasswordRequestForm` не обеспечивает этого.
|
||||
|
||||
Если вам необходимо использовать `grant_type`, воспользуйтесь `OAuth2PasswordRequestFormStrict` вместо `OAuth2PasswordRequestForm`.
|
||||
///
|
||||
|
||||
* Необязательное поле `client_id` (в нашем примере он не нужен).
|
||||
* Необязательное поле `client_secret` (в нашем примере он не нужен).
|
||||
|
||||
/// info | Дополнительная информация
|
||||
Форма `OAuth2PasswordRequestForm` не является специальным классом для **FastAPI**, как `OAuth2PasswordBearer`.
|
||||
|
||||
`OAuth2PasswordBearer` указывает **FastAPI**, что это схема безопасности. Следовательно, она будет добавлена в OpenAPI.
|
||||
|
||||
Но `OAuth2PasswordRequestForm` - это всего лишь класс зависимости, который вы могли бы написать самостоятельно или вы могли бы объявить параметры `Form` напрямую.
|
||||
|
||||
Но, поскольку это распространённый вариант использования, он предоставляется **FastAPI** напрямую, просто чтобы облегчить задачу.
|
||||
///
|
||||
|
||||
### Использование данных формы
|
||||
|
||||
/// tip | Подсказка
|
||||
В экземпляре зависимого класса `OAuth2PasswordRequestForm` атрибут `scope`, состоящий из одной длинной строки, разделенной пробелами, заменен на атрибут `scopes`, состоящий из списка отдельных строк, каждая из которых соответствует определенному уровню доступа.
|
||||
|
||||
В данном примере мы не используем `scopes`, но если вам это необходимо, то такая функциональность имеется.
|
||||
///
|
||||
|
||||
Теперь получим данные о пользователе из (ненастоящей) базы данных, используя `username` из поля формы.
|
||||
|
||||
Если такого пользователя нет, то мы возвращаем ошибку "неверное имя пользователя или пароль".
|
||||
|
||||
Для ошибки мы используем исключение `HTTPException`:
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *}
|
||||
|
||||
### Проверка пароля
|
||||
|
||||
На данный момент у нас есть данные о пользователе из нашей базы данных, но мы еще не проверили пароль.
|
||||
|
||||
Давайте сначала поместим эти данные в модель Pydantic `UserInDB`.
|
||||
|
||||
Ни в коем случае нельзя сохранять пароли в открытом виде, поэтому мы будем использовать (пока что ненастоящую) систему хеширования паролей.
|
||||
|
||||
Если пароли не совпадают, мы возвращаем ту же ошибку.
|
||||
|
||||
#### Хеширование паролей
|
||||
|
||||
"Хеширование" означает: преобразование некоторого содержимого (в данном случае пароля) в последовательность байтов (просто строку), которая выглядит как тарабарщина.
|
||||
|
||||
Каждый раз, когда вы передаете точно такое же содержимое (точно такой же пароль), вы получаете точно такую же тарабарщину.
|
||||
|
||||
Но преобразовать тарабарщину обратно в пароль невозможно.
|
||||
|
||||
##### Зачем использовать хеширование паролей
|
||||
|
||||
Если ваша база данных будет украдена, то у вора не будет паролей пользователей в открытом виде, только хэши.
|
||||
|
||||
Таким образом, вор не сможет использовать эти же пароли в другой системе (поскольку многие пользователи используют одни и те же пароли повсеместно, это было бы опасно).
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *}
|
||||
|
||||
#### Про `**user_dict`
|
||||
|
||||
`UserInDB(**user_dict)` означает:
|
||||
|
||||
*Передавать ключи и значения `user_dict` непосредственно в качестве аргументов ключ-значение, что эквивалентно:*
|
||||
|
||||
```Python
|
||||
UserInDB(
|
||||
username = user_dict["username"],
|
||||
email = user_dict["email"],
|
||||
full_name = user_dict["full_name"],
|
||||
disabled = user_dict["disabled"],
|
||||
hashed_password = user_dict["hashed_password"],
|
||||
)
|
||||
```
|
||||
|
||||
/// info | Дополнительная информация
|
||||
Более полное объяснение `**user_dict` можно найти в [документации к **Дополнительным моделям**](../extra-models.md#about-user_indict){.internal-link target=_blank}.
|
||||
///
|
||||
|
||||
## Возврат токена
|
||||
|
||||
Ответ эндпоинта `token` должен представлять собой объект в формате JSON.
|
||||
|
||||
Он должен иметь `token_type`. В нашем случае, поскольку мы используем токены типа "Bearer", тип токена должен быть "`bearer`".
|
||||
|
||||
И в нем должна быть строка `access_token`, содержащая наш токен доступа.
|
||||
|
||||
В этом простом примере мы нарушим все правила безопасности, и будем считать, что имя пользователя (username) полностью соответствует токену (token)
|
||||
|
||||
/// tip | Подсказка
|
||||
В следующей главе мы рассмотрим реальную защищенную реализацию с хешированием паролей и токенами <abbr title="JSON Web Tokens">JWT</abbr>.
|
||||
|
||||
Но пока давайте остановимся на необходимых нам деталях.
|
||||
///
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *}
|
||||
|
||||
/// tip | Подсказка
|
||||
Согласно спецификации, вы должны возвращать JSON с `access_token` и `token_type`, как в данном примере.
|
||||
|
||||
Это то, что вы должны сделать сами в своем коде и убедиться, что вы используете эти JSON-ключи.
|
||||
|
||||
Это практически единственное, что нужно не забывать делать самостоятельно, чтобы следовать требованиям спецификации.
|
||||
|
||||
Все остальное за вас сделает **FastAPI**.
|
||||
///
|
||||
|
||||
## Обновление зависимостей
|
||||
|
||||
Теперь мы обновим наши зависимости.
|
||||
|
||||
Мы хотим получить значение `current_user` *только* если этот пользователь активен.
|
||||
|
||||
Поэтому мы создаем дополнительную зависимость `get_current_active_user`, которая, в свою очередь, использует в качестве зависимости `get_current_user`.
|
||||
|
||||
Обе эти зависимости просто вернут HTTP-ошибку, если пользователь не существует или неактивен.
|
||||
|
||||
Таким образом, в нашем эндпоинте мы получим пользователя только в том случае, если он существует, правильно аутентифицирован и активен:
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *}
|
||||
|
||||
/// info | Дополнительная информация
|
||||
Дополнительный заголовок `WWW-Authenticate` со значением `Bearer`, который мы здесь возвращаем, также является частью спецификации.
|
||||
|
||||
Ответ сервера с HTTP-кодом 401 "UNAUTHORIZED" должен также возвращать заголовок `WWW-Authenticate`.
|
||||
|
||||
В случае с bearer-токенами (наш случай) значение этого заголовка должно быть `Bearer`.
|
||||
|
||||
На самом деле этот дополнительный заголовок можно пропустить и все будет работать.
|
||||
|
||||
Но он приведён здесь для соответствия спецификации.
|
||||
|
||||
Кроме того, могут существовать инструменты, которые ожидают его и могут использовать, и это может быть полезно для вас или ваших пользователей сейчас или в будущем.
|
||||
|
||||
В этом и заключается преимущество стандартов...
|
||||
///
|
||||
|
||||
## Посмотим как это работает
|
||||
|
||||
Откроем интерактивную документацию: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
### Аутентификация
|
||||
|
||||
Нажмите кнопку "Авторизация".
|
||||
|
||||
Используйте учётные данные:
|
||||
|
||||
Пользователь: `johndoe`
|
||||
|
||||
Пароль: `secret`
|
||||
|
||||
<img src="/img/tutorial/security/image04.png">
|
||||
|
||||
После авторизации в системе вы увидите следующее:
|
||||
|
||||
<img src="/img/tutorial/security/image05.png">
|
||||
|
||||
### Получение собственных пользовательских данных
|
||||
|
||||
Теперь, используя операцию `GET` с путем `/users/me`, вы получите данные пользователя, например:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"username": "johndoe",
|
||||
"email": "johndoe@example.com",
|
||||
"full_name": "John Doe",
|
||||
"disabled": false,
|
||||
"hashed_password": "fakehashedsecret"
|
||||
}
|
||||
```
|
||||
|
||||
<img src="/img/tutorial/security/image06.png">
|
||||
|
||||
Если щелкнуть на значке замка и выйти из системы, а затем попытаться выполнить ту же операцию ещё раз, то будет выдана ошибка HTTP 401:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": "Not authenticated"
|
||||
}
|
||||
```
|
||||
|
||||
### Неактивный пользователь
|
||||
|
||||
Теперь попробуйте пройти аутентификацию с неактивным пользователем:
|
||||
|
||||
Пользователь: `alice`
|
||||
|
||||
Пароль: `secret2`
|
||||
|
||||
И попробуйте использовать операцию `GET` с путем `/users/me`.
|
||||
|
||||
Вы получите ошибку "Inactive user", как тут:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": "Inactive user"
|
||||
}
|
||||
```
|
||||
|
||||
## Резюме
|
||||
|
||||
Теперь у вас есть инструменты для реализации полноценной системы безопасности на основе `имени пользователя` и `пароля` для вашего API.
|
||||
|
||||
Используя эти средства, можно сделать систему безопасности совместимой с любой базой данных, с любым пользователем или моделью данных.
|
||||
|
||||
Единственным недостатком нашей системы является то, что она всё ещё не защищена.
|
||||
|
||||
В следующей главе вы увидите, как использовать библиотеку безопасного хеширования паролей и токены <abbr title="JSON Web Tokens">JWT</abbr>.
|
||||
|
|
@ -0,0 +1,839 @@
|
|||
# Виртуальная среда
|
||||
|
||||
При работе с проектами в Python рекомендуется использовать **виртуальную среду разработки** (или какой-нибудь другой подобный механизм). Это нужно для того, чтобы изолировать устанавливаемые пакеты для каждого отдельного проекта.
|
||||
|
||||
/// info | Дополнительная информация
|
||||
|
||||
Если вы уже знакомы с виртуальными средами разработки, знаете как их создавать и использовать, то вы можете свободно пропустить данный раздел. 🤓
|
||||
|
||||
///
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
**Виртуальная среда** и **переменная окружения** это две разные вещи.
|
||||
|
||||
**Переменная окружения** это системная переменная, которую могут использовать программы.
|
||||
|
||||
**Виртуальная среда** это папка, содержащая файлы.
|
||||
|
||||
///
|
||||
|
||||
/// info | Дополнительная информация
|
||||
|
||||
В этом разделе мы научим вас пользоваться виртуальными средами разработки и расскажем, как они работают.
|
||||
|
||||
Если же вы готовы воспользоваться инструментом, **который умеет управлять всем, что касается Python-проектов**,
|
||||
(включая установку Python), то попробуйте <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>.
|
||||
|
||||
///
|
||||
|
||||
## Создание проекта
|
||||
|
||||
В первую очередь, создайте директорию для вашего проекта.
|
||||
|
||||
Я обычно создаю папку под названием `code` внутри моего домашнего каталога `/home/user`.
|
||||
|
||||
Затем внутри данной папки я создаю отдельную директорию под каждый свой проект.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Перейдите в домашний каталог
|
||||
$ cd
|
||||
// Создайте отдельную папку под все будущие программные проекты (code)
|
||||
$ mkdir code
|
||||
// Войдите в директорию code
|
||||
$ cd code
|
||||
// Создайте директрорию под данный проект (awesome-project)
|
||||
$ mkdir awesome-project
|
||||
// Перейдите в созданную директорию проекта
|
||||
$ cd awesome-project
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Создание виртуальной среды разработки
|
||||
|
||||
Начиная работу с Python-проектом, сразу же создавайте виртуальную среду разработки
|
||||
**<abbr title="есть и другие опции, но мы рассмотрим наиболее простой вариант">внутри вашего проекта</abbr>**.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Виртуальная среда разработки создается один раз, и в дальнейшем, работая с проектом, этого больше делать не придется.
|
||||
|
||||
///
|
||||
|
||||
//// tab | `venv`
|
||||
|
||||
Для создания виртуальной среды вы можете воспользоваться модулем `venv`, который является частью встроенной библиотеки Python.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python -m venv .venv
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// details | Что делает эта команда?
|
||||
|
||||
* `python`: использовать программу под именем `python`
|
||||
* `-m`: вызывать модуль как скрипт, в следующей инструкции мы скажем какой именно модуль вызвать
|
||||
* `venv`: использовать модуль под названием `venv`, который обычно устанавливается вместе с Python
|
||||
* `.venv`: создать виртуальную среду разработки в новой директории `.venv`
|
||||
|
||||
///
|
||||
|
||||
////
|
||||
|
||||
//// tab | `uv`
|
||||
|
||||
Если вы установили <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>, то вы можете им воспользоваться для создания виртуальной среды разработки.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uv venv
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
По умолчанию `uv` создаст виртуальную среду разработки в папке под названием `.venv`.
|
||||
|
||||
Но вы можете это изменить, передав дополнительный аргумент с именем директории.
|
||||
|
||||
///
|
||||
|
||||
////
|
||||
|
||||
Данная команда создаст новую виртуальную среду разработки в папке `.venv`.
|
||||
|
||||
/// details | `.venv` или другое имя?
|
||||
|
||||
Вы можете поместить виртуальную среду разработки в папку с другим именем, но традиционным (конвенциональным) названием является `.venv` .
|
||||
|
||||
///
|
||||
|
||||
## Активация виртуальной среды разработки
|
||||
|
||||
Активируйте виртуальную среду разработки, и тогда любая запускаемая Python-команда или устанавливаемый пакет будут ее использовать.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
При работе над проектом делайте это **каждый раз** при запуске **новой сессии в терминале**.
|
||||
|
||||
///
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ source .venv/bin/activate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ .venv\Scripts\Activate.ps1
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows Bash
|
||||
|
||||
Или при использовании Bash для Windows (напр. <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>):
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ source .venv/Scripts/activate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
## Проверка активации виртуальной среды
|
||||
|
||||
Проверьте, активна ли виртуальная среда (удостоверимся, что предыдущая команда сработала).
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Убедитесь в том, что все работает так, как нужно и вы используете именно ту виртуальную среду разработки, которую нужно. Делать это необязательно, но желательно.
|
||||
|
||||
///
|
||||
|
||||
//// tab | Linux, macOS, Windows Bash
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ which python
|
||||
|
||||
/home/user/code/awesome-project/.venv/bin/python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Если данная команда показывает, что исполняемый файл `python` (`.venv\bin\python`), находится внутри виртуальной среды вашего проекта (у нас это `awesome-project`), значит все отработало как нужно. 🎉
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ Get-Command python
|
||||
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts\python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Если данная команда показывает, что исполняемый файл `python` (`.venv\Scripts\python`), находится внутри виртуальной среды вашего проекта (у нас это `awesome-project`), значит все отработало как нужно. 🎉
|
||||
|
||||
////
|
||||
|
||||
## Обновление `pip`
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Если вы используете <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>, то вы должны будете его использовать для установки пакетов вместо `pip`, поэтому обновлять `pip` вам ненужно. 😎
|
||||
|
||||
///
|
||||
|
||||
Если для установки пакетов вы используете `pip` (он устанавливается по умолчанию вместе с Python), то обновите `pip` до последней версии.
|
||||
|
||||
Большинство экзотических ошибок, возникающих при установке пакетов, устраняется предварительным обновлением `pip`.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Обычно это делается только один раз, сразу после создания виртуальной среды разработки.
|
||||
|
||||
///
|
||||
|
||||
Убедитесь в том, что виртуальная среда активирована (с помощью вышеуказанной команды) и запустите следующую команду:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python -m pip install --upgrade pip
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Добавление `.gitignore`
|
||||
|
||||
Если вы используете **Git** (а вы должны его использовать), то добавьте файл `.gitignore` и исключите из Git всё, что находится в папке `.venv`.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Если для создания виртуальной среды вы используете <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>, то для вас все уже сделано и вы можете пропустить этот шаг. 😎
|
||||
|
||||
///
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Это делается один раз, сразу после создания виртуальной среды разработки.
|
||||
|
||||
///
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ echo "*" > .venv/.gitignore
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// details | Что делает эта команда?
|
||||
|
||||
* `echo "*"`: напечатать `*` в консоли (следующий шаг это слегка изменит)
|
||||
* `>`: все что находится слева от `>` не печатать в консоль, но записать в файл находящийся справа от `>`
|
||||
* `.gitignore`: имя файла, в который нужно записать текст.
|
||||
|
||||
`*` в Git означает "всё". Т.е. будет проигнорировано всё, что содержится в папке `.venv`.
|
||||
|
||||
Данная команда создаст файл `.gitignore` следующего содержания:
|
||||
|
||||
```gitignore
|
||||
*
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
## Установка пакетов
|
||||
|
||||
После установки виртуальной среды, вы можете устанавливать в нее пакеты.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Сделайте это **один раз**, при установке или обновлении пакетов, нужных вашему проекту.
|
||||
|
||||
Если вам понадобится обновить версию пакета или добавить новый пакет, то вы должны будете **сделать это снова**.
|
||||
|
||||
///
|
||||
|
||||
### Установка пакетов напрямую
|
||||
|
||||
Если вы торопитесь и не хотите объявлять зависимости проекта в отдельном файле, то вы можете установить их напрямую.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Объявление пакетов, которые использует ваш проект, и их версий в отдельном файле (например, в `requirements.txt` или в `pyproject.toml`) - это отличная идея.
|
||||
|
||||
///
|
||||
|
||||
//// tab | `pip`
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "fastapi[standard]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | `uv`
|
||||
|
||||
Если вы используете <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uv pip install "fastapi[standard]"
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
### Установка из `requirements.txt`
|
||||
|
||||
Если у вас есть `requirements.txt`, то вы можете использовать его для установки пакетов.
|
||||
|
||||
//// tab | `pip`
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install -r requirements.txt
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | `uv`
|
||||
|
||||
Если вы используете <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uv pip install -r requirements.txt
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
/// details | `requirements.txt`
|
||||
|
||||
`requirements.txt` с парочкой пакетов внутри выглядит приблизительно так:
|
||||
|
||||
```requirements.txt
|
||||
fastapi[standard]==0.113.0
|
||||
pydantic==2.8.0
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
## Запуск программы
|
||||
|
||||
После активации виртуальной среды разработки вы можете запустить свою программу и она будет использовать версию Python и пакеты, установленные в виртуальной среде.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python main.py
|
||||
|
||||
Hello World
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Настройка редактора
|
||||
|
||||
Вероятно, вы захотите воспользоваться редактором. Убедитесь, что вы настроили его на использование той самой виртуальной среды, которую вы создали. (Скорее всего, она автоматически будет обнаружена). Это позволит вам использовать авто-завершение и выделение ошибок в редакторе.
|
||||
|
||||
Например:
|
||||
|
||||
* <a href="https://code.visualstudio.com/docs/python/environments#_select-and-activate-an-environment" class="external-link" target="_blank">VS Code</a>
|
||||
* <a href="https://www.jetbrains.com/help/pycharm/creating-virtual-environment.html" class="external-link" target="_blank">PyCharm</a>
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Обычно это делается один раз, при создании виртуальной среды разработки.
|
||||
|
||||
///
|
||||
|
||||
## Деактивация виртуальной среды разработки
|
||||
|
||||
По окончании работы над проектом вы можете деактивировать виртуальную среду.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ deactivate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Таким образом, при запуске `python`, будет использована версия `python` установленная глобально, а не из этой виртуальной среды вместе с установленными в ней пакетами.
|
||||
|
||||
## Все готово к работе
|
||||
|
||||
Теперь вы готовы к тому, чтобы начать работу над своим проектом.
|
||||
|
||||
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Хотите разобраться со всем, что написано выше?
|
||||
|
||||
Продолжайте читать. 👇🤓
|
||||
|
||||
///
|
||||
|
||||
## Зачем использовать виртуальную среду?
|
||||
|
||||
Для работы с FastAPI вам потребуется установить <a href="https://www.python.org/" class="external-link" target="_blank">Python</a>.
|
||||
|
||||
После этого вам нужно будет **установить** FastAPI и другие **пакеты**, которые вы собираетесь использовать.
|
||||
|
||||
Для установки пакетов обычно используют `pip`, который устанавливается вместе с Python (или же используют альтернативные решения).
|
||||
|
||||
Тем не менее, если вы просто используете `pip` напрямую, то пакеты будут установлены в **глобальное Python-окружение** (глобально установленный Python).
|
||||
|
||||
### Проблема
|
||||
|
||||
Так в чем же проблема с установкой пакетов в глобальную среду Python?
|
||||
|
||||
В какой-то момент вам, вероятно, придется писать множество разных программ, которые используют различные пакеты. 😱
|
||||
|
||||
Например, вы создаете проект `philosophers-stone`, который зависит от пакета под названием **`harry`, версии `1`**. Таким образом, вам нужно установить `harry`.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
stone(philosophers-stone) -->|requires| harry-1[harry v1]
|
||||
```
|
||||
|
||||
Затем, в какой-то момент, вы создаете другой проект под названием `prisoner-of-azkaban`, и этот проект тоже зависит от `harry`, но он уже использует **`harry` версии `3`**.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3]
|
||||
```
|
||||
|
||||
Проблема заключается в том, что при установке в глобальное окружение, а не в локальную виртуальную среду разработки, вам нужно будет выбирать, какую версию пакета `harry` устанавливать.
|
||||
|
||||
Если вам нужен `philosophers-stone`, то вам нужно сначала установить `harry` версии `1`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "harry==1"
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
И тогда в вашем глобальном окружении Python будет установлен `harry` версии `1`:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph global[global env]
|
||||
harry-1[harry v1]
|
||||
end
|
||||
subgraph stone-project[philosophers-stone project]
|
||||
stone(philosophers-stone) -->|requires| harry-1
|
||||
end
|
||||
```
|
||||
|
||||
Но если позднее вы захотите запустить `prisoner-of-azkaban`, то вам нужно будет удалить `harry` версии 1, и установить `harry` версии `3` (при установке пакета версии `3` поверх пакета версии `1`, пакет версии `1` удаляется автоматически).
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "harry==3"
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
И тогда, в вашей глобальной среде окружения Python, будет установлен пакет `harry` версии `3`.
|
||||
|
||||
И когда вы снова попытаетесь запустить `philosophers-stone`, то существует вероятность того, что он не будет работать, так как он использует `harry` версии `1`.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph global[global env]
|
||||
harry-1[<strike>harry v1</strike>]
|
||||
style harry-1 fill:#ccc,stroke-dasharray: 5 5
|
||||
harry-3[harry v3]
|
||||
end
|
||||
subgraph stone-project[philosophers-stone project]
|
||||
stone(philosophers-stone) -.-x|⛔️| harry-1
|
||||
end
|
||||
subgraph azkaban-project[prisoner-of-azkaban project]
|
||||
azkaban(prisoner-of-azkaban) --> |requires| harry-3
|
||||
end
|
||||
```
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
В пакетах Python очень часто стараются изо всех сил избегать внесения критических изменений в новые версии, но лучше перестраховаться и планово устанавливать новые версии, а затем запускать тесты, чтобы проверить, все ли работает правильно.
|
||||
|
||||
///
|
||||
|
||||
Теперь представьте, что это происходит со многими другими пакетами, которые используются в ваших проектах. С этим очень сложно справиться. И скорее всего, в конечном итоге вы будете запускать некоторые проекты с некоторыми несовместимыми зависимостями и не будете знать, почему что-то не работает.
|
||||
|
||||
Кроме того, в зависимости от вашей операционной системы (напр. Linux, Windows, macOS), она может поставляться с уже установленным Python. Вероятно, что в этом случае в ней уже установлены системные пакеты определенных версий. Если вы устанавливаете пакеты глобально, то вы можете **поломать** программы, являющиеся частью ОС.
|
||||
|
||||
## Куда устанавливаются пакеты?
|
||||
|
||||
Когда вы устанавливаете Python, то на вашей машине создается некоторое количество директорий, содержащих некоторое количество файлов.
|
||||
|
||||
Среди них есть каталоги, отвечающие за хранение всех устанавливаемых вами пакетов.
|
||||
|
||||
Когда вы запустите команду:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Не запускайте эту команду, это просто пример 🤓
|
||||
$ pip install "fastapi[standard]"
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
То будет скачан сжатый файл, содержащий код FastAPI, обычно скачивание происходит с <a href="https://pypi.org/project/fastapi/" class="external-link" target="_blank">PyPI</a>.
|
||||
|
||||
Также будут скачаны файлы, содержащие пакеты, которые использует FastAPI.
|
||||
|
||||
Затем все файлы будут извлечены и помещены в директорию на вашем компьютере.
|
||||
|
||||
По умолчанию эти файлы будут загружены и извлечены в один из каталогов установки Python, т.е. в глобальную среду.
|
||||
|
||||
## Что такое виртуальная среда разработки?
|
||||
|
||||
Решением проблемы размещения всех пакетов в глобальной среде будет использование отдельной виртуальной среды под каждый проект, над которым вы работаете.
|
||||
|
||||
Виртуальная среда это обычная папка, очень похожая на глобальную, куда вы можете устанавливать пакеты для вашего проекта.
|
||||
|
||||
Таким образом, каждый проект будет иметь свою отдельную виртуальную среду разработки (в директории `.venv`) вместе со своими пакетами.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph stone-project[philosophers-stone project]
|
||||
stone(philosophers-stone) --->|requires| harry-1
|
||||
subgraph venv1[.venv]
|
||||
harry-1[harry v1]
|
||||
end
|
||||
end
|
||||
subgraph azkaban-project[prisoner-of-azkaban project]
|
||||
azkaban(prisoner-of-azkaban) --->|requires| harry-3
|
||||
subgraph venv2[.venv]
|
||||
harry-3[harry v3]
|
||||
end
|
||||
end
|
||||
stone-project ~~~ azkaban-project
|
||||
```
|
||||
|
||||
## Что означает активация виртуальной среды?
|
||||
|
||||
Когда вы активируете виртуальную среду разработки, например, так:
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ source .venv/bin/activate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ .venv\Scripts\Activate.ps1
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows Bash
|
||||
|
||||
Или если вы воспользуетесь Bash под Windows (напр. <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>):
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ source .venv/Scripts/activate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
|
||||
Эта команда создаст или изменит некоторые [переменные окружения](environment-variables.md){.internal-link target=_blank}, которые будут доступны для последующих команд.
|
||||
|
||||
Одной из таких переменных является `PATH`.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Вы можете узнать больше о переменной окружения `PATH` в разделе [Переменные окружения](environment-variables.md#path-environment-variable){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
При активации виртуальной среды путь `.venv/bin` (для Linux и macOS) или `.venv\Scripts` (для Windows) добавляется в переменную окружения `PATH`.
|
||||
|
||||
Предположим, что до активации виртуальной среды переменная `PATH` выглядела так:
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
```plaintext
|
||||
/usr/bin:/bin:/usr/sbin:/sbin
|
||||
```
|
||||
|
||||
Это означает, что система ищет программы в следующих каталогах:
|
||||
|
||||
* `/usr/bin`
|
||||
* `/bin`
|
||||
* `/usr/sbin`
|
||||
* `/sbin`
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows
|
||||
|
||||
```plaintext
|
||||
C:\Windows\System32
|
||||
```
|
||||
|
||||
Это означает, что система ищет программы в:
|
||||
|
||||
* `C:\Windows\System32`
|
||||
|
||||
////
|
||||
|
||||
После активации виртуальной среды переменная окружение `PATH` будет выглядеть примерно так:
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
```plaintext
|
||||
/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin
|
||||
```
|
||||
|
||||
Это означает, что система теперь будет искать программы в:
|
||||
|
||||
```plaintext
|
||||
/home/user/code/awesome-project/.venv/bin
|
||||
```
|
||||
|
||||
прежде чем начать искать в других каталогах.
|
||||
|
||||
Таким образом, когда вы введете в консоль `python`, система будет искать Python в
|
||||
|
||||
```plaintext
|
||||
/home/user/code/awesome-project/.venv/bin/python
|
||||
```
|
||||
|
||||
и будет использовать именно его.
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows
|
||||
|
||||
```plaintext
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32
|
||||
```
|
||||
|
||||
Это означает, что система в первую очередь начнет искать программы в:
|
||||
|
||||
```plaintext
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts
|
||||
```
|
||||
|
||||
прежде чем начать искать в других директориях.
|
||||
|
||||
Таким образом, если вы введете в консоль команду `python`, то система найдет Python в:
|
||||
|
||||
```plaintext
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts\python
|
||||
```
|
||||
|
||||
и использует его.
|
||||
|
||||
////
|
||||
|
||||
Очень важной деталью является то, что путь к виртуальной среде будет помещен в самое начало переменной `PATH`. Система обнаружит данный путь к Python раньше, чем какой-либо другой. Таким образом, при запуске команды `python`, будет использован именно Python из виртуальной среды разработки, а не какой-нибудь другой (например, Python из глобальной среды)
|
||||
|
||||
Активация виртуальной среды разработки также меняет и несколько других вещей, но данная функция является основной.
|
||||
|
||||
## Проверка виртуальной среды
|
||||
|
||||
Когда вы проверяете активна ли виртуальная среда разработки, например, так:
|
||||
|
||||
//// tab | Linux, macOS, Windows Bash
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ which python
|
||||
|
||||
/home/user/code/awesome-project/.venv/bin/python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ Get-Command python
|
||||
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts\python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
Это означает, что будет использоваться `python` **из виртуальной среды разработки**.
|
||||
|
||||
Вы используете `which` для Linux и macOS и `Get-Command` для Windows PowerShell.
|
||||
|
||||
Эта команда работает следующим образом: она проверяет переменную окружения `PATH`, проходя по очереди каждый указанный путь в поисках программы под названием `python`. И когда она её находит, то возвращает путь к данной программе.
|
||||
|
||||
Основной момент при вызове команды `python` состоит в том, какой именно "`python`" будет запущен.
|
||||
|
||||
Таким образом, вы можете убедиться, что используете правильную виртуальную среду разработки.
|
||||
|
||||
/// tip | Подсказка
|
||||
|
||||
Легко активировать одну виртуальную среду, вызвать один Python и **перейти к следующему проекту**.
|
||||
|
||||
И следующий проект не будет работать потому, что вы используете **неправильный Python** из виртуальной среды другого проекта.
|
||||
|
||||
Так что, будет нелишним проверить, какой `python` вы используете. 🤓
|
||||
|
||||
///
|
||||
|
||||
## Зачем деактивируют виртуальную среду?
|
||||
|
||||
Предположим, что вы работаете над проектом `philosophers-stone`, **активируете виртуальную среду разработки**, устанавливаете пакеты и работаете с данной средой.
|
||||
|
||||
И позже вам понадобилось поработать с **другим проектом** `prisoner-of-azkaban`.
|
||||
|
||||
Вы переходите к этому проекту:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ cd ~/code/prisoner-of-azkaban
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Если вы не деактивировали виртуальное окружение проекта `philosophers-stone`, то при запуске `python` через консоль будет вызван Python из `philosophers-stone`
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ cd ~/code/prisoner-of-azkaban
|
||||
|
||||
$ python main.py
|
||||
|
||||
// Error importing sirius, it's not installed 😱
|
||||
Traceback (most recent call last):
|
||||
File "main.py", line 1, in <module>
|
||||
import sirius
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Но если вы деактивируете виртуальную среду разработки и активируете новую среду для `prisoner-of-askaban`, то вы тогда запустите Python из виртуального окружения `prisoner-of-azkaban`.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ cd ~/code/prisoner-of-azkaban
|
||||
|
||||
// Вам не требуется находится в старой директории для деактивации среды разработки, вы можете это сделать откуда угодно, даже из каталога другого проекта, в который вы перешли. 😎
|
||||
$ deactivate
|
||||
|
||||
// Активируйте виртуальную среду разработки в prisoner-of-azkaban/.venv 🚀
|
||||
$ source .venv/bin/activate
|
||||
|
||||
// Тепреь, когда вы запустите python, он найдет пакет sirius, установленный в виртуальной среде ✨
|
||||
$ python main.py
|
||||
|
||||
Я торжественно клянусь в этом! 🐺
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Альтернативы
|
||||
|
||||
Это простое руководство поможет вам начать работу и научит тому, как всё работает **изнутри**.
|
||||
|
||||
Существует много альтернативных решений для работы с виртуальными средами разработки, с программными зависимостями, а также с проектами.
|
||||
|
||||
Когда вы будете готовы использовать единый инструмент для управления проектом, программными зависимостями, виртуальными средами разработки и т.д., то я рекомендую вам попробовать <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>.
|
||||
|
||||
`uv` может очень многое. Он умеет:
|
||||
|
||||
* **Устанавливать Python**, включая установку различных версий
|
||||
* Управлять средой виртуального окружения вашего проекта
|
||||
* Устанавливать **пакеты**
|
||||
* Управлять пакетами и их версиями внутри вашего проекта
|
||||
* Удостовериться, что вы используете **точный** набор пакетов и версий при установке, включая зависимости. Таким образом, вы можете быть уверенны, что проект, запускается в production, точно также, как и при разработке, этот механизм называется *locking*
|
||||
* Многие другие вещи
|
||||
|
||||
## Заключение
|
||||
|
||||
Если вы прочитали и поняли всё это, то теперь вы знаете **гораздо больше** о виртуальных средах разработки, чем многие другие разработчики. 🤓
|
||||
|
||||
Скорее всего, знание этих деталей будет полезно вам в будущем. Когда вы будете отлаживать что-то, кажущееся сложным, вы будете знать, **как это работает под капотом**. 😎
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
# 特性
|
||||
|
||||
## FastAPI 特性
|
||||
|
||||
**FastAPI** 提供了以下内容:
|
||||
|
||||
### 建立在開放標準的基礎上
|
||||
|
||||
* 使用 <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank"><strong>OpenAPI</strong></a> 來建立 API,包含<abbr title="path,也被叫做: endpoints, routes">路徑</abbr><abbr title="也叫做 HTTP 方法,例如 POST, GET, PUT, DELETE">操作</abbr>、參數、請求內文、安全性等聲明。
|
||||
* 使用 <a href="https://json-schema.org/" class="external-link" target="_blank"><strong>JSON Schema</strong></a>(因為 OpenAPI 本身就是基於 JSON Schema)自動生成資料模型文件。
|
||||
* 經過縝密的研究後圍繞這些標準進行設計,而不是事後在已有系統上附加的一層功能。
|
||||
* 這也讓我們在多種語言中可以使用自動**用戶端程式碼生成**。
|
||||
|
||||
### 能夠自動生成文件
|
||||
|
||||
FastAPI 能生成互動式 API 文件和探索性的 Web 使用者介面。由於該框架基於 OpenAPI,因此有多種選擇,預設提供了兩種。
|
||||
|
||||
* <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank"><strong>Swagger UI</strong></a> 提供互動式探索,讓你可以直接從瀏覽器呼叫並測試你的 API 。
|
||||
|
||||

|
||||
|
||||
* <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank"><strong>ReDoc</strong></a> 提供結構性的文件,讓你可以在瀏覽器中查看。
|
||||
|
||||

|
||||
|
||||
|
||||
### 現代 Python
|
||||
|
||||
這一切都基於標準的 **Python 型別**宣告(感謝 Pydantic)。無需學習新的語法,只需使用標準的現代 Python。
|
||||
|
||||
如果你需要 2 分鐘來學習如何使用 Python 型別(即使你不使用 FastAPI),可以看看這個簡短的教學:[Python 型別](python-types.md){.internal-link target=_blank}。
|
||||
|
||||
如果你寫帶有 Python 型別的程式碼:
|
||||
|
||||
```python
|
||||
from datetime import date
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
# 宣告一個變數為 string
|
||||
# 並在函式中獲得 editor support
|
||||
def main(user_id: str):
|
||||
return user_id
|
||||
|
||||
|
||||
# 宣告一個 Pydantic model
|
||||
class User(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
joined: date
|
||||
```
|
||||
|
||||
|
||||
可以像這樣來使用:
|
||||
|
||||
```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` 意思是:
|
||||
|
||||
將 `second_user_data` 字典直接作為 key-value 引數傳遞,等同於:`User(id=4, name="Mary", joined="2018-11-30")`
|
||||
|
||||
///
|
||||
|
||||
### 多種編輯器支援
|
||||
|
||||
整個框架的設計是為了讓使用變得簡單且直觀,在開始開發之前,所有決策都在多個編輯器上進行了測試,以確保提供最佳的開發體驗。
|
||||
|
||||
在最近的 Python 開發者調查中,我們能看到<a href="https://www.jetbrains.com/research/python-developers-survey-2017/#tools-and-features" class="external-link" target="_blank"> 被使用最多的功能是 autocompletion</a>,此功能可以預測將要輸入文字,並自動補齊。
|
||||
|
||||
整個 **FastAPI** 框架就是基於這一點,任何地方都可以進行自動補齊。
|
||||
|
||||
你幾乎不需要經常來回看文件。
|
||||
|
||||
在這裡,你的編輯器可能會這樣幫助你:
|
||||
|
||||
* <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> 中:
|
||||
|
||||

|
||||
|
||||
你將能進行程式碼補齊,這是在之前你可能曾認為不可能的事。例如,請求 JSON body(可能是巢狀的)中的鍵 `price`。
|
||||
|
||||
這樣比較不會輸錯鍵名,不用來回翻看文件,也不用來回滾動尋找你最後使用的 `username` 或者 `user_name`。
|
||||
|
||||
|
||||
|
||||
### 簡潔
|
||||
|
||||
FastAPI 為你提供了**預設值**,讓你不必在初期進行繁瑣的配置,一切都可以自動運作。如果你有更具體的需求,則可以進行調整和自定義,
|
||||
|
||||
但在大多數情況下,你只需要直接使用預設值,就能順利完成 API 開發。
|
||||
|
||||
### 驗證
|
||||
|
||||
所有的驗證都由完善且強大的 **Pydantic** 處理。
|
||||
|
||||
* 驗證大部分(甚至所有?)的 Python **資料型別**,包括:
|
||||
* JSON 物件 (`dict`)。
|
||||
* JSON 陣列 (`list`) 定義項目型別。
|
||||
* 字串 (`str`) 欄位,定義最小或最大長度。
|
||||
* 數字 (`int`, `float`) 與其最大值和最小值等。
|
||||
|
||||
* 驗證外來的型別,比如:
|
||||
* URL
|
||||
* Email
|
||||
* UUID
|
||||
|
||||
|
||||
### 安全性及身份驗證
|
||||
|
||||
FastAPI 已經整合了安全性和身份驗證的功能,但不會強制與特定的資料庫或資料模型進行綁定。
|
||||
|
||||
OpenAPI 中定義的安全模式,包括:
|
||||
|
||||
* HTTP 基本認證。
|
||||
* **OAuth2**(也使用 **JWT tokens**)。在 [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank} 查看教學。
|
||||
* API 密鑰,在:
|
||||
* 標頭(Header)
|
||||
* 查詢參數
|
||||
* Cookies,等等。
|
||||
|
||||
加上来自 Starlette(包括 **session cookie**)的所有安全特性。
|
||||
|
||||
所有的這些都是可重複使用的工具和套件,可以輕鬆與你的系統、資料儲存(Data Stores)、關聯式資料庫(RDBMS)以及非關聯式資料庫(NoSQL)等等整合。
|
||||
|
||||
|
||||
### 依賴注入(Dependency Injection)
|
||||
|
||||
FastAPI 有一個使用簡單,但是非常強大的<abbr title='也叫做 "components", "resources", "services", "providers"'><strong>依賴注入</strong></abbr>系統。
|
||||
|
||||
* 依賴項甚至可以有自己的依賴,從而形成一個層級或**依賴圖**的結構。
|
||||
* 所有**自動化處理**都由框架完成。
|
||||
* 依賴項不僅能從請求中提取資料,還能**對 API 的路徑操作進行強化**,並自動生成文檔。
|
||||
* 即使是依賴項中定義的*路徑操作參數*,也會**自動進行驗證**。
|
||||
* 支持複雜的用戶身份驗證系統、**資料庫連接**等。
|
||||
* 不與資料庫、前端等進行強制綁定,但能輕鬆整合它們。
|
||||
|
||||
|
||||
### 無限制「擴充功能」
|
||||
|
||||
或者說,無需其他額外配置,直接導入並使用你所需要的程式碼。
|
||||
|
||||
任何整合都被設計得非常簡單易用(通過依賴注入),你只需用與*路徑操作*相同的結構和語法,用兩行程式碼就能為你的應用程式建立一個「擴充功能」。
|
||||
|
||||
|
||||
### 測試
|
||||
|
||||
* 100% 的<abbr title="有自動測試的程式碼">測試覆蓋率</abbr>。
|
||||
* 100% 的程式碼有<abbr title="Python 型別註釋,有了這個你的編輯器和外部工具可以給你更好的支援">型別註釋</abbr>。
|
||||
* 已能夠在生產環境應用程式中使用。
|
||||
|
||||
## Starlette 特性
|
||||
|
||||
**FastAPI** 完全相容且基於 <a href="https://www.starlette.io/" class="external-link" target="_blank"><strong>Starlette</strong></a>。所以,你有其他的 Starlette 程式碼也能正常運作。FastAPI 繼承了 Starlette 的所有功能,如果你已經知道或者使用過 Starlette,大部分的功能會以相同的方式運作。
|
||||
|
||||
通過 **FastAPI** 你可以獲得所有 **Starlette** 的特性(FastAPI 就像加強版的 Starlette):
|
||||
|
||||
* 性能極其出色。它是 <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">Python 可用的最快框架之一,和 **NodeJS** 及 **Go** 相當</a>。
|
||||
* **支援 WebSocket**。
|
||||
* 能在行程內處理背景任務。
|
||||
* 支援啟動和關閉事件。
|
||||
* 有基於 HTTPX 的測試用戶端。
|
||||
* 支援 **CORS**、GZip、靜態檔案、串流回應。
|
||||
* 支援 **Session 和 Cookie** 。
|
||||
* 100% 測試覆蓋率。
|
||||
* 100% 型別註釋的程式碼庫。
|
||||
|
||||
## Pydantic 特性
|
||||
|
||||
**FastAPI** 完全相容且基於 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank"><strong>Pydantic</strong></a>。所以,你有其他 Pydantic 程式碼也能正常工作。
|
||||
|
||||
相容包括基於 Pydantic 的外部函式庫, 例如用於資料庫的 <abbr title="Object-Relational Mapper">ORM</abbr>s, <abbr title="Object-Document Mapper">ODM</abbr>s。
|
||||
|
||||
這也意味著在很多情況下,你可以把從請求中獲得的物件**直接傳到資料庫**,因為所有資料都會自動進行驗證。
|
||||
|
||||
反之亦然,在很多情況下,你也可以把從資料庫中獲取的物件**直接傳給客戶端**。
|
||||
|
||||
通過 **FastAPI** 你可以獲得所有 **Pydantic** 的特性(FastAPI 基於 Pydantic 做了所有的資料處理):
|
||||
|
||||
* **更簡單**:
|
||||
* 不需要學習新的 micro-language 來定義結構。
|
||||
* 如果你知道 Python 型別,你就知道如何使用 Pydantic。
|
||||
* 和你的 **<abbr title="Integrated Development Environment,和程式碼編輯器類似">IDE</abbr>/<abbr title="一個檢查程式碼錯誤的工具">linter</abbr>/brain** 都能好好配合:
|
||||
* 因為 Pydantic 的資料結構其實就是你自己定義的類別實例,所以自動補齊、linting、mypy 以及你的直覺都能很好地在經過驗證的資料上發揮作用。
|
||||
* 驗證**複雜結構**:
|
||||
* 使用 Pydantic 模型時,你可以把資料結構分層設計,並且用 Python 的 `List` 和 `Dict` 等型別來定義。
|
||||
* 驗證器讓我們可以輕鬆地定義和檢查複雜的資料結構,並把它們轉換成 JSON Schema 進行記錄。
|
||||
* 你可以擁有深層**巢狀的 JSON** 物件,並對它們進行驗證和註釋。
|
||||
* **可擴展**:
|
||||
* Pydantic 讓我們可以定義客製化的資料型別,或者你可以使用帶有 validator 裝飾器的方法來擴展模型中的驗證功能。
|
||||
* 100% 測試覆蓋率。
|
||||
|
|
@ -0,0 +1,331 @@
|
|||
# 第一步
|
||||
|
||||
最簡單的 FastAPI 檔案可能看起來像這樣:
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py *}
|
||||
|
||||
將其複製到一個名為 `main.py` 的文件中。
|
||||
|
||||
執行即時重新載入伺服器(live server):
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:single">main.py</u>
|
||||
<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font>
|
||||
<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font>
|
||||
<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files
|
||||
<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
|
||||
╭─ <font color="#8AE234"><b>Python module file</b></font> ─╮
|
||||
│ │
|
||||
│ 🐍 main.py │
|
||||
│ │
|
||||
╰──────────────────────╯
|
||||
|
||||
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font>
|
||||
<font color="#3465A4">INFO </font> Found importable FastAPI app
|
||||
|
||||
╭─ <font color="#8AE234"><b>Importable FastAPI app</b></font> ─╮
|
||||
│ │
|
||||
│ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> │
|
||||
│ │
|
||||
╰──────────────────────────╯
|
||||
|
||||
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font>
|
||||
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">╭────────── FastAPI CLI - Development mode ───────────╮</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ Serving at: http://127.0.0.1:8000 │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ API docs: http://127.0.0.1:8000/docs │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ Running in development mode, for production use: │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ </font></span><span style="background-color:#C4A000"><font color="#555753"><b>fastapi run</b></font></span><span style="background-color:#C4A000"><font color="#2E3436"> │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">╰─────────────────────────────────────────────────────╯</font></span>
|
||||
|
||||
<font color="#4E9A06">INFO</font>: Will watch for changes in these directories: ['/home/user/code/awesomeapp']
|
||||
<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://127.0.0.1:8000</b> (Press CTRL+C to quit)
|
||||
<font color="#4E9A06">INFO</font>: Started reloader process [<font color="#34E2E2"><b>2265862</b></font>] using <font color="#34E2E2"><b>WatchFiles</b></font>
|
||||
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">2265873</font>]
|
||||
<font color="#4E9A06">INFO</font>: Waiting for application startup.
|
||||
<font color="#4E9A06">INFO</font>: Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
在輸出中,有一列類似於:
|
||||
|
||||
```hl_lines="4"
|
||||
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
那列顯示了你的應用程式正在本地端機器上運行的 URL。
|
||||
|
||||
### 查看它
|
||||
|
||||
在瀏覽器中打開 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>.
|
||||
|
||||
你將看到如下的 JSON 回應:
|
||||
|
||||
```JSON
|
||||
{"message": "Hello World"}
|
||||
```
|
||||
|
||||
### 互動式 API 文件
|
||||
|
||||
現在,前往 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
你將看到自動的互動式 API 文件(由 <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a> 提供):
|
||||
|
||||

|
||||
|
||||
### 替代 API 文件
|
||||
|
||||
現在,前往 <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
|
||||
|
||||
你將看到另一種自動文件(由 <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a> 提供):
|
||||
|
||||

|
||||
|
||||
### OpenAPI
|
||||
|
||||
**FastAPI** 使用定義 API 的 **OpenAPI** 標準來生成一個 「schema」 與你的所有 API。
|
||||
|
||||
#### 「Schema」
|
||||
|
||||
「schema」是對某個事物的定義或描述。它並不是實作它的程式碼,而僅僅是一個抽象的描述。
|
||||
|
||||
#### API 「schema」
|
||||
|
||||
在這種情況下,<a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> 是一個規範,它規定了如何定義 API 的 schema。
|
||||
|
||||
這個 schema 定義包含了你的 API 路徑、可能接收的參數等內容。
|
||||
|
||||
#### 資料 「schema」
|
||||
|
||||
「schema」這個術語也可能指某些資料的結構,比如 JSON 內容的結構。
|
||||
|
||||
在這種情況下,它指的是 JSON 的屬性、資料型別等。
|
||||
|
||||
#### OpenAPI 和 JSON Schema
|
||||
|
||||
OpenAPI 定義了 API 的 schema。這個 schema 包含了使用 **JSON Schema** 定義的資料,這是 JSON 資料 schema 的標準。
|
||||
|
||||
#### 檢查 `openapi.json`
|
||||
|
||||
如果你好奇原始的 OpenAPI schema 長什麼樣子,FastAPI 會自動生成一個包含所有 API 描述的 JSON (schema)。
|
||||
|
||||
你可以直接在 <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a> 查看它。
|
||||
|
||||
它會顯示一個 JSON,類似於:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "FastAPI",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
|
||||
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
#### OpenAPI 的用途
|
||||
|
||||
OpenAPI schema 驅動了兩個互動式文件系統。
|
||||
|
||||
而且有許多替代方案,所有這些都是基於 OpenAPI。你可以輕鬆地將任何這些替代方案添加到使用 **FastAPI** 建置的應用程式中。
|
||||
|
||||
你也可以用它自動生成程式碼,讓前端、手機應用程式或物聯網設備等與你的 API 進行通訊。
|
||||
|
||||
## 逐步回顧
|
||||
|
||||
### 第一步:引入 `FastAPI`
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py h1[1] *}
|
||||
|
||||
`FastAPI` 是一個 Python 類別,提供所有 API 的全部功能。
|
||||
|
||||
/// note | Technical Details
|
||||
|
||||
`FastAPI` 是一個直接繼承自 `Starlette` 的類別。
|
||||
|
||||
你同樣可以透過 `FastAPI` 來使用 <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> 所有的功能。
|
||||
|
||||
///
|
||||
|
||||
### 第二步:建立一個 `FastAPI` 「實例」
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py h1[3] *}
|
||||
|
||||
這裡的 `app` 變數將會是 `FastAPI` 類別的「實例」。
|
||||
|
||||
這將是你建立所有 API 的主要互動點。
|
||||
|
||||
### 第三步:建立一個 *路徑操作*
|
||||
|
||||
#### 路徑
|
||||
|
||||
這裡的「路徑」指的是 URL 中自第一個 `/` 以後的部分。
|
||||
|
||||
例如,在 URL 中:
|
||||
|
||||
```
|
||||
https://example.com/items/foo
|
||||
```
|
||||
|
||||
……的路徑將會是:
|
||||
|
||||
```
|
||||
/items/foo
|
||||
```
|
||||
|
||||
/// info
|
||||
|
||||
「路徑」也常被稱為「端點 endpoint」或「路由 route」。
|
||||
|
||||
///
|
||||
|
||||
在建置 API 時,「路徑」是分離「關注點」和「資源」的主要方式。
|
||||
|
||||
#### 操作
|
||||
|
||||
這裡的「操作」指的是 HTTP 的「方法」之一。
|
||||
|
||||
其中包括:
|
||||
|
||||
* `POST`
|
||||
* `GET`
|
||||
* `PUT`
|
||||
* `DELETE`
|
||||
|
||||
……以及更少見的:
|
||||
|
||||
* `OPTIONS`
|
||||
* `HEAD`
|
||||
* `PATCH`
|
||||
* `TRACE`
|
||||
|
||||
在 HTTP 協定中,你可以使用這些「方法」之一(或更多)與每個路徑進行通信。
|
||||
|
||||
---
|
||||
|
||||
在建置 API 時,你通常使用這些特定的 HTTP 方法來執行特定的動作。
|
||||
|
||||
通常你使用:
|
||||
|
||||
* `POST`:用來建立資料。
|
||||
* `GET`:用來讀取資料。
|
||||
* `PUT`:用來更新資料。
|
||||
* `DELETE`:用來刪除資料。
|
||||
|
||||
所以,在 OpenAPI 中,每個 HTTP 方法都被稱為「操作」。
|
||||
|
||||
我們將會稱它們為「**操作**」。
|
||||
|
||||
#### 定義一個 *路徑操作裝飾器*
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py h1[6] *}
|
||||
|
||||
`@app.get("/")` 告訴 **FastAPI** 那個函式負責處理請求:
|
||||
|
||||
* 路徑 `/`
|
||||
* 使用 <abbr title="HTTP GET 方法"><code>get</code>操作</abbr>
|
||||
|
||||
/// info | `@decorator` Info
|
||||
|
||||
Python 中的 `@something` 語法被稱為「裝飾器」。
|
||||
|
||||
你把它放在一個函式上面。像一個漂亮的裝飾帽子(我猜這是術語的來源)。
|
||||
|
||||
一個「裝飾器」會對下面的函式做一些事情。
|
||||
|
||||
在這種情況下,這個裝飾器告訴 **FastAPI** 那個函式對應於 **路徑** `/` 和 **操作** `get`.
|
||||
|
||||
這就是「**路徑操作裝飾器**」。
|
||||
|
||||
///
|
||||
|
||||
你也可以使用其他的操作:
|
||||
|
||||
* `@app.post()`
|
||||
* `@app.put()`
|
||||
* `@app.delete()`
|
||||
|
||||
以及更少見的:
|
||||
|
||||
* `@app.options()`
|
||||
* `@app.head()`
|
||||
* `@app.patch()`
|
||||
* `@app.trace()`
|
||||
|
||||
/// tip
|
||||
|
||||
你可以自由地使用每個操作(HTTP 方法)。
|
||||
|
||||
**FastAPI** 不強制任何特定的意義。
|
||||
|
||||
這裡的資訊作為一個指南,而不是要求。
|
||||
|
||||
例如,當使用 GraphQL 時,你通常只使用 `POST` 操作。
|
||||
|
||||
///
|
||||
|
||||
### 第四步:定義 **路徑操作函式**
|
||||
|
||||
這是我們的「**路徑操作函式**」:
|
||||
|
||||
* **path**: 是 `/`.
|
||||
* **operation**: 是 `get`.
|
||||
* **function**: 是裝飾器下面的函式(在 `@app.get("/")` 下面)。
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py h1[7] *}
|
||||
|
||||
這就是一個 Python 函式。
|
||||
|
||||
它將會在 **FastAPI** 收到一個請求時被呼叫,使用 `GET` 操作。
|
||||
|
||||
在這種情況下,它是一個 `async` 函式。
|
||||
|
||||
---
|
||||
|
||||
你可以將它定義為一個正常的函式,而不是 `async def`:
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial003.py h1[7] *}
|
||||
|
||||
/// note
|
||||
|
||||
如果你不知道差別,請查看 [Async: *"In a hurry?"*](../async.md#in-a-hurry){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
### 第五步:回傳內容
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py h1[8] *}
|
||||
|
||||
你可以返回一個 `dict`、`list`、單個值作為 `str`、`int` 等。
|
||||
|
||||
你也可以返回 Pydantic 模型(稍後你會看到更多關於這方面的內容)。
|
||||
|
||||
有很多其他物件和模型會自動轉換為 JSON(包括 ORMs,等等)。試用你最喜歡的,很有可能它們已經有支援。
|
||||
|
||||
## 回顧
|
||||
|
||||
* 引入 `FastAPI`.
|
||||
* 建立一個 `app` 實例。
|
||||
* 寫一個 **路徑操作裝飾器** 使用裝飾器像 `@app.get("/")`。
|
||||
* 定義一個 **路徑操作函式**;例如,`def root(): ...`。
|
||||
* 使用命令 `fastapi dev` 執行開發伺服器。
|
||||
|
|
@ -0,0 +1,844 @@
|
|||
# 虛擬環境
|
||||
|
||||
當你在 Python 專案中工作時,你可能會需要使用一個**虛擬環境**(或類似的機制)來隔離你為每個專案安裝的套件。
|
||||
|
||||
/// info
|
||||
|
||||
如果你已經了解虛擬環境,知道如何建立和使用它們,你可以考慮跳過這一部分。🤓
|
||||
|
||||
///
|
||||
|
||||
/// tip
|
||||
|
||||
**虛擬環境**和**環境變數**是不同的。
|
||||
|
||||
**環境變數**是系統中的一個變數,可以被程式使用。
|
||||
|
||||
**虛擬環境**是一個包含一些檔案的目錄。
|
||||
|
||||
///
|
||||
|
||||
/// info
|
||||
|
||||
這個頁面將教你如何使用**虛擬環境**以及了解它們的工作原理。
|
||||
|
||||
如果你計畫使用一個**可以為你管理一切的工具**(包括安裝 Python),試試 <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>。
|
||||
|
||||
///
|
||||
|
||||
## 建立一個專案
|
||||
|
||||
首先,為你的專案建立一個目錄。
|
||||
|
||||
我(指原作者 —— 譯者注)通常會在我的主目錄下建立一個名為 `code` 的目錄。
|
||||
|
||||
在這個目錄下,我再為每個專案建立一個目錄。
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// 進入主目錄
|
||||
$ cd
|
||||
// 建立一個用於存放所有程式碼專案的目錄
|
||||
$ mkdir code
|
||||
// 進入 code 目錄
|
||||
$ cd code
|
||||
// 建立一個用於存放這個專案的目錄
|
||||
$ mkdir awesome-project
|
||||
// 進入這個專案的目錄
|
||||
$ cd awesome-project
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## 建立一個虛擬環境
|
||||
|
||||
在開始一個 Python 專案的**第一時間**,**<abbr title="還有其他做法,此處僅作為一個簡單的指引">在你的專案內部</abbr>**建立一個虛擬環境。
|
||||
|
||||
/// tip
|
||||
|
||||
你只需要**在每個專案中操作一次**,而不是每次工作時都操作。
|
||||
|
||||
///
|
||||
|
||||
//// tab | `venv`
|
||||
|
||||
你可以使用 Python 自帶的 `venv` 模組來建立一個虛擬環境。
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python -m venv .venv
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// details | 上述命令的含義
|
||||
|
||||
* `python`: 使用名為 `python` 的程式
|
||||
* `-m`: 以腳本的方式呼叫一個模組,我們將告訴它接下來使用哪個模組
|
||||
* `venv`: 使用名為 `venv` 的模組,這個模組通常隨 Python 一起安裝
|
||||
* `.venv`: 在新目錄 `.venv` 中建立虛擬環境
|
||||
|
||||
///
|
||||
|
||||
////
|
||||
|
||||
//// tab | `uv`
|
||||
|
||||
如果你安裝了 <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>,你也可以使用它來建立一個虛擬環境。
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uv venv
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// tip
|
||||
|
||||
預設情況下,`uv` 會在一個名為 `.venv` 的目錄中建立一個虛擬環境。
|
||||
|
||||
但你可以透過傳遞一個額外的引數來自訂它,指定目錄的名稱。
|
||||
|
||||
///
|
||||
|
||||
////
|
||||
|
||||
這個命令會在一個名為 `.venv` 的目錄中建立一個新的虛擬環境。
|
||||
|
||||
/// details | `.venv`,或是其他名稱
|
||||
|
||||
你可以在不同的目錄下建立虛擬環境,但通常我們會把它命名為 `.venv`。
|
||||
|
||||
///
|
||||
|
||||
## 啟動虛擬環境
|
||||
|
||||
啟動新的虛擬環境來確保你運行的任何 Python 指令或安裝的套件都能使用到它。
|
||||
|
||||
/// tip
|
||||
|
||||
**每次**開始一個**新的終端會話**來在這個專案工作時,你都需要執行這個操作。
|
||||
|
||||
///
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ source .venv/bin/activate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ .venv\Scripts\Activate.ps1
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows Bash
|
||||
|
||||
或者,如果你在 Windows 上使用 Bash(例如 <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>):
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ source .venv/Scripts/activate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
/// tip
|
||||
|
||||
每次你在這個環境中安裝一個**新的套件**時,都需要**重新啟動**這個環境。
|
||||
|
||||
這麼做確保了當你使用一個由這個套件安裝的**終端(<abbr title="命令列介面">CLI</abbr>)程式**時,你使用的是你的虛擬環境中的程式,而不是全域安裝、可能版本不同的程式。
|
||||
|
||||
///
|
||||
|
||||
## 檢查虛擬環境是否啟動
|
||||
|
||||
檢查虛擬環境是否啟動(前面的指令是否生效)。
|
||||
|
||||
/// tip
|
||||
|
||||
這是**非必需的**,但這是一個很好的方法,可以**檢查**一切是否按預期工作,以及你是否使用了你打算使用的虛擬環境。
|
||||
|
||||
///
|
||||
|
||||
//// tab | Linux, macOS, Windows Bash
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ which python
|
||||
|
||||
/home/user/code/awesome-project/.venv/bin/python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
如果它顯示了在你專案(在這個例子中是 `awesome-project`)的 `.venv/bin/python` 中的 `python` 二進位檔案,那麼它就生效了。🎉
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ Get-Command python
|
||||
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts\python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
如果它顯示了在你專案(在這個例子中是 `awesome-project`)的 `.venv\Scripts\python` 中的 `python` 二進位檔案,那麼它就生效了。🎉
|
||||
|
||||
////
|
||||
|
||||
## 升級 `pip`
|
||||
|
||||
/// tip
|
||||
|
||||
如果你使用 <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a> 來安裝內容,而不是 `pip`,那麼你就不需要升級 `pip`。😎
|
||||
|
||||
///
|
||||
|
||||
如果你使用 `pip` 來安裝套件(它是 Python 的預設元件),你應該將它**升級**到最新版本。
|
||||
|
||||
在安裝套件時出現的許多奇怪的錯誤都可以透過先升級 `pip` 來解決。
|
||||
|
||||
/// tip
|
||||
|
||||
通常你只需要在建立虛擬環境後**執行一次**這個操作。
|
||||
|
||||
///
|
||||
|
||||
確保虛擬環境是啟動的(使用上面的指令),然後運行:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python -m pip install --upgrade pip
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## 加入 `.gitignore`
|
||||
|
||||
如果你使用 **Git**(這是你應該使用的),加入一個 `.gitignore` 檔案來排除你的 `.venv` 中的所有內容。
|
||||
|
||||
/// tip
|
||||
|
||||
如果你使用 <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a> 來建立虛擬環境,它會自動為你完成這個操作,你可以跳過這一步。😎
|
||||
|
||||
///
|
||||
|
||||
/// tip
|
||||
|
||||
通常你只需要在建立虛擬環境後**執行一次**這個操作。
|
||||
|
||||
///
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ echo "*" > .venv/.gitignore
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// details | 上述指令的含義
|
||||
|
||||
- `echo "*"`: 將在終端中「顯示」文本 `*`(接下來的部分會對這個操作進行一些修改)
|
||||
- `>`: 使左邊的指令顯示到終端的任何內容實際上都不會被顯示,而是會被寫入到右邊的檔案中
|
||||
- `.gitignore`: 被寫入文本的檔案的名稱
|
||||
|
||||
而 `*` 對於 Git 來說意味著「所有內容」。所以,它會忽略 `.venv` 目錄中的所有內容。
|
||||
|
||||
該指令會建立一個名為 .gitignore 的檔案,內容如下:
|
||||
|
||||
```gitignore
|
||||
*
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
## 安裝套件
|
||||
|
||||
在啟用虛擬環境後,你可以在其中安裝套件。
|
||||
|
||||
/// tip
|
||||
|
||||
當你需要安裝或升級套件時,執行本操作**一次**;
|
||||
|
||||
如果你需要再升級版本或新增套件,你可以**再次執行此操作**。
|
||||
|
||||
///
|
||||
|
||||
### 直接安裝套件
|
||||
|
||||
如果你急於安裝,不想使用檔案來聲明專案的套件依賴,你可以直接安裝它們。
|
||||
|
||||
/// tip
|
||||
|
||||
將程式所需的套件及其版本放在檔案中(例如 `requirements.txt` 或 `pyproject.toml`)是個好(而且非常好)的主意。
|
||||
|
||||
///
|
||||
|
||||
//// tab | `pip`
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "fastapi[standard]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | `uv`
|
||||
|
||||
如果你有 <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uv pip install "fastapi[standard]"
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
### 從 `requirements.txt` 安裝
|
||||
|
||||
如果你有一個 `requirements.txt` 檔案,你可以使用它來安裝其中的套件。
|
||||
|
||||
//// tab | `pip`
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install -r requirements.txt
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | `uv`
|
||||
|
||||
如果你有 <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uv pip install -r requirements.txt
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
/// details | 關於 `requirements.txt`
|
||||
|
||||
一個包含一些套件的 `requirements.txt` 檔案看起來應該是這樣的:
|
||||
|
||||
```requirements.txt
|
||||
fastapi[standard]==0.113.0
|
||||
pydantic==2.8.0
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
## 執行程式
|
||||
|
||||
在啟用虛擬環境後,你可以執行你的程式,它將使用虛擬環境中的 Python 和你在其中安裝的套件。
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python main.py
|
||||
|
||||
Hello World
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## 設定編輯器
|
||||
|
||||
你可能會用到編輯器,請確保設定它使用你建立的相同虛擬環境(它可能會自動偵測到),以便你可以獲得自動完成和内嵌錯誤提示。
|
||||
|
||||
例如:
|
||||
|
||||
* <a href="https://code.visualstudio.com/docs/python/environments#_select-and-activate-an-environment" class="external-link" target="_blank">VS Code</a>
|
||||
* <a href="https://www.jetbrains.com/help/pycharm/creating-virtual-environment.html" class="external-link" target="_blank">PyCharm</a>
|
||||
|
||||
/// tip
|
||||
|
||||
通常你只需要在建立虛擬環境時執行此操作**一次**。
|
||||
|
||||
///
|
||||
|
||||
## 退出虛擬環境
|
||||
|
||||
當你完成工作後,你可以**退出**虛擬環境。
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ deactivate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
這樣,當你執行 `python` 時它不會嘗試從已安裝套件的虛擬環境中執行。
|
||||
|
||||
## 開始工作
|
||||
|
||||
現在你已經準備好開始你的工作了。
|
||||
|
||||
|
||||
|
||||
/// tip
|
||||
|
||||
你想要理解上面的所有內容嗎?
|
||||
|
||||
繼續閱讀。👇🤓
|
||||
|
||||
///
|
||||
|
||||
## 為什麼要使用虛擬環境
|
||||
|
||||
你需要安裝 <a href="https://www.python.org/" class="external-link" target="_blank">Python</a> 才能使用 FastAPI。
|
||||
|
||||
接下來,你需要**安裝** FastAPI 以及你想使用的其他**套件**。
|
||||
|
||||
要安裝套件,你通常會使用隨 Python 一起提供的 `pip` 指令(或類似的替代工具)。
|
||||
|
||||
然而,如果你直接使用 `pip`,套件將會安裝在你的**全域 Python 環境**中(即 Python 的全域安裝)。
|
||||
|
||||
### 存在的問題
|
||||
|
||||
那麼,在全域 Python 環境中安裝套件有什麼問題呢?
|
||||
|
||||
有時候,你可能會開發許多不同的程式,而這些程式各自依賴於**不同的套件**;有些專案甚至需要依賴於**相同套件的不同版本**。😱
|
||||
|
||||
例如,你可能會建立一個名為 `philosophers-stone` 的專案,這個程式依賴於另一個名為 **`harry` 的套件,並使用版本 `1`**。因此,你需要安裝 `harry`。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
stone(philosophers-stone) -->|需要| harry-1[harry v1]
|
||||
```
|
||||
|
||||
然而,在此之後,你又建立了另一個名為 `prisoner-of-azkaban` 的專案,而這個專案也依賴於 `harry`,但需要的是 **`harry` 版本 `3`**。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
azkaban(prisoner-of-azkaban) --> |需要| harry-3[harry v3]
|
||||
```
|
||||
|
||||
現在的問題是,如果你在全域環境中安裝套件而不是在本地**虛擬環境**中,你將面臨選擇安裝哪個版本的 `harry` 的困境。
|
||||
|
||||
如果你想運行 `philosophers-stone`,你需要先安裝 `harry` 版本 `1`,例如:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "harry==1"
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
然後你會在全域 Python 環境中安裝 `harry` 版本 `1`。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph global[全域環境]
|
||||
harry-1[harry v1]
|
||||
end
|
||||
subgraph stone-project[專案 philosophers-stone]
|
||||
stone(philosophers-stone) -->|需要| harry-1
|
||||
end
|
||||
```
|
||||
|
||||
但如果你想運行 `prisoner-of-azkaban`,你需要解除安裝 `harry` 版本 `1` 並安裝 `harry` 版本 `3`(或者只要你安裝版本 `3`,版本 `1` 就會自動移除)。
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "harry==3"
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
於是,你在全域 Python 環境中安裝了 `harry` 版本 `3`。
|
||||
|
||||
如果你再次嘗試運行 `philosophers-stone`,很可能會**無法正常運作**,因為它需要的是 `harry` 版本 `1`。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph global[全域環境]
|
||||
harry-1[<strike>harry v1</strike>]
|
||||
style harry-1 fill:#ccc,stroke-dasharray: 5 5
|
||||
harry-3[harry v3]
|
||||
end
|
||||
subgraph stone-project[專案 philosophers-stone]
|
||||
stone(philosophers-stone) -.-x|⛔️| harry-1
|
||||
end
|
||||
subgraph azkaban-project[專案 prisoner-of-azkaban]
|
||||
azkaban(prisoner-of-azkaban) --> |需要| harry-3
|
||||
end
|
||||
```
|
||||
|
||||
/// tip
|
||||
|
||||
Python 套件在推出**新版本**時通常會儘量**避免破壞性更改**,但最好還是要謹慎,在安裝新版本前進行測試,以確保一切能正常運行。
|
||||
|
||||
///
|
||||
|
||||
現在,想像一下如果有**許多**其他**套件**,它們都是你的**專案所依賴的**。這樣是非常難以管理的。你可能會發現有些專案使用了一些**不相容的套件版本**,而無法得知為什麼某些程式無法正常運作。
|
||||
|
||||
此外,取決於你的操作系統(例如 Linux、Windows、macOS),它可能已經預先安裝了 Python。在這種情況下,它可能已經有一些系統所需的套件和特定版本。如果你在全域 Python 環境中安裝套件,可能會**破壞**某些隨作業系統一起安裝的程式。
|
||||
|
||||
## 套件安裝在哪裡
|
||||
|
||||
當你安裝 Python 時,它會在你的電腦中建立一些目錄並放置一些檔案。
|
||||
|
||||
其中一些目錄專門用來存放你所安裝的所有套件。
|
||||
|
||||
當你運行:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// 先別去運行這個指令,這只是個示例 🤓
|
||||
$ pip install "fastapi[standard]"
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
這會從 <a href="https://pypi.org/project/fastapi/" class="external-link" target="_blank">PyPI</a> 下載一個壓縮檔案,其中包含 FastAPI 的程式碼。
|
||||
|
||||
它還會**下載** FastAPI 所依賴的其他套件的檔案。
|
||||
|
||||
接著,它會**解壓**所有這些檔案,並將它們放在你的電腦中的某個目錄中。
|
||||
|
||||
預設情況下,這些下載和解壓的檔案會放置於隨 Python 安裝的目錄中,即**全域環境**。
|
||||
|
||||
## 什麼是虛擬環境
|
||||
|
||||
解決套件都安裝在全域環境中的問題方法是為你所做的每個專案使用一個**虛擬環境**。
|
||||
|
||||
虛擬環境是一個**目錄**,與全域環境非常相似,你可以在其中針對某個專案安裝套件。
|
||||
|
||||
這樣,每個專案都會有自己的虛擬環境(`.venv` 目錄),其中包含自己的套件。
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph stone-project[專案 philosophers-stone]
|
||||
stone(philosophers-stone) --->|需要| harry-1
|
||||
subgraph venv1[.venv]
|
||||
harry-1[harry v1]
|
||||
end
|
||||
end
|
||||
subgraph azkaban-project[專案 prisoner-of-azkaban]
|
||||
azkaban(prisoner-of-azkaban) --->|需要| harry-3
|
||||
subgraph venv2[.venv]
|
||||
harry-3[harry v3]
|
||||
end
|
||||
end
|
||||
stone-project ~~~ azkaban-project
|
||||
```
|
||||
|
||||
## 啟用虛擬環境意味著什麼
|
||||
|
||||
當你啟用了虛擬環境,例如:
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ source .venv/bin/activate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ .venv\Scripts\Activate.ps1
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows Bash
|
||||
|
||||
或者如果你在 Windows 上使用 Bash(例如 <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>):
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ source .venv/Scripts/activate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
這個命令會建立或修改一些[環境變數](environment-variables.md){.internal-link target=_blank},這些環境變數將在接下來的指令中可用。
|
||||
|
||||
其中之一是 `PATH` 變數。
|
||||
|
||||
/// tip
|
||||
|
||||
你可以在 [環境變數](environment-variables.md#path-environment-variable){.internal-link target=_blank} 部分了解更多關於 `PATH` 環境變數的內容。
|
||||
|
||||
///
|
||||
|
||||
啟用虛擬環境會將其路徑 `.venv/bin`(在 Linux 和 macOS 上)或 `.venv\Scripts`(在 Windows 上)加入到 `PATH` 環境變數中。
|
||||
|
||||
假設在啟用環境之前,`PATH` 變數看起來像這樣:
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
```plaintext
|
||||
/usr/bin:/bin:/usr/sbin:/sbin
|
||||
```
|
||||
|
||||
這意味著系統會在以下目錄中查找程式:
|
||||
|
||||
* `/usr/bin`
|
||||
* `/bin`
|
||||
* `/usr/sbin`
|
||||
* `/sbin`
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows
|
||||
|
||||
```plaintext
|
||||
C:\Windows\System32
|
||||
```
|
||||
|
||||
這意味著系統會在以下目錄中查找程式:
|
||||
|
||||
* `C:\Windows\System32`
|
||||
|
||||
////
|
||||
|
||||
啟用虛擬環境後,`PATH` 變數會變成這樣:
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
```plaintext
|
||||
/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin
|
||||
```
|
||||
|
||||
這意味著系統現在會首先在以下目錄中查找程式:
|
||||
|
||||
```plaintext
|
||||
/home/user/code/awesome-project/.venv/bin
|
||||
```
|
||||
|
||||
然後再在其他目錄中查找。
|
||||
|
||||
因此,當你在終端機中輸入 `python` 時,系統會在以下目錄中找到 Python 程式:
|
||||
|
||||
```plaintext
|
||||
/home/user/code/awesome-project/.venv/bin/python
|
||||
```
|
||||
|
||||
並使用這個。
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows
|
||||
|
||||
```plaintext
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32
|
||||
```
|
||||
|
||||
這意味著系統現在會首先在以下目錄中查找程式:
|
||||
|
||||
```plaintext
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts
|
||||
```
|
||||
|
||||
然後再在其他目錄中查找。
|
||||
|
||||
因此,當你在終端機中輸入 `python` 時,系統會在以下目錄中找到 Python 程式:
|
||||
|
||||
```plaintext
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts\python
|
||||
```
|
||||
|
||||
並使用這個。
|
||||
|
||||
////
|
||||
|
||||
一個重要的細節是,虛擬環境路徑會被放在 `PATH` 變數的**開頭**。系統會在找到任何其他可用的 Python **之前**找到它。這樣,當你運行 `python` 時,它會使用**虛擬環境中的** Python,而不是任何其他 `python`(例如,全域環境中的 `python`)。
|
||||
|
||||
啟用虛擬環境還會改變其他一些內容,但這是它所做的最重要的事情之一。
|
||||
|
||||
## 檢查虛擬環境
|
||||
|
||||
當你檢查虛擬環境是否啟動時,例如:
|
||||
|
||||
//// tab | Linux, macOS, Windows Bash
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ which python
|
||||
|
||||
/home/user/code/awesome-project/.venv/bin/python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ Get-Command python
|
||||
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts\python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
這表示將使用的 `python` 程式是**在虛擬環境中**的那一個。
|
||||
|
||||
在 Linux 和 macOS 中使用 `which`,在 Windows PowerShell 中使用 `Get-Command`。
|
||||
|
||||
這個指令的運作方式是,它會在 `PATH` 環境變數中搜尋,依序**逐個路徑**查找名為 `python` 的程式。一旦找到,它會**顯示該程式的路徑**。
|
||||
|
||||
最重要的是,當你呼叫 `python` 時,將執行的就是這個確切的 "`python`"。
|
||||
|
||||
因此,你可以確認是否在正確的虛擬環境中。
|
||||
|
||||
/// tip
|
||||
|
||||
啟動一個虛擬環境,取得一個 Python,然後**切換到另一個專案**是件很容易的事;
|
||||
|
||||
但如果第二個專案**無法正常運作**,那可能是因為你使用了來自其他專案的虛擬環境的、**不正確的 Python**。
|
||||
|
||||
因此,檢查正在使用的 `python` 是非常實用的。🤓
|
||||
|
||||
///
|
||||
|
||||
## 為什麼要停用虛擬環境
|
||||
|
||||
例如,你可能正在一個專案 `philosophers-stone` 上工作,**啟動了該虛擬環境**,安裝了套件並使用了該環境,
|
||||
|
||||
然後你想要在**另一個專案** `prisoner-of-azkaban` 上工作,
|
||||
|
||||
你進入那個專案:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ cd ~/code/prisoner-of-azkaban
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
如果你不去停用 `philosophers-stone` 的虛擬環境,當你在終端中執行 `python` 時,它會嘗試使用 `philosophers-stone` 中的 Python。
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ cd ~/code/prisoner-of-azkaban
|
||||
|
||||
$ python main.py
|
||||
|
||||
// 匯入 sirius 錯誤,未安裝 😱
|
||||
Traceback (most recent call last):
|
||||
File "main.py", line 1, in <module>
|
||||
import sirius
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
但如果你停用虛擬環境並啟用 `prisoner-of-askaban` 的新虛擬環境,那麼當你執行 `python` 時,它會使用 `prisoner-of-askaban` 中虛擬環境的 Python。
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ cd ~/code/prisoner-of-azkaban
|
||||
|
||||
// 你不需要在舊目錄中操作停用,你可以在任何地方操作停用,甚至在切換到另一個專案之後 😎
|
||||
$ deactivate
|
||||
|
||||
// 啟用 prisoner-of-azkaban/.venv 中的虛擬環境 🚀
|
||||
$ source .venv/bin/activate
|
||||
|
||||
// 現在當你執行 python 時,它會在這個虛擬環境中找到已安裝的 sirius 套件 ✨
|
||||
$ python main.py
|
||||
|
||||
I solemnly swear 🐺
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## 替代方案
|
||||
|
||||
這是一個簡單的指南,幫助你入門並教會你如何理解一切**底層**的原理。
|
||||
|
||||
有許多**替代方案**來管理虛擬環境、套件依賴(requirements)、專案。
|
||||
|
||||
當你準備好並想要使用一個工具來**管理整個專案**、套件依賴、虛擬環境等,建議你嘗試 <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>。
|
||||
|
||||
`uv` 可以執行許多操作,它可以:
|
||||
|
||||
* 為你**安裝 Python**,包括不同的版本
|
||||
* 為你的專案管理**虛擬環境**
|
||||
* 安裝**套件**
|
||||
* 為你的專案管理套件的**依賴和版本**
|
||||
* 確保你有一個**精確**的套件和版本集合來安裝,包括它們的依賴項,這樣你可以確保專案在生產環境中運行的狀態與開發時在你的電腦上運行的狀態完全相同,這被稱為**鎖定**
|
||||
* 還有很多其他功能
|
||||
|
||||
## 結論
|
||||
|
||||
如果你讀過並理解了所有這些,現在**你對虛擬環境的了解已超過許多開發者**。🤓
|
||||
|
||||
未來當你為看起來複雜的問題除錯時,了解這些細節很可能會有所幫助,你會知道**它是如何在底層運作的**。😎
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
# Cookie 参数模型
|
||||
|
||||
如果您有一组相关的 **cookie**,您可以创建一个 **Pydantic 模型**来声明它们。🍪
|
||||
|
||||
这将允许您在**多个地方**能够**重用模型**,并且可以一次性声明所有参数的验证方式和元数据。😎
|
||||
|
||||
/// note
|
||||
|
||||
自 FastAPI 版本 `0.115.0` 起支持此功能。🤓
|
||||
|
||||
///
|
||||
|
||||
/// tip
|
||||
|
||||
此技术同样适用于 `Query` 、 `Cookie` 和 `Header` 。😎
|
||||
|
||||
///
|
||||
|
||||
## 带有 Pydantic 模型的 Cookie
|
||||
|
||||
在 **Pydantic** 模型中声明所需的 **cookie** 参数,然后将参数声明为 `Cookie` :
|
||||
|
||||
{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *}
|
||||
|
||||
**FastAPI** 将从请求中接收到的 **cookie** 中**提取**出**每个字段**的数据,并提供您定义的 Pydantic 模型。
|
||||
|
||||
## 查看文档
|
||||
|
||||
您可以在文档 UI 的 `/docs` 中查看定义的 cookie:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/cookie-param-models/image01.png">
|
||||
</div>
|
||||
|
||||
/// info
|
||||
|
||||
请记住,由于**浏览器**以特殊方式**处理 cookie**,并在后台进行操作,因此它们**不会**轻易允许 **JavaScript** 访问这些 cookie。
|
||||
|
||||
如果您访问 `/docs` 的 **API 文档 UI**,您将能够查看您*路径操作*的 cookie **文档**。
|
||||
|
||||
但是即使您**填写数据**并点击“执行”,由于文档界面使用 **JavaScript**,cookie 将不会被发送。而您会看到一条**错误**消息,就好像您没有输入任何值一样。
|
||||
|
||||
///
|
||||
|
||||
## 禁止额外的 Cookie
|
||||
|
||||
在某些特殊使用情况下(可能并不常见),您可能希望**限制**您想要接收的 cookie。
|
||||
|
||||
您的 API 现在可以控制自己的 <abbr title="顺带一提,这是一个笑话。它与 cookie 同意无关,但现在连API都能拒绝那些可怜的 cookie,真是太有意思了。来,吃块小饼干(cookie)吧。🍪">cookie 同意</abbr>。🤪🍪
|
||||
|
||||
您可以使用 Pydantic 的模型配置来禁止( `forbid` )任何额外( `extra` )字段:
|
||||
|
||||
{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *}
|
||||
|
||||
如果客户尝试发送一些**额外的 cookie**,他们将收到**错误**响应。
|
||||
|
||||
可怜的 cookie 通知条,费尽心思为了获得您的同意,却被<abbr title="这又是一个笑话,别管我了,给您的小饼干(cookie)配上点咖啡吧。☕">API 拒绝了</abbr>。🍪
|
||||
|
||||
例如,如果客户端尝试发送一个值为 `good-list-please` 的 `santa_tracker` cookie,客户端将收到一个**错误**响应,告知他们 `santa_tracker` <abbr title="圣诞老人(Santa)不赞成没有小饼干(cookie)。🎅 好吧,不会再开 cookie 的玩笑了。">cookie 是不允许的</abbr>:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "extra_forbidden",
|
||||
"loc": ["cookie", "santa_tracker"],
|
||||
"msg": "Extra inputs are not permitted",
|
||||
"input": "good-list-please",
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
您可以使用 **Pydantic 模型**在 **FastAPI** 中声明 <abbr title="走之前再来块小饼干吧。 🍪">**cookie**</abbr>。😎
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# 表单模型
|
||||
|
||||
您可以使用 **Pydantic 模型**在 FastAPI 中声明**表单字段**。
|
||||
|
||||
/// info
|
||||
|
||||
要使用表单,需预先安装 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a> 。
|
||||
|
||||
确保您创建、激活一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank}后再安装。
|
||||
|
||||
```console
|
||||
$ pip install python-multipart
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
/// note
|
||||
|
||||
自 FastAPI 版本 `0.113.0` 起支持此功能。🤓
|
||||
|
||||
///
|
||||
|
||||
## 表单的 Pydantic 模型
|
||||
|
||||
您只需声明一个 **Pydantic 模型**,其中包含您希望接收的**表单字段**,然后将参数声明为 `Form` :
|
||||
|
||||
{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *}
|
||||
|
||||
**FastAPI** 将从请求中的**表单数据**中**提取**出**每个字段**的数据,并提供您定义的 Pydantic 模型。
|
||||
|
||||
## 检查文档
|
||||
|
||||
您可以在文档 UI 中验证它,地址为 `/docs` :
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/request-form-models/image01.png">
|
||||
</div>
|
||||
|
||||
## 禁止额外的表单字段
|
||||
|
||||
在某些特殊使用情况下(可能并不常见),您可能希望将表单字段**限制**为仅在 Pydantic 模型中声明过的字段,并**禁止**任何**额外**的字段。
|
||||
|
||||
/// note
|
||||
|
||||
自 FastAPI 版本 `0.114.0` 起支持此功能。🤓
|
||||
|
||||
///
|
||||
|
||||
您可以使用 Pydantic 的模型配置来禁止( `forbid` )任何额外( `extra` )字段:
|
||||
|
||||
{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *}
|
||||
|
||||
如果客户端尝试发送一些额外的数据,他们将收到**错误**响应。
|
||||
|
||||
例如,如果客户端尝试发送这样的表单字段:
|
||||
|
||||
* `username`: `Rick`
|
||||
* `password`: `Portal Gun`
|
||||
* `extra`: `Mr. Poopybutthole`
|
||||
|
||||
他们将收到一条错误响应,表明字段 `extra` 是不被允许的:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "extra_forbidden",
|
||||
"loc": ["body", "extra"],
|
||||
"msg": "Extra inputs are not permitted",
|
||||
"input": "Mr. Poopybutthole"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
您可以使用 Pydantic 模型在 FastAPI 中声明表单字段。😎
|
||||
|
|
@ -14,6 +14,6 @@ cairosvg==2.7.1
|
|||
mkdocstrings[python]==0.26.1
|
||||
griffe-typingdoc==0.2.7
|
||||
# For griffe, it formats with black
|
||||
black==24.3.0
|
||||
black==24.10.0
|
||||
mkdocs-macros-plugin==1.0.5
|
||||
markdown-include-variants==0.0.3
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@
|
|||
pytest >=7.1.3,<9.0.0
|
||||
coverage[toml] >= 6.5.0,< 8.0
|
||||
mypy ==1.8.0
|
||||
dirty-equals ==0.6.0
|
||||
dirty-equals ==0.8.0
|
||||
sqlmodel==0.0.22
|
||||
flask >=1.1.2,<4.0.0
|
||||
anyio[trio] >=3.2.1,<4.0.0
|
||||
PyJWT==2.8.0
|
||||
pyyaml >=5.3.1,<7.0.0
|
||||
passlib[bcrypt] >=1.7.2,<2.0.0
|
||||
inline-snapshot==0.13.0
|
||||
inline-snapshot==0.14.0
|
||||
# types
|
||||
types-ujson ==5.7.0.1
|
||||
types-ujson ==5.10.0.20240515
|
||||
types-orjson ==3.6.2
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ def test_fastapi_cli():
|
|||
encoding="utf-8",
|
||||
)
|
||||
assert result.returncode == 1, result.stdout
|
||||
assert "Using path non_existent_file.py" in result.stdout
|
||||
assert "Path does not exist non_existent_file.py" in result.stdout
|
||||
|
||||
|
||||
def test_fastapi_cli_not_installed():
|
||||
|
|
|
|||
Loading…
Reference in New Issue