データベースは、最新のアプリケーションの動力源となるデータを格納するため、重要な役割を担っています。
一般に、私たちは構造化照会言語 (Structured Query Language, SQL) を使用して、データベースに対する問い合わせを行い、データベース内のデータを操作しています。
当初は専用の SQL ツールを使用していましたが、現在ではアプリケーションの中から SQL を使用してクエリを実行するようになりました。
当然ながら、時間の経過とともにオブジェクトリレーショナルマッパー (ORM) が登場し、実際にクエリを実行してデータを操作する必要なく、安全、簡単、便利にデータベースにプログラム的に接続することができるようになりました。
そのような ORM の一つが SQLAlchemy です。
このポストでは、ORM、特に SQLAlchemy について深く掘り下げ、それを使って Flask フレームワークを使ったデータベース駆動型の Web アプリケーションを構築します。
ORMとは何か、なぜそれを使うのか?
オブジェクトリレーショナルマッピングとは、その名が示すように、オブジェクトをリレーショナルエンティティにマッピングすることです。
オブジェクト指向のプログラミング言語では、オブジェクトはリレーショナルエンティティとそれほど違いはなく、特定のフィールドや属性を持っているので、相互にマッピングすることができます。
つまり、オブジェクトをデータベースにマッピングするのは非常に簡単なので、その逆もまた簡単なのである。
このため、ソフトウェアの開発プロセスが容易になり、プレーンなSQLコードを書く際に手作業でミスをする可能性も低くなります。
ORM を使うもう一つの利点は、データベースにアクセスする必要があるたびに SQL コードを書く代わりに、モデルを使ってデータを操作することで DRY (Don’t Repeat Yourself) 原則に従ったコードを書くことを助けてくれる点です。
ORMはデータベースをアプリケーションから抽象化し、複数のデータベースを使用したり、データベースを簡単に切り替えることができます。
例えば、アプリケーションでSQLを使用してMySQLデータベースに接続した場合、MSSQLデータベースに切り替えると、構文が異なるため、コードを修正する必要があります。
SQLがアプリケーションの複数の場所に統合されている場合、これは非常に面倒なことになります。
ORMを使えば、必要な変更は、いくつかの設定パラメータを変更する程度で済みます。
ORM はデータベース操作を抽象化することで私たちの生活を楽にしますが、フードの下で何が起きているのかを忘れないように注意する必要があります。
また、ORM をより効率的に使用するために、ORM に精通し、それらを学ぶ必要があり、これはちょっとした学習曲線を導入します。
SQLAlchemy ORM
SQLAlchemy は Python で書かれた ORM で、開発者に SQL のパワーと柔軟性を、実際に SQL を使う手間を与えずに提供します。
SQLAlchemy は Python に付属する Python Database API (Python DBAPI) をラップし、 Python モジュールとデータベースの間のやりとりを容易にするために作成されました。
DBAPI はデータベース管理の一貫性と移植性を確立するために作られましたが、 SQLAlchemy が私たちの接点となるため、直接 DBAPI とやりとりする必要はないでしょう。
SQLAlchemy ORM は、 SQLAlchemy Core – DBAPI の統合と SQL の実装 – の上に構築されていることに注意することも重要です。
言い換えれば、SQLAlchemy Core は SQL クエリを生成する手段を提供します。
SQLAlchemy ORM はアプリケーションをデータベースに依存しないものにしますが、特定のデータベースに接続するためには、特定のドライバを必要とすることに注意することが重要です。
Pyscopg は PostgreSQL の DBAPI の実装で、SQLAlchemy と一緒に使うと Postgres データベースを操作することができます。
MySQL データベースについては、 PyMySQL ライブラリが DBAPI の実装を提供し、データベー スとやりとりするのに必要です。
SQLAlchemy は、Oracle や Microsoft SQL Server も扱えます。
SQLAlchemy を利用している有名企業には、Reddit, Yelp, DropBox, Survey Monkey があります。
ORM を紹介したところで、Postgres データベースとやりとりする簡単な Flask API を作ってみましょう。
FlaskとSQLAlchemy
Flask は最小限の Web アプリケーションを構築するために使われる軽量なマイクロフレームワークですが、サードパーティのライブラリを通じて、その柔軟性を利用して堅牢で機能豊富な Web アプリケーションを構築することができます。
今回は、シンプルな RESTful API を構築し、Flask-SQLAlchemy 拡張を使用して API を Postgres データベースに接続する予定です。
前提条件
私たちはPostgreSQL(Postgresとしても知られています)を使用して、APIによって処理されるデータを保存します。
Postgres データベースと対話するために、コマンドラインまたはグラフィカルユーザインターフェースを備えたクライアントを使用することができ、これらはより使いやすく、より速くナビゲートすることができます。
Mac OSでは、シンプルで直感的、かつきれいなユーザインタフェースを提供するPosticoを使用することをお勧めします。
PgAdminは、すべての主要なオペレーティングシステムをサポートし、Docker化されたバージョンも提供する、もう一つの優れたクライアントです。
これらのクライアントを使用して、データベースを作成し、またアプリケーションの開発と実行中にデータを表示することになります。
インストールが終わったところで、環境を構築し、アプリケーションに必要な依存関係をインストールしましょう。
$ virtualenv --python=python3 env --no-site-packages
$ source env/bin/activate
$ pip install psycopg2-binary
$ pip install flask-sqlalchemy
$ pip install Flask-Migrate
上記のコマンドは virtualenv の作成と有効化、Psycopg2 ドライバのインストール、flask-sqlalchemy のインストール、データベースの移行を処理する Flask-Migrate のインストールを行います。
Flask-Migrate` は Alembic を使用しています。
これは軽いデータベース移行ツールで、データベースの作成と再作成、データベースへのデータの移動、データベースの状態の特定を支援することにより、より明確な方法でデータベースと対話することを可能にします。
私たちの場合、アプリケーションが起動するたびにデータベースやテーブルを再作成する必要はなく、どちらも存在しない場合に自動的に再作成してくれます。
実装
自動車に関する情報を扱い、操作するための簡単なAPIを構築します。
データはPostgreSQLのデータベースに格納され、APIを通じてCRUD操作を行う。
まず、任意のPostgreSQLクライアントを使用して cars_api
データベースを作成する必要があります。
データベースを作成したら、接続してみましょう。
まず、apps.py
ファイルで Flask API をブートストラップします。
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return {"hello": "world"}
if __name__ == '__main__':
app.run(debug=True)
まず、Flaskアプリケーションと、JSONオブジェクトを返すエンドポイントを1つ作成します。
このデモでは、Flask-SQLAlchemy を使います。
これは Flask アプリケーションに SQLAlchemy の機能を追加するための拡張機能です。
Flask-SQLAlchemyとFlask-Migrateを app.py
に組み込んで、車に関するデータを保存するモデルを作りましょう。
# Previous imports remain...
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = "postgresql://postgres:postgres@localhost:5432/cars_api"
db = SQLAlchemy(app)
migrate = Migrate(app, db)
class CarsModel(db.Model):
__tablename__ = 'cars'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String())
model = db.Column(db.String())
doors = db.Column(db.Integer())
def __init__(self, name, model, doors):
self.name = name
self.model = model
self.doors = doors
def __repr__(self):
return f"<car {self.name}="""
flask_sqlalchemy` をインポートしたら、まずアプリケーションの設定にデータベースURIを追加します。
この URI には、アプリケーションで使用する認証情報、サーバーアドレス、データベースが含まれています。
それから、Flask-SQLAlchemy のインスタンスを db
という名前で作成し、すべてのデータベースとのやりとりに使用します。
Flask-Migrate インスタンスは migrate
という名前で、プロジェクトのマイグレーションを行うために作成する。
CarsModel` はモデルクラスであり、データの定義と操作に使用される。
このクラスの属性は、データベースに格納するフィールドを表します。
テーブルの名前は、データを格納するカラムと一緒に __tablename__
で定義します。
Flask にはコマンドラインインターフェースと専用のコマンドが付属しています。
例えば、アプリケーションを起動するには、 flask run
というコマンドを使用します。
このスクリプトを利用するには、Flask アプリケーションをホストするスクリプトを指定する環境変数を定義すればよいのです。
$ export FLASK_APP=app.py
$ flask run
* Serving Flask app "app.py" (lazy loading)
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 172-503-577
モデルが完成し、 Flask-Migrate
が統合されたので、それを使ってデータベースに cars
テーブルを作成してみましょう。
$ flask db init
$ flask db migrate
$ flask db upgrade
まず、データベースを初期化して、migrations を有効にします。
生成された migrations はデータベースに対して行われる操作を定義したスクリプトです。
今回は初回なので、スクリプトはモデルで指定されたカラムを持つ cars
テーブルを生成するだけです。
flask db upgrade` コマンドはマイグレーションを実行し、テーブルを作成します。
カラムを追加、削除、または変更した場合は、 migrate
と upgrade
コマンドを実行して、これらの変更をデータベースに反映させることができます。
エンティティの作成と読み込み
データベースを作成し、アプリに接続したら、あとは CRUD 操作を実装するだけです。
まず、car
を作成し、既存のものをすべて取得するところから始めましょう。
# Imports and CarsModel truncated
@app.route('/cars', methods=['POST', 'GET'])
def handle_cars():
if request.method == 'POST':
if request.is_json:
data = request.get_json()
new_car = CarsModel(name=data['name'], model=data['model'], doors=data['doors'])
db.session.add(new_car)
db.session.commit()
return {"message": f"car {new_car.name} has been created successfully."}
else:
return {"error": "The request payload is not in JSON format"}
elif request.method == 'GET':
cars = CarsModel.query.all()
results = [
{
"name": car.name,
"model": car.model,
"doors": car.doors
} for car in cars]
return {"count": len(results), "cars": results}
まず、 GET
と POST
リクエストを受け付ける /cars
ルートを定義します。
GETリクエストはデータベースに保存されているすべての車のリストを返し、
POST` メソッドは車のデータを JSON フォーマットで受け取り、提供された情報をデータベースに入力します。
新しい車を作成するには、 CarsModel
クラスを使用して、 cars
テーブルのカラムを埋めるために必要な情報を提供します。
CarsModelオブジェクトを作成した後、データベースセッションを作成し、
car` を追加します。
データベースに車を保存するには、db.session.commit()
でセッションをコミットして DB トランザクションを終了し、車を保存します。
では、Postmanのようなツールを使って車を追加してみましょう。
レスポンスメッセージは、車が作成され、データベースに保存されたことを知らせてくれます。
データベースに車のレコードがあることがわかります。
データベースに保存された車について、 GET
リクエストはすべてのレコードを取得するのに役に立ちます。
Flask-SQLAlchemy が提供する CarsModel.query.all()
関数を用いて、データベースに保存されている全ての車を問い合わせます。
この関数は CarsModel
オブジェクトのリストを返し、それをリスト内包を使って整形してリストに追加し、データベースにある車の台数と一緒にレスポンスに渡します。
PostmanのAPIで車のリストを要求すると、以下のようになります。
carsエンドポイントの
GET` メソッドは、データベースに登録されている車のリストと、その総数を返します。
Note: このコードにはSQLクエリが1つもないことに注意してください。
SQLAlchemy がそれをやってくれているのです。
エンティティの更新と削除
ここまでで、1台の車を作成し、データベースに保存されているすべての車のリストを取得することができました。
我々のAPIで車に対するCRUD操作のセットを完成させるには、1台の車の詳細、変更、削除を返す機能を追加する必要があります。
そのために使うHTTPメソッド/動詞は GET
、PUT
、DELETE
で、これらは handle_car()
という一つのメソッドにまとめられることになります。
# Imports, Car Model, handle_cars() method all truncated
@app.route('/cars/<car_id', methods=['GET', 'PUT', 'DELETE'])
def handle_car(car_id):
car = CarsModel.query.get_or_404(car_id)
if request.method == 'GET':
response = {
"name": car.name,
"model": car.model,
"doors": car.doors
}
return {"message": "success", "car": response}
elif request.method == 'PUT':
data = request.get_json()
car.name = data['name']
car.model = data['model']
car.doors = data['doors']
db.session.add(car)
db.session.commit()
return {"message": f"car {car.name} successfully updated"}
elif request.method == 'DELETE':
db.session.delete(car)
db.session.commit()
return {"message": f"Car {car.name} successfully deleted."}
メソッド handle_car()
は URL から car_id
を受け取り、データベースに格納されている car オブジェクトを取得します。
リクエストメソッドが GET
の場合、車の詳細が返されます。
車の詳細を更新するには、 PATCH
ではなく PUT
メソッドを使用します。
どちらのメソッドも詳細を更新するために使用できますが、 PUT
メソッドは更新されたバージョンのリソースを受け取り、データベースに保存されているものを置き換えます。
PATCHメソッドは、データベースに保存されているものを置き換えることなく、単に変更するだけです。
したがって、データベースのCarsModel` レコードを更新するために、更新するものを含む車のすべての属性を提供する必要があります。
その情報を使ってcarオブジェクトを変更し、 db.session.commit()
を使って変更をコミットし、ユーザーにレスポンスを返します。
車は正常に更新されました。
最後に、車を削除するために、同じエンドポイントに DELETE
リクエストを送信します。
すでに CarsModel
オブジェクトにクエリが届いているので、あとは現在のセッションを使用して db.session.delete(car)
を実行し、トランザクションをコミットしてデータベースに変更を反映させるだけです。
結論
実際のアプリケーションは私たちのように単純ではなく、通常は複数のテーブルにまた がる関連したデータを扱います。
SQLAlchemy を使えば、リレーションシップを定義して、関連するデータを操作することもできます。
リレーションシップの扱いに関するより詳しい情報は、Flask-SQLAlchemy の公式ドキュメントにあります。
このアプリケーションは、リレーションシップやより多くのテーブルを扱うために、 簡単に拡張することができます。
また、Binds を使って複数のデータベースに接続することもできます。
Binds に関するより詳しい情報は Binds documentation page にあります。
この投稿では ORM、特に SQLAlchemy ORM について紹介しました。
Flask と Flask-SQLAlchemy を使って、ローカルの PostgreSQL データベースに保存されている自動車に関するデータを公開し、扱う簡単な API を作成しました。
この投稿のプロジェクトのソースコードは GitHub で見ることができます。