🐧

Linux 使いになりたい人のための入門版 Python 開発コンテナーの使い方 - 2024年度版

2024/04/29に公開

はじめに

ここでは Linux 使いになりたい人向けに入門版 Python 開発コンテナーの使い方について説明します。入門版の開発コンテナーを対象とするので、devcontainer.json を使った本格的な開発コンテナーについては説明していませんので注意してください。

ここで、「Linux 使いになりたい人」とタイトルにつけているのは、この記事では、簡単なシェルスクリプトやコマンドを使う方法で説明している部分が多いからです。最近は Infrastructure as Code (IaC) という用語があることからわかるように、「コードを使用して IT インフラの管理やプロビジョニングを自動化すること」が重要視されています。こういったことを意識からすると、できるだけ GUI に頼らずに環境構築したり、開発作業ができるようになりたいところでしょう。

自分は、このスキルを身につけるのには、「Linux 使いになる」というのが近道だと考えています。IaC 以外でも Linux を使えること自体が開発者としてアドバンテージがあるはずです。ということで、今は Linux 使いでなくても、将来 Linux 使いになりたいと考えている人に、実用的なシェルスクリプトを含むドキュメントを提供することで役に立てればと思い、「Linux 使いになりたい人のため」シリーズの記事を執筆しています。

本題に戻ります。動作確認は Linux 環境を使っているため、Windows や macOS とは違っている場合があります。とはいえ、基本的には Docker が動作する環境であれば動くはずです。

この記事の対象者の前提は下記となります。

  • VS Code を使った開発コンテナーに興味があること
  • Visual Studio Code(VS Code)が使えること
  • Docker が使えること
  • Python の開発について基本的なことを知っていること
  • Docker Compose の基本的な使い方を知っていること
  • Linux の基本的な使い方を知っていること

使用する環境は下記です。

Docker Desktop については、これに同梱されている Docker Engine と Docker Compose があれば大丈夫です。

動作の確認については Ubuntu 22.04 で行っています。おそらく WSL2 Ubuntu 22.04 でも動作します。

説明にあたっては、Docker ホストで実行するスクリプトも用意してあり、それは Ubuntu 22.04 を前提としています。ただし、単純なものなので、Windows や macOS でも手動で似たような処理を実行することは容易なはずです。

開発コンテナーについて

この記事では、開発コンテナーといったら Developing inside a Container using Visual Studio Code Remote Development で説明されているものを指します。コンテナーの中で VS Code Server を動かして、ユーザーは VS Code の画面経由でコンテナーのコンピュータリソースを使って開発作業をします。

開発コンテナーを使うメリットのひとつには、開発者が使う環境の差が基本的になくなることがあります。開発者が、使用する Docker イメージの作り方、VS Code での開発コンテナーの使い方を知っていれば、開発環境の構築時間は非常に少なくて済むようになります。

本格的な開発コンテナーは devcontainer.json を使ったものになりますが、そこまで高機能なものを使わなくても開発コンテナーは便利なものです。VS Code の Dev Container 拡張機能をインストールすると、Visual Studio Code をアタッチする というメニューが使えるようになり、これを使うとコンテナーの中で VS Code Server が自動で稼働して、ユーザーは VS Code の画面経由でコンテナーのコンピュータリソースを使って開発作業をすることができるようになります。

ということで、ここでは、Visual Studio Code をアタッチする というメニューで使う開発コンテナーのことを「入門版の開発コンテナー」と呼んでいます。そして、これを使って開発をするときは、どんな感じで作業をすることになるのかを、この記事では説明しています。

自分で開発コンテナーを使いながら、開発をしてみるとわかるのですが、開発をしていると、最初に用意した環境を改良したくなります。アプリの開発と、開発環境のベースとなる開発コンテナーの両方について、開発を進めていくことになるので、効率よく作業をするには、いろいろとノウハウがあります。そういったことは、経験してみないとわからないことが多いかと思います。

本記事では、次のサイクルをまわしながら作業します。それぞれのタイミングで何が必要になるかは、大体同じになるはずなので、参考になることが多いはずです。

  1. 開発コンテナーの用意
  2. アプリの開発
  3. 開発コンテナー用 Docker イメージの作成(1. へ戻る)

開発コンテナーでの Python 開発

本記事では、具体的に使ってみるプログラミング言語として、Python を選択しています。人気のあるプログラミング言語で、開発コンテナーでの使用方法に興味がある人も多いことでしょう。ここでは、Web サービス用のアプリをプログラミング言語 Python で開発することになって、そこで使う技術選定をしていると想定します。

この場合、パッケージ管理に何を使うか、Web フレームワークはどれにするか、DB 関連はどうするか、といったことを順番に検討することになるでしょう。今回、この記事を執筆するに当たり、筆者の場合は次のような検討をしました。

パッケージ管理について、普段は pip を使っていますが、Poetry、や Rye にも興味があり、機会があれば使ってみたいと考えていました。

次に、Python の Web フレームワークとして有名なのものとして、FastAPIFlaskDjango などがあります。この中で FastAPI はきちんと使ったことがなく、機会があれば使ってみたいと考えていました。

DB 関連では SQLAlchemyPrisma Client Python などがあります。Django を使うなら、Django Models を使うことになります。Django Models は、いわゆる Django ORM の機能を提供するものです。SQLAlchemy は使ったことがあり、Prisma は Node.js のアプリ開発時に使ったことがありますが、Prisma Client Python は使ったことがありません。Prisma と Python の組み合わせには興味があります。

ということで、このような選択肢があるなかで、今回は Poetry、FastAPI、Prisma を使ってみることにしました。ここでは、これらが使える開発環境を開発コンテナーで用意します。

開発コンテナーを用意するにあたっては、実際の開発作業について考慮しながらとなります。必要になった時点で環境も用意するのが効率が良いはずなので、大体、次のような順番で作業を進めます。

  1. プロジェクト用ファイルの作成
  2. FastAPI アプリの雛形の用意
  3. 最初の FastAPI アプリの動作確認
  4. バージョン管理への対応
  5. 開発で使う Docker イメージのビルドと起動
  6. FastAPI 用プログラムの開発
  7. ターミナルのプロンプト改善
  8. 開発したプログラムを Dcoker イメージへ反映
  9. Prisma を使うアプリの作成
  10. 開発した Prisma 対応版プログラムを Dcoker イメージへ反映

実際に動くプログラムを用意するので、FastAPI と Prisma の知識があった方が理解はしやすいと思いますが、ここでは「Python の開発コンテナーをどのようにして使うか」の紹介を主目的とするので、FastAPI と Prisma についての説明は最低限とします。

なお、使用するコードは https://github.com/hiro345g/dcfwd のリポジトリに用意してあります。ただし、用意してあるコードは、説明用のものなので、できるだけ単純なコードにしてあります。また、機能も限られているので、複雑な処理はできません。説明用途以外では使いにくいものもあるので、承知しておいてください。

ここでは、https://github.com/hiro345g/dcfwd のリポジトリに含まれる devcon-python-fastapi-prisma ディレクトリーを ${PROJ_DIR} と表現します。

たとえば、${PROJ_DIR} として ${HOME}/workspace/devcon-python-fastapi-prisma ディレクトリーを用意する場合は次のようにします。

cd ${HOME}/workspace
curl -sLO https://github.com/hiro345g/dcfwd/archive/refs/heads/main.zip
unzip main.zip
mv dcfwd-main/devcon-python-fastapi-prisma .
rm -fr main.zip dcfwd-main/

ちなみに、環境変数 ${PROJ_DIR} を次のように設定しておくと、この後のコマンド入力が楽になります。

cd ${HOME}/workspace/devcon-python-fastapi-prisma
PROJ_DIR=$(pwd)

環境変数 ${PROJ_DIR} の値を確認するには、echo コマンドを使います。${PROJ_DIR}$PROJ_DIR{} を省略して参照することもできます。

echo ${PROJ_DIR}

実際の実行例は次のようになります。この例では ${HOME}/home/user001 の場合です。

$ echo ${PROJ_DIR}
/home/user001/workspace/devcon-python-fastapi-prisma

$ echo $PROJ_DIR
/home/user001/devcon-python-fastapi-prisma

ディレクトリーの構成

ここで、用意するディレクトリーの構成について説明しておきます。開発にあたっては、次のようなディレクトリー構成でファイルを用意することにします。ここで例として作成するアプリの名前は app001 とすることにします。

devcon-python-fastapi-prisma/
├── build-image/devcon-python-fastapi-prisma/ ... Python アプリ学習用の開発コンテナービルド用
│   ├── compose.yaml ... 開発コンテナー用 compose.yaml
│   └── resource/ ... 初期化時に使うリソースファイルを置く
├── dev/devcon-python-fastapi-prisma/ ... 開発環境用
│   ├── app001/ ... Python アプリのプロジェクト
│   ├── compose.yaml ... 開発コンテナー用 compose.yaml
│   └── script/ ... Docker ホスト側で使用するスクリプト
└── share/ ... Python アプリ学習用の開発コンテナーと Docker ホスト側とで共有

