committed by
GitHub
52 changed files with 3132 additions and 1231 deletions
File diff suppressed because it is too large
@ -0,0 +1,831 @@ |
|||||
|
# 仮想環境 |
||||
|
|
||||
|
Pythonプロジェクトの作業では、**仮想環境**(または類似の仕組み)を使用し、プロジェクトごとにインストールするパッケージを分離するべきでしょう。 |
||||
|
|
||||
|
/// info | 情報 |
||||
|
|
||||
|
もし、仮想環境の概要や作成方法、使用方法について既にご存知なら、このセクションをスキップすることができます。🤓 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
/// tip | 豆知識 |
||||
|
|
||||
|
**仮想環境**は、**環境変数**とは異なります。 |
||||
|
|
||||
|
**環境変数**は、プログラムが使用できるシステム内の変数です。 |
||||
|
|
||||
|
**仮想環境**は、ファイルをまとめたディレクトリのことです。 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
/// info | 情報 |
||||
|
このページでは、**仮想環境**の使用方法と、そのはたらきについて説明します。 |
||||
|
|
||||
|
もし**すべてを管理するツール**(Pythonのインストールも含む)を導入する準備ができているなら、<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a> をお試しください。 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## プロジェクトの作成 |
||||
|
|
||||
|
まず、プロジェクト用のディレクトリを作成します。 |
||||
|
|
||||
|
私は通常 home/user ディレクトリの中に `code` というディレクトリを用意していて、プロジェクトごとに1つのディレクトリをその中に作成しています。 |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
// Go to the home directory |
||||
|
$ cd |
||||
|
// Create a directory for all your code projects |
||||
|
$ mkdir code |
||||
|
// Enter into that code directory |
||||
|
$ cd code |
||||
|
// Create a directory for this project |
||||
|
$ mkdir awesome-project |
||||
|
// Enter into that project directory |
||||
|
$ cd awesome-project |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
## 仮想環境の作成 |
||||
|
|
||||
|
Pythonプロジェクトでの**初めての**作業を開始する際には、**<abbr title="他の選択肢もありますが、これはシンプルなガイドラインです">プロジェクト内</abbr>**に仮想環境を作成してください。 |
||||
|
|
||||
|
/// tip | 豆知識 |
||||
|
|
||||
|
これを行うのは、**プロジェクトごとに1回だけ**です。作業のたびに行う必要はありません。 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
//// tab | `venv` |
||||
|
|
||||
|
仮想環境を作成するには、Pythonに付属している `venv` モジュールを使用できます。 |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ python -m venv .venv |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
/// details | このコマンドの意味 |
||||
|
|
||||
|
- `python` : `python` というプログラムを呼び出します |
||||
|
- `-m` : モジュールをスクリプトとして呼び出します。どのモジュールを呼び出すのか、この次に指定します |
||||
|
- `venv` : 通常Pythonに付随してインストールされる `venv`モジュールを使用します |
||||
|
- `.venv` : 仮想環境を`.venv`という新しいディレクトリに作成します |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | `uv` |
||||
|
|
||||
|
もし <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a> をインストール済みなら、仮想環境を作成するために `uv` を使うこともできます。 |
||||
|
|
||||
|
<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="command line interface">CLI</abbr>)プログラム**を使用する場合に、仮想環境内のものが確実に使われ、グローバル環境にインストールされている別のもの(おそらく必要なものとは異なるバージョン)を誤って使用することを防ぎます。 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## 仮想環境が有効であることを確認する |
||||
|
|
||||
|
仮想環境が有効である(前のコマンドが正常に機能した)ことを確認します。 |
||||
|
|
||||
|
/// tip | 豆知識 |
||||
|
|
||||
|
これは**任意**ですが、すべてが期待通りに機能し、意図した仮想環境を使用していることを**確認する**良い方法です。 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
//// tab | Linux, macOS, Windows Bash |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ which python |
||||
|
|
||||
|
/home/user/code/awesome-project/.venv/bin/python |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
`.venv/bin/python` にある `python` バイナリが、プロジェクト(この場合は `awesome-project` )内に表示されていれば、正常に動作しています 🎉。 |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Windows PowerShell |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
``` console |
||||
|
$ Get-Command python |
||||
|
|
||||
|
C:\Users\user\code\awesome-project\.venv\Scripts\python |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
`.venv\Scripts\python` にある `python` バイナリが、プロジェクト(この場合は `awesome-project` )内に表示されていれば、正常に動作しています 🎉。 |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
## `pip` をアップグレードする |
||||
|
|
||||
|
/// tip | 豆知識 |
||||
|
|
||||
|
もし <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a> を使用している場合は、 `pip` の代わりに `uv` を使ってインストールを行うため、 `pip` をアップグレードする必要はありません 😎。 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
もしパッケージのインストールに `pip`(Pythonに標準で付属しています)を使用しているなら、 `pip` を最新バージョンに**アップグレード**しましょう。 |
||||
|
|
||||
|
パッケージのインストール中に発生する想定外のエラーの多くは、最初に `pip` をアップグレードしておくだけで解決されます。 |
||||
|
|
||||
|
/// tip | 豆知識 |
||||
|
|
||||
|
通常、これは仮想環境を作成した直後に**一度だけ**実行します。 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
仮想環境が有効であることを(上で説明したコマンドで)確認し、アップグレードを実行しましょう: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ python -m pip install --upgrade pip |
||||
|
|
||||
|
---> 100% |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
## `.gitignore` を追加する |
||||
|
|
||||
|
**Git**を使用している場合(使用するべきでしょう)、 `.gitignore` ファイルを追加して、 `.venv` 内のあらゆるファイルをGitの管理対象から除外します。 |
||||
|
|
||||
|
/// 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` ディレクトリ内のすべてがGitに無視されるようになります。 |
||||
|
|
||||
|
このコマンドは以下のテキストを持つ `.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を使った作業をするには、 [Python](https://www.python.org/) のインストールが必要です。 |
||||
|
|
||||
|
それから、FastAPIや、使用したいその他の**パッケージ**を**インストール**する必要があります。 |
||||
|
|
||||
|
パッケージをインストールするには、通常、Python に付属する `pip` コマンド (または同様の代替コマンド) を使用します。 |
||||
|
|
||||
|
ただし、`pip` を直接使用すると、パッケージは**グローバルなPython環境**(OS全体にインストールされた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> |
||||
|
|
||||
|
これにより、`harry` (ハリー)バージョン1がグローバルなPython環境にインストールされます。 |
||||
|
|
||||
|
```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` をインストールし直す必要があります。(あるいは、単に`harry` (ハリー)のバージョン `3` をインストールすることで、自動的にバージョン `1` がアンインストールされます) |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ pip install "harry==3" |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
このようにして、グローバル環境への `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のパッケージでは、**新しいバージョン**で**互換性を損なう変更を避ける**よう努めるのが一般的ですが、それでも注意が必要です。すべてが正常に動作することをテストで確認してから、意図的に指定して新しいバージョンをインストールするのが良いでしょう。 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
あなたのすべての**プロジェクトが依存している**、**多数の**他の**パッケージ**が上記の問題を抱えていると想像してください。これは管理が非常に困難です。そして、**互換性のないバージョン**のパッケージを使ってプロジェクトを実行し、なぜ動作しないのか分からなくなるでしょう。 |
||||
|
|
||||
|
また、使用しているOS(Linux、Windows、macOS など)によっては、Pythonがすでにインストールされていることがあります。この場合、特定のバージョンのパッケージが**OSの動作に必要である**ことがあります。グローバル環境にパッケージをインストールすると、OSに付属するプログラムを**壊してしまう**可能性があります。 |
||||
|
|
||||
|
## パッケージのインストール先 |
||||
|
|
||||
|
Pythonをインストールしたとき、ファイルを含んだいくつかのディレクトリが作成されます。 |
||||
|
|
||||
|
これらの中には、インストールされたパッケージを保存するためのものもあります。 |
||||
|
|
||||
|
以下のコマンドを実行したとき: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
// Don't run this now, it's just an example 🤓 |
||||
|
$ pip install "fastapi[standard]" |
||||
|
---> 100% |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
FastAPIのコードを含む圧縮ファイルが、通常は [PyPI](https://pypi.org/project/fastapi/) からダウンロードされます。 |
||||
|
|
||||
|
また、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 |
||||
|
|
||||
|
あるいは、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 | 豆知識 |
||||
|
|
||||
|
`PATH` 変数についての詳細は [環境変数](environment-variables.md#path環境変数){.internal-link target=_blank} を参照してください。 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
仮想環境を有効にすると、その仮想環境のパス `.venv/bin` (LinuxとmacOS)、あるいは `.venv\Scripts` (Windows)が `PATH` 変数に追加されます。 |
||||
|
|
||||
|
その環境を有効にする前の `PATH` 変数が次のようになっているとします。 |
||||
|
|
||||
|
//// tab | Linux, macOS |
||||
|
|
||||
|
```plaintext |
||||
|
/usr/bin:/bin:/usr/sbin:/sbin |
||||
|
``` |
||||
|
|
||||
|
これは、OSが以下のディレクトリ中でプログラムを探すことを意味します: |
||||
|
|
||||
|
* `/usr/bin` |
||||
|
* `/bin` |
||||
|
* `/usr/sbin` |
||||
|
* `/sbin` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Windows |
||||
|
|
||||
|
```plaintext |
||||
|
C:\Windows\System32 |
||||
|
``` |
||||
|
|
||||
|
これは、OSが以下のディレクトリ中でプログラムを探すことを意味します: |
||||
|
|
||||
|
* `C:\Windows\System32` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
仮想環境を有効にすると、 `PATH` 変数は次のようになります。 |
||||
|
|
||||
|
//// tab | Linux, macOS |
||||
|
|
||||
|
```plaintext |
||||
|
/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin |
||||
|
``` |
||||
|
|
||||
|
これは、OSが他のディレクトリを探すより前に、最初に以下のディレクトリ中でプログラムを探し始めることを意味します: |
||||
|
|
||||
|
```plaintext |
||||
|
/home/user/code/awesome-project/.venv/bin |
||||
|
``` |
||||
|
|
||||
|
そのため、ターミナルで `python` と入力した際に、OSはPythonプログラムを以下のパスで発見し、使用します。 |
||||
|
|
||||
|
```plaintext |
||||
|
/home/user/code/awesome-project/.venv/bin/python |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Windows |
||||
|
|
||||
|
```plaintext |
||||
|
C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 |
||||
|
``` |
||||
|
|
||||
|
これは、OSが他のディレクトリを探すより前に、最初に以下のディレクトリ中でプログラムを探し始めることを意味します: |
||||
|
|
||||
|
```plaintext |
||||
|
C:\Users\user\code\awesome-project\.venv\Scripts |
||||
|
``` |
||||
|
|
||||
|
そのため、ターミナルで `python` と入力した際に、OSはPythonプログラムを以下のパスで発見し、使用します。 |
||||
|
|
||||
|
```plaintext |
||||
|
C:\Users\user\code\awesome-project\.venv\Scripts\python |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
重要な点は、仮想環境のパスを `PATH` 変数の**先頭**に配置することです。OSは利用可能な他のPythonを見つけるより**前に**、この仮想環境の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 |
||||
|
|
||||
|
// Error importing sirius, it's not installed 😱 |
||||
|
Traceback (most recent call last): |
||||
|
File "main.py", line 1, in <module> |
||||
|
import sirius |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
しかし、その仮想環境を無効化し、 `prisoner-of-azkaban` (アズカバンの囚人)のための新しい仮想環境を有効にすれば、 `python` を実行したときに `prisoner-of-azkaban` (アズカバンの囚人)の仮想環境の Python が使用されるようになります。 |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ cd ~/code/prisoner-of-azkaban |
||||
|
|
||||
|
// You don't need to be in the old directory to deactivate, you can do it wherever you are, even after going to the other project 😎 |
||||
|
$ deactivate |
||||
|
|
||||
|
// Activate the virtual environment in prisoner-of-azkaban/.venv 🚀 |
||||
|
$ source .venv/bin/activate |
||||
|
|
||||
|
// Now when you run python, it will find the package sirius installed in this virtual environment ✨ |
||||
|
$ 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 のインストール** |
||||
|
* プロジェクトごとの**仮想環境**の管理 |
||||
|
* **パッケージ**のインストール |
||||
|
* プロジェクトのパッケージの**依存関係やバージョン**の管理 |
||||
|
* パッケージとそのバージョンの、依存関係を含めた**厳密な**組み合わせを保持し、これによって、本番環境で、開発環境と全く同じようにプロジェクトを実行できる(これは**locking**と呼ばれます) |
||||
|
* その他のさまざまな機能 |
||||
|
|
||||
|
## まとめ |
||||
|
|
||||
|
ここまで読みすべて理解したなら、世間の多くの開発者と比べて、仮想環境について**あなたはより多くのことを知っています**。🤓 |
||||
|
|
||||
|
これらの詳細を知ることは、将来、複雑に見える何かのデバッグにきっと役立つでしょう。しかし、その頃には、あなたは**そのすべての動作を根本から**理解しているでしょう。😎 |
@ -0,0 +1,273 @@ |
|||||
|
# 패스워드 해싱을 이용한 OAuth2, JWT 토큰을 사용하는 Bearer 인증 |
||||
|
|
||||
|
모든 보안 흐름을 구성했으므로, 이제 <abbr title="JSON Web Tokens">JWT</abbr> 토큰과 패스워드 해싱을 사용해 애플리케이션을 안전하게 만들 것입니다. |
||||
|
|
||||
|
이 코드는 실제로 애플리케이션에서 패스워드를 해싱하여 DB에 저장하는 등의 작업에 활용할 수 있습니다. |
||||
|
|
||||
|
이전 장에 이어서 시작해 봅시다. |
||||
|
|
||||
|
## JWT |
||||
|
|
||||
|
JWT 는 "JSON Web Tokens" 을 의미합니다. |
||||
|
|
||||
|
JSON 객체를 공백이 없는 긴 문자열로 인코딩하는 표준이며, 다음과 같은 형태입니다: |
||||
|
|
||||
|
``` |
||||
|
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c |
||||
|
``` |
||||
|
|
||||
|
JWT는 암호화되지 않아 누구든지 토큰에서 정보를 복원할 수 있습니다. |
||||
|
|
||||
|
하지만 JWT는 서명되어 있습니다. 그래서 자신이 발급한 토큰을 받았을 때, 실제로 자신이 발급한게 맞는지 검증할 수 있습니다. |
||||
|
|
||||
|
만료 기간이 일주일인 토큰을 발행했다고 가정해 봅시다. 다음 날 사용자가 토큰을 가져왔을 때, 그 사용자가 시스템에 여전히 로그인되어 있다는 것을 알 수 있습니다. |
||||
|
|
||||
|
일주일 뒤에는 토큰이 만료될 것이고, 사용자는 인가되지 않아 새 토큰을 받기 위해 다시 로그인해야 할 것입니다. 만약 사용자(또는 제3자)가 토큰을 수정하거나 만료일을 변경하면, 서명이 일치하지 않기 때문에 알아챌 수 있을 것입니다. |
||||
|
|
||||
|
만약 JWT 토큰을 다뤄보고, 작동 방식도 알아보고 싶다면 <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a> 을 확인하십시오. |
||||
|
|
||||
|
## `PyJWT` 설치 |
||||
|
|
||||
|
파이썬으로 JWT 토큰을 생성하고 검증하려면 `PyJWT` 를 설치해야 합니다. |
||||
|
|
||||
|
[가상환경](../../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> 에서 확인할 수 있습니다. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## 패스워드 해싱 |
||||
|
|
||||
|
"해싱(Hashing)"은 어떤 내용(여기서는 패스워드)을 해석할 수 없는 일련의 바이트 집합(단순 문자열)으로 변환하는 것을 의미합니다. |
||||
|
|
||||
|
동일한 내용(똑같은 패스워드)을 해싱하면 동일한 문자열을 얻습니다. |
||||
|
|
||||
|
하지만 그 문자열을 다시 패스워드로 되돌릴 수는 없습니다. |
||||
|
|
||||
|
### 패스워드를 해싱하는 이유 |
||||
|
|
||||
|
데이터베이스를 탈취당하더라도, 침입자는 사용자의 평문 패스워드 대신 해시 값만 얻을 수 있습니다. |
||||
|
|
||||
|
따라서 침입자는 훔친 사용자 패스워드를 다른 시스템에서 활용할 수 없습니다. (대다수 사용자가 여러 시스템에서 동일한 패스워드를 사용하기 때문에 평문 패스워드가 유출되면 위험합니다.) |
||||
|
|
||||
|
## `passlib` 설치 |
||||
|
|
||||
|
PassLib는 패스워드 해시를 다루는 훌륭한 파이썬 패키지입니다. |
||||
|
|
||||
|
많은 안전한 해시 알고리즘과 도구들을 지원합니다. |
||||
|
|
||||
|
추천하는 알고리즘은 "Bcrypt"입니다. |
||||
|
|
||||
|
[가상환경](../../virtual-environments.md){.internal-link target=_blank} 을 만들고 활성화한 다음 PassLib와 Bcrypt를 설치하십시오: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ pip install "passlib[bcrypt]" |
||||
|
|
||||
|
---> 100% |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
/// tip | 팁 |
||||
|
|
||||
|
`passlib`를 사용하여, **Django**, **Flask** 의 보안 플러그인이나 다른 도구로 생성한 패스워드를 읽을 수 있도록 설정할 수도 있습니다. |
||||
|
|
||||
|
예를 들자면, FastAPI 애플리케이션과 Django 애플리케이션이 같은 데이터베이스에서 데이터를 공유할 수 있습니다. 또는 같은 데이터베이스를 사용하여 Django 애플리케이션을 점진적으로 마이그레이션 할 수도 있습니다. |
||||
|
|
||||
|
그리고 사용자는 FastAPI 애플리케이션과 Django 애플리케이션에 동시에 로그인할 수 있습니다. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## 패스워드의 해시와 검증 |
||||
|
|
||||
|
필요한 도구를 `passlib`에서 임포트합니다. |
||||
|
|
||||
|
PassLib "컨텍스트(context)"를 생성합니다. 이것은 패스워드를 해싱하고 검증하는데 사용합니다. |
||||
|
|
||||
|
/// 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`에 대입합니다. (이 예제의 변수 값을 그대로 사용하지 마십시오.) |
||||
|
|
||||
|
JWT 토큰을 서명하는 데 사용될 알고리즘을 위한 변수 `ALGORITHM` 을 생성하고 `"HS256"` 으로 설정합니다. |
||||
|
|
||||
|
토큰 만료 기간을 위한 변수를 생성합니다. |
||||
|
|
||||
|
응답을 위한 토큰 엔드포인트에 사용될 Pydantic 모델을 정의합니다. |
||||
|
|
||||
|
새 액세스 토큰을 생성하기 위한 유틸리티 함수를 생성합니다. |
||||
|
|
||||
|
{* ../../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 "주체(subject)" `sub`에 대한 기술 세부 사항 |
||||
|
|
||||
|
JWT 명세에 따르면 토큰의 주체를 포함하는 `sub`라는 키가 있습니다. |
||||
|
|
||||
|
사용 여부는 선택사항이지만, 사용자의 식별 정보를 저장할 수 있으므로 여기서는 이를 사용합니다. |
||||
|
|
||||
|
JWT는 사용자를 식별하고 사용자가 API를 직접 사용할 수 있도록 허용하는 것 외에도 다른 용도로 사용될 수도 있습니다. |
||||
|
|
||||
|
예를 들어 "자동차"나 "블로그 게시물"을 식별하는 데 사용할 수 있습니다. |
||||
|
|
||||
|
그리고 "자동차를 운전하다"나 "블로그 게시물을 수정하다"처럼 해당 엔터티에 대한 권한을 추가할 수 있습니다. |
||||
|
|
||||
|
그 후 이 JWT 토큰을 사용자(또는 봇)에게 제공하면, 그들은 계정을 따로 만들 필요 없이 API가 생성한 JWT 토큰만으로 작업(자동차 운전 또는 블로그 게시물 편집)을 수행할 수 있습니다. |
||||
|
|
||||
|
이러한 개념을 활용하면 JWT는 훨씬 더 복잡한 시나리오에도 사용할 수 있습니다. |
||||
|
|
||||
|
이 경우 여러 엔터티가 동일한 ID를 가질 수 있습니다. 예를 들어 foo라는 ID를 가진 사용자, 자동차, 블로그 게시물이 있을 수 있습니다. |
||||
|
|
||||
|
그래서 ID 충돌을 방지하기 위해, 사용자의 JWT 토큰을 생성할 때 접두사로 `sub` 키를 추가할 수 있습니다. 예를 들어 `username:` 을 붙이는 방식입니다. 이 예제에서는 `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 |
||||
|
|
||||
|
`Bearer `로 시작하는 `Authorization` 헤더에 주목하십시오. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## `scopes` 의 고급 사용법 |
||||
|
|
||||
|
OAuth2는 "스코프(scopes)" 라는 개념을 갖고 있습니다. |
||||
|
|
||||
|
이를 사용하여 JWT 토큰에 특정 권한 집합을 추가할 수 있습니다. |
||||
|
|
||||
|
그 후 이 토큰을 사용자에게 직접 제공하거나 제3자에게 제공하여, 특정 제한사항 하에있는 API와 통신하도록 할 수 있습니다. |
||||
|
|
||||
|
**FastAPI** 에서의 사용 방법과 통합 방식은 **심화 사용자 안내서** 에서 자세히 배울 수 있습니다. |
||||
|
|
||||
|
## 요약 |
||||
|
|
||||
|
지금까지 살펴본 내용을 바탕으로, OAuth2와 JWT 같은 표준을 사용하여 안전한 **FastAPI** 애플리케이션을 만들 수 있습니다. |
||||
|
|
||||
|
거의 모든 프레임워크에서 보안 처리는 상당히 복잡한 주제입니다. |
||||
|
|
||||
|
이를 단순화하는 많은 패키지는 데이터 모델, 데이터베이스, 사용 가능한 기능들에 대해 여러 제약이 있습니다. 그리고 지나치게 단순화하는 일부 패키지들은 심각한 보안 결함을 가질 수도 있습니다. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**FastAPI** 는 어떤 데이터베이스, 데이터 모델, 도구도 강요하지 않습니다. |
||||
|
|
||||
|
프로젝트에 가장 적합한 것을 선택할 수 있는 유연성을 제공합니다. |
||||
|
|
||||
|
그리고 `passlib` 와 `PyJWT` 처럼 잘 관리되고 널리 사용되는 패키지들을 바로 사용할 수 있습니다. **FastAPI** 는 외부 패키지 통합을 위해 복잡한 메커니즘이 필요하지 않기 때문입니다. |
||||
|
|
||||
|
그러나 유연성, 견고성, 보안성을 해치지 않으면서 과정을 단순화할 수 있는 도구들을 제공합니다. |
||||
|
|
||||
|
그리고 OAuth2와 같은 표준 프로토콜을 비교적 간단한 방법으로 구현하고 사용할 수 있습니다. |
||||
|
|
||||
|
더 세분화된 권한 체계를 위해 OAuth2의 "스코프"를 사용하는 방법은 **심화 사용자 안내서**에서 더 자세히 배울 수 있습니다. OAuth2의 스코프는 제3자 애플리케이션이 사용자를 대신해 그들의 API와 상호작용하도록 권한을 부여하기 위해, Facebook, Google, GitHub, Microsoft, Twitter 등의 많은 대형 인증 제공업체들이 사용하는 메커니즘입니다. |
@ -0,0 +1,72 @@ |
|||||
|
# Модели Header-параметров |
||||
|
|
||||
|
Если у вас есть группа связанных **header-параметров**, то вы можете объединить их в одну **Pydantic-модель**. |
||||
|
|
||||
|
Это позволит вам **переиспользовать модель** в **разных местах**, а также задать валидацию и метаданные сразу для всех параметров. 😎 |
||||
|
|
||||
|
/// note | Заметка |
||||
|
|
||||
|
Этот функционал доступен в FastAPI начиная с версии `0.115.0`. 🤓 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Header-параметры в виде Pydantic-модели |
||||
|
|
||||
|
Объявите нужные **header-параметры** в **Pydantic-модели** и затем аннотируйте параметр как `Header`: |
||||
|
|
||||
|
{* ../../docs_src/header_param_models/tutorial001_an_py310.py hl[9:14,18] *} |
||||
|
|
||||
|
**FastAPI** **извлечёт** данные для **каждого поля** из **заголовков** запроса и выдаст заданную вами Pydantic-модель. |
||||
|
|
||||
|
## Проверьте документацию |
||||
|
|
||||
|
Вы можете посмотреть нужные header-параметры в графическом интерфейсе сгенерированной документации по пути `/docs`: |
||||
|
|
||||
|
<div class="screenshot"> |
||||
|
<img src="/img/tutorial/header-param-models/image01.png"> |
||||
|
</div> |
||||
|
|
||||
|
## Как запретить дополнительные заголовки |
||||
|
|
||||
|
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** заголовки, которые вы хотите получать. |
||||
|
|
||||
|
Вы можете использовать возможности конфигурации Pydantic-модели для того, чтобы запретить (`forbid`) любые дополнительные (`extra`) поля: |
||||
|
|
||||
|
{* ../../docs_src/header_param_models/tutorial002_an_py310.py hl[10] *} |
||||
|
|
||||
|
Если клиент попробует отправить **дополнительные заголовки**, то в ответ он получит **ошибку**. |
||||
|
|
||||
|
Например, если клиент попытается отправить заголовок `tool` со значением `plumbus`, то в ответ он получит ошибку, сообщающую ему, что header-параметр `tool` не разрешен: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"detail": [ |
||||
|
{ |
||||
|
"type": "extra_forbidden", |
||||
|
"loc": ["header", "tool"], |
||||
|
"msg": "Extra inputs are not permitted", |
||||
|
"input": "plumbus", |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Как отключить автоматическое преобразование подчеркиваний |
||||
|
|
||||
|
Как и в случае с обычными заголовками, если у вас в именах параметров имеются символы подчеркивания, они **автоматически преобразовываются в дефис**. |
||||
|
|
||||
|
Например, если в коде есть header-параметр `save_data`, то ожидаемый HTTP-заголовок будет `save-data` и именно так он будет отображаться в документации. |
||||
|
|
||||
|
Если по каким-то причинам вам нужно отключить данное автоматическое преобразование, это можно сделать и для Pydantic-моделей для header-параметров. |
||||
|
|
||||
|
{* ../../docs_src/header_param_models/tutorial003_an_py310.py hl[19] *} |
||||
|
|
||||
|
/// warning | Внимание |
||||
|
|
||||
|
Перед тем как устанавливать для параметра `convert_underscores` значение `False`, имейте в виду, что некоторые HTTP-прокси и серверы не разрешают использовать заголовки с символами подчеркивания. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Резюме |
||||
|
|
||||
|
Вы можете использовать **Pydantic-модели** для объявления **header-параметров** в **FastAPI**. 😎 |
@ -0,0 +1,76 @@ |
|||||
|
# Моделі для Cookie-параметрів |
||||
|
|
||||
|
Якщо у Вас є група **cookies** параметрів, які пов'язані між собою, Ви можете створити **Pydantic-модель**, щоб оголосити їх. 🍪 |
||||
|
|
||||
|
Це дозволить Вам повторно **використовувати модель** у **різних місцях**, а також оголосити валідацію та метадані для всіх параметрів одночасно. 😎 |
||||
|
|
||||
|
/// note | Нотатки |
||||
|
|
||||
|
Це підтримується з версії FastAPI `0.115.0`. 🤓 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
/// tip | Порада |
||||
|
|
||||
|
Ця ж техніка застосовується до `Query`, `Cookie`, та `Header`. 😎 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Cookie з Pydantic-моделлю |
||||
|
|
||||
|
Оголосіть **cookie-параметри**, які Вам потрібні, у **Pydantic-моделі**, а потім оголосіть параметр як `Cookie`: |
||||
|
|
||||
|
{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *} |
||||
|
|
||||
|
**FastAPI** буде **витягувати** дані для **кожного поля** з **cookie** параметрів, отриманих у запиті, і передавати Вам Pydantic-модель, яку Ви визначили. |
||||
|
|
||||
|
## Перевірка у документації |
||||
|
|
||||
|
Ви можете побачити визначені cookie в інтерфейсі документації за адресою `/docs`: |
||||
|
|
||||
|
<div class="screenshot"> |
||||
|
<img src="/img/tutorial/cookie-param-models/image01.png"> |
||||
|
</div> |
||||
|
|
||||
|
/// info | Інформація |
||||
|
|
||||
|
Майте на увазі, що оскільки **браузери обробляють cookie** особливим чином і "за лаштунками", вони **не** дозволяють **JavaScript** легко з ними працювати. |
||||
|
|
||||
|
Якщо Ви зайдете до **інтерфейсу документації API** за адресою `/docs`, Ви зможете побачити **документацію** для cookie у Ваших **операціях шляху**. |
||||
|
|
||||
|
Але навіть якщо Ви заповните дані й натиснете "Execute", оскільки інтерфейс документації працює з **JavaScript**, cookie не будуть відправлені, і Ви побачите **помилку**, ніби Ви не ввели жодних значень. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Заборона додаткових cookie |
||||
|
|
||||
|
У деяких спеціальних випадках (ймовірно, не дуже поширених) Ви можете захотіти **обмежити** список cookie, які хочете отримувати. |
||||
|
|
||||
|
Ваша API тепер має можливість контролювати власну <abbr title="Це жарт, якщо що. Це не має нічого спільного зі згодою на використання cookie, але це кумедно, що навіть API тепер може відхиляти бідні cookie. Ловіть печиво. 🍪">згоду на cookie</abbr>. 🤪🍪 |
||||
|
|
||||
|
Ви можете використовувати налаштування моделі Pydantic, щоб `заборонити` будь-які `додаткові` поля: |
||||
|
|
||||
|
{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *} |
||||
|
|
||||
|
Якщо клієнт спробує надіслати якісь **додаткові cookie**, він отримає відповідь з **помилкою**. |
||||
|
|
||||
|
Бідні банери cookie, які так старанно намагаються отримати Вашу згоду, щоб <abbr title="Це ще один жарт. Не звертайте уваги. Візьміть каву для свого печива. ☕">API її відхилила</abbr>. 🍪 |
||||
|
|
||||
|
Наприклад, якщо клієнт спробує надіслати cookie `santa_tracker` зі значенням `good-list-please`, він отримає відповідь з помилкою, яка повідомить, що <abbr title="Санта не схвалює відсутність cookie. 🎅 Гаразд, більше жартів не буде.">cookie `santa_tracker` не дозволено</abbr>: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"detail": [ |
||||
|
{ |
||||
|
"type": "extra_forbidden", |
||||
|
"loc": ["cookie", "santa_tracker"], |
||||
|
"msg": "Extra inputs are not permitted", |
||||
|
"input": "good-list-please", |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Підсумок |
||||
|
|
||||
|
Ви можете використовувати **Pydantic-моделі** для оголошення <abbr title="Отримайте останнє печиво перед тим, як піти. 🍪">cookie</abbr> у FastAPI. 😎 |
@ -0,0 +1,58 @@ |
|||||
|
# Моделі Параметрів Заголовків |
||||
|
|
||||
|
Якщо у Вас є група пов’язаних параметрів заголовків, Ви можете створити **Pydantic модель** для їх оголошення. |
||||
|
|
||||
|
Це дозволить Вам повторно **використовувати модель** в **різних місцях**, а також оголосити валідації та метадані для всіх параметрів одночасно. 😎 |
||||
|
|
||||
|
/// note | Нотатки |
||||
|
|
||||
|
Ця можливість підтримується починаючи з версії FastAPI `0.115.0`. 🤓 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Параметри Заголовків з Використанням Pydantic Model |
||||
|
|
||||
|
Оголосіть потрібні **параметри заголовків** у **Pydantic моделі**, а потім оголосіть параметр як `Header`: |
||||
|
|
||||
|
{* ../../docs_src/header_param_models/tutorial001_an_py310.py hl[9:14,18] *} |
||||
|
|
||||
|
FastAPI буде витягувати дані для кожного поля з заголовків у запиті та передавати їх у створену Вами Pydantic модель. |
||||
|
|
||||
|
**FastAPI** буде **витягувати** дані для **кожного поля** з **заголовків** у запиті та передавати їх у створену Вами Pydantic модель. |
||||
|
|
||||
|
## Перевірка в Документації |
||||
|
|
||||
|
Ви можете побачити необхідні заголовки в інтерфейсі документації за адресою `/docs`: |
||||
|
|
||||
|
<div class="screenshot"> |
||||
|
<img src="/img/tutorial/header-param-models/image01.png"> |
||||
|
</div> |
||||
|
|
||||
|
## Заборона Додаткових Заголовків |
||||
|
|
||||
|
У деяких особливих випадках (ймовірно, не дуже поширених) Ви можете захотіти **обмежити** заголовки, які хочете отримати. |
||||
|
|
||||
|
Ви можете використати конфігурацію моделі Pydantic, щоб `заборонити` будь-які `додаткові` поля: |
||||
|
|
||||
|
{* ../../docs_src/header_param_models/tutorial002_an_py310.py hl[10] *} |
||||
|
|
||||
|
Якщо клієнт спробує надіслати **додаткові заголовки**, він отримає **помилку** у відповіді. |
||||
|
|
||||
|
Наприклад, якщо клієнт спробує надіслати заголовок `tool` зі значенням `plumbus`, він отримає **помилку** з повідомленням про те, що параметр заголовка `tool` не дозволений: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"detail": [ |
||||
|
{ |
||||
|
"type": "extra_forbidden", |
||||
|
"loc": ["header", "tool"], |
||||
|
"msg": "Extra inputs are not permitted", |
||||
|
"input": "plumbus", |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Підсумок |
||||
|
|
||||
|
Ви можете використовувати **Pydantic моделі** для оголошення **заголовків** у **FastAPI**. 😎 |
@ -0,0 +1,120 @@ |
|||||
|
# Метадані та URL-адреси документації |
||||
|
|
||||
|
Ви можете налаштувати кілька конфігурацій метаданих у Вашому додатку **FastAPI**. |
||||
|
|
||||
|
## Метадані для API |
||||
|
|
||||
|
Ви можете встановити такі поля, які використовуються в специфікації OpenAPI та в автоматично згенерованих інтерфейсах документації API: |
||||
|
|
||||
|
| Параметр | Тип | Опис | |
||||
|
|------------|------|-------------| |
||||
|
| `title` | `str` | Назва API. | |
||||
|
| `summary` | `str` | Короткий опис API. <small>Доступно з OpenAPI 3.1.0, FastAPI 0.99.0.</small> | |
||||
|
| `description` | `str` | Більш детальний опис API. Може використовувати Markdown. | |
||||
|
| `version` | `string` | Версія API. Це версія Вашого додатка, а не OpenAPI. Наприклад, `2.5.0`. | |
||||
|
| `terms_of_service` | `str` | URL до умов використання API. Якщо вказано, має бути у форматі URL. | |
||||
|
| `contact` | `dict` | Інформація для контакту з API. Може містити кілька полів. <details><summary><code>contact</code> поля</summary><table><thead><tr><th>Параметр</th><th>Тип</th><th>Опис</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td>Ім'я контактної особи або організації.</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL з інформацією для контакту. Повинен бути у форматі URL.</td></tr><tr><td><code>email</code></td><td><code>str</code></td><td>Email контактної особи або організації. Повинен бути у форматі електронної пошти.</td></tr></tbody></table></details> | |
||||
|
| `license_info` | `dict` | Інформація про ліцензію для API. Може містити кілька полів. <details><summary><code>license_info</code> поля</summary><table><thead><tr><th>Параметр</th><th>Тип</th><th>Опис</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>ОБОВ'ЯЗКОВО</strong> (якщо встановлено <code>license_info</code>). Назва ліцензії для API.</td></tr><tr><td><code>identifier</code></td><td><code>str</code></td><td>Ліцензійний вираз за <a href="https://spdx.org/licenses/" class="external-link" target="_blank">SPDX</a> для API. Поле <code>identifier</code> взаємовиключне з полем <code>url</code>. <small>Доступно з OpenAPI 3.1.0, FastAPI 0.99.0.</small></td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL до ліцензії, яка використовується для API. Повинен бути у форматі URL.</td></tr></tbody></table></details> | |
||||
|
|
||||
|
Ви можете налаштувати їх наступним чином: |
||||
|
|
||||
|
{* ../../docs_src/metadata/tutorial001.py hl[3:16, 19:32] *} |
||||
|
|
||||
|
/// tip | Підказка |
||||
|
|
||||
|
У полі `description` можна використовувати Markdown, і він буде відображатися у результаті. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
З цією конфігурацією автоматична документація API виглядатиме так: |
||||
|
|
||||
|
<img src="/img/tutorial/metadata/image01.png"> |
||||
|
|
||||
|
## Ідентифікатор ліцензії |
||||
|
|
||||
|
З початку використання OpenAPI 3.1.0 та FastAPI 0.99.0 Ви також можете налаштувати `license_info` за допомогою `identifier` замість `url`. |
||||
|
|
||||
|
Наприклад: |
||||
|
|
||||
|
{* ../../docs_src/metadata/tutorial001_1.py hl[31] *} |
||||
|
|
||||
|
## Метадані для тегів |
||||
|
|
||||
|
Ви також можете додати додаткові метадані для різних тегів, які використовуються для групування операцій шляхів, за допомогою параметра `openapi_tags`. |
||||
|
|
||||
|
Він приймає список, який містить один словник для кожного тега. |
||||
|
|
||||
|
Кожен словник може містити: |
||||
|
|
||||
|
* `name` (**обов'язково**): `str` з тією ж назвою тегу, яку Ви використовуєте у параметрі `tags` у Ваших *операціях шляху* та `APIRouter`s. |
||||
|
* `description`: `str` з коротким описом тегу. Може містити Markdown і буде відображено в інтерфейсі документації. |
||||
|
* `externalDocs`: `dict` який описує зовнішню документацію з такими полями: |
||||
|
* `description`: `str` з коротким описом зовнішньої документації. |
||||
|
* `url` (**обов'язково**): `str`з URL-адресою зовнішньої документації. |
||||
|
|
||||
|
### Створення метаданих для тегів |
||||
|
|
||||
|
Спробуймо це на прикладі з тегами для `users` та `items`. |
||||
|
|
||||
|
Створіть метадані для своїх тегів і передайте їх у параметр `openapi_tags`: |
||||
|
|
||||
|
{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *} |
||||
|
|
||||
|
Зверніть увагу, що в описах можна використовувати Markdown, наприклад, "login" буде показано жирним шрифтом (**login**), а "fancy" буде показано курсивом (_fancy_). |
||||
|
|
||||
|
/// tip | Порада |
||||
|
|
||||
|
Не обов'язково додавати метадані для всіх тегів, які Ви використовуєте. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Використання тегів |
||||
|
|
||||
|
Використовуйте параметр `tags` зі своїми *операціями шляху* (і `APIRouter`) для призначення їх до різних тегів: |
||||
|
|
||||
|
{* ../../docs_src/metadata/tutorial004.py hl[21,26] *} |
||||
|
|
||||
|
/// info | Інформація |
||||
|
|
||||
|
Детальніше про теги читайте в розділі [Конфігурація шляхів операцій](path-operation-configuration.md#tags){.internal-link target=_blank}. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Перевірка документації |
||||
|
|
||||
|
Якщо Ви зараз перевірите документацію, вона покаже всі додаткові метадані: |
||||
|
|
||||
|
<img src="/img/tutorial/metadata/image02.png"> |
||||
|
|
||||
|
### Порядок тегів |
||||
|
|
||||
|
Порядок кожного словника метаданих тегу також визначає порядок відображення в інтерфейсі документації. |
||||
|
|
||||
|
Наприклад, хоча `users` мав би йти після `items` в алфавітному порядку, він відображається перед ними, оскільки ми додали його метадані як перший словник у списку. |
||||
|
|
||||
|
## URL для OpenAPI |
||||
|
|
||||
|
За замовчуванням схема OpenAPI надається за адресою `/openapi.json`. |
||||
|
|
||||
|
Але Ви можете налаштувати це за допомогою параметра `openapi_url`. |
||||
|
|
||||
|
Наприклад, щоб налаштувати його на `/api/v1/openapi.json`: |
||||
|
|
||||
|
{* ../../docs_src/metadata/tutorial002.py hl[3] *} |
||||
|
|
||||
|
Якщо Ви хочете повністю вимкнути схему OpenAPI, Ви можете встановити `openapi_url=None`, це також вимкне інтерфейси документації, які її використовують. |
||||
|
|
||||
|
## URL-адреси документації |
||||
|
|
||||
|
Ви можете налаштувати два інтерфейси користувача для документації, які включені: |
||||
|
|
||||
|
* **Swagger UI**: доступний за адресою `/docs`. |
||||
|
* Ви можете змінити його URL за допомогою параметра `docs_url`. |
||||
|
* Ви можете вимкнути його, встановивши `docs_url=None`. |
||||
|
* **ReDoc**: доступний за адресою `/redoc`. |
||||
|
* Ви можете змінити його URL за допомогою параметра `redoc_url`. |
||||
|
* Ви можете вимкнути його, встановивши `redoc_url=None`. |
||||
|
|
||||
|
Наприклад, щоб налаштувати Swagger UI на `/documentation` і вимкнути ReDoc: |
||||
|
|
||||
|
{* ../../docs_src/metadata/tutorial003.py hl[3] *} |
@ -0,0 +1,100 @@ |
|||||
|
# Статус коди Відповідей |
||||
|
|
||||
|
Так само як Ви можете вказати модель відповіді, Ви також можете оголосити HTTP код статусу для відповіді за допомогою параметра `status_code` в будь-якій з *операцій шляху*: |
||||
|
|
||||
|
* `@app.get()` |
||||
|
* `@app.post()` |
||||
|
* `@app.put()` |
||||
|
* `@app.delete()` |
||||
|
* тощо. |
||||
|
|
||||
|
{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} |
||||
|
|
||||
|
/// note | Нотатка |
||||
|
|
||||
|
Зверніть увагу, що `status_code` є параметром методу "декоратора" (`get`, `post` і т.д.), а не Вашої *функції операції шляху*, як усі інші параметри та тіло запиту. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Параметр `status_code` приймає число, яке відповідає HTTP коду статусу. |
||||
|
|
||||
|
/// info | Інформація |
||||
|
`status_code` також може отримувати значення з `IntEnum`, наприклад, з Python <a href="https://docs.python.org/3/library/http.html#http.HTTPStatus" class="external-link" target="_blank">`http.HTTPStatus`</a>. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Він буде: |
||||
|
|
||||
|
* Повертати вказаний код статусу у відповіді. |
||||
|
* Документувати його як такий у схемі OpenAPI (і, таким чином, в інтерфейсі користувача): |
||||
|
|
||||
|
<img src="/img/tutorial/response-status-code/image01.png"> |
||||
|
|
||||
|
/// note | Нотатка |
||||
|
|
||||
|
Деякі коди відповіді (див. наступний розділ) вказують, що відповідь не має тіла. |
||||
|
|
||||
|
FastAPI знає про це і створить OpenAPI документацію, яка вказує, що тіла відповіді немає. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Про HTTP статус коди |
||||
|
|
||||
|
/// note | Нотатка |
||||
|
|
||||
|
Якщо Ви вже знаєте, що таке HTTP коди статусу, переходьте до наступного розділу. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
В HTTP Ви надсилаєте числовий код статусу з 3 цифр як частину відповіді. |
||||
|
|
||||
|
Ці коди статусу мають пов’язану назву для їх розпізнавання, але найважливішою частиною є саме число. |
||||
|
|
||||
|
Коротко: |
||||
|
|
||||
|
* **`100 - 199`** "Інформаційні" відповіді. Ви рідко використовуєте їх напряму. Відповіді з такими кодами не можуть мати тіла. |
||||
|
* **`200 - 299`** "Успішні" відповіді. Це ті, які Ви використовуватимете найчастіше. |
||||
|
* `200` - код за замовчуванням, який означає, що все пройшло "OK". |
||||
|
* Інший приклад – `201`, "Created" (створено). Його зазвичай використовують після створення нового запису в базі даних. |
||||
|
* Особливий випадок – `204`, "No Content" (немає вмісту). Ця відповідь використовується, коли немає даних для повернення клієнту, тому відповідь не повинна мати тіла. |
||||
|
* **`300 - 399`** "Перенаправлення". Відповіді з цими кодами можуть мати або не мати тіла, за винятком `304`, "Not Modified" (не змінено), яка не повинна мати тіла. |
||||
|
* **`400 - 499`** "Помилка клієнта". Це другий тип, який Ви, ймовірно, будете використовувати найчастіше. |
||||
|
* Приклад `404`, "Not Found" (не знайдено). |
||||
|
* Для загальних помилок клієнта можна використовувати `400`. |
||||
|
* `500 - 599` "Помилки сервера". Ви майже ніколи не використовуєте їх напряму. Якщо в коді Вашого застосунку або на сервері щось пішло не так, автоматично буде повернено один із цих кодів статусу. |
||||
|
|
||||
|
/// tip | Порада |
||||
|
|
||||
|
Щоб дізнатися більше про кожен код статусу і призначення кожного з них, перегляньте документацію <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> про HTTP коди статусу</a>. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Легкий спосіб запам'ятати назви |
||||
|
|
||||
|
Розглянемо ще раз попередній приклад: |
||||
|
|
||||
|
{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} |
||||
|
|
||||
|
`201` - це код статусу для "Created" (створено). |
||||
|
|
||||
|
Але Вам не потрібно запам'ятовувати, що означає кожен із цих кодів. |
||||
|
|
||||
|
Ви можете використовувати зручні змінні з `fastapi.status` |
||||
|
|
||||
|
{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *} |
||||
|
|
||||
|
Ці змінні просто для зручності. Вони містять ті ж самі числа, але Ви можете скористатися автозаповненням в редакторі: |
||||
|
|
||||
|
<img src="/img/tutorial/response-status-code/image02.png"> |
||||
|
|
||||
|
/// note | Технічні деталі |
||||
|
|
||||
|
Ви також можете використати `from starlette import status`. |
||||
|
|
||||
|
**FastAPI** надає ті ж самі змінні `starlette.status` як `fastapi.status`, просто для зручності розробника. Однак вони походять безпосередньо зі Starlette. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Зміна значення за замовчуванням |
||||
|
|
||||
|
Далі, у Посібнику для досвідчених користувачів{.internal-link target=_blank}, Ви дізнаєтесь, як повернути інший код статусу, ніж той, який Ви оголосили тут. |
@ -0,0 +1,17 @@ |
|||||
|
# Triển khai FastAPI trên các Dịch vụ Cloud |
||||
|
|
||||
|
Bạn có thể sử dụng **bất kỳ nhà cung cấp dịch vụ cloud** nào để triển khai ứng dụng FastAPI của mình. |
||||
|
|
||||
|
Trong hầu hết các trường hợp, các nhà cung cấp dịch vụ cloud lớn đều có hướng dẫn triển khai FastAPI với họ. |
||||
|
|
||||
|
## Nhà cung cấp dịch vụ Cloud - Nhà tài trợ |
||||
|
Một vài nhà cung cấp dịch vụ cloud ✨ [**tài trợ cho FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, điều này giúp đảm bảo sự phát triển liên tục và khỏe mạnh của FastAPI và hệ sinh thái của nó. |
||||
|
|
||||
|
Thêm nữa, điều này cũng thể hiện cam kết thực sự của họ đối với FastAPI và **cộng đồng người dùng** (bạn), vì họ không chỉ muốn cung cấp cho bạn một **dịch vụ tốt** mà còn muốn đảm bảo rằng bạn có một **framework tốt và bền vững**, đó chính là FastAPI. 🙇 |
||||
|
|
||||
|
Bạn có thể thử các dịch vụ của họ và làm theo hướng dẫn của họ: |
||||
|
|
||||
|
* <a href="https://docs.platform.sh/languages/python.html?utm_source=fastapi-signup&utm_medium=banner&utm_campaign=FastAPI-signup-June-2023" class="external-link" target="_blank">Platform.sh</a> |
||||
|
* <a href="https://docs.porter.run/language-specific-guides/fastapi" class="external-link" target="_blank">Porter</a> |
||||
|
* <a href="https://www.withcoherence.com/?utm_medium=advertising&utm_source=fastapi&utm_campaign=website" class="external-link" target="_blank">Coherence</a> |
||||
|
* <a href="https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi" class="external-link" target="_blank">Render</a> |
@ -0,0 +1,19 @@ |
|||||
|
from typing import List, Union |
||||
|
|
||||
|
from fastapi import FastAPI, Header |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class CommonHeaders(BaseModel): |
||||
|
host: str |
||||
|
save_data: bool |
||||
|
if_modified_since: Union[str, None] = None |
||||
|
traceparent: Union[str, None] = None |
||||
|
x_tag: List[str] = [] |
||||
|
|
||||
|
|
||||
|
@app.get("/items/") |
||||
|
async def read_items(headers: CommonHeaders = Header(convert_underscores=False)): |
||||
|
return headers |
@ -0,0 +1,22 @@ |
|||||
|
from typing import List, Union |
||||
|
|
||||
|
from fastapi import FastAPI, Header |
||||
|
from pydantic import BaseModel |
||||
|
from typing_extensions import Annotated |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class CommonHeaders(BaseModel): |
||||
|
host: str |
||||
|
save_data: bool |
||||
|
if_modified_since: Union[str, None] = None |
||||
|
traceparent: Union[str, None] = None |
||||
|
x_tag: List[str] = [] |
||||
|
|
||||
|
|
||||
|
@app.get("/items/") |
||||
|
async def read_items( |
||||
|
headers: Annotated[CommonHeaders, Header(convert_underscores=False)], |
||||
|
): |
||||
|
return headers |
@ -0,0 +1,21 @@ |
|||||
|
from typing import Annotated |
||||
|
|
||||
|
from fastapi import FastAPI, Header |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class CommonHeaders(BaseModel): |
||||
|
host: str |
||||
|
save_data: bool |
||||
|
if_modified_since: str | None = None |
||||
|
traceparent: str | None = None |
||||
|
x_tag: list[str] = [] |
||||
|
|
||||
|
|
||||
|
@app.get("/items/") |
||||
|
async def read_items( |
||||
|
headers: Annotated[CommonHeaders, Header(convert_underscores=False)], |
||||
|
): |
||||
|
return headers |
@ -0,0 +1,21 @@ |
|||||
|
from typing import Annotated, Union |
||||
|
|
||||
|
from fastapi import FastAPI, Header |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class CommonHeaders(BaseModel): |
||||
|
host: str |
||||
|
save_data: bool |
||||
|
if_modified_since: Union[str, None] = None |
||||
|
traceparent: Union[str, None] = None |
||||
|
x_tag: list[str] = [] |
||||
|
|
||||
|
|
||||
|
@app.get("/items/") |
||||
|
async def read_items( |
||||
|
headers: Annotated[CommonHeaders, Header(convert_underscores=False)], |
||||
|
): |
||||
|
return headers |
@ -0,0 +1,17 @@ |
|||||
|
from fastapi import FastAPI, Header |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class CommonHeaders(BaseModel): |
||||
|
host: str |
||||
|
save_data: bool |
||||
|
if_modified_since: str | None = None |
||||
|
traceparent: str | None = None |
||||
|
x_tag: list[str] = [] |
||||
|
|
||||
|
|
||||
|
@app.get("/items/") |
||||
|
async def read_items(headers: CommonHeaders = Header(convert_underscores=False)): |
||||
|
return headers |
@ -0,0 +1,19 @@ |
|||||
|
from typing import Union |
||||
|
|
||||
|
from fastapi import FastAPI, Header |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class CommonHeaders(BaseModel): |
||||
|
host: str |
||||
|
save_data: bool |
||||
|
if_modified_since: Union[str, None] = None |
||||
|
traceparent: Union[str, None] = None |
||||
|
x_tag: list[str] = [] |
||||
|
|
||||
|
|
||||
|
@app.get("/items/") |
||||
|
async def read_items(headers: CommonHeaders = Header(convert_underscores=False)): |
||||
|
return headers |
@ -1,4 +1,4 @@ |
|||||
# For mkdocstrings and tests |
# For mkdocstrings and tests |
||||
httpx >=0.23.0,<0.28.0 |
httpx >=0.23.0,<0.28.0 |
||||
# For linting and generating docs versions |
# For linting and generating docs versions |
||||
ruff ==0.6.4 |
ruff ==0.11.2 |
||||
|
@ -1 +1 @@ |
|||||
pydantic-ai==0.0.15 |
pydantic-ai==0.0.30 |
||||
|
@ -0,0 +1,285 @@ |
|||||
|
import importlib |
||||
|
|
||||
|
import pytest |
||||
|
from dirty_equals import IsDict |
||||
|
from fastapi.testclient import TestClient |
||||
|
from inline_snapshot import snapshot |
||||
|
|
||||
|
from tests.utils import needs_py39, needs_py310 |
||||
|
|
||||
|
|
||||
|
@pytest.fixture( |
||||
|
name="client", |
||||
|
params=[ |
||||
|
"tutorial003", |
||||
|
pytest.param("tutorial003_py39", marks=needs_py39), |
||||
|
pytest.param("tutorial003_py310", marks=needs_py310), |
||||
|
"tutorial003_an", |
||||
|
pytest.param("tutorial003_an_py39", marks=needs_py39), |
||||
|
pytest.param("tutorial003_an_py310", marks=needs_py310), |
||||
|
], |
||||
|
) |
||||
|
def get_client(request: pytest.FixtureRequest): |
||||
|
mod = importlib.import_module(f"docs_src.header_param_models.{request.param}") |
||||
|
|
||||
|
client = TestClient(mod.app) |
||||
|
return client |
||||
|
|
||||
|
|
||||
|
def test_header_param_model(client: TestClient): |
||||
|
response = client.get( |
||||
|
"/items/", |
||||
|
headers=[ |
||||
|
("save_data", "true"), |
||||
|
("if_modified_since", "yesterday"), |
||||
|
("traceparent", "123"), |
||||
|
("x_tag", "one"), |
||||
|
("x_tag", "two"), |
||||
|
], |
||||
|
) |
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == { |
||||
|
"host": "testserver", |
||||
|
"save_data": True, |
||||
|
"if_modified_since": "yesterday", |
||||
|
"traceparent": "123", |
||||
|
"x_tag": ["one", "two"], |
||||
|
} |
||||
|
|
||||
|
|
||||
|
def test_header_param_model_no_underscore(client: TestClient): |
||||
|
response = client.get( |
||||
|
"/items/", |
||||
|
headers=[ |
||||
|
("save-data", "true"), |
||||
|
("if-modified-since", "yesterday"), |
||||
|
("traceparent", "123"), |
||||
|
("x-tag", "one"), |
||||
|
("x-tag", "two"), |
||||
|
], |
||||
|
) |
||||
|
assert response.status_code == 422 |
||||
|
assert response.json() == snapshot( |
||||
|
{ |
||||
|
"detail": [ |
||||
|
IsDict( |
||||
|
{ |
||||
|
"type": "missing", |
||||
|
"loc": ["header", "save_data"], |
||||
|
"msg": "Field required", |
||||
|
"input": { |
||||
|
"host": "testserver", |
||||
|
"traceparent": "123", |
||||
|
"x_tag": [], |
||||
|
"accept": "*/*", |
||||
|
"accept-encoding": "gzip, deflate", |
||||
|
"connection": "keep-alive", |
||||
|
"user-agent": "testclient", |
||||
|
"save-data": "true", |
||||
|
"if-modified-since": "yesterday", |
||||
|
"x-tag": "two", |
||||
|
}, |
||||
|
} |
||||
|
) |
||||
|
| IsDict( |
||||
|
# TODO: remove when deprecating Pydantic v1 |
||||
|
{ |
||||
|
"type": "value_error.missing", |
||||
|
"loc": ["header", "save_data"], |
||||
|
"msg": "field required", |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def test_header_param_model_defaults(client: TestClient): |
||||
|
response = client.get("/items/", headers=[("save_data", "true")]) |
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == { |
||||
|
"host": "testserver", |
||||
|
"save_data": True, |
||||
|
"if_modified_since": None, |
||||
|
"traceparent": None, |
||||
|
"x_tag": [], |
||||
|
} |
||||
|
|
||||
|
|
||||
|
def test_header_param_model_invalid(client: TestClient): |
||||
|
response = client.get("/items/") |
||||
|
assert response.status_code == 422 |
||||
|
assert response.json() == snapshot( |
||||
|
{ |
||||
|
"detail": [ |
||||
|
IsDict( |
||||
|
{ |
||||
|
"type": "missing", |
||||
|
"loc": ["header", "save_data"], |
||||
|
"msg": "Field required", |
||||
|
"input": { |
||||
|
"x_tag": [], |
||||
|
"host": "testserver", |
||||
|
"accept": "*/*", |
||||
|
"accept-encoding": "gzip, deflate", |
||||
|
"connection": "keep-alive", |
||||
|
"user-agent": "testclient", |
||||
|
}, |
||||
|
} |
||||
|
) |
||||
|
| IsDict( |
||||
|
# TODO: remove when deprecating Pydantic v1 |
||||
|
{ |
||||
|
"type": "value_error.missing", |
||||
|
"loc": ["header", "save_data"], |
||||
|
"msg": "field required", |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def test_header_param_model_extra(client: TestClient): |
||||
|
response = client.get( |
||||
|
"/items/", headers=[("save_data", "true"), ("tool", "plumbus")] |
||||
|
) |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() == snapshot( |
||||
|
{ |
||||
|
"host": "testserver", |
||||
|
"save_data": True, |
||||
|
"if_modified_since": None, |
||||
|
"traceparent": None, |
||||
|
"x_tag": [], |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def test_openapi_schema(client: TestClient): |
||||
|
response = client.get("/openapi.json") |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() == snapshot( |
||||
|
{ |
||||
|
"openapi": "3.1.0", |
||||
|
"info": {"title": "FastAPI", "version": "0.1.0"}, |
||||
|
"paths": { |
||||
|
"/items/": { |
||||
|
"get": { |
||||
|
"summary": "Read Items", |
||||
|
"operationId": "read_items_items__get", |
||||
|
"parameters": [ |
||||
|
{ |
||||
|
"name": "host", |
||||
|
"in": "header", |
||||
|
"required": True, |
||||
|
"schema": {"type": "string", "title": "Host"}, |
||||
|
}, |
||||
|
{ |
||||
|
"name": "save_data", |
||||
|
"in": "header", |
||||
|
"required": True, |
||||
|
"schema": {"type": "boolean", "title": "Save Data"}, |
||||
|
}, |
||||
|
{ |
||||
|
"name": "if_modified_since", |
||||
|
"in": "header", |
||||
|
"required": False, |
||||
|
"schema": IsDict( |
||||
|
{ |
||||
|
"anyOf": [{"type": "string"}, {"type": "null"}], |
||||
|
"title": "If Modified Since", |
||||
|
} |
||||
|
) |
||||
|
| IsDict( |
||||
|
# TODO: remove when deprecating Pydantic v1 |
||||
|
{ |
||||
|
"type": "string", |
||||
|
"title": "If Modified Since", |
||||
|
} |
||||
|
), |
||||
|
}, |
||||
|
{ |
||||
|
"name": "traceparent", |
||||
|
"in": "header", |
||||
|
"required": False, |
||||
|
"schema": IsDict( |
||||
|
{ |
||||
|
"anyOf": [{"type": "string"}, {"type": "null"}], |
||||
|
"title": "Traceparent", |
||||
|
} |
||||
|
) |
||||
|
| IsDict( |
||||
|
# TODO: remove when deprecating Pydantic v1 |
||||
|
{ |
||||
|
"type": "string", |
||||
|
"title": "Traceparent", |
||||
|
} |
||||
|
), |
||||
|
}, |
||||
|
{ |
||||
|
"name": "x_tag", |
||||
|
"in": "header", |
||||
|
"required": False, |
||||
|
"schema": { |
||||
|
"type": "array", |
||||
|
"items": {"type": "string"}, |
||||
|
"default": [], |
||||
|
"title": "X Tag", |
||||
|
}, |
||||
|
}, |
||||
|
], |
||||
|
"responses": { |
||||
|
"200": { |
||||
|
"description": "Successful Response", |
||||
|
"content": {"application/json": {"schema": {}}}, |
||||
|
}, |
||||
|
"422": { |
||||
|
"description": "Validation Error", |
||||
|
"content": { |
||||
|
"application/json": { |
||||
|
"schema": { |
||||
|
"$ref": "#/components/schemas/HTTPValidationError" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"components": { |
||||
|
"schemas": { |
||||
|
"HTTPValidationError": { |
||||
|
"properties": { |
||||
|
"detail": { |
||||
|
"items": { |
||||
|
"$ref": "#/components/schemas/ValidationError" |
||||
|
}, |
||||
|
"type": "array", |
||||
|
"title": "Detail", |
||||
|
} |
||||
|
}, |
||||
|
"type": "object", |
||||
|
"title": "HTTPValidationError", |
||||
|
}, |
||||
|
"ValidationError": { |
||||
|
"properties": { |
||||
|
"loc": { |
||||
|
"items": { |
||||
|
"anyOf": [{"type": "string"}, {"type": "integer"}] |
||||
|
}, |
||||
|
"type": "array", |
||||
|
"title": "Location", |
||||
|
}, |
||||
|
"msg": {"type": "string", "title": "Message"}, |
||||
|
"type": {"type": "string", "title": "Error Type"}, |
||||
|
}, |
||||
|
"type": "object", |
||||
|
"required": ["loc", "msg", "type"], |
||||
|
"title": "ValidationError", |
||||
|
}, |
||||
|
} |
||||
|
}, |
||||
|
} |
||||
|
) |
Loading…
Reference in new issue