LoginSignup
2
0

[Azure] Functions(Python)からSQL DBにアクセスする

Last updated at Posted at 2024-03-15

前回Functionsのデバッグ環境を整えたわけですが、今回はAzure SQLにFunctionsからアクセスしていきたいと思います。

おおまかな流れ

  1. パッケージのインストール
  2. コードの確認
  3. AzureFunctionsにpublish、そして確認
  4. 余談、パッケージのバージョン指定

パッケージのインストール

今回、Functionsの中でAzureSQLに接続する中で、最初はODBCを使うという選択をしていました。

私が参考にしたコードの中でpyodbcが使われていたんですね。

しかし、これはあんまり上手くない方法だと分かりました。

理由は、ODBCを使うにはシステムにODBCを使うためのインストール作業を行わなければならなかったからです。

aptにMicrosoftのリポジトリを追加して、システムにODBCをインストールし、そのインタフェースとしてPythonインタプリター側にpyodbcパッケージをインストールし、コード内で使っていく、という流れになります。

しかしこうなると、このODBC自体はFunctionsの環境に入っているのか?という疑問が当然起こります。

requirements.txtにパッケージ情報を書くことで、Pythonインタプリターにインターフェースとしてのパッケージをインストールすることはできますが、そのインタフェースの先にODBCコアがなければコードは正常に動きません。

それで調べてみたんですが、どうやらFunctionsの中にはODBC環境はないようでした。

ということで、ODBCを使わずにAzureSQLに接続する方法を探したところ、sqlalchemyと
pymssqlを使えばイケるということが分かりました。

ついては、これらをインストールしていきます。

pip install sqlalchemy
pip install pymssql

ついでにパスワード等、DB接続情報の安全運用を行うため

pip install python-dotenv

もやっておきます。

続いて、コードベースで説明していきます。

コードの確認

こちらがFunctionsのコードの全文となります。

__init__.py
import os
import logging
import json
from datetime import datetime
from sqlalchemy import create_engine, text

from azure.functions import EventHubEvent
from typing import List
from dotenv import load_dotenv

load_dotenv()


def main(events: List[EventHubEvent]):
    server = os.getenv('DB_SERVER')
    database = os.getenv('DB_NAME')
    username = os.getenv('DB_USER')
    password = os.getenv('DB_PASSWORD')

    # SQLAlchemy 接続文字列の作成
    connection_string = f"mssql+pymssql://{username}:{password}@{server}/{database}"
    # SQLAlchemy エンジンの作成
    engine = create_engine(connection_string)

    for event in events:
        # イベントデータの解析
        event_data = json.loads(event.get_body().decode('utf-8'))
        logging.info(
            f"Python EventHub trigger processed an event: {event_data}")

        temperature = event_data['temperature']
        humidity = event_data['humidity']
        create_time = datetime.now()

        try:
            # データベースへのデータ挿入
            with engine.connect() as connection:
                insert_statement = text(
                    "INSERT INTO test_table (create_time, temperature, humidity) VALUES (:create_time, :temperature, :humidity)"
                )
                # パラメータを辞書形式に変換
                params = {'create_time': create_time,
                          'temperature': temperature, 'humidity': humidity}
                connection.execute(insert_statement, params)
                connection.commit()
                logging.info("データベースにデータを挿入しました。")

        except Exception as e:
            logging.error(f"データベースへのデータ挿入中にエラーが発生しました: {e}")

データベースの接続文字列の作り方

冒頭でSQLAlchemy向けのデータベース接続文字列を作っています。

    server = os.getenv('DB_SERVER')
    database = os.getenv('DB')
    username = os.getenv('DB_USER')
    password = os.getenv('DB_PASSWORD')

    # SQLAlchemy 接続文字列の作成
    connection_string = f"mssql+pymssql://{username}:{password}@{server}/{database}"

この内容についてはデータベースの接続文字列の画面から取得することができます。

スクリーンショット 2024-03-15 104658.png

Server=tcp:<DB_SERVER>,1433;Initial Catalog=<DB_NAME>;Persist Security Info=False;User ID=<DB_USER>;Password=<DB_PASSWORD>;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;

というようなフォーマットでDB接続に必要な各情報が収められていますので、ここから取得して環境変数として保持します。

python-dotenvについて

環境変数に格納するにあたって、.envに書いておき、python-dotenvを使ってロードするとコードの中に重要情報を書き込むことなく、デバッグにもAzureにアップロードした後にも使えていい感じでした。

os.getenv()はローカルデバッグ時にはプロジェクト直下.envに書かれた情報を参照してくれます。

また、Azure環境上にアップロードされた後には、Functionsの「構成」で設定した環境変数を参照してくれます。

したがって、本番コードもデバッグコードも同じコードを使用することができます。

スクリーンショット 2024-03-15 105512.png

IoT Hubへの接続文字列の追加

構成には、忘れずIoT Hubへの接続文字列も追加しておきましょう。

これは前回の記事で紹介した function.js の "connection": "IoTHubConnectionString"に当たる文字列のことです。ローカルデバッグのときにはlocal.settings.jsonのIoTHubConnectionStringの値を読みに行って接続していましたが、local.settings.jsonはFunction上にアップロードされないため、IoTHubConnectionStringの値はFunctionsの構成に設定しておく必要があります。

AzureFunctionsにpublish、そして確認

ローカルデバッグで動作を確認したら、Functionsにデプロイします。

func azure functionapp publish <function-app-name> --python  

下記の表示が出たら成功です。

スクリーンショット 2024-03-15 105925.png

アップロードできたら、その動作を確認します。

Functionsリソースの概要から、先ほどアップした<function-app-name>の書かれている「名前」欄の項目をクリックします。

スクリーンショット 2024-03-15 110120.png

更に左側のペインからモニターの画面に行き、ログのタブをクリックしてしばらくするとApplication Insights に接続しています...というメッセージが表示され接続されました。となります。

この状況でIoT Hubに対してメッセージを送信すると、リアルタイムでログが表示されます。

スクリーンショット 2024-03-15 110829.png

データベースでクエリを発行すると、無事データはDBの中に収められていました。

ただ、create_timeに関してはAzureの中ではUTCが採用されるようなので、このあたり+9時間で入力するかどうかは考えものですね。
デバッグ時にはシステムの時間(日本時間)がcreate_timeに入ってくるので、常に日本時間を入力しようとすると、本番とデバッグ時でコードを変更する必要があり、これは中々面倒な問題です。

余談、パッケージのバージョン指定

最後に余談ですが、AzureFunctions上のランタイムにインストールされるリストとして、requirements.txtを用意する必要がありますが、こちらの内容について、私の場合はバージョンを指定しました。

greenlet==3.0.3
pymssql==2.2.11
python-dotenv==1.0.1
sqlalchemy==2.0.28
typing-extensions==4.10.0

バージョンを指定しない場合、常に最新のバージョンがインストールされることになると思います。
そうなると、バージョン互換性の問題が発生する可能性があります。

バージョンを指定していても、そのバージョンがパッケージリポジトリから削除されたりした場合、逆に問題の原因になりますが、まあバージョンは指定しておいたほうがいいのではないかなと思います。

これらバージョンについてはpoetryを使って抽出することができます。パッケージが依存しているパッケージのバージョンも固めることができ、より運用安全性が増すと思われます。

poetry export --without-hashes -f requirements.txt > requirements.txt 
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0