また、開発コンテナー用の Docker イメージは、開発対象のアプリのバージョンに合わせてバージョンをつけることにします。つまり、アプリ app001 のバージョン 0.1 を開発するときの Docker イメージは devcon-python-fastapi-prisma:0.1 とします。また、このイメージの完成形では、Docker コンテナーを起動したら app001 のバージョン 0.1 が実行されるようにします。

Docker イメージについては build-image ディレクトリーに compose.yaml ファイルを用意してビルドすることにして、開発時に使用する compose.yaml ファイルは dev ディレクトリーに置いて、分けておきます。これは、開発コンテナー起動中に Dockerfilecompose.yaml ファイルを編集したくなることがあるので、事前に分けておいたほうが管理しやすいからです。

なお、事前に用意してある devcon-python-fastapi-prisma ディレクトリーは次のようになっています。サンプルとして sample ディレクトリーに各バージョンのコードを入れてあります。一番最初に用意する開発コンテナー用 compose.yaml については、特殊なので v0.0_init としてあります。

devcon-python-fastapi-prisma/
├── README.md ... 説明ファイル
├── build-image/devcon-python-fastapi-prisma/ ... Python アプリ学習用の開発コンテナービルド用
│   └── resource/ ... 初期化時に使うリソースファイルを置く
├── dev/devcon-python-fastapi-prisma/ ... ここに compose.yaml を用意して Python アプリ学習用の開発コンテナーを使用
│   └── script/ ... Docker ホスト側で使用するスクリプト
├── sample/ ... サンプル
│   ├── build-image/ ... Python アプリ学習用の開発コンテナービルド用
│   │   ├── v0.0_init/devcon-python-fastapi-prisma ... 最初のビルド用
│   │   ├── v0.1/devcon-python-fastapi-prisma ... v0.1 ビルド用
│   │   ├── v0.2/devcon-python-fastapi-prisma ... v0.2 ビルド用
│   │   └── v0.3/devcon-python-fastapi-prisma ... v0.3 ビルド用
│   ├── python_src/ ... Python プログラム
│   │   ├── v0.0/app001 ... v0.0 版 Python プログラム
│   │   ├── v0.1/app001 ... v0.1 版 Python プログラム
│   │   ├── v0.2/app001 ... v0.2 版 Python プログラム
│   │   └── v0.3/app001 ... v0.3 版 Python プログラム
│   ├── compose/ ... Python アプリ学習用の開発コンテナー起動用サンプル
│   │   ├── v0.1/devcon-python-fastapi-prisma/compose.yaml
│   │   ├── v0.2/devcon-python-fastapi-prisma/compose.yaml
│   │   ├── v0.2-demo/devcon-python-fastapi-prisma/compose.yaml
│   │   └── v0.3/devcon-python-fastapi-prisma/compose.yaml
│   └── sample.env ... .env のサンプル
└── share/ ... Python アプリ学習用の開発コンテナーと Docker ホスト側とで共有

プロジェクトの用意

最初に開発用プロジェクトを用意します。作業にあたっては開発コンテナーを使います。

プロジェクト用ファイルの作成

Python 用アプリの開発を始めるにあたり、最初に Poetry のプロジェクトファイルを作成します。Poetry のプロジェクトを作成するにあたっては、開発時に使う Docker コンテナーに近いものを使って作業するのが確実です。

ここでは、${PROJ_DIR}/build-image/devcon-python-fastapi-prisma/Dockerfilecompose.yaml を用意します。

Dockerfile については次のようにします。Docker Hub の公式の Python イメージを使い、poetry パッケージをインストール済みの Docker イメージを用意することにしました。

build-image/devcon-python-fastapi-prisma/Dockerfile
FROM python:3.10.14-bookworm

ENV PYTHONUNBUFFERED=1

# poetry
RUN curl -sSL https://install.python-poetry.org | python -

ENV PATH /root/.local/bin:$PATH

これをビルドするための compose.yaml も用意します。このファイルを用意しておくと VS Code の Docker 拡張機能からビルドもできますし、ビルドするイメージ名の指定なども、このファイルに書いておくことができます。こうしておくことで、Docker イメージ作成についての情報をバージョン管理して Git リポジトリへ記録できます。

build-image/devcon-python-fastapi-prisma/compose.yaml
name: devcon-python-fastapi-prisma
services:
  devcon-python-fastapi-prisma:
    build: .
    image: devcon-python-fastapi-prisma:0.1
    container_name: devcon-python-fastapi-prisma
    hostname: devcon-python-fastapi-prisma
    tty: true

作成した Docker イメージのクイック動作確認にも使えるのと、この後に使う compose.yaml の雛形にもなっているので、ビルド時にはなくても良い container_name:hostname: を指定してます。

Dockerfilecompose.yaml ファイルを用意したらビルドします。

DC_DIR_PATH=${PROJ_DIR}/build-image/devcon-python-fastapi-prisma
DC_FILE_PATH=${DC_DIR_PATH}/compose.yaml
docker compose -f ${DC_FILE_PATH} build

実行結果は次のようになります。

$ docker compose -f ${DC_FILE_PATH} build
[+] Building 1.1s (6/6) FINISHED                                                   docker:default
 => [devcon-python-fastapi-prisma internal] load build definition from Dockerfile            0.0s
(略k)
 => => writing image sha256:a22210aa32e4f84a397a2175c5b446f51d270af3b009d9adfc79943e6302407  0.0s
 => => naming to docker.io/library/devcon-python-fastapi-prisma:0.1                          0.0s

用意した Docker イメージ devcon-python-fastapi-prisma:0.1 について、簡単に動作確認します。ビルドで使った compose.yamldevcon-python-fastapi-prisma コンテナーを用意して ls コマンドを実行してみます。

docker compose -f ${DC_FILE_PATH} run --rm devcon-python-fastapi-prisma ls

実行結果は次のようになります。

$ docker compose -f ${DC_FILE_PATH} run --rm devcon-python-fastapi-prisma ls
[+] Creating 1/0
(略)
bin  boot  dev  etc  home  lib  lib64  media  mnt  (略)

動作したら、自動で作成された devcon-python-fastapi-prisma_default を破棄するため、docker compose down を実行します。Docker Compose のプロジェクト名が devcon-python-fastapi-prisma なので、-p オプションで指定します。-f だと起動時に使った compose.yaml のパス指定が必要ですが、こちらを使うと Docker Compose のプロジェクト名で済むので楽です。

docker compose -p devcon-python-fastapi-prisma down

実行結果は次のようになります。

$ docker compose -p devcon-python-fastapi-prisma down
[+] Running 1/1
 ✔ Network devcon-python-fastapi-prisma_default
(略)

それでは、Docker イメージ devcon-python-fastapi-prisma:0.1 を使って、開発プロジェクト用ファイル ${PROJ_DIR}/dev/devcon-python-fastapi-prisma/compose.yaml を作成します。

ここで使う compose.yaml は次のようになります。コンテナーの中で作成したファイルを保持するために devcon-python-fastapi-prisma-workspace-data ボリュームを用意して、そこへ保存するようにします。また、working_dir: /workspace/app001 で作業用ディレクトリーを指定します。なお、この指定があると、コンテナー起動時に /workspace/app001 ディレクトリーがない場合は、自動で作成します。

dev/devcon-python-fastapi-prisma/compose.yaml
name: devcon-python-fastapi-prisma
services:
  devcon-python-fastapi-prisma:
    image: devcon-python-fastapi-prisma:0.1
    container_name: devcon-python-fastapi-prisma
    hostname: devcon-python-fastapi-prisma
    tty: true
    working_dir: /workspace/app001
    ports:
      - 127.0.0.1:8000:8000
    volumes:
      - workspace-data:/workspace

volumes:
  workspace-data:
    name: devcon-python-fastapi-prisma-workspace-data

まずは、この compose.yaml ファイルを使う docker compose コマンドで、devcon-python-fastapi-prisma:/workspace/app001(devcon-python-fastapi-prisma コンテナーの /workspace/app001 ディレクトリー)に pyproject.toml を作成します。

使用する Docker イメージのエントリーポイントを --entrypoint オプションで上書きすることで、Docker コンテナー起動時に実行するコマンドを変更しています。

ここでは、poetry init コマンド実行時に --dependency オプションで依存するパッケージを指定しています。ここでは依存パッケージとして fastapiuvicorn[standard] を指定するので、生成される pyproject.toml に、このアプリがこれらに依存するという情報が書き込まれます。

docker compose run --rm \
    --workdir /workspace/app001 --entrypoint "\
    /root/.local/bin/poetry init \
        --no-interaction \
        --name app001 \
        --dependency fastapi \
        --dependency uvicorn[standard] \
    " devcon-python-fastapi-prisma

実際に実行すると次のようになります。カレントディレクトリーを ${PROJ_DIR}/dev/devcon-python-fastapi-prisma としてから実行が必要です。

