FAST APIを使って簡易CRMを作った話し その2

# はじめに

この記事は JSL (日本システム技研) Advent Calendar 2024 - Qiita 6日目の記事です(カレンダー的には7日目です)。

[前回](URL"https://qiita.com/katekichi/items/925dcfadd1d926feba3d")の続きでFAST APIを使った簡易CRM実装の続きを記事にします。
今回は、`sqlite`と`SQLAlchemy`を使ってデータ永続化するに辺りDBとテーブル生成までを記事にします。

FAST APIの公式ドキュメントを参考に進めてきます。

https://fastapi.tiangolo.com/ja/tutorial/sql-databases/#create-an-engine

また、[こちら](URL "https://qiita.com/katekichi/items/04e70237c97af53fc6fa")の記事を間に書いた関係で、パッケージ管理を`pip`から`poetry`に移行しています。


# おさらい
環境：
・macOS 12.6 Apple M1 Pro
・Python 3.12.7 

現状のフォルダ構成は以下となります。
```
├── backend
│   ├── app
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-312.pyc
│   │   │   ├── main.cpython-312.pyc
│   │   │   └── models.cpython-312.pyc
│   │   ├── api
│   │   │   ├── __init__.py
│   │   │   ├── __pycache__
│   │   │   │   ├── __init__.cpython-312.pyc
│   │   │   │   └── main.cpython-312.pyc
│   │   │   ├── main.py
│   │   │   └── routes
│   │   │       ├── __init__.py
│   │   │       ├── __pycache__
│   │   │       │   ├── __init__.cpython-312.pyc
│   │   │       │   ├── customer.cpython-312.pyc
│   │   │       │   └── hello.cpython-312.pyc
│   │   │       ├── customer.py
│   │   │       └── hello.py
│   │   ├── core
│   │   │   ├── __init__.py
│   │   │   ├── config.py
│   │   │   └── db.py
│   │   ├── main.py
│   │   ├── models.py
│   │   └── tests
│   └── requirements.txt
├── poetry.lock
└── pyproject.toml
```

# CustomerをSQLModelへ書き換える

モデル`Customer`をBaseModelからSQLModelへ書きかえます。

## SQLModelのインストール

```
$ poetry add sqlmodel --group dev
```

```app/models.py
from pydantic import BaseModel


class Customer(BaseModel):
    id: int
    name: str
    address: str
```
↓
```app/models.py
from sqlmodel import SQLModel, Field


class Customer(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    address: str = Field(default=None)
```

`table=True`と定義しておくと、`SQLModel.metadata.create_all()`した際に自動生成してくれます。

# DBとテーブル生成

```app/core/db.py
from sqlmodel import SQLModel, create_engine

sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, connect_args=connect_args)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)
```

```app/main.py
from fastapi import FastAPI
from app.api.main import api_router

from app.core.db import create_db_and_tables

app = FastAPI()


@app.on_event("startup")
def on_startup():
    create_db_and_tables()

app.include_router(api_router)
```

`@app.on_event("startup")`とすると、実行時のみ関数が呼ばれるようです。

実行してDBとテーブル作成が出来るか確認してみます。
```
$ uvicorn app.main:app --reload
```

`database.db`が作成されました。
```
├── app
│   ├── api
│   ├── core
│   └── tests
└── database.db
```

DBとテーブルが生成されているか確認します。

```sql
# sqlite3 database.db

sqlite> .databases
main: /xxx/backend/database.db r/w

sqlite> .table
customer

sqlite> .schema
CREATE TABLE customer (
	id INTEGER NOT NULL, 
	name VARCHAR NOT NULL, 
	address VARCHAR NOT NULL, 
	PRIMARY KEY (id)
);
CREATE INDEX ix_customer_name ON customer (name);
```

# まとめ
今回は、SQLModelを使ったモデル定義とFAST APIからDBとテーブル生成するまでを記事にしました。普段は、DjangoによるModel定義とmigrationでらDBとテーブル生成する機会が多いので、`SQLModel.metadata.create_all`によってアプリケーション側で生成するのは新鮮でした。

次回は、生成してCustomerモデルへのCRUD機能の作り込みをしていきます。