はじめに
技術の進歩は目覚ましいものです。
Web記事を検索すると、検索ボリュームの大きい結果から表示されるため、知らず知らずのうちに古い技術を使ってしまうことが多々あります。
そこで、2021年時点で、windows 環境で python でDB操作+OR Mapper した構成を書いておきたいと思います。
ツール | 説明 |
---|---|
chocolatey | Windows用パッケージ管理システム。 |
python3.9.2 | python 本体。 |
poetry | python のパッケージ管理システム。 pip は requirements.txt を手書きしないとダメだったが、npmやyarn の様に pyproject.toml を自動で更新してくれる。 |
mysqlalchemy | OR Mapper。簡単なクラス定義で簡単にDB操作ができる。 |
alembic | DB 構築/マイグレーションヘルパ。mysqlalchemy のクラス定義に沿ったスキーマをDBに構築。 |
※以後の説明のコードブロックの null ではじまるファイル名は記事でハイライトされる様に定義したファイル名で実際にはファイルを作っていません。
chocolatey
chocolatey
インストーラをダウンロードしてインストールしていたようなパッケージをコマンド一発でインストールできます。
管理者権限で PowerShell を開いて以下コマンド実行でインストールできます。
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
記事を書く時にググると新しいモノが見つかって微妙な気持ちになりますね。。。
scoop は未チェックでした。
Windows開発環境の構築をChocolateyからscoopに切り替える
python
chocolatey で簡単にインストールできます。
バージョン番号を指定しないと、インストール時点の最新版がインストールされます。
choco install python
poetry
pip を使ってインストールします。
pip はこれ以後利用しません。
py -m pip install poetry
poetry は仮想環境(virtualenv)を利用します。
以下設定をするとプロジェクト内に.venv ファイルを作成しモジュールの保存も行う様になります。(nodejs の node_modulesの様なフォルダ)
容量的にはPCに優しくないのですがこの設定をしないとモジュール参照の解決が面倒くさいのでした方がよいかなと思っています。
poetry config virtualenvs.in-project true
alembic, SQLAlchemy
alembic 以前は、SQLAlchemy-Migrate だったそうです。
SQLAlchemy の記事からたどってくと SQLAlchemy-Migrate にたどり着くのでどちらを使うかはググって判断してください。
poetry で、alembic, sqlalchemy をインストールします。
poetry init # 対話コンソールになるので適宜回答
poetry add alembic sqlalchemy
alembic
SQLAlchemy の class 定義と、DBを突き合わせて、テーブルを構築するのに必要なスクリプトの生成、実行が行えます。
初期化
最初に alembic init を実行し、DB構築の準備をします。
# alembic init {フォルダ名}
poetry run alembic init migrations
コマンドを実行すると、DB構築用のスクリプトと、migration 用のスクリプトを保存するフォルダが生成されます。
migrations/
versions/
env.py
README
script.py.mako
alembic.ini
生成された、alembic.ini のDB接続文字列を環境に合わせて修正します。
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = driver://user:pass@localhost/dbname
ORマッパーclass定義
このままだと、作るテーブルがないので適当にクラス定義します。
db/
models.py
SQLAlchemy の Baseクラスから派生したクラスを定義します。
全てのテーブルに埋めたい created, modified などのフィールドを含むクラスを __abstract__
で定義し、そのクラスより派生すると少し楽になれるかもしれません。
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.schema import Column
from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql.functions import random
from sqlalchemy.sql.schema import UniqueConstraint
from sqlalchemy.sql.sqltypes import DATETIME
from sqlalchemy.types import Integer, String
from datetime import datetime
Base=declarative_base()
class BaseModel(Base):
__abstract__ = True
id = Column(Integer, primary_key=True, autoincrement=True)
created = Column(DATETIME, default=datetime.now)
modified = Column(DATETIME, default=datetime.now)
def __init__(self):
self.created = datetime.now
self.modified = datetime.now
class User(BaseModel):
"""
service users.
"""
__tablename__ = "users"
email = Column(String(255), unique=True)
password = Column(String(255))
def __init__(self, email, password):
self.email = email
self.password = password
class Match(BaseModel):
__tablename__ = "matches"
seed = Column(Integer, nullable=False)
def __init__(self):
self.seed = random.randint()
class MatchUser(BaseModel):
__table_args__= ((UniqueConstraint("match_id", "user_id"), ))
__tablename__ = "match_users"
match_id = Column(Integer, ForeignKey("matches.id", onupdate="cascade", ondelete="cascade"))
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
def __init__(self, match_id, user_id):
self.match_id = match_id
self.user_id = user_id
class MatchTurn(BaseModel):
__tablename__ = "match_turns"
id = Column(Integer, primary_key=True, autoincrement=True)
match_id = Column(Integer, ForeignKey("matches.id", onupdate="cascade", ondelete="cascade"))
no = Column(Integer, nullable=False)
def __init__(self, match_id, no):
self.match_id = match_id
self.no = no
model定義を参照する様に、migrations/env.py を編集
migrations/env.py
を開くと、model の MetaData を定義する様にコメントが記載されているので、定義します。
# ★before
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = None
↓先程定義した model のメタデータを定義
# ★after
# add your model's MetaData object here
from db.models import Base
target_metadata = Base.metadata
upgrade 作成
alembic revision でDB接続し、現在のDBのスキーマと models.py
の内容を比較して、スキーマ最新化に必要なスクリプトを作成し、migrations/versions
に保存します。
# alembic revision --autogenerate -m {任意のバージョン番号}
poetry run alembic revision --autogenerate -m v0.01
upgrade 実行
alembic upgrade コマンドで、DBのスキーマを任意のバージョンにします。
poetry run alembic upgrade head
おわりに
細切れ記事は色々あるのですが、まとめ記事はないのでまとめました。
これだけ色々なものを入れたにも関わらず、割と短い記事で書き終わって、随分便利な世の中になってるなぁと思いました。
Java で同じアウトプットの記事を書こうと思ったら eclipse のインストールや設定も細かく解説が必要で、@ITあたりで全10回構成の記事にする物量になりそうだなと、なぜかムーアの法則に思いをはぜました。