$ cd ${PROJ_DIR}/dev/devcon-python-fastapi-prisma
$ docker compose run --rm \
    --workdir /workspace/app001 --entrypoint "\
    /root/.local/bin/poetry init \
        --no-interaction \
        --name app001 \
        --dependency fastapi \
        --dependency uvicorn[standard] \
    " devcon-python-fastapi-prisma
[+] Creating 1/0
 ✔ Network devcon-python-fastapi-prisma_default Created   0.1s 
Using version ^0.110.2 for fastapi
Using version ^0.29.0 for uvicorn

続いて、カレントディレクトリーを変えずに devcon-python-fastapi-prisma コンテナーで poetry install を実行します。これで、依存関係にあるパッケージのバージョンを固定する poetry.lock ファイルが作成されます。

docker compose run --rm \
    --workdir /workspace/app001 --entrypoint "\
    /root/.local/bin/poetry install --no-root\
    " devcon-python-fastapi-prisma

実際に実行すると次のようになります。

$ docker compose run --rm \
    --workdir /workspace/app001 --entrypoint "\
    /root/.local/bin/poetry install --no-root\
    " devcon-python-fastapi-prisma
Creating virtualenv app001-ROkNicwB-py3.10 in /root/.cache/pypoetry/virtualenvs
Updating dependencies
Resolving dependencies... (2.7s)

Package operations: 19 installs, 0 updates, 0 removals

  - Installing exceptiongroup (1.2.1)
  - Installing idna (3.7)
  - Installing sniffio (1.3.1)
  - Installing typing-extensions (4.11.0)
  - Installing annotated-types (0.6.0)
  - Installing anyio (4.3.0)
  - Installing pydantic-core (2.18.1)
  - Installing click (8.1.7)
  - Installing h11 (0.14.0)
  - Installing httptools (0.6.1)
  - Installing pydantic (2.7.0)
  - Installing python-dotenv (1.0.1)
  - Installing pyyaml (6.0.1)
  - Installing starlette (0.37.2)
  - Installing uvloop (0.19.0)
  - Installing watchfiles (0.21.0)
  - Installing websockets (12.0)
  - Installing fastapi (0.110.2)
  - Installing uvicorn (0.29.0)

Writing lock file

以上、pyproject.tomlpoetry.lock のファイルを用意できたら、これらを含む devcon-python-fastapi-prisma:/workspace/app001 を Docker ホストへコピーします。まずは docker compose up コマンドでコンテナーを起動します。-d--detach の略で、ターミナルからデタッチした状態のデタッチモードでコンテナーを起動します。その結果、コンテナーのプロセスは起動後にバックグラウンドで処理されることになります。

docker compose up -d

実際に実行すると次のようになります。ここでは、念の為 cd コマンドで compose.yaml ファイルがあるディレクトリーをカレントディレクトリーにしてから実行しています。

$ cd ${PROJ_DIR}/dev/devcon-python-fastapi-prisma
$ docker compose up -d
[+] Running 1/2
 ⠸ Network devcon-python-fastapi-prisma_default  Created 0.1s
 ✔ Container devcon-python-fastapi-prisma        Started 0.3s 

コンテナーが起動したことを docker compose ls コマンドで確認します。grep コマンドとパイプライン(|)を組み合わせて、devcon-python-fastapi-prisma コンテナーの状態を見ます。running(1) となっていれば大丈夫です。

docker compose ls | grep devcon-python-fastapi-prisma

実際に実行すると次のようになります。

$ docker compose ls | grep devcon-python-fastapi-prisma
devcon-python-fastapi-prisma   running(1) (略)

コンテナーの起動が確認できたら、docker compose cp コマンドでコンテナー内の /workspace/app001 をコピーします。

docker compose cp devcon-python-fastapi-prisma:/workspace/app001 .

実際に実行すると次のようになります。

$ docker compose cp devcon-python-fastapi-prisma:/workspace/app001 .
[+] Copying 1/0
 ✔ devcon-python-fastapi-prisma copy devcon-python-fastapi-prisma:/workspace/app001 to (略)

コンテナーを停止します。

docker compose down

実際に実行すると次のようになります。

$ docker compose down
[+] Running 2/2
 ✔ Container devcon-python-fastapi-prisma        Removed 10.3s 
 ✔ Network devcon-python-fastapi-prisma_default  Removed  0.3s 

以上で、次のように ${PROJ_DIR}/dev/devcon-python-fastapi-prisma/app001/ が用意されます。

app001/
├── poetry.lock
└── pyproject.toml

手元で生成されたものを ${PROJ_DIR}/sample/python_src/v0.0/app001 に用意しました。参考にしてください。

ここで用意された pyproject.toml は Python の開発プロジェクト用ファイルで次のようになっているはずです。[tool.poetry.dependencies] を見ると、このプロジェクトは fastapi と uvicorn パッケージに依存していることがわかります。

pyproject.toml
[tool.poetry]
name = "app001"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.10"
fastapi = "^0.110.2"
uvicorn = {extras = ["standard"], version = "^0.29.0"}


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

pyproject.toml については、authors などはデフォルトのままになっています。必要に応じて編集してください。

poetry.lock は Python の開発で使うパッケージの管理用ファイルになります。pyproject.toml のパッケージ指定を元にして、実際にインストールしたもののバージョン情報を記録するファイルになります。

poetry.lock
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
(略)
[[package]]
name = "fastapi"
version = "0.110.2"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false
python-versions = ">=3.8"
files = [
    {file = "fastapi-0.110.2-py3-none-any.whl", hash = "sha256:239403f2c0a3dda07a9420f95157a7f014ddb2b770acdbc984f9bdf3ead7afdb"},
    {file = "fastapi-0.110.2.tar.gz", hash = "sha256:b53d673652da3b65e8cd787ad214ec0fe303cad00d2b529b86ce7db13f17518d"},
]
(略)
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "e6d9b012f054c6abafe974815f9535d0c4dcd10676cf4fb74c1d89d69371b3b6"

FastAPI アプリの雛形の用意

次に FastAPI アプリの雛形となる Python のコードを app001 に用意します。VS Code で使うワークスペース用ファイルも追加します。devcon-python-fastapi-prisma コンテナーを起動して、最初の開発コンテナーとして利用します。

cd ${PROJ_DIR}/dev/devcon-python-fastapi-prisma
docker compose up -d

コンテナーの用意ができたら、続けて VS Code を起動して、次の手順で devcon-python-fastapi-prisma コンテナーへ VS Code をアタッチします。

  1. Docker 拡張機能の画面を表示
  2. 「CONTAINERS」の一覧にある devcon-python-fastapi-prisma コンテナーをマウス右クリック
  3. 表示されるメニューの「Visual Studio Code をアタッチする」をクリック

これ以降、この操作を「VS Code をアタッチ」と表現します。また、VS Code をアタッチしたコンテナー用の VS Code の画面を「コンテナー用 VS Code の画面」と表現します。たとえば、「devcon-python-fastapi-prisma コンテナー用 VS Code の画面」は、devcon-python-fastapi-prisma コンテナーへ VS Code をアタッチしたときに表示される VS Code の画面のことを意味します。

VS Code をアタッチすると、devcon-python-fastapi-prisma コンテナー用 VS Code の画面が表示されます。その画面で、/workspace/app001 を開いて FastAPI 用のプログラムを作成します。

  1. devcon-python-fastapi-prisma コンテナー用 VS Code の画面のメニューで「ファイル」-「フォルダを開く」をクリック
  2. 入力欄に /workspace/app001 を指定して「OK」をクリック

ここで作成する app001 のディレクトリーの内容は次のようになります。poetry.lockpyproject.toml はすでに用意されているはずです。

/workspace/app001/
├── api
│   ├── __init__.py
│   └── main.py
├── .gitignore
├── app001.code-workspace
├── init_venv.sh
├── poetry.lock
└── pyproject.toml

最初に init_venv.sh を用意します。開発コンテナーに VS Code をアタッチしたとき、VS Code が Python のインタプリターを認識できるように Python 仮想環境を用意するときに使います。

init_venv.sh
#!/bin/sh

# 現在の Python 仮想環境があるなら、それを削除
current_venv=$(poetry env list | awk '{print $1}')
if [ "x${current_venv}" != "x" ]; then
    poetry env remove ${current_venv}
fi

# プロジェクト直下に Python 仮想環境を作成
poetry config virtualenvs.in-project true
poetry install --no-root

これを作成してから実行すると、/workspace/app001/.venv に Python 仮想環境が用意されます。

sh /workspace/app001/init_venv.sh

次に VS Code 用のワークスペースファイルを用意します。ここで用意する app001.code-workspace では、設定として python.defaultInterpreterPath へ先程用意した Python 仮想環境用の python コマンドのパスを指定します。また、推奨拡張機能に Python 拡張機能(ms-python.python)を指定します。

app001.code-workspace
{
  "folders": [
    {
      "path": "."
    }
  ],
  "settings": {
    "python.defaultInterpreterPath": "/workspace/app001/.venv/bin/python"
  },
  "extensions": {
    "recommendations": [
      "ms-python.python"
    ]
  }
}

