Slide 1

Slide 1 text

Pydantic x Database API Turu-py の開発 yassun7010 GitHub Repo

Slide 2

Slide 2 text

自己紹介 GitHub Account: yassun7010 所属企業: サイバーエージェント AI事業本部 バックエンドエンジニア 業務内容 小売事業のデータ分析基盤を開発中 Github ロ ゴ マ ー ク - ダ ウ ン ロ ー ド 無 料 の ア イ コ ン

Slide 3

Slide 3 text

本日のお題目 • 夢のクエリビルダへの挑戦! …と断念 • Turu-py の誕生:Pydantic x Database API への転換

Slide 4

Slide 4 text

発表の動機 Python × Snowflake で 型ヒントの恩恵を受けた 型安全でロバスト な開発がしたかった

Slide 5

Slide 5 text

Snowflake - SaaS型データプラットフォーム - メリット • ストレージとコンピューティングが分離・個別にスケール可能 • パフォーマンスが優れている • AI に向けた機能も豊富 デメリット • ローカル開発ができないため、自動テストに工夫が必要 • 独自の機能があるため ORM で利用できない機能も多い

Slide 6

Slide 6 text

当初は snowflake-sqlalchemy を検討する SQLAlchemy の V2 に対応していないことが判明(2024/05/28 PyCon応募時点) V2 を利用したかったためマージされるまで見送り

Slide 7

Slide 7 text

当初は snowflake-sqlalchemy を検討する SQLAlchemy の V2 に対応していないことが判明(2024/05/28 PyCon応募時点) V2 を利用したかったためマージされるまで見送り

Slide 8

Slide 8 text

SQLAlchemy V2 も型安全ではない SQLAlchemy V2 は、より SQL に近い書き方に変わったものの 型安全な開発ができていないのが現状

Slide 9

Slide 9 text

ところで Python 3.12 が出た ジェネリックプログラミングが非常に書きやすくなった。 これで何か遊びたい…

Slide 10

Slide 10 text

Snowflake を扱える データベースクライアント ライブラリを作ってみよう

Slide 11

Slide 11 text

ライブラリ開発の方針(ざっくり) • 静的解析の恩恵を受け、変更に強いチーム開発がしたい • コードの変更による影響範囲を自動検出。ロバストな開発によるチームの生産性の向上 • チームの学習コストを下げたい(よく知られた UI) • SQL のクエリを知っていれば OK • 自動テストを簡単に書けるようにしたい • Mock 機能をライブラリ側で提供 • 複雑な機能は外部ライブラリに頼りたい • Pydantic を利用。省エネは大事 データサイエンティストの多いチーム。みんな SQL が書けた。 分析が主な用途だったので 複雑な SQL 文 を書けることが重要。

Slide 12

Slide 12 text

クエリ文字列で型推論 クエリビルダで型推論 クエリ文から 戻り値の型を 推測してくれたらなぁ… まるでSQLのように 書けたらなぁ… 理想のデータベースアクセスを夢想する

Slide 13

Slide 13 text

理想のデータベースアクセスを夢想する クエリ文字列で型推論 クエリビルダで型推論 クエリ文から 戻り値の型を 推測してくれたらなぁ… まずこちらを検討 まるでSQLのように 書けたらなぁ…

Slide 14

Slide 14 text

余談:クエリ文字列でも開発しやすくなっている エディタ上で色がつく! 関数の補完も効く! 自動フォーマット・レコードの型推論が課題… リテラル文字列の多機能化は TypeScript が先行の印象

Slide 15

Slide 15 text

開発し始めてからしばらく…ついに

Slide 16

Slide 16 text

型安全なクエリビルダの検討…を断念 SELECT 文は複雑 単体のテーブルクエリは作れたが… 複数のテーブル合成の戻り型を作れず… フィールドの部分取得の 推論が困難 テーブルの結合が困難 CTEなどを型で表現できない テーブルのレコードを そのまま返すのは可能

Slide 17

Slide 17 text

振り返り)ここが面白かった クラスでテーブルを表現する場合、直感的には Class:テーブル Instance:レコード ただし、 __init__ の型定義は上手くできない ※ 現状の dataclass_transform という仕組みで コンストラクタ引数の型を指定できない Class / Instance プロパティ型を別に定義可能 SQLAlchemy などで実装実績あり User.id ⇨ Column[int] user.id ⇨ int クラスプロパティと インスタンスプロパティで 別の型を指定可能 コンストラクタは Field[int] を要求

Slide 18

Slide 18 text

振り返り)こんな機能があったらよかった 1. Generic な型を既定クラスにできれば… 2. dataclass_transform の機能不足 dataclass 風のクラスを作る機能。 コンストラクタ引数の型定義のために converter が欲しかった (PEP 712 など 関連する議論 あり。前のスライドの問題) 3. プロパティを動的に型推論できれば… 写像や JOIN の結果を 型で表現できた… Proxy +追加機能で SQLの式を 定義できた…

Slide 19

Slide 19 text

(現状) 理想のクエリビルダを 作れないことはわかった…

Slide 20

Slide 20 text

