次回の Python コンテストでは、Python を使用して IRIS をデータベースとして使用する簡単な REST アプリケーションを作成する方法についての小さなデモを作成しようと思います。 以下のツールを使用します。
- FastAPI フレームワーク: 高パフォーマンス、学習しやすい、高速コーディング、プロダクション対応
- SQLAlchemy: Python SQL ツールキットで、アプリケーション開発者が SQL の全性能と柔軟性を活用できるオブジェクトリレーションマッパーです。
- Alembic: Python 用の SQLAlchemy データベースツールキットと使用する軽量のデータベース移行ツール。
- Uvicorn: Python の ASGI ウェブサーバー実装。
環境の準備
バージョン 3.7 以降の Python がすでにインストール済みだと思います。 プロジェクトフォルダを作成し、その中に以下のコンテンツで requirements.txt ファイルを作成します。
fastapi==0.101.1
alembic==1.11.1
uvicorn==0.22.0
sqlalchemy==2.0.20
sqlalchemy-iris==0.10.5
Python で仮想環境を使用することをお勧めします。新しい環境を作成して有効化しましょう。
python -m venv env && source env/bin/activate
そして、依存関係をインストールします。
pip install -r requirements.txt
クイックスタート
FastAPI を使って最も単純な REST Api を作成しましょう。 これを行うには、app/main.py を作成します。
from fastapi import FastAPI
app = FastAPI(
title='TODO Application',
version='1.0.0',
)
@app.get("/ping")asyncdefpong():return {"ping": "pong!"}
この時点で、アプリケーションを純分に起動して動作させることができます。 サーバーの起動には、uvicorn を使用します。
$ uvicorn app.main:app
INFO: Started server process [94936]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
ping リクエストを発行できます。
$ curl http://localhost:8000/ping
{"ping":"pong!"}FastAPI には API をテストできる UI が用意されています。
.png)
Docker 化環境
IRIS をアプリケーションに追加するには、コンテナーを使用します。 IRIS イメージはそのままで使用できますが、Python アプリケーション用の Docker イメージを作成する必要があります。 また、Dockerfile が必要です。
FROM python:3.11-slim-buster
WORKDIR /usr/src/app
RUN --mount=type=bind,src=.,dst=.
pip install --upgrade pip &&
pip install -r requirements.txt
COPY . .
ENTRYPOINT [ "/usr/src/app/entrypoint.sh" ]
コンテナー内でアプリケーションを起動するには、簡単な entrypoint.sh が必要です。
#!/bin/sh
alembic upgrade head
uvicorn app.main:app
--workers 1
--host 0.0.0.0
--port 8000 "$@"
実行フラグを忘れずに追加しましょう。
chmod +x entrypoint.sh
docker-compose.yml で IRIS と組み合わせます。
version:"3"services: iris: image:intersystemsdc/iris-community ports: -1972 environment: -IRISUSERNAME=demo -IRISPASSWORD=demo healthcheck: test:/irisHealth.sh interval:5s app: build:. ports: -8000:8000 environment: -DATABASE_URL=iris://demo:demo@iris:1972/USER volumes: -./:/usr/src/app depends_on: iris: condition:service_healthy command: ---reload
ではビルドしましょう。
docker-compose build
最初のデータモデル
アプリケーションに IRIS データベースへのアクセスを宣言し、app/db.py ファイルを追加しましょう。このファイルによって データベースにアクセスできるように SQLAlchemy が構成されます。これは docker-compose.yml によって渡される URL で定義されます。これには後でアプリで使用するハンドラーがいくつか含まれています。
import os
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = os.environ.get("DATABASE_URL")
ifnot DATABASE_URL:
DATABASE_URL = "iris://demo:demo@localhost:1972/USER"
engine = create_engine(DATABASE_URL, echo=True, future=True)
Base: DeclarativeMeta = declarative_base()
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
definit_db():
engine.connect()
defget_session():
session = SessionLocal()
yield session
では、初の唯一のモデルをアプリケーションに定義しましょう。 ファイル app/models.py を作成して編集します。SQLAlchemy を使用して、Todo と言う、id、title、description の 3 つ列を持つモデルを定義します。
from sqlalchemy import Column, Integer, String, Text
from app.db import Base
classTodo(Base):tablename = 'todo'
id = Column(Integer, primary_key=True, index=True)
title = Column(String(200), index=True, nullable=False)
description = Column(Text, nullable=False)
SQL の移行の準備
変化し続ける世界では、アプリケーションは将来的に改善されると考えるため、テーブル構造は最終的なものではなく、さらにテーブル、列、インデックスなどを追加できるとわかっています。 この場合の最善のシナリオは、SQL Migration ツールを使用することです。これは、アプリケーションのバージョンに応じてデータベースの現在の構造をアップグレードできるツールであり、何かが誤ってしまった場合には、これらのツールを使用することでダウングレードすることもできます。 このプロジェクトでは Python と SQLAlchemy を使用していますが、SQLAlchemy の作者は Alembic というツールを提供しており、ここではそれを使用します。
IRIS と、アプリケーションを含むコンテナーを起動する必要がありますが、この時点では、コマンドを実行できるように Bash する必要があります。
$ docker-compose run --entrypoint bash app
[+] Creating 2/0
✔ Network fastapi-iris-demo_default Created 0.0s
✔ Container fastapi-iris-demo-iris-1 Created 0.0s
[+] Running 1/1
✔ Container fastapi-iris-demo-iris-1 Started 0.1s
root@7bf903cd2721:/usr/src/app#
コマンド alembic init app/migrations を実行します。
root@7bf903cd2721:/usr/src/app# alembic init app/migrations
Creating directory '/usr/src/app/app/migrations' ... done
Creating directory '/usr/src/app/app/migrations/versions' ... done
Generating /usr/src/app/app/migrations/README ... done
Generating /usr/src/app/app/migrations/script.py.mako ... done
Generating /usr/src/app/app/migrations/env.py ... done
Generating /usr/src/app/alembic.ini ... done
Please edit configuration/connection/logging settings in '/usr/src/app/alembic.ini' before proceeding.
root@7bf903cd2721:/usr/src/app#
これにより Alembic 構成が準備されたため、アプリケーションのニーズに適合するように修正する必要があります。 これを行うには app/migrations/env.py ファイルを編集します。 これはファイルの始まりに過ぎないため、更新する必要があります。sqlalchemy.url と target_metadata を更新することに専念しましょう。 その以下の変更はありません。
import os
import urllib.parse
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
config = context.config
DATABASE_URL = os.environ.get("DATABASE_URL")
decoded_uri = urllib.parse.unquote(DATABASE_URL)
config.set_main_option("sqlalchemy.url", decoded_uri)
if config.config_file_name isnotNone:
fileConfig(config.config_file_name)
from app.models import Base
target_metadata = Base.metadata
すでにモデルが存在するため、コマンド alembic revision --autogenerate で移行を作成する必要があります。
root@7bf903cd2721:/usr/src/app# alembic revision --autogenerate
INFO [alembic.runtime.migration] Context impl IRISImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'todo'
INFO [alembic.autogenerate.compare] Detected added index 'ix_todo_id' on '['id']'
INFO [alembic.autogenerate.compare] Detected added index 'ix_todo_title' on '['title']'
Generating /usr/src/app/app/migrations/versions/1e4d3b4d51ca_.py ... done
root@7bf903cd2721:/usr/src/app#
新しいテーブル ToDo とすべてのインデックスが見つかったと述べ、ファイルを生成します。このファイルを確認できます。編集の必要はありませんが、それを確認すると、
upgrade と
downgrade 関数が含まれています。
"""empty message
Revision ID: 1e4d3b4d51ca
Revises:
Create Date: 2023-08-22 07:08:01.586330
"""
from alembic import op
import sqlalchemy as sa
revision = '1e4d3b4d51ca'
down_revision = None
branch_labels = None
depends_on = Nonedefupgrade() -> None:
op.create_table('todo',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(length=200), nullable=False),
sa.Column('description', sa.Text(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_todo_id'), 'todo', ['id'], unique=False)
op.create_index(op.f('ix_todo_title'), 'todo', ['title'], unique=False)
defdowngrade() -> None:
op.drop_index(op.f('ix_todo_title'), table_name='todo')
op.drop_index(op.f('ix_todo_id'), table_name='todo')
op.drop_table('todo')
では、これをデータベースに適用しましょう。コマンド alembic upgrade head を使用します。ここで、head は最新バージョンにアップグレードするためのキーワードです。
root@7bf903cd2721:/usr/src/app# alembic upgrade head
INFO [alembic.runtime.migration] Context impl IRISImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> 1e4d3b4d51ca, empty message
アプリケーションのアップグレード中に元に戻す必要があることが分かった場合に、例えば 1 つ前のリビジョンにデータベースをダウングレードするには
head-1 とします。
root@7bf903cd2721:/usr/src/app# alembic downgrade head-1
INFO [alembic.runtime.migration] Context impl IRISImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running downgrade 1e4d3b4d51ca -> , empty message
and to completely downgrade back to an empty state, use keyword base
現在の状態をいつでも確認できます。いくつかの移行が欠落している場合にはその情報を得られます。
root@7bf903cd2721:/usr/src/app# alembic check
INFO [alembic.runtime.migration] Context impl IRISImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
No new upgrade operations detected.
データをアクセス可能にする
REST に戻り、実行させる必要があります。現在のコンテナーを終了し、アプリサービスを通常どおり実行すると、uvicorn には --reload フラグが設定されているため、Python ファイル内の変更をチェックし、変更がある場合には再起動されます。
$ docker-compose up app
[+] Running 2/0
✔ Container fastapi-iris-demo-iris-1 Running 0.0s
✔ Container fastapi-iris-demo-app-1 Created 0.0s
Attaching to fastapi-iris-demo-app-1, fastapi-iris-demo-iris-1
fastapi-iris-demo-app-1 | INFO [alembic.runtime.migration] Context impl IRISImpl.
fastapi-iris-demo-app-1 | INFO [alembic.runtime.migration] Will assume non-transactional DDL.
fastapi-iris-demo-app-1 | INFO: Will watch for changes in these directories: ['/usr/src/app']
fastapi-iris-demo-app-1 | INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
fastapi-iris-demo-app-1 | INFO: Started reloader process [8] using StatReload
fastapi-iris-demo-app-1 | INFO: Started server process [10]
fastapi-iris-demo-app-1 | INFO: Waiting for application startup.
fastapi-iris-demo-app-1 | INFO: Application startup complete.
FastAPI は Pydantic プロジェクトを使用してデータスキーマを宣言しているため、それも必要です。app/schemas.py を作成しましょう。列は models.py と同じですが、単純な Python フォームを使用します。
from pydantic import BaseModel
classTodoCreate(BaseModel):
title: str
description: str
classTodo(TodoCreate):
id: int
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Config</span>:</span>
from_attributes = <span class="hljs-keyword">True</span>
SQLAlchemy ORM を使ってデータベースを操作する app/crud.py で CRUD 操作を宣言します。
from sqlalchemy.orm import Session
from . import models, schemas
defget_todos(db: Session, skip: int = 0, limit: int = 100):return db.query(models.Todo).offset(skip).limit(limit).all()
defcreate_todo(db: Session, todo: schemas.TodoCreate):
db_todo = models.Todo(**todo.dict())
db.add(db_todo)
db.commit()
db.refresh(db_todo)
return db_todo
そして最後に、app/main.py を更新し、ToDo を読み取って作成するルートを追加できます。
from fastapi import FastAPI, Depends
from .db import init_db, get_session
from . import crud, schemas
app = FastAPI(
title='TODO Application',
version='1.0.0',
)
@app.on_event("startup")defon_startup():
init_db()
@app.get("/ping")asyncdefpong():return {"ping": "pong!"}
@app.get("/todo", response_model=list[schemas.Todo])asyncdefread_todos(skip: int = 0, limit: int = 100, session=Depends(get_session)):
todos = crud.get_todos(session, skip=skip, limit=limit)
return todos
@app.post("/todo", response_model=schemas.Todo)asyncdefcreate_todo(todo: schemas.TodoCreate, session=Depends(get_session)):return crud.create_todo(db=session, todo=todo)
これに応じてドキュメント ページが更新され、実際に操作できるようになりました。
.png)
新しい ToDo を追加します。
.png)
その内容を確認しましょう。
.png)
IRIS で確認してみましょう。
─$ docker-compose exec iris irissqlcli iris+emb:///
Server: IRIS for UNIX (Ubuntu Server LTS for ARM64 Containers) 2023.2 (Build 227U) Mon Jul 31 2023 17:43:25 EDT
Version: 0.5.4
[SQL]irisowner@/usr/irissys/:USER> .tables
+-------------------------+
| TABLE_NAME |
+-------------------------+
| SQLUser.alembic_version |
| SQLUser.todo |
+-------------------------+
Time: 0.043s
[SQL]irisowner@/usr/irissys/:USER> select * from todo
+----+-------+---------------------+
| id | title | description |
+----+-------+---------------------+
| 1 | demo | it's really working |
+----+-------+---------------------+
1 row in set
Time: 0.004s
[SQL]irisowner@/usr/irissys/:USER> select * from alembic_version
+--------------+
| version_num |
+--------------+
| 1e4d3b4d51ca |
+--------------+
1 row in set
Time: 0.045s
[SQL]irisowner@/usr/irissys/:USER>
REST の作成において Python と FastAPI を簡単に使用していただけたなら幸いです。 このプロジェクトのソースコードは、GitHub の https://github.com/caretdev/fastapi-iris-demo にあります。