このワークスペースファイルを作成すると、VS Code の画面に「ワークスペースを開く」ボタンが表示されるので、クリックします。ワークスペースが開いた時に、Python 拡張機能をインストールするように通知が表示される場合は、指示に従ってインストールします。

これで devcon-python-fastapi-prisma コンテナー用 VS Code の画面で Python プログラムの開発をするために必要な最低限の環境が用意できます。

次に /workspace/app001/api/__init__.py を用意します。これは、ファイルがあれば良く、中身は空のものになるので、コマンドで作成します。

mkdir /workspace/app001/api
touch /workspace/app001/api/__init__.py

次に /workspace/app001/api/main.py を用意します。ここでは、Web ブラウザで /hello のパスへアクセスすると {"message": "Hello FastAPI!"} の JSON データを返す単純なものとします。具体的なコードは次のようになります。

from fastapi import FastAPI

app = FastAPI()


@app.get("/hello")
async def hello():
    return {"message": "Hello FastAPI!"}

最初の FastAPI アプリの動作確認

動作確認をするために、uvircorn を使って api/main.py に用意した FastAPI オブジェクト app を動かしてみます。ポート番号 8000 で、すべてのネットワークインタフェース上で接続待機させるために、次のコマンドを実行します。

/root/.local/bin/poetry run --directory /workspace/app001 \
    uvicorn api.main:app --host 0.0.0.0 --port 8000 --reload

devcon-python-fastapi-prisma コンテナー用 VS Code の画面のメニューで「ターミナル」-「新しいターミナル」をクリックします。これで新しく用意されたターミナルで curl コマンドで http://localhost:8000/hello へアクセスします。出力結果へ改行コードを追加するためにオプション -w'\n' をつけると、結果とプロンプトの区別がつきやすくなります。

curl -w'\n' http://localhost:8000/hello

これで {"message": "Hello FastAPI!"} が表示されたら、動作確認できたということになります。

実際にコマンドを実行すると、次のような表示となります。

# curl -w'\n' http://localhost:8000/hello
{"message":"Hello FastAPI!"}

動作確認ができたら、uvicorn を実行していたターミナルでは Ctrl+C (CtrlとCのキーを同時入力)して、uvicorn を停止させます。

バージョン管理への対応

開発コンテナーの中でもファイルのバージョン管理をしたいところです。最初に /workspace/app01/.gitignore ファイルを用意しておきます。

/workspace/app01/.gitignore
__pycache__
.venv/

それから git init コマンドで /workspace/app001 を Git リポジトリにします。また、更新をするときのユーザー情報も登録しておきます。ユーザー情報は例としてメールアドレス user001@example.jp、ユーザー名 user001 を指定しています。実際に使うものを指定してください。

git init /workspace/app001
git -C /workspace/app001 config user.email "user001@example.jp"
git -C /workspace/app001 config user.name "user001"

次に、バージョン管理が必要なファイルを Git リポジトリへ登録します。

git -C /workspace/app001/ add .
git -C /workspace/app001/ commit -m "init"

このようにすると、開発コンテナー内のローカル環境でバージョン管理ができます。

Docker イメージに必要なファイルへの対応

この後に、app001 を起動可能かつ開発で使う Docker イメージをビルドするので、コンテナー内の /workspace/app001 ディレクトリーを Docker ホストの ${PROJ_DIR}/build-image/devcon-python-fastapi-prisma/app001 へコピーします。ただし、.venv.git などは除いて必要なファイルだけを対象とします。

これを実現するには、Docker ホストで ${PROJ_DIR}/dev/devcon-python-fastapi-prisma/script/copy_from_workspace_app_git.sh のようなスクリプトを用意して実行すると良いでしょう。

dev/devcon-python-fastapi-prisma/script/copy_from_workspace_app_git.sh
#!/bin/sh
PROJ_NAME=devcon-python-fastapi-prisma
APP_DIR_NAME=app001
BASE_DIR=$(cd $(dirname $0)/..;pwd)
DEST_DIR="${BASE_DIR}/${APP_DIR_NAME}"
DC_PROJ_NAME=${PROJ_NAME}
DC_SERVICE_NAME=${PROJ_NAME}

# コンテナーから Git リポジトリー経由でファイルコピー
## - 既存のものは削除
if [ -e ${DEST_DIR} ]; then
    rm -fr ${DEST_DIR}
fi

## - リポジトリ用ワーキングディレクトリの用意。
mkdir ${DEST_DIR}

## - リポジトリのコピー
docker compose -p ${DC_PROJ_NAME} \
    cp ${DC_SERVICE_NAME}:/workspace/${APP_DIR_NAME}/.git \
        ${DEST_DIR}/.git

## - リポジトリから HEAD をチェックアウト
git -C ${DEST_DIR} checkout -- .

## - リポジトリの削除
rm -fr ${DEST_DIR}/.git

このスクリプトを利用すれば、devcon-python-fastapi-prisma:/workspace/app001/.git のリポジトリに登録してあるものだけしか Docker ホスト側にコピーされません。

実行して、${PROJ_DIR}/app001 に Docker イメージに必要なファイルを用意します。

cd ${PROJ_DIR}/dev/devcon-python-fastapi-prisma
sh ./script/copy_from_workspace_app_git.sh

ここで、注意点としては、app001/.gitignore で指定されているファイルはコピー対象から除外されていることです。たとえば、.env ファイルのような環境設定用のファイルはリポジトリへいれないことが多いですが、Docker イメージには含めたいこともあるでしょう。こういったファイルを使う場合は対応が必要になります。

対応方法としては、Docker イメージを作成する前に手作業で app001 へ含める、Docker イメージには含めずに docker compose cp コマンドを使って devcon-python-fastapi-prisma-workspace-data ボリュームへコピーして用意する、環境変数なら compose.yamlenvironment: で指定する、などがあります。

これについては、具体的に必要になったところで対応します。

Docker ホストへコピーしてバックアップ

ここで、devcon-python-fastapi-prisma:/workspace/app001 について、丸ごと Docker ホストへコピーしてバックアップをしたい場合があります。devcon-python-fastapi-prisma:/workspace は Docker ボリュームをマウントしているため、それを削除する前にバックアップしておきたいことがあります。

単純な方法としては、Docker ホストで docker compose cp コマンドを実行してコピーする方法があります。たとえば、devcon-python-fastapi-prisma:/workspace/app001${PROJ_DIR}/share/app001 へコピーするには、次のようにします。

cd ${PROJ_DIR}
docker compose -p devcon-python-fastapi-prisma \
    cp devcon-python-fastapi-prisma:/workspace/app001 ./share/

ただし、Docker ホストが Windows や macOS だと、Linux とファイルシステムがちがうものになるので、この方法では完全なバックアップとすることができません。より良いバックアップ方法としては、少し手間が増えますが、tar コマンドでアーカイブしたものをバックアップするというものがあります。

アーカイブしたファイルをバックアップするために、ここでは ${PROJ_DIR}/dev/devcon-python-fastapi-prisma/script/copy_from_workspace_app_tgz.sh スクリプトを用意してあります。

dev/devcon-python-fastapi-prisma/script/copy_from_workspace_app_tgz.sh
#!/bin/sh
PROJ_NAME=devcon-python-fastapi-prisma
APP_DIR_NAME=app001
BASE_DIR=$(cd $(dirname $0)/../../..;pwd)
DEST_DIR="${BASE_DIR}/share"
DC_PROJ_NAME=${PROJ_NAME}
DC_SERVICE_NAME=${PROJ_NAME}

# コンテナー内でアーカイブ
docker compose -p ${DC_PROJ_NAME} \
    exec --workdir=/workspace ${DC_SERVICE_NAME} \
        tar cvzf ${APP_DIR_NAME}.tgz ${APP_DIR_NAME}

# コンテナーからコピー
docker compose -p ${DC_PROJ_NAME} \
    cp ${DC_SERVICE_NAME}:/workspace/${APP_DIR_NAME}.tgz ${DEST_DIR}/

# コンテナー内のアーカイブ削除
docker compose -p ${DC_PROJ_NAME} \
    exec --workdir=/workspace ${DC_SERVICE_NAME} \
        rm /workspace/${APP_DIR_NAME}.tgz

これを実行すると、${PROJ_DIR}/share/app001.tgz ファイルにバックアップができます。

cd ${PROJ_DIR}/dev/devcon-python-fastapi-prisma
sh ./script/copy_from_workspace_app_tgz.sh

後で、devcon-python-fastapi-prisma:/workspace をマウントしているボリュームは削除するので、ここで必要ならバックアップしておきましょう。

作業が終わったら、devcon-python-fastapi-prisma コンテナー用 VS Code の画面を閉じてから、コンテナーを停止します。

docker compose -p devcon-python-fastapi-prisma down

開発で使う Docker イメージのビルドと起動

次に、開発で使う Docker イメージをビルドします。

