SQLAlchemyのよろこび

SQLAlchemy はMichael Bayerによって開発中の新しいデータベースライブラリです。TurboGearsにおける主な利用法は、ORMレイヤーにおけるSQLObjectの置換です。SQLAlchemyはSQLObject以上の数々のアドバンテージを持っています:

  • 整数でないプライマリキーのサポート
  • 複合(マルチカラム)プライマリキー
  • 任意のselectをオブジェクトにマップできる
  • いくつかのテーブルを一つのオブジェクトにマップできる(その逆も可能)
  • 作業単位のコンセプトに基づいて、可能な限りクエリを効率的に実行する

もちろんいくつかのSQLAlchemyを使うことでのマイナス点もあります:

  • 宣言とマッピングがSQLObjectよりも冗長
  • SQLObject?の方がプロダクトとして長く使われている
  • 開発途中であり、APIがリリースポイント間で変更される可能性がある
  • いくつかのTurboGearsの機能はSQLObjectでのみ利用可能

これらのマイナス点にもかかわらず、ある程度のTurboGearsの開発者はそのプロジェクトでSQLAlchemyを使っており、将来的には公式なORMとなるかもしれません。

TurboGears?のSQLAlchemyサポート

動作するもの:

  • 自動トランザクション
  • クイックスタート
  • Identity

動作しないもの:

  • Catwalk
  • Model Designer
  • fastdata

SQLAlchemyをゲットする

cheeseshop経由で最新版を得ることができます:

easy_install SQLAlchemy

また SQLAlchemy download ページから得ることもできます。 プロジェクトドキュメント は非常に徹底しており、 sql construction のページや basic data mapping 、そして advanced data mapping は特に読んでみたくなるでしょう。

TurboGears?でSQLAlchemyを使う

SQLAlchemyを使ってみる一番シンプルな方法は、新しいプロジェクトを --sqlalchemy を使ってクイックスタートしてみることです:

tg-admin quickstart --sqlalchemy

これによって model.py がSQLAlchemyを使うように正しくセットされます。またidentityを使うかどうかのプロンプトでyesを選ぶと、ActiveMapperバージョンのidentityテーブルが生成されます。適切なデータベース接続をセットするように sqlalchemy.dburi を編集した後に、データベーススキーマを初期化するために以下を実行する必要があります:

tg-admin sql create

もしあなたがSQLObjectからSQLAlchemyにプロジェクトを変換しようとしているならば、新しいプロジェクトをクイックスタートし、identityテーブルを一つ一つコピーすることをおすすめします。

ActiveMapper?について

ActiveMapper?は標準のSQLAlchemyマッパの最上位となる軽量な宣言型レイヤーです。これによってSQLObjectでやるようにクラスを宣言することができますが、少々文法は異なります。

不幸にもActiveMapperに関する公式なドキュメントは存在しませんが、 ActiveMapper? Crash Course を参照するとよいでしょう。

assign_mapper() を使う

ActiveMapper?のただ一つの問題は、少なくとも現時点では、特に primaryjoinsecondaryjoin オプションについて、SQLAlchemyインタフェイスすべてをexposeすることができないということです。これが何らかの高度なマッピングをするときの唯一の問題ですが、この問題が起こるときっとあなたの頭を悩ませることでしょう。さらにActiveMapperとActiveMapperでないテーブルのjoinはいつも動くわけではありません。もしテーブルをそれ自身に関連づけることがわかっているならば、それをSQLAlchemyの文法で assign_mapper() を使って宣言したくなるでしょう。

assign_mapper はもう一つのSQLAlchemyの拡張です。この拡張はマップされたクラスオブジェクトに、SQLObjectやActiveMapperオブジェクトのように振る舞うようにするモンキーパッチです。これはデフォルトのSQLAlchemyマッパと違って、クラスに mapping 属性を追加するだけです。ですので myclass.select ではなく myclass.mapping.select() する必要があります。

active_mapper() はコンテキストパラメータを取ります。TurboGearsのコンテキストを使いたくなるでしょう。そういう場合は以下のようにします:

  1. TurboGears?metadatasession を使う:

    from turbogears.database import metadata, session
    
  2. session.context を使ってそのコンテキストで得る:

    active_mapper(session.context, table_class, TargetClass)
    

Tips

デバッグ

dev.cfg ファイルで sqlalchemy.echoTrue にすれば、データベースで実行されたクエリを見ることができます。

カラムのタイプをカスタマイズする

SQLObject?はPythonにデータベース変換をさせるためにformencodeを使いますが、SQLAlchemyはそのカラムデータタイプの定義を通して変換を行います。ここではSQLAlchemyのカラムデータタイプをどのように使うかを示します。

以下では二つの例を示しています。一つはシステムのタイムスタンプを表す数字をPythonの datetime に変換しています。もう一つはIPv4アドレスを整数と8ビット間で変換します。 convert_bind_param はデータベース のもので convert_result_value はデータベース から のものであることを覚えておいてください。

TIMESTAMP カラムタイプを作成します:

import time
from datetime import datetime

class TIMESTAMP(Numeric):
    # to the database
    def convert_bind_param(self,value,engine):
        return super(TIMESTAMP,self).convert_bind_param(time.mktime(value.timetuple()),engine)
    # from the database
    def convert_result_value(self,value,engine):
        return datetime.fromtimestamp(super(TIMESTAMP,self).convert_result_value(value,engine))

IPV4 カラムタイプを作成します:

import struct
from socket import inet_aton, inet_ntoa, error as socket_error

class IPv4AddrTypeError(TypeError):
    def __init__(self, addr):
        self.addr = addr
    def __str__(self):
        return "Illegal IPv4 address '%s'" % self.addr

class IPV4(Numeric):
    # to the database
    def convert_bind_param(self,value,engine):
        try:
            return super(IPV4,self).convert_bind_param(struct.unpack('!L',inet_aton(value))[0],engine)
        except socket_error:
            raise IPv4AddrTypeError(value)
    # from the database
    def convert_result_value(self,value,engine):
        return inet_ntoa(struct.pack('!L',super(IPV4,self).convert_result_value(value,engine))

さらなる開発

Evan RossonはGoogle Summer of Code 2006で、コントロールスキーママイグレーションを可能にするツールを提供する Migrate プロジェクトを作りました。