🥟

Django + SQLServer(Azure SQL Database) + SQLAlchemyで、Webアプリを構築

2022/06/21に公開

はじめに

この記事は次の2つの要素を同時にやります。

  • DjangoのDBにSQLServer(Azure SQL Database)を使う
  • DjangoでORMにSQLAlchemyも使えるようにする

作ったもの

https://github.com/k-ibaraki/sample-django-azure-webapps

Djangoとは?

Djangoはジャンゴと読みます。Dは何処に行った?
DjangoはPythonで最もメジャーなWebアプリケーションフレームワークです。
Djangoはフルスタック・フレームワークなので、DBアクセスとかユーザー認証とか管理画面とか便利な機能が搭載されています。

個人的には、Djangoを使ったことはほとんどありません。以下が理由です。

  • 極力フロントエンドとバックエンドを分離したいので、フルスタック・フレームワークを使うことは少ない
  • フロントエンドにPythonを使うことは無いし、バックエンドのAPIをPythonで書くとしたらFastAPIを使うことが多い
  • データ分析や画像処理やAI以外では、Pythonを積極的には使わない

ですが、最近少し触る機会がありましたので知見を残しておきたいと思います。

この記事のきっかけ

Djangoは、素の状態ではSQLServerに対応していません。
使いたい要件があったので、下記の2案を考えました。

  • DjangoのORMをSQLServerに対応させる
  • SQLAlchemyなどDjango外のORMを使う

DjangoもSQLServerもSQLAlchemyも初心者すぎて、どちらの案がよいか分からないので、両方を同時にぶち込んで基本的にDjangoで頑張りながら必要な時にSQLAlchemyも使えるようにしようと思いました。

やったこと

その前に、、、

pythonのパッケージ管理にpoetryを使っているので、その前提で書きます。
poetry使わない人は適当にpipとかに置き換えて理解してください。

必要なライブラリのインストール

mssql-djangoを追加する

DjangoをSQLServerに対応させる為のライブラリです。

poetry add mssql-django

https://docs.microsoft.com/en-us/samples/azure-samples/mssql-django-samples/mssql-django-samples/

mssql-djangoの補足

「Django SQLServer」とかでググると、django-pyodbc-azureを入れようと書いてあるサイトが出てきますが、コレは古くて動かないです。古いPythonなら動くかも知れませんが、検証していません。少なくともPython3.8では動きませんでした。
「古い情報しか見つからない」と嘆いている記事の情報も古いという罠。この記事もいずれそうなるのでしょう。

調べてみると、django-pyodbc-azuredjango-mssql-backendmssql-djangoの順にforkされていました。なのでmssql-djangoが最新と考えてよいと思いますし、何よりMicrosoft公式に置いてあるライブラリなので一番確実だと思います。

aldjemyを追加する

DjangoとSQLAlchemyを連携する為のライブラリです。
aldjemyを入れることで、DjangoのModelを使いながら、DB操作にSQLAlchemyも使えるようになります。

poetry add aldjemy

https://github.com/aldjemy/aldjemy

SQLServer用のドライバー(ODBCドライバー)をインストールする

SQLServerを使うのに別途ODBCドライバーが必要なのでinstallします。
OSごとに手順が違うのでMicrosoftのホームページを参照してインストールしてください。
https://docs.microsoft.com/ja-jp/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-ver15

dockerのimage作成時にドライバーがインストールされるように、Dockerfileにインストールコマンドを書いたので、参考に貼っておきます。
https://github.com/k-ibaraki/sample-django-azure-webapps/blob/2022-06-09-main/Dockerfile#L9-L14
mssql-djangoのドキュメントではドライバのバージョン17が指定されていますが、バージョン18がリリースされているのでそちらを使っています。

Djangoの設定(settings.py)

Djangoの設定ファイルであるsettings.pyに下記のような設定をいれます。

settings.py
ALDJEMY_ENGINES = {
    "mssql": 'mssql+pyodbc'
}
DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": os.getenv('MS_DB_NAME', ''),
        "USER": os.getenv('MS_DB_USER', ''),
        "PASSWORD": os.getenv('MS_DB_PASSWORD', ''),
        "HOST": os.getenv('MS_DB_HOST', 'localhost'),
        "PORT": os.getenv('MS_DB_PORT', '1433'),
        "OPTIONS": {
            "driver": os.getenv('MS_DB_DRIVER', 'ODBC Driver 18 for SQL Server'),
            "extra_params": os.getenv('MS_DB_EXTRA_PARAMS', ''),
        },
    },
}

aldjemyの設定

djangoはデフォルトではSQLServerに対応していないので、当然aldjemyもSQLServerに対応していません。
詰んだかと思ったのですが、aldjemyのソースを読んだらALDJEMY_ENGINESに設定を追加することで動きそうに見えたので、上に貼ったように追加したら動きました。
なぜ、ドキュメントに書いておいてくれないのか。。。

mssql-djangoの設定

DATABASESを設定すれば動きます。
上に貼ったものは、外から値を投入できるように環境変数を参照するようにしています。

  • ENGINE : mssqlを指定する
  • NAME : DB名
  • USER : DBのユーザー名
  • PASSWORD : DBのパスワード
  • HOST : DBのホスト
  • PORT : DBのポート
  • OPTIONS
    • driver : ODBCドライバーを指定する文字列(ODBC Driver 18 for SQL Server)
    • extra_params : 追加のパラメーター
      • 基本は空でOKだが、ローカルで開発用DBを動かす時は注意(※後で補足します)