ここでは build-image/devcon-python-fastapi-prisma/build.sh というスクリプトを用意します。このスクリプトは次のような内容として、${PROJ_DIR}/dev/devcon-python-fastapi-prisma/app001 をコピーして Docker イメージへ書き込むようにしています。

build-image/devcon-python-fastapi-prisma/build.sh
#!/bin/sh
PROJ_NAME=devcon-python-fastapi-prisma
APP_DIR_NAME=app001
BASE_DIR=$(cd $(dirname $0);pwd)
SRC_DIR=$(cd ${BASE_DIR}/../../../dev/${PROJ_NAME};pwd)

DC_FILE_PATH="${BASE_DIR}/compose.yaml"
DC_PROJ_NAME=${PROJ_NAME}
DC_SERVICE_NAME=${PROJ_NAME}

# アプリ用のファイル(イメージ作成用)をコピー。
APP_SRC_PATH=${SRC_DIR}/${APP_DIR_NAME}

# アプリ用のファイルがない場合は処理を終了
if [ ! -e "${APP_SRC_PATH}" ]; then
    echo "not found: ${APP_SRC_PATH}"
    exit 1
fi

# コピー
cp -r "${APP_SRC_PATH}" "${BASE_DIR}/${APP_DIR_NAME}"

# ビルド
docker compose -f "${DC_FILE_PATH}" build
# キャッシュを使わないビルド(下記を有効にする場合は上記をコメントにすること)
# docker compose -f "${DC_FILE_PATH}" build --no-cache

# アプリ用のファイル(イメージ作成用)を削除
rm -fr ${BASE_DIR}/${APP_DIR_NAME}

このスクリプトの処理内容としては、${PROJ_DIR}/dev/devcon-python-fastapi-prisma にある app001 をコピーし、compose.yaml を使ってビルドしているだけです。

compose.yamlbuild-image/init/devcon-python-fastapi-prisma/compose.yaml と同じで、同じディレクトリーにある Dockerfile を使って Docker イメージをビルドします。

Dockerfilesample/build-image/v0.0_init/devcon-python-fastapi-prisma/Dockerfile に処理を追加してあります。app001 をコピーして、poetry install を実行してから、uvicorn を実行するようにします。app001 には、動作確認済みの FastAPI 用プログラムがあるので、これで動作します。

具体的には次のようになります。

FROM python:3.10.14-bookworm

(略)

COPY app001 /workspace/app001

WORKDIR /workspace/app001
RUN /root/.local/bin/poetry config virtualenvs.in-project true
RUN if [ -f /workspace/app001/pyproject.toml ]; then /root/.local/bin/poetry install --no-root; fi

# uvicorn 起動
ENTRYPOINT ["/root/.local/bin/poetry", "run", "uvicorn", "api.main:app", "--host", "0.0.0.0", "--reload"]

${PROJ_DIR}/build-image/devcon-python-fastapi-prisma/Dockerfile を更新してから、次のように実行すると、開発で使う Docker イメージとして、FastAPI が動作する devcon-python-fastapi-prisma:0.1 イメージが新しくビルドされます。

cd {PROJ_DIR}
sh ./build-image/v0.1/devcon-python-fastapi-prisma/build.sh

以上で、devcon-python-fastapi-prisma:0.1 イメージを使って、FastAPI 用プログラム app001 のバージョン 0.1 のデモができるようになりました。${PROJ_DIR}/sample/compose/v0.1-demo/devcon-python-fastapi-prisma/compose.yaml にデモ用の compose.yaml があります。

このファイルの内容は、次のようになります。デモで使うので /workspace に Docker ボリュームをマウントすることはしません。

sample/compose/v0.1-demo/devcon-python-fastapi-prisma/compose.yaml
name: devcon-python-fastapi-prisma
services:
  devcon-python-fastapi-prisma:
    image: devcon-python-fastapi-prisma:0.1
    container_name: devcon-python-fastapi-prisma
    hostname: devcon-python-fastapi-prisma
    tty: true
    working_dir: /workspace/app001
    ports:
      - 127.0.0.1:8000:8000

これを使って app001 のバージョン 0.1 デモ用の devcon-python-fastapi-prisma コンテナーを起動しましょう。${PROJ_DIR}/dev/devcon-python-fastapi-prisma ディレクトリーに用意した開発コンテナー用の compose.yaml ファイルを使って devcon-python-fastapi-prisma コンテナーを起動します。

cd ${PROJ_DIR}/sample/compose/v0.1-demo/devcon-python-fastapi-prisma
docker compose up -d

起動した devcon-python-fastapi-prisma コンテナーでは、作成した FastAPI のプログラムが動作しています。そのため、次の URL を Web ブラウザで開くことができるようになっています。

URL 説明
http://127.0.0.1:8000/hello 作成した main/app.py の hello() 関数
http://127.0.0.1:8000/docs Swagger UI による画面
http://127.0.0.1:8000/redoc Redoc による画面

なお、各 URL へアクセスした時にエラーとなる場合は、コンテナー内の uvicorn がうまく動作していません。

そのときは、コンテナー内で別の uvicorn を起動してみると原因がわかる場合があります。ポート番号 8000 は最初に自動起動する uvicorn が使ってしまっているので、別のポート番号で起動します。例えばポート番号 8001 を使う場合は、次のようにします。

/root/.local/bin/poetry run --directory /workspace/app001 \
    uvicorn api.main:app --host 0.0.0.0 --port 8001 --reload

これを実行すると、VS Code が自動でポート番号 8001 の転送を有効にして、Docker ホストの Web ブラウザから http://127.0.0.1:8001 へアクセスできるようになります。こちらを動かして動作の確認をする場合は、使用する URL について、ポート番号を 8000 から 8001 へ変更して Web ブラウザからアクセスします。

デモの動作確認ができたら、コンテナーを終了します。

docker compose -p devcon-python-fastapi-prisma down

FastAPI 用プログラムの開発

次に、FastAPI 用プログラム app001 のバージョン 0.2 の開発をしましょう。

開発コンテナーの用意

0.2 用の Docker イメージは開発を開始する時点では devcon-python-fastapi-prisma:0.1 と同じで良いので、docker tag コマンドを使って、次のように用意します。

docker tag devcon-python-fastapi-prisma:0.1 devcon-python-fastapi-prisma:0.2

次に、app001 のバージョン 0.2 を開発するときに使う開発コンテナー用に ${PROJ_DIR}/dev/devcon-python-fastapi-prisma/compose.yaml を修正します。

次のような内容になります。単に、使用するイメージを devcon-python-fastapi-prisma:0.2 と変更しただけです。

sample/compose/v0.2/devcon-python-fastapi-prisma/compose.yaml
name: devcon-python-fastapi-prisma
services:
  devcon-python-fastapi-prisma:
    image: devcon-python-fastapi-prisma:0.2
    container_name: devcon-python-fastapi-prisma
    hostname: devcon-python-fastapi-prisma
    tty: true
    working_dir: /workspace/app001
    ports:
      - 127.0.0.1:8000:8000
    volumes:
      - workspace-data:/workspace

volumes:
  workspace-data:
    name: devcon-python-fastapi-prisma-workspace-data

用意ができたら起動したいところですが、devcon-python-fastapi-prisma:0.1 イメージ作成のために devcon-python-fastapi-prisma コンテナーを実行した時に自動で作成された devcon-python-fastapi-prisma-workspace-data ボリュームを残しておくと、これが使われてしまいます。基本的に Docker イメージに含まれるものと同じはずですが、Docker イメージに書き込まれているファイルを確認するには、一度削除しておいた方が良いです。次のように docker volume rm コマンドで削除しておきます。

docker volume rm devcon-python-fastapi-prisma-workspace-data

それでは devcon-python-fastapi-prisma コンテナーを起動します。

cd ${PROJ_DIR}/dev/devcon-python-fastapi-prisma
docker compose up -d

ここで、Docker イメージには .git は登録していないので、それを使って自動で作成される devcon-python-fastapi-prisma:/workspace/app001 ディレクトリーはリポジトリーではなくなっています。devcon-python-fastapi-prisma コンテナー内で ls -al コマンドを実行して確認してみましょう。

docker compose -p devcon-python-fastapi-prisma \
  exec devcon-python-fastapi-prisma \
    ls -al /workspace/app001/

これで、用意した devcon-python-fastapi-prisma:0.2 イメージ(この時点では devcon-python-fastapi-prisma:0.1 イメージと同じ)に app001 のバージョン 0.1 のコードが .git ディレクトリーがない状態で含まれていることが確認できるはずです。

次に、取ってあったバックアップを使って復旧しましょう。バックアップから復旧するためのスクリプトが ${PROJ_DIR}/dev/devcon-python-fastapi-prisma/script/restore_workspace_app_git.sh にあります。これを Docker ホストで実行します。

cd ${PROJ_DIR}/dev/devcon-python-fastapi-prisma
sh ./script/restore_workspace_app_git.sh

これで、devcon-python-fastapi-prisma:/workspace/app001 ディレクトリーは復旧されて、Git リポジトリーになります。

FastAPI アプリ v0.2 の開発