Turu-py の誕生

Slide 21

Slide 21 text

コンセプトは Pydantic x Database API execute (query run) + map (results validation) ⇨ execute_map メソッドを追加

Slide 22

Slide 22 text

ライブラリ開発の方針 • 静的解析の恩恵を受け、変更に強いチーム開発がしたい • コードの変更による影響範囲を自動検出。ロバストな開発によるチームの生産性の向上 • チームの学習コストを下げたい(よく知られた UI) • SQL のクエリを知っていれば OK • PEP 249: Python Database API Specification v2.0 • 複雑な機能は外部ライブラリに頼りたい • Pydantic ・既存のDBクライアントを利用。省エネは大事 • 自動テストを簡単に書けるようにしたい • Mock 機能をライブラリ側で提供

Slide 23

Slide 23 text

PEP 249:Python Database API Specification v2.0 データベースクライアントが実装すべき API ・非常に枯れている(1999年〜) ・異なる DB を同じインターフェースで扱える ・複数のデータベースサポートが容易 ・学習コストが少ない これを前提にすると…

Slide 24

Slide 24 text

Turu-py は戻り値の型を保証した Database API execute execute_map turu-py の追加機能 +Map to Pydantic Model

Slide 25

Slide 25 text

余談: Snowflake 向けの特殊機能 Snowflake 拡張 Pandas / PyArrow への対応 Pandera による Dataframe の検証への対応

Slide 26

Slide 26 text

多くの PEP 249 に従った Python ライブラリが存在 PEP 249 に従った typing.Protocol を定義して 各lib の conn を受け取れば楽できないか PostgreSQL:psycopg2 MySQL:mysql-connector-python SQLite:sqlite3 Snowflake:snowflake-connector-python DuckDB:duckdb BigQuery:google-cloud-bigquery データベースとのやり取りは既存のライブラリにお任せ

Slide 27

Slide 27 text

Protocol とは ライブラリ間の依存関係はないから継承より Protocol が適切 どの DB クライアントも PEP に従っているから大丈夫 検討理由 Protocol: 継承ではなく ダックタイピングによる型安全 Protocol の利用イメージ 各ライブラリが提供する Connection クラスを そのまま渡せば動くかな?

Slide 28

Slide 28 text

Protocol は上手くいかなかった 定義が厳密に一致していないといけない だがしかし! • 引数名が微妙に違う(parameters ≠ params) • 引数の型が微妙に違う( Sequence ≠ list ) • 各ライブラリには独自の追加引数があったり… 本来ならば PEP 249 のメソッド仕様の一部

Slide 29

Slide 29 text

Protocol は上手くいかなかった 定義が厳密に一致していないといけない だがしかし! • 引数名が微妙に違う(parameters ≠ params) • 引数の型が微妙に違う( Sequence ≠ list ) • 各ライブラリには独自の追加引数があったり… 本来ならば 検討したすべてのライブラリが不一致 結局ラッパークラスを各 DB ごとに定義

Slide 30

Slide 30 text

Pydantic と DB Client を 直接使うだけでいいのでは?

Slide 31

Slide 31 text

NO! 時代はデータベース SaaS 時代 分散トランザクション・AI 機能・ベクトルデータの対応など SQL で独自機能が加わったデータベースの SaaS で利益が出せる時代 PostgreSQL・MySQL・SQLite のようなコンテナデータベースと異なり 複数人が同時に自動テストを行うことは困難 クラウドネイティブのデータベースは自動テストしにくい 独自の方言を持つクラウドネイティブ DB ビジネスの発展

Slide 32

Slide 32 text

Turu-py による自動テスト用モックの提供 Snowflake はローカル環境を提供しないため 自動テストが困難 期待値を型安全に注入できるモックツールを用意 本番コードだけでなく 自動テストでも型安全に テスト方法を公式が提供し、コードの作法を統一 ライブラリは自動テスト用のモック機能も提供すべき テストデータ挿入 テストデータで実行 モッククライアント作成

Slide 33

Slide 33 text

チームでの利用例:レコード機能 + モック クエリの正しさは実際のデータベースで動作確認 したいが Snowflake はローカル上で実行できない 妥協案として テストデータを簡単に作れる 方針を採用 ① ローカル環境時に dev 環境の Snowflake を実行 ENABLE_RECORDING 環境変数が true の場合のみ Git レポジトリ上の自動テストデータ を更新 ③ 自動テストでは記録したデータを再生

Slide 34

Slide 34 text

まとめ 型安全なクエリビルダに挑戦 テーブル結合の結果の戻り型を自動生成できず断念 でも Python 3.12 のジェネリックプログラミングは快適 Pydantic x Database API でシンプルな turu-py を作成 自動テスト用のモック機能をライブラリ側で用意

Slide 35

Slide 35 text

おまけ: 懲りずに型安全なクエリビルダ snowman-py ターゲットDBを Snowflake に限定 INSERT / UPDATE を最初の標的 • 単一テーブルで完結できる • 定型のクエリ記述量が多い 型安全にできる機能を徐々に拡大中

Slide 36

Slide 36 text

おわり