DB連携のサンプル

とりあえず、Djangoでモデルを作って、

  • Django ORMを使ってPOST
  • SQLAlchemyを使ってGET

をそれぞれ書いてみました。
(そうするのがお勧めとかは全く無く、両方使えることを確認するために分けてます。)

Modelを書く

  • DjangoでModelを作ります。

https://github.com/k-ibaraki/sample-django-azure-webapps/blob/2022-06-09-main/sample/models.py#L9-L13

DjangoでPOST

  • DjangoでFormを作ってPOSTしてます。

https://github.com/k-ibaraki/sample-django-azure-webapps/blob/2022-06-09-main/sample/forms.py#L7-L11
https://github.com/k-ibaraki/sample-django-azure-webapps/blob/2022-06-09-main/sample/views.py#L17-L19

エラー処理が雑なのは、サンプルアプリなので許してください。

SQLAlchemyでGET

  • aldjemyを入れたことにより、DjangoのModelからSQLAlchemyのModelが取り出せます。

https://github.com/k-ibaraki/sample-django-azure-webapps/blob/2022-06-09-main/sample/infra/users.py#L13-L15

pythonではよくあることですが、aldjemyでは型アノテーションが定義されてなくUser.saの型が分からなくて結構困りました。SQLAlchemy上級者なら一目瞭然なのかもしれないですが、初心者には辛いです。。。

  • aldjemyを使って、SQLAlchemyのsessionを取得します。

https://github.com/k-ibaraki/sample-django-azure-webapps/blob/2022-06-09-main/sample/infra/users.py#L17-L18
sessionmaker()戻り値がSessionの関数ではなくsessionmakerというクラスのインスタンス生成であること気がつくのに時間がかかり、ここ苦戦しました。
最後に()をつけることで、sessionmakerクラスの__call__メソッドが呼び出されてSessionが取得出来ます。Pythonの文法難しいな。

  • Getします。
session.query(User_sa).all()

上記で、全てのUserのlistがGETできます。

中身がSQLAlchemyの型のためDjangoのViewに放り込んだ時の扱いが面倒くさく感じたので、雑にdictに変換しました。
https://github.com/k-ibaraki/sample-django-azure-webapps/blob/2022-06-09-main/sample/infra/users.py#L24-L25

まとめ

mssql-djangoaldjemyを使うことで、
Django + SQLServer(Azure SQL Database) + SQLAlchemyの環境が作れる

補足

ローカル環境にてSQLServerをDockerで動かす

開発時は全部ローカルでやりたいことも多いと思うので、一応書いておきます。
むしろこの記事の本編はここかもしれない。

DockerでSQLServerを動かす

SQLServer起動用のcompose.yamlを書きました。
(最新の仕様に基づき、docker-compose.ymlではなくcompose.yamlにしています。)
https://github.com/k-ibaraki/sample-django-azure-webapps/blob/2022-06-09-main/sql_db_docker/compose.yaml
docker compose upで起動できます。
SQLServerの起動までで、DBの作成やユーザーの作成は書いていないので、そこは自力でやってください。

SSLエラー対策

開発用にローカルでSQLServerを立ち上げた時は、通常は証明書とか暗号化とかは頑張りたくないと思います。しかし、それだとドライバーの仕様か何かで、セキュリティに引っかかってエラーになってしまいます。

django.db.utils.OperationalError: ('08001', '[08001] [Microsoft][ODBC Driver 18 for SQL Server]TCP Provider: Error code 0x2749 (10057) (SQLDriverConnect)')

これを回避するには、DB設定のextra_paramsTrustServerCertificate=yesを設定してください。サンプルに作ったアプリは環境変数で設定できるようにしてあります。
https://github.com/k-ibaraki/sample-django-azure-webapps/blob/2022-06-09-main/.env.sample#L27
これ、世の中の情報が少なくてかなり苦労しました。

DBとアプリの両方を、ローカル環境のDockerで動かすときのネットワーク設定

このサンプルアプリは、アプリ本体起動用のcompose.yamlも書きました。あると便利なので。
https://github.com/k-ibaraki/sample-django-azure-webapps/blob/2022-06-09-main/compose.yaml
DBとアプリの両方をdocker compose upで動かそうとすると、別々にdockerを立ち上げることになります。この時ネットワークが分離されるので、アプリからDBを参照できません。ですのでネットワークの設定が追加で必要になります。しかし普段はいらない設定なのでcompose.yamlに書いてしまうと邪魔になります。
ということで、ローカルのDBに接続する為のネットワーク設定だけを別ファイル分離して書きました。
https://github.com/k-ibaraki/sample-django-azure-webapps/blob/2022-06-09-main/compose.with_local_db.yaml
アプリ本体のcompose.yamlは、環境変数を.envから読むようにしています。
.envCOMPOSE_FILEを設定したときのみ追加ファイルも読み込んでネットワーク設定をoverrideできます。
https://github.com/k-ibaraki/sample-django-azure-webapps/blob/2022-06-09-main/.env.sample#L34

Discussion