それから、VS Code をアタッチし、その画面で /workspace/app001/app001.code-workspace のワークスペースを次の手順で開きます。

  1. devcon-python-fastapi-prisma コンテナー用 VS Code の画面のメニューで「ファイル」-「ファイルでワークスペースを開く」をクリック
  2. /workspace/app001/app001.code-workspace のファイルを指定して「開く」をクリック

これで、VS Code で app001.code-workspace のワークスペースが開きます。推奨する拡張機能として、拡張機能の ID が ms-python.python である Python 拡張機能を登録してあるため、この拡張機能をインストールするかを確認する通知が表示されます。インストールして使うと良いでしょう。

これで、devcon-python-fastapi-prisma コンテナー用 VS Code の画面でアプリの開発ができます。

ここでは、app001/api/main.py に、タスク API を Python のリストを使って実装した例を用意しました。モデルクラスとして TaskItem クラスと TaskList クラスを用意して、Python のリストでタスク管理をしています。これを FastAPI 用の関数から利用して Web API を実装しています。

app001/api/main.py(抜粋)
import uuid
import uvicorn
from typing import List
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pydantic import BaseModel


class CreateTaskItem(BaseModel):
    """タスクアイテム新規作成用オブジェクトを表すクラス"""

    title: str
    description: str = None
    completed: bool = False


class TaskItem(BaseModel):
    """タスクアイテムを表すクラス"""

    id: str
    title: str
    description: str = None
    completed: bool = False


class TaskList:
    """タスクリストを管理するクラス"""

    def __init__(self):
        """コンストラクタ"""
        self.tasks = []

    def add_task(self, id, title, description, completed):
# 略


app = FastAPI()
taskNone = TaskItem(id="", title="", description="", completed=False)
task_list = TaskList()


@app.get("/")
async def api_root():
    return {"message": "OK"}


@app.post("/tasks/", response_model=TaskItem)
async def create_taskitem(task: CreateTaskItem):
    id = str(uuid.uuid4())
    values = task.model_dump()
    new_task = {"id": id, **values}
    ret = task_list.add_task(**new_task)
    if ret:
        return JSONResponse(content=new_task, status_code=201)
    else:
        return JSONResponse(content=taskNone, status_code=400)
# 略


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=18000)

app001/api/main.py をデバッグ起動できるように、このファイルを直接実行したら、uvicorn.run(app, host="0.0.0.0", port=18000)uvicorn が起動するようにしてあります。コードからわかるように、デバッグ実行するときは、ポート番号 18000 を使います。

VS Code ワークスペースの設定追加

VS Code をアタッチする開発コンテナーを使うにあたり、app001.code-workspace ファイルでは設定を増やして開発しやすくしています。

Python のコードをフォーマットできるように、推奨する拡張機能へ ms-python.black-formatter を追加し、それをフォーマッターとして使うように "settings": で指定します。また、Python が参照するパッケージのパスの設定も追加してあります。

また、デバッグ実行ができるように "launch": の設定も追加してあります。

app001.code-workspace
{
  "folders": [
    {
      "path": "."
    }
  ],
  "settings": {
    "python.defaultInterpreterPath": "/workspace/app001/.venv/bin/python",
        "python.analysis.extraPaths": [
            "/workspace/app001"
        ],
        // `PYTHONPATH=/workspace/app001 python api/main.py` で実行可能
    "[python]": {
      "editor.defaultFormatter": "ms-python.black-formatter",
      "editor.formatOnSave": true
    },
  },
  "launch": {
    "version": "0.2.0",
    "configurations": [
      {
        "name": "Python デバッガー: 現在のファイル",
        "type": "debugpy",
        "request": "launch",
        "program": "${file}",
        "env": {
          "PYTHONPATH": "/workspace/app001"
        },
        "console": "integratedTerminal"
      }
    ],
    "compounds": []
  },
  "extensions": {
    "recommendations": [
      "ms-python.python",
      "ms-python.black-formatter"
    ]
  }
}

なお、app001.code-workspace ファイルを更新したら、devcon-python-fastapi-prisma コンテナー用の VS Code 画面のメニューで「ファイル」-「ワークスペースを閉じる」をクリックします。それから、「ファイル」-「ファイルでワークスペースを開く」で app001.code-workspace ファイルを指定しワークスペースを開き直します。

ここで、拡張機能の画面を開いて ms-python.pythonms-python.black-formatter がインストールされていなかったら、手動でインストールしましょう。それから再度 app001.code-workspace のワークスペースを開くと、Python ファイルはエディタでコードフォーマットができるようになり、実行とデバッグの画面でデバッグ実行ができるようになります。

ターミナルのプロンプト改善

ここで、ターミナルでコマンド実行する時のプロンプトがシンプルすぎるので表示される情報を増やしたいと思うかもしれません。その場合は、starship を使ってみると良いでしょう。

${PROJ_DIR}/sample/build-image/v0.3/devcon-python-fastapi-prisma/install_starship.sh に starship をインストールするスクリプトがあります。

build-image/v0.3/devcon-python-fastapi-prisma/install_starship.sh
#! /bin/sh

DL_URL=https://github.com/starship/starship/releases/latest/download
cd /workspace \
  && curl -LO ${DL_URL}/starship-x86_64-unknown-linux-musl.tar.gz \
  && tar xf starship-x86_64-unknown-linux-musl.tar.gz \
  && rm starship-x86_64-unknown-linux-musl.tar.gz
echo 'eval "$(/workspace/starship init bash)"' >> /root/.bashrc
if [ ! -e /root/.config ]; then mkdir /root/.config; fi
/workspace/starship preset plain-text-symbols > /root/.config/starship.toml
sed -i 's/Rocky = "rky "//' /root/.config/starship.toml

これを devcon-python-fastapi-prisma コンテナーへ用意して実行すると starship が適用された bash が使えるようになります。

ここで、手動で devcon-python-fastapi-prisma コンテナーに install_starship.sh ファイルを作成して実行しても良いのですが、Docker ホスト側から一連の作業を実行できるスクリプトを用意することもできます。${PROJ_DIR}/dev/devcon-python-fastapi-prisma/copy_and_install_starship.sh に、あらかじめ作成したものがあります。

dev/devcon-python-fastapi-prisma/copy_and_install_starship.sh
#!/bin/sh
SCRIPT_NAME=install_starship.sh
BASE_DIR=$(cd $(dirname $0)/../../..;pwd)
SRC_DIR=${BASE_DIR}/sample/build-image/v0.3/devcon-python-fastapi-prisma
SRC_FILE_PATH=${SRC_DIR}/${SCRIPT_NAME}
DC_PROJ_NAME=devcon-python-fastapi-prisma
DC_SERVICE_NAME=devcon-python-fastapi-prisma

## - ファイルのコピー
docker compose -p ${DC_PROJ_NAME} \
    cp ${SRC_FILE_PATH} ${DC_SERVICE_NAME}:/

## - インストール
docker compose -p ${DC_PROJ_NAME} \
    exec ${DC_SERVICE_NAME} sh /${SCRIPT_NAME}

これを Docker ホストで、次のように実行します。これでコンテナーで starship が使えるようになります。

cd ${PROJ_DIR}/dev/devcon-python-fastapi-prisma
sh ./script/copy_and_install_starship.sh

これ以降、devcon-python-fastapi-prisma コンテナーのターミナルで bash を起動すると starship が有効になり、次のような情報が多いプロンプト表示になります。

root@devcon-python-fastapi-prisma:/workspace# bash
app001 on git main is pkg v0.1.0 via py v3.10.14 (app001-py3.10) 
⬢ [Docker] > 

気に入ったら、この機能を Docker イメージへ反映すると良いでしょう。とはいえ、一度インストールしたら、コンテナーを down で破棄しなければ、使い続けることができるので、Docker イメージへ反映しなくても、それほど不便はしないはずです。

ここではすぐには反映せずに Prisma の対応をした Docker イメージを作成する時に反映することにします。それまでは、必要に応じて手動でインストールして使うことにします。

FastAPI アプリ v0.2 の動作確認とリポジトリへの反映

プログラムの更新ができたら、 Web ブラウザで次の URL を開いて動作確認します。/hello は削除したので使えなくなっています。代わりに / を用意したので、こちらを開きます。

URL 説明
http://127.0.0.1:8000/ 作成した main/app.py の api_root() 関数
http://127.0.0.1:8000/docs Swagger UI による画面
http://127.0.0.1:8000/redoc Redoc による画面

用意したタスク用の API について、/docs/redoc の画面に反映されているはずです。ここでは使い方については省略します。

動作確認ができたら、リポジトリへ反映します。

cd /workspace/app001
git add app001.code-workspace 
git add api/main.py 
git commit -m "v0.2"

開発したプログラムを Dcoker イメージへ反映

Python アプリの app001 バージョン 0.2 について、devcon-python-fastapi-prisma コンテナー用 VS Code の画面で開発ができたら、これを Dcoker イメージへ反映します。

バージョン 0.1 のときと同様にして Docker イメージを更新することができます。Docker ホストへソースコードをコピーするためのスクリプトとして、copy_from_workspace_app_git.sh を用意してありました。devcon-python-fastapi-prisma コンテナーが起動している状態で、これを実行します。なお、実行時に既存の ${PROJ_DIR}/dev/devcon-python-fastapi-prisma/app001 は削除されます。残しておきたい場合はバックアップしておきましょう。

cd ${PROJ_DIR}/dev/devcon-python-fastapi-prisma
sh ./script/copy_from_workspace_app_git.sh

このあと、devcon-python-fastapi-prisma:/workspace で使っているボリュームは削除してしまいます。この時点でバックアップしておきましょう。

cd ${PROJ_DIR}/dev/devcon-python-fastapi-prisma
sh ./script/copy_from_workspace_app_tgz.sh

次に ${PROJ_DIR}/dev/devcon-python-fastapi-prisma にある compose.yaml をバージョン 0.2 用のものにします。ちなみに Dockerfile は変更の必要がありません。

compose.yaml は、ビルドして作成するイメージを devcon-python-fastapi-prisma:0.2 と変更するだけです。

sample/build-image/v0.2/devcon-python-fastapi-prisma/compose.yaml
name: devcon-python-fastapi-prisma
services:
  devcon-python-fastapi-prisma:
    build: .
    image: devcon-python-fastapi-prisma:0.2
    container_name: devcon-python-fastapi-prisma
    hostname: devcon-python-fastapi-prisma
    tty: true

準備ができたら、devcon-python-fastapi-prisma コンテナー用 VS Code の画面を閉じて、devcon-python-fastapi-prisma コンテナーを破棄します。

docker compose -p devcon-python-fastapi-prisma down

それから、build-image/v0.2/devcon-python-fastapi-prisma/build.sh を実行すると、Docker イメージが更新できます。

cd ${PROJ_DIR}/build-image/devcon-python-fastapi-prisma
sh ./build.sh

これで app001 のバージョン 0.2 のデモを動かせる Docker イメージが用意できました。デモ用の compose.yaml ファイルのサンプルを sample/compose/v0.2-demo/devcon-python-fastapi-prisma/compose.yaml に用意しました。次のように起動して使うことができます。

cd ${PROJ_DIR}/sample/compose/v0.2-demo/devcon-python-fastapi-prisma/
docker compose up -d

これで、開発時に動作確認した URL を開けるようになります。確認したら停止します。

docker compose -p devcon-python-fastapi-prisma down

Prisma を使うアプリの作成

次に app001 のバージョン 0.3 を開発します。ここでは Prisma を導入します。

開発コンテナーの用意

開発用の Docker イメージ devcon-python-fastapi-prisma:0.3 イメージは devcon-python-fastapi-prisma:0.2 イメージから用意します。

docker tag devcon-python-fastapi-prisma:0.2 devcon-python-fastapi-prisma:0.3

${PROJ_DIR}/dev/devcon-python-fastapi-prisma/compose.yaml で使う Docker イメージも devcon-python-fastapi-prisma:0.3 へ修正します。

ここでは、devcon-python-fastapi-prisma:0.3 イメージから起動したコンテナーを開発環境として使用します。

ところで、devcon-python-fastapi-prisma:0.2 イメージから起動したコンテナーを開発に使う時、devcon-python-fastapi-prisma-workspace-data ボリュームを削除して動作確認しました。ボリューム削除をすると、Git リポジトリが削除されてしまうので、復旧が必要となります。

それは手間なので、今回は devcon-python-fastapi-prisma-workspace-data ボリュームを削除せずに、そのまま使用することにします。

それでは、devcon-python-fastapi-prisma:0.3 イメージを使う compose.yaml を用意したら devcon-python-fastapi-prisma コンテナーを起動して、VS Code をアタッチします。 devcon-python-fastapi-prisma コンテナー用の VS Code の画面でターミナルを起動して、app001 アプリのバージョン 0.3 を開発するための環境を整備します。

v0.3 開発に必要な環境の整備

Prisma を使うには https://deb.nodesource.com から入手可能な nodejs パッケージが必要なので、インストールします。

curl -fsSL https://deb.nodesource.com/setup_21.x | bash - && apt-get install -y nodejs

それから、SQLite3 の DB を使うため、sqlite3 コマンドも使えるようにしておきます。これは apt-get コマンドでインストールできる sqlite3 パッケージに含まれているので、これを使います。

apt-get -y install sqlite3 

それから、poetry add コマンドで Python 用の prisma パッケージを app001 のプロジェクトへ追加します。

/root/.local/bin/poetry add --directory /workspace/app001 prisma

ここでは、DB として SQLite3 を使うことにします。また、SQLite3 のデータベースファイルは /workspace/app001/data/app.db ファイルで用意することにします。DB アクセス時に使う URL は環境変数 DATABASE_URL で指定することにして、/workspace/app001/.env へ保存します。

/workspace/app001/.env
DATABASE_URL=file:data/app.db

このファイルは、次のように作成すれば良いでしょう。

echo "DATABASE_URL=file:data/app.db" > /workspace/app001/.env

ちなみに、このファイルは .gitignore でリポジトリ管理対象外とします。また、DATABASE_URL で指定する file:data/app.db/workspace/app001/prisma/data と対応します。ここに作成される app.db などもリポジトリ管理対象外とします。そのため、次のように .gitignore を修正します。

echo "" >> /workspace/app001/.gitignore
echo ".env" >> /workspace/app001/.gitignore
echo "prisma/data" >> /workspace/app001/.gitignore

TaskItem 用テーブルの用意

ここでは TaskItem 用のテーブルを用意することにします。Prisma では DB の構造をスキーマファイルで指定することができます。/workspace/app001/prisma/schema.prisma を作成します。

prisma/schema.prisma
generator client {
  provider             = "prisma-client-py"
  interface            = "asyncio"
  recursive_type_depth = 5
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

model TaskItem {
  id          String   @id @default(uuid())
  title       String
  description String
  completed   Boolean  @default(false)
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}

スキーマファイルを用意したら、prisma migrate dev コマンドを実行して schema.prisma の指定に従った開発用 DB を作成します。

cd /workspace/app001
prisma migrate dev --name init

ここではマイグレーション名の指定に --name init としてあります。これを省略すると、コマンド実行時の途中に Enter a name for the new migration: とマイグレーション名を指定するためのプロンプトが表示されます。そこで init と入力して進めるのと同じになります。

コマンド実行時に表示される Applying migration に続く文字列から正確なマイグレーション名がわかります。

実際の実行例を次に示します。

# cd /workspace/app001
# /root/.local/bin/poetry run prisma migrate dev --name init
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": SQLite database "app.db" at "file:data/app.db"

SQLite database app.db created at file:data/app.db

Applying migration `20240428225915_init`

The following migration(s) have been created and applied from new schema changes:

migrations/
  └─ 20240428225915_init/
    └─ migration.sql

Your database is now in sync with your schema.

✔ Generated Prisma Client Python (v0.13.1) to ./.venv/lib/python3.10/site-packages/prisma in 145ms

この結果から、ここでのマイグレーション名は 20240428225915_init になったことがわかります。

マイグレーションの実行が終了すると、そのときに使用した SQL が prisma/migrations/<マイグレーション名>/migration.sql に出力されます。今回の 20240428225915_init については、次のようになっていました。

# cat prisma/migrations/20240428225915_init/migration.sql 
-- CreateTable
CREATE TABLE "TaskItem" (
    "id" TEXT NOT NULL PRIMARY KEY,
    "title" TEXT NOT NULL,
    "description" TEXT NOT NULL,
    "completed" BOOLEAN NOT NULL DEFAULT false,
    "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "updatedAt" DATETIME NOT NULL
);

なお、prisma migrate コマンド実行時に Python の DB クライアントプログラムが内部的に生成されて使えるようになっています。これは prisma generate コマンドを実行することで明示的に生成することができます。

Docker イメージを作成するときは、prisma migrate dev コマンドを実行するので、そのときに Python の DB クライアントプログラムが生成されます。覚えておきましょう。

次に、DB アクセスする FastAPI のプログラムを作成します。

Prisma で DB アクセス

ここで作成した DB アクセスする FastAPI のプログラム app001/api/main.py は次のようになります。TaskList の実装を Prisma を使うものへ変更しています。また、Prisma を使うようにしたことで、それに合わせて API の方も若干修正をしています。

app001/api/main.py(抜粋)
import uuid
import uvicorn
from contextlib import asynccontextmanager
from typing import List
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from prisma import Prisma
from pydantic import BaseModel

prisma = Prisma()


@asynccontextmanager
async def lifespan(app: FastAPI):
    await prisma.connect()
    yield
    await prisma.disconnect()


app = FastAPI(lifespan=lifespan)


class ApiTaskItemBase(BaseModel):
    """タスクアイテム新規作成用オブジェクトを表すクラス"""

    title: str
    description: str = ""
    completed: bool = False


class ApiTaskItem(ApiTaskItemBase):
    """タスクアイテムを表すクラス"""

    id: str


class TaskList:
    """タスクリストを管理するクラス"""

    def __init__(self):
        """コンストラクタ"""
        self.prisma = prisma

    async def add_task(self, taskitem: ApiTaskItem):
        """タスクを追加

        Args:
          taskitem (ApiTaskItem):

        Returns:
          True: タスクを追加した場合
          False: タスクを追加しなかった場合
        """
        task = await prisma.taskitem.create(data=taskitem)
        if task is not None:
            return True
        else:
            return False
# 略


task_none = ApiTaskItem(id="", title="", description="", completed=False)
task_list = TaskList()


@app.get("/")
async def api_root():
    return {"message": "OK"}


@app.post("/tasks/", response_model=ApiTaskItem)
async def create_taskitem(task: ApiTaskItemBase):
    id = str(uuid.uuid4())
    values = task.model_dump()
    new_task = {"id": id, **values}
    ret = await task_list.add_task(new_task)
    if ret:
        return JSONResponse(content=new_task, status_code=201)
    else:
        return JSONResponse(content=task_none, status_code=400)
# 略


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=18000)

動作確認については、これまでと同じです。少し API に変更をいれているので、Web ページをきちんとリロードして表示させるようにしてください。

URL 説明
http://127.0.0.1:8000/ 作成した main/app.py の api_root() 関数
http://127.0.0.1:8000/docs Swagger UI による画面
http://127.0.0.1:8000/redoc Redoc による画面

動作確認をしたら Git リポジトリへ修正を反映します。

cd /workspace/app001
git add .gitignore
git add pyproject.toml
git add poetry.lock
git add prisma
git add api/main.py
git commit -m "v0.3"

開発した Prisma 対応版プログラムを Dcoker イメージへ反映

Prisma 対応版プログラムが動作するようになったら、それを Dcoker イメージへ反映します。

バージョン 0.2 のときと同様にして Docker イメージを更新することができます。Docker ホストへのソースコードコピーとバックアップをします。バックアップをしたら devcon-python-fastapi-prisma コンテナーは破棄します。

cd ${PROJ_DIR}/dev/devcon-python-fastapi-prisma
sh ./script/copy_from_workspace_app_git.sh
sh ./script/copy_from_workspace_app_tgz.sh
docker compose -p devcon-python-fastapi-prisma down

今回は、プログラム以外に DB 関連のファイルとして .env の追加が必要です。

.env ファイルについては、${PROJ_DIR}/build-image/devcon-python-fastapi-prisma/resource/.env に Docker イメージへ含める .env ファイルを置くことにします。

build-image/devcon-python-fastapi-prisma/resource/.env
DATABASE_URL=file:data/app.db

また、この .env ファイルを使うように ${PROJ_DIR}/build-image/devcon-python-fastapi-prisma/build.sh スクリプトを修正します。

build-image/devcon-python-fastapi-prisma/build.sh
#!/bin/sh
PROJ_NAME=devcon-python-fastapi-prisma
APP_DIR_NAME=app001
BASE_DIR=$(cd $(dirname $0);pwd)
SRC_DIR=$(cd ${BASE_DIR}/../../dev/${PROJ_NAME};pwd)
RESOURCE_DIR=${BASE_DIR}/resource

DC_FILE_PATH="${BASE_DIR}/compose.yaml"
DC_PROJ_NAME=${PROJ_NAME}
DC_SERVICE_NAME=${PROJ_NAME}

# アプリ用のファイル(イメージ作成用)をコピー。
APP_SRC_PATH=${SRC_DIR}/${APP_DIR_NAME}

# アプリ用のファイルがない場合は処理を終了
if [ ! -e "${APP_SRC_PATH}" ]; then
    echo "not found: ${APP_SRC_PATH}"
    exit 1
fi

# コピー
cp -r "${APP_SRC_PATH}" "${BASE_DIR}/${APP_DIR_NAME}"

# .env ファイルの用意
APP_ENV_FILE_PATH=${RESOURCE_DIR}/.env
if [ ! -e "${APP_ENV_FILE_PATH}" ]; then
    # .env ファイルがない場合はエラー
    echo "not found: ${APP_ENV_FILE_PATH}"
    exit 1
fi
cp ${APP_ENV_FILE_PATH} "${BASE_DIR}/${APP_DIR_NAME}/"

# ビルド
docker compose -f "${DC_FILE_PATH}" build
# キャッシュを使わないビルド(下記を有効にする場合は上記をコメントにすること)
# docker compose -f "${DC_FILE_PATH}" build --no-cache

# アプリ用のファイル(イメージ作成用)を削除
rm -fr ${BASE_DIR}/${APP_DIR_NAME}

DB ファイル(開発時に指定したもの、サンプルの .env に指定されたものをそのまま使うなら data/app.db)については、Docker イメージ作成時に、prisma migrate dev コマンドを実行すれば、新規の SQLite3 用 DB ファイルが作成できます。この修正を Dockerfile にします。

他にも nodejsstarshipsqlite3 といったものもインストールします。

FROM python:3.10.14-bookworm

ENV PYTHONUNBUFFERED=1

# poetry
RUN curl -sSL https://install.python-poetry.org | python -

# nodejs
RUN curl -fsSL https://deb.nodesource.com/setup_21.x | bash - && apt-get install -y nodejs

# starship
COPY install_starship.sh /workspace/install_starship.sh
RUN sh /workspace/install_starship.sh

ENV PATH /root/.local/bin:$PATH

# app001
COPY app001 /workspace/app001

WORKDIR /workspace/app001
RUN /root/.local/bin/poetry config virtualenvs.in-project true
RUN if [ -f /workspace/app001/pyproject.toml ]; then /root/.local/bin/poetry install --no-root; fi

# prisma
RUN if [ -f /workspace/app001/prisma/schema.prisma ]; then /root/.local/bin/poetry run prisma migrate dev; fi

# SQLite3
RUN apt-get update \
    && apt-get -y upgrade \
    && apt-get -y install --no-install-recommends \
        sqlite3 \
    && apt-get autoremove -y \
    && rm -rf /var/cache/apt /var/lib/apt/lists

# uvicorn 起動
ENTRYPOINT ["/root/.local/bin/poetry", "run", "uvicorn", "api.main:app", "--host", "0.0.0.0", "--reload"]

これらを ${REPO_DIR}/build-image/devcon-python-fastapi-prisma に用意したら、Docker イメージをビルドします。

cd ${REPO_DIR}/build-image/devcon-python-fastapi-prisma
sh ./build.sh

作成した Docker イメージを起動するには、{PROJ_DIR}/sample/compose/v0.3/devcon-python-fastapi-prisma/compose.yaml ファイルを使います。compose.yaml を、このファイルの内容で置き換えてから docker compose コマンドで起動します。

cd ${PROJ_DIR}
cp sample/compose/v0.3/devcon-python-fastapi-prisma/compose.yaml .
docker compose up -d

デモとして動作させるには、{PROJ_DIR}/sample/compose/v0.3-demo/devcon-python-fastapi-prisma/compose.yaml ファイルを使います。

cd ${PROJ_DIR}/sample/compose/v0.3/devcon-python-fastapi-prisma/compose.yaml
docker compose up -d

まとめ

以上で説明はおしまいです。入門版の開発コンテナーを使って、Poetry、FastAPI、Prisma Client Python を使用する Python アプリの開発をするときの様子を紹介しましたが、いかがだったでしょうか。

開発コンテナーに慣れている人だと、このような感じで開発をしていくと、各バージョンでアプリの開発ができる環境も一緒に Git リポジトリに残るため、少し前のバージョンで動作確認をしつつデバッグもしたい場合に便利そうだと感じたのではないかと思います。

一方で、まだあまり開発コンテナーに慣れていない人だと、ざっくりとした印象としては次のような抵抗感がありそうです。

  • Docker イメージの開発とアプリの開発の区別がわかりにくい
  • Docker イメージへの反映が手間
  • バージョン管理で混乱が起きそう

今回は、自分が開発コンテナーを使う時にどうやっているかを説明するために、開発コンテナーとして利用する前提の Docker イメージの開発とアプリの開発の両輪を回してみたのですが、事前に想像していたよりも大変そうな感じになってしまいました。

ただし、これは開発コンテナーも用意しつつ開発をするような立場にいる人だけが理解していれば良い話でもあります。開発コンテナーを利用するだけの立場の場合は、devcon-python-fastapi-prisma:0.3 用の Dockerfilecompose.yaml ファイルを渡されるだけで、app001 アプリの開発へ参加できるようになります。自分で開発に必要なバージョンの Python や Poetry を用意したり、調べたりする必要はありません。

また、今回のように開発コンテナーを用意すると、アプリ開発に必要な要件が自然と compose.yamlDockerfile に記録されていきます。そういったメリットに気がつくと、こういった開発コンテナーを用意することが重要だということがわかるはずです。

開発コンテナーを利用して、楽しい開発ができるようになれるといいですね。

Discussion