このドキュメントは、Python モジュール SQLObject に付属する SQLObject.txt を、柴田淳氏と増田が翻訳したものです。まだ未完成。
SQLObject は Ian Bicking (ianb@colorstudy.com) と 多くの協力者 の手で作られています。 ウェブサイトは sqlobject.org です。
SQLObject のコードは Lesser General Public License (LGPL) でライセンスさ れています。
このプログラムは皆さんの役に立つことを願って配布されてはいますが、保証は一 切行いません。明示、黙示を問わず、市場性、特定目的への適合性を保証しません。 詳しくは GNU General Public License を参照してください。
SQLObject はプログラミング言語 Python 用の オブジェクト-リレーショナルマッパ (Object-Relational Mapper, ORM) です。 SQLObject を使うと、 RDBMS のテーブル内の行 (row) を Python オブジェクトに 変換し、オブジェクトの操作を介して透過的にデータベース を操作できるようになります。
SQLObject を使う場合、オブジェクトをどうやってデータベースのテーブルに変換 するかを決めるクラスを定義することになります。 SQLObject はデータベースにア クセスするためのコードを生成し、オブジェクトの変更に応じてデータベースを更 新します。 SQLObject が生成するインタフェースは見かけ上他の Python のインタ フェースとほぼ同じなので、インタフェースの呼び出し側はデータベースのバック エンドについて気にしなくて済みます。
また、 SQLObject には SQL の問い合わせ文 (query) をテキストで作成せずに済む ようにするための斬新な機能も盛り込まれています。この機能を使うと、SQL を使 わないデータベースを同じ問い合わせの構文で使えるようになります。
現在のところ、 SQLObject は MySQLdb 経由で MySQL を, psycopg 経由で PostgreSQL を、 PySQLite 経由で SQLite を、 kinterbasedb 経由で Firebird を、 Sybase 経由で Sybase を、そして sapdb 経由で MAX DB (い わゆる SAP DB) をサポートしています。
Python 2.2 またはそれ以降のバージョンが必要です。 SQLObject は新形式のクラス を徹底的に使っているからです。
Python 向けの ORM は数多くあります。正直、私はそれぞれのパッケージの出来に ついて詳しくコメントできる立場にはないのですが、できるだけ客観的な視点から SQLObject の位置付けを説明してみたいと思います。
SQLObject は徹底して新形式のクラスを使っています。その結果、 SQLObject が 返すオブジェクトには新形式クラスならではの操作性、例えば属性の設定に副作用 (データベースへの変更) が伴ったり、 (メタクラスを使っているために) クラス の定義に副作用が伴う、といった具合です。Attributes are generally exposed, not marked private, knowing that they can be made dynamic or write-only later.
SQLObject は通常の Python オブジェクトによく似た操作性の (新形式クラスのセ マンティクスを持った) オブジェクトを生成します。ある列 (column) を表すよう に属性を割り当てると、その属性は見かけ上、ファイルを表す属性や計算で求まる 属性値と大して変わらなくなります。これこそが SQLObject の目的、すなわち、デー タベースへのインタフェースに何ら変更を加えたり、データベースの役割を単なる ストレージメカニズムから拡大縮小せずにデータベースを入れ換えられるようにす るということです。
これに対して、 ORM のいくつか (例えば PyDO) では、データベースに対して辞書 に似たインタフェースを提供しています。こうした辞書インタフェースでは、各行 は通常の Python オブジェクトとは別のもので表現しています。また、私は、属性 を使う方が自然な場合には、値の選ぶのに文字列を使う方法が好きではありません。 テーブルの列は限られた数しかなく、しかもあらかじめ定義されているものですが、 これは属性と同じではないでしょうか (注: PyDO の最近のバージョンでも、属性 を使ったアクセスを行えるようです)。
私の知る限り、SQLObjec は上記のような機能をメタクラスを使ってシームレスに 実現しているという点でユニークです。他の ORM ではコード生成を通してインタ フェースを生成したり、スキーマを CSV や XML ファイルで表現しています (Webware の一部である MiddleKit がその例です)。 メタクラスを使えば、Python ソースコード内で気楽にスキーマを定義できます。コード生成や面倒なツール、コ ンパイル手順などは必要ないのです。
SQLObject では強力なデータベース抽象化を行うことで、異なるデータベース間で も (SQLObject の枠からはみ出さない限り) 互換性を保てるようになっています。
SQLObject では、一対多の結合 (join) に加え、他の ORM にはない多対多の結合も サポートしています。 join のからくりは拡張できるようにもなっています。
データベース上の名前と Python の属性名やクラス名は対応づけできます。という のも、二つの名前が一致しなかったり、データベースのスタイルが Python の属性 名として不適切な場合があるからです。これにより、データベースのスキーマを SQLObject に合わせて設計しなくてもよく、生成するクラスがデータベースの名前 づけ規則を必ずしも継承していなくてもかまわないようになっています。
手っ取り早く入門編といきましょう。 まずは sqlobject クラスから 全てを import します:
>>> from sqlobject import * >>> import sys, os
接続用の URI は以下のような標準 URI 構文に従うようにしてください:
scheme://[user[:password]@]host[:port]/database[?parameters]
スキーム (scheme) には sqlite, mysql, postgres, firebird, interbase, maxdb, sapdb, sybase のいずれかを使います。
例をいくつか示しましょう:
mysql://user:passwd@host/database mysql://host/database?debug=1 postgres://user@host/database?debug=&cache= postgres://host:5432/database sqlite:///full/path/to/database sqlite:/C|/full/path/to/database sqlite:/:memory:
w32 システムでは完全パスを指定する特殊な構文を使うことに気を付けてください (この構文は sqlite や firebird で利用できます)。また、 sqlite では特殊な メモリ上のデータベースを操作できます。
指定できるパラメタには、 debug (default: False), debugOutput (default: False), cache (default: True), autoCommit (default: True), debugThreading (default: False) などがあります。
接続 URI 中で値を True に指定したい場合には、空文字列でない何らかの文字列を 指定してください。空文字列を指定すると、値は False になります。
では接続を確立しましょう:
>>> db_filename = os.path.abspath('data.db')
>>> if os.path.exists(db_filename):
...     os.unlink(db_filename)
>>> connection_string = 'sqlite:' + db_filename
>>> connection = connectionForURI(connection_string)
>>> sqlhub.processConnection = connection
sqlhub.processConnection に代入すると、これ以降はデフォルトで今確立した 接続を使うようになります。
簡単なアドレスブック風のデータベースを作成します。あらかじめ手作業でテーブ ルを作っておいて、 SQLObject からアクセスするようにもできますが、ここでは SQLObject 自体に作業させてみましょう。まず、クラスを定義します:
>>> class Person(SQLObject): ... ... firstName = StringCol() ... middleInitial = StringCol(length=1, default=None) ... lastName = StringCol()
基本的なテーブルのスキーマは、たいていこれよりは複雑にならないでしょう。
firstName, middleInitial, および lastName はいずれもデータベースの列 (column) です。このクラス定義から導かれる一般的なスキーマは、以下のようにな ります:
CREATE TABLE person (
    id INT PRIMARY KEY AUTO_INCREMENT,
    first_name TEXT,
    middle_initial CHAR(1),
    last_name TEXT
);
このスキーマは SQLite や MySQL 向けの書き方です。他のデータベースの場合には、 (特に id の列が) やや違った表現になります。 列名が大小文字を混合するや り方 (mixedCase) から、アンダースコアを使った方法 (underscore_separated) に 変わっていることにお気づきでしょう。これはスタイルオブジェクト (style object) のせいです。こうした名前の変換にそぐわないような列名を扱う方法も いくつか用意されています ( 不正な名前づけの扱い を参照してください)。
さて、テーブルをデータベース上に作成しましょう:
>>> Person.createTable()
StringCol 以外のコンストラクタを使ったり、引数の指定方法を変えることで、 列のデータ型を変更できます。詳しくは 列のデータ型 で説明しています。
さて、 クラスで定義していないにもかかわらず、 id という列が入っているこ とにお気づきでしょう。 MySQL データベースでは、この列は INT PRIMARY KEY AUTO_INCREMENT のように、 Postgres では SERIAL PRIMARY KEY 、また SQLite では INTEGER PRIMARY KEY のように定義せねばなりません。 SQLObject では、単一の主キー (primary key) のないテーブルは使えません。 また、主キーは不変の値でなければなりません (さもないと、 SQLObject を ひどい混乱に陥れることになります)。
列名は オーバライドできます が、 Python からは常に .id と いう名前で見えます。
さて、クラスの定義はうまくいきました。では、どのように使えばよいのでしょう か? 上で定義したクラスについて考えてみましょう。
新たなオブジェクト (あるいは新たな行) を作るには、以下のようにしてクラスの インスタンスを生成します:
>>> Person(firstName="John", lastName="Doe") <Person 1 firstName='John' middleInitial=None lastName='Doe'>
Note
SQLObject における NULL/None はデフォルト値を意味 しません 。 NULL は 変わり者で、状況や人によって意味が様々に変わります。時には「デフォルト値」 であり、また時には「利用不可」そしてまたある時には「不明」を意味します。 SQLObject でデフォルト値を指定したければ、 NULL にしろそれ以外の値にしろ それをクラス定義で明示的に指定せねばなりません。
また、 SQLObject のデフォルト値はデータベースにおけるデフォルト値とも同 じではないことに注意してください (SQLObject はデータベースのデフォルト値 を使おうとはしません)。
firstName や lastName といった列にはデフォルト値がないので、これら の引数の指定を忘れるとエラーになります (middleInitial はデータベースに おける None に相当するデフォルト値 NULL が設定されます)。
.get() メソッドを使うと、すでにテーブルに入っているインスタンスを取得 (fetch) します:
>>> Person.get(1) <Person 1 firstName='John' middleInitial=None lastName='Doe'>
オブジェクトを生成すると、その内容はすぐにデータベースに挿入されます。他の システムではユーザがオブジェクトを明示的にデータベースに挿入せねばなりませ んが、SQLObject はデータベースを即時性 (immediate) のストレージとして扱う点 が異なります。
クラスを使ったもう少し長い例を紹介しましょう:
>>> p = Person.get(1) >>> p <Person 1 firstName='John' middleInitial=None lastName='Doe'> >>> p.firstName 'John' >>> p.middleInitial = 'Q' >>> p.middleInitial 'Q' >>> p2 = Person.get(1) >>> p2 <Person 1 firstName='John' middleInitial='Q' lastName='Doe'> >>> p is p2 True
ここでは、列データを属性のようにアクセスしています。(この機能は、属性値の取 り出し時や設定時にコードを実行できる、Python 2.2 の プロパティ (property) を使っています。) また、各オブジェクトが一意に表現さてれいる --- ある id を持った Person のインスタンスはメモリ上に一つしかない --- ことにも注意してください。 ある id を持った Person インスタンスを複数生成しても、得られるインスタンス は同じになります。このため、複数スレッドが同じデータにアクセスしているよう な状況でも (もちろんプロセス間の場合には同じインスタンスを共有したりできま せんが) 一定の一貫性を確保できます。ただし、 トランザクション を使ってい る場合にはあてはまりません。
SQLObject の内部で何が起こっているかを理解するために、実際に送信されている SQL をコメント付きで示しましょう:
>>> # This will make SQLObject print out the SQL it executes:
>>> Person._connection.debug = True
>>> p = Person(firstName='Bob', lastName='Hope')
 1/QueryIns:  INSERT INTO person (last_name, middle_initial, first_name) VALU\
ES ('Hope', NULL, 'Bob')
 1/COMMIT  :  auto
 1/QueryOne:  SELECT first_name, middle_initial, last_name FROM person WHERE \
id = 2
 1/COMMIT  :  auto
>>> p
<Person 2 firstName='Bob' middleInitial=None lastName='Hope'>
>>> p.middleInitial = 'Q'
 1/Query   :  UPDATE person SET middle_initial = 'Q' WHERE id = 2
 1/COMMIT  :  auto
>>> p2 = Person.get(1)
>>> # Note: no database access, since we're just grabbing the same
>>> # instance we already had.
おそらく、分かりやすく、予想通りの SQL が送信されていると思ったことでしょう。 送信される SQL を見るには、接続 URI に ?debug=t を追加するか、キーワー ド引数 debug=1 を接続オブジェクトに渡してください。全ての SQL をコンソー ルに出力します。これは心強い機能で、ぜひ試してみるようお勧めします。
各属性を個別に代入する代わりに、以下のように set メソッドを使って複数の 属性に代入を行うと、ちょっとした最適化になります:
>>> p.set(firstName='Robert', lastName='Hope Jr.')
この方法では、 UPDATE 文を一度しか発行しません。 set はデータベースと 関係のないプロパティにも適用できます (特に利点があるわけではありませんが、 データベース関連とそうでない属性との違いを隠蔽する手助けにはなります)。
デフォルトでは、 SQLObject はオブジェクトの属性に代入したり、 .set() メ ソッドを使うたびに、 UPDATE をデータベースに送信します。何度も更新が起 きるのを避けたければ、 _lazyUpdate = True をクラス定義に追加してくださ い。これによって、 inst.syncUpdate() や obj.sync() を呼び出したとき にだけ更新を行うようになります: .sync() はデータベースからデータを再取 得し、 .syncUpdate() はしません。
更新の遅延を有効にすると、インスタンスに反映待ちの更新が存在するかどうかを 表す .sqlmeta.dirty というプロパティが追加されます。更新の遅延が有効で も、行の挿入は即座に行われます。現時点では、挿入を遅延させる方法はありませ ん。
ところで、アドレスの入っていないアドレスブックなど、全く意味がありません。 そこで、アドレス用のテーブルを新たに定義しましょう。いうまでもなく、ある人 (People オブジェクト) は、複数のアドレスを持ち得ます:
>>> class Address(SQLObject):
...
...     street = StringCol()
...     city = StringCol()
...     state = StringCol(length=2)
...     zip = StringCol(length=9)
...     person = ForeignKey('Person')
>>> Address.createTable()
列定義 person = ForeignKey("Person") に注意してください。ここでは Person オブジェクトに対して参照を行っています。 SQLObject では他のクラスを (文字列の) 名前で参照します。データベースには persion_id という列が作られ、 person 列を示す INT 型の 値になります。
Note
SQLObject が他のクラスを参照するのに文字列を使うのは、参照した時点でクラ スがまだ存在しない場合があるからです。 Python におけるクラスは、モジュー ルが import され、その中の命令が実行されるときに 宣言される のではなく、 生成される ものです。 class 文もまた、あるクラスを生成して、指定し た名前に代入するという一つの命令にすぎません。
仮に、クラス A がクラス B を参照していて、クラス B は同じモ ジュールの A の下で定義されていたとしましょう。この場合、 A が生 成される (そして全てのカラム属性も生成される) まさにそのとき、 B ク ラスはまだ存在していません。クラスを名前で参照しようとすると、クラス間の リンクを張るには全てのクラスを生成し終わるまで待たねばならないのです。
さて、ある人のアドレスを表現するための属性が必要です。本来なら、クラス定義 で以下のようにすべきでした:
class Person(SQLObject):
    ...
    addresses = MultipleJoin('Address')
とはいえ、すでにクラスは生成済みなので、その場でクラスに追加しましょう:
>>> Person.sqlmeta.addJoin(MultipleJoin('Address',
...                        joinMethodName='addresses'))
Note
ほとんどの場合、 SQLObject のクラスは生成した後にも手を加えられます。 *Col オブジェクトの入った属性をクラス定義に入れるのは、 (addColumn() のような) クラスメソッドの呼び出しと等価だからです。
さて、これで aPerson.addresses を使って逆参照 (backreference) を行える ようになりました。この逆参照は以下の例のようにリストを返します:
>>> p.addresses [] >>> Address(street='123 W Main St', city='Smallsville', ... state='MN', zip='55407', person=p) <Address 1 ...> >>> p.addresses [<Address 1 ...>]
Many-to-Many Relationships
この節の例では、ユーザ (user) と役割 (role) オブジェクトを使います。これら 二つのオブジェクトは、互いに多対多に対応しています。多対多の対応は RelatedJoin で表現します。
>>> class User(SQLObject): ... ... class sqlmeta: ... # user is a reserved word in some databases, so we won't ... # use that for the table name: ... table = "user_table" ... ... username = StringCol(alternateID=True, length=20) ... # We'd probably define more attributes, but we'll leave ... # that exercise to the reader... ... ... roles = RelatedJoin('Role')>>> class Role(SQLObject): ... ... name = StringCol(alternateID=True, length=20) ... ... users = RelatedJoin('User')>>> User.createTable() >>> Role.createTable()
sqlmeta クラスを使っていることに注意してください。このクラスは様々なメ タデータを保存しておく (そして table のような既存のメタデータをオーバラ イドする) ためのクラスです。この機能は SQLObject 0.7 で新たに追加されたもの です。 sqlmeta クラスのからくりや特殊な意味を持つ属性については、 sqlmeta クラス を参照してください。
使い方は以下の通りです:
>>> bob = User(username='bob') >>> tim = User(username='tim') >>> jay = User(username='jay') >>> admin = Role(name='admin') >>> editor = Role(name='editor') >>> bob.addRole(admin) >>> bob.addRole(editor) >>> tim.addRole(editor) >>> bob.roles [<Role 1 name='admin'>, <Role 2 name='editor'>] >>> tim.roles [<Role 2 name='editor'>] >>> jay.roles [] >>> admin.users [<User 1 username='bob'>] >>> editor.users [<User 1 username='bob'>, <User 2 username='tim'>]
処理の過程で、両方のオブジェクトに対して参照を持つような、 role_user と いう中間テーブルが生成されます。このテーブルはクラスの形で表に出ることはな く、テーブルの行レコードに対応した Python オブジェクトもありません。これに よって、多対多の関係にまつわる厄介事を隠蔽しておけます。
列に alternateID というキーワード引数があることにお気づきでしょうか。 alternateID=True にすると、その列はある行を一意に指定できるような値を持 つことを示します -- あるユーザ名があるユーザを一意に指定するようなものです。 とはいえ、この識別子は主キー (id) に対する追加情報にすぎず、主キーを置 き換えるわけではありません。
Note
SQLObject では、主キーが一意で 変更不能 (immutable) でなければならない という強い制約があります。主キーの値は SQLObject からは変更できません。 他の手段を使って主キーの値を変更してしまうと、実行中の SQLObject を使っ たプログラム (や、大事なデータ) の一貫性が失われてしまいます。このため、 主キーには無意味な整数の ID を使うよう勧めています。ユーザ名のような、将 来変更されてしまう可能性のあるものでも一つの行を一意に指定できますが、そ の一意性や対応関係は将来敗れてしまうかもしれないのです。逆に、行の参照に 使わない限り、将来どんな列データの変更があっても 問題は起きません 。
alternateID を指定すると、例えば username という列名に対して byUsername のような名前のクラスメソッドが作られます (キーワード引数 alternateMethodName を指定すればメソッド名を上書きできます)。 byUsername は以下のようにして使います:
>>> User.byUsername('bob')
<User 1 username='bob'>
>>> Role.byName('admin')
<Role 1 name='admin'>
Selecting Multiple Objects
SQLObject では、関係データベースで使える全ての種類の結合 (join) のパワーを 完全に引き出せるようにする代わりに、簡単な SELECT を使えるようにしてい ます。
select はクラスメソッドで、以下のようにして呼び出します (生成された SQL を一緒に示します):
>>> Person._connection.debug = True >>> peeps = Person.select(Person.q.firstName=="John") >>> list(peeps) 1/Select : SELECT person.id, person.first_name, person.middle_initial, per\ son.last_name FROM person WHERE (person.first_name = 'John') 1/COMMIT : auto [<Person 1 firstName='John' middleInitial='Q' lastName='Doe'>]
この例では、ファーストネームが John の人を全て返します。もっと複雑な条件式 も使えます:
>>> peeps = Person.select(
...         AND(Address.q.personID == Person.q.id,
...             Address.q.zip.startswith('504')))
>>> list(peeps)
 1/Select  :  SELECT person.id, person.first_name, person.middle_initial, per\
son.last_name FROM person, address WHERE ((address.person_id = person.id) AND\
(address.zip LIKE '504%'))
 1/COMMIT  :  auto
[]
クラスに q という属性があるのにお気づきでしょうか。この属性は、問い合わ せ文の各節を構築するための特殊なオブジェクトです。 q を介して属性を参照 すると列名を返し、この値で論理式を作ると、それに対応した SQL 文を生成するよ うになっているのです。同じような操作はもっと用手的に SQL 文を作成することで も実現できます:
>>> peeps = Person.select("""address.id = person.id AND
...                          address.zip LIKE '504%'""",
...                       clauseTables=['address'])
select を行うテーブル以外のテーブルを使う場合には、 clauseTables を指定 せねばならないことに注意してください。属性 q を使うと、どのクラスを使う かを SQLObject が自動的に判断します。
SQL 文を手動で生成させる時には、 MyClass.sqlrepr を使って値をクオート せねばなりません (q を使えば自動的にクオートされます) 。
キーワード引数 orderBy を使えば、 select 文に ORDER BY が入ります: orderBy は文字列を引数にとりますが、これは列の データベース上の 名前か、 Person.q.firstName 形式の名前でなければなりません。逆順にした ければ、 "-colname" の形式にするか、 MyClass.select().reversed() を 呼び出してください。
特殊なクラス変数 _defaultOrder を使うと、 select 操作全てに対するデフォル トの並べ規則を指定できます。 _defaultOrder が指定されている状態で並べ変え なしの結果を取り出したい場合には、 orderBy=None を使います。
select の結果はジェネレータであり、値は遅延して評価 (lazily evaluate) されます。すなわち、 select 結果に対して反復処理を実行したり、 list() を使って強制しないかぎり、 SQL は実行されません。 select 結果に対する反復処 理を行う場合には、各行は一度に一行づつ取り出されます。これにより、巨大な select 結果に対する反復処理で、結果全体をメモリに保持する必要がなくなります。 .reversed() のような操作も、結果全体を取り出して反転したりせずに実行で きます -- SQLObject はそうする代わりに、同じ結果が得られるように SQL を変更 します。
また、 select 結果に対するスライスも行えます。スライス操作は SQL クエリの 変更の形をとります。例えば peeps[:10] とすると、 SQL クエリには LIMIT 10 が追加されることになります。 SQL で実現できないスライス (例え ば peeps[:-10]) の場合、一度 select を実行して、結果のリストに対してスライ スを行います。この処理が行われるのは負のインデクスを使ったときだけです。
join を行ったときなど、場合によっては一つのオブジェクトが select 結果に何度 も出現する場合があります。これを抑制したい場合、キーワード引数 MyClass.select(..., distinct=True) を追加します。この操作は、 SQL 文を SELECT DISTINCT 呼び出しにします。
MyClass.select().count() のように、結果のオブジェクトに対して count を呼び出すと、結果を全て取り出さなくても得られた結果の数を取得できます。 これには COUNT(*) クエリが使われ、実際のオブジェクトはデータベースから 取り出されません。こうした機能をスライスと合わせると、バッチ問い合わせを簡 単に書けるようになります:
start = 20 size = 10 query = Table.select() results = query[start:start+size] total = query.count() print "Showing page %i of %i" % (start/size + 1, total/size + 1)
Note
この手のバッチの効率を考えるには、様々なファクタが絡んで来ますし、バッチ の用途にも大きな影響を受けます。平均して 100 件の検索結果が得られ、それ を一度に 10 件づつ、データベース上で関連づけられた日付に応じて並べ変えて 表示する web アプリケーションを考えてみましょう。 スライスの利用によって、データベースに毎回全ての結果を返させなくても済む (通信に掛かる時間も節約できる) 一方で、データベースは結果全体を走査して、 (最初の 10 件が何か分かるまで) 結果をソートするせねばならず、 (インデク スの指定がまずければ) 問い合わせの内容によってはテーブル全体にわたって走 査せねばなりません。このような場合、パフォーマンスの向上にはインデクス (index) が最も重要な手段になるでしょう。また、スライスよりキャッシュを使っ た方がはるかに効率的かもしれません。キャッシュの利用は、結果を 完全に 取り出すことを意味します。これには list(MyClass.select(...)) を使い ます。このデータをユーザがページからページへと目を通すだけの間一定時 間保存しておくわけです。この場合には、最初のページの結果を得るのに少 し余分なコストを要しますが、それ以降のページは極めて低いコストで提供 できます。
問い合わせ時の where 節の利用については、 SQLBuilder ドキュメント も参照 してください。
.select の代わりに .selectBy もあります。 .selectBy は以下のよ うに動作します:
>>> peeps = Person.selectBy(firstName="John", lastName="Doe")
各キーワード引数は列で、各キーワード引数の制約は AND をとります。戻り値は SelectResult オブジェクトなので、スライスやカウント、並べ変えなどを行えま す。
This new class is available starting with SQLObject 0.7 and allows specifying metadata in a clearer way, without polluting the class namespace with more attributes.
There are some special attributes that can be used inside this class that will change the behaviour of the class that contains it. Those values are:
ブール値 (デフォルト値は True) です。値が真の場合、インスタンスが保持さ れている間 (あるいは inst.expire() を呼び出すまでの間) 行内の値を キャッシュします。
値を False に指定すると、データベース上の値をキャッシュしません。そ のため、オブジェクトの属性値にアクセスするたびに、データベースへの問い合 わせ、例えば SELECT が発行されます。複数プロセスからの同一オブジェク トへのアクセスを処理したいなら、この方法を使って下さい。 (必ずというわけ ではありませんが トランザクション も使うことになるはずです。
インスタンス属性も一つだけあります:
While in previous versions of SQLObject those attributes were defined directly at the class that will map your database data to Python and all of them were prefixed with an underscore, now it is suggested that you change your code to this new style. The old way will be removed when SQLObject 0.8 is out and you'll receive lots of warning messages saying that if you're not using the sqlmeta class you're doing things in a deprecated way.
To use sqlmeta you should write code like this example:
class MyClass(SQLObject):
    class sqlmeta:
        lazyUpdate = True
        cacheValues = False
    columnA = StringCol()
    columnB = IntCol()
    def _set_attr1(self, value):
        # do something with value
    def _get_attr1(self):
        # do something to retrieve value
The above definition is creating a table my_class (the name may be different if you changet the style used) with two columns called columnA and columnB. There's also a third field that can be accessed using MyClass.attr1. The sqlmeta class is changing the behaviour of MyClass so that it will perform lazy updates (you'll have to call the .sync() method to write the updates to the database) and it is also telling that MyClass won't have any cache, so that every time you ask for some information it will be retrieved from the database.
Besides sqlmeta and columns specifications, there are a number of other special attributes you can set in your class. Those are listed below but, except for the _connection attribute, all other are superseded by the implementation shown before with sqlmeta. If you use them with SQLObject 0.7 you'll get warnings about their deprecation and you'll probably have to convert your code in future releases, when these are dropped.
DBConnection クラスから生成された接続オブジェクトです。 クラスを定義しているモジュール内で __connection__ を指定しておく こともでき、その場合には __connection__ の値を使います (クラス定義の前に __connection__ が定義されているようにして下さい) トランザクション でも述べたように、接続オブジェクトはインスタンス生 成時に渡せます。
sqlhub.processConnection が指定されている場合、この属性がクラス内で 定義されていても無視され、 sqlhub の値を使うようになります。この機能は、 同じ接続オブジェクトを使えるような複数のクラスがある場合に、単にタイプ 数を減らす以上に役立つはずです。
これまでの例題にはありませんでしたが、クラス定義には自作のメソッドも入れら れます。自作のメソッドを書く方法は明確 (単に他のクラスと同じように定義する だけ) ですが、気を付けておかねばならない点がいくつかあります。
SQLObject のインスタンスが生成されるには、データベースから行を取り出す、デー タベースに挿入する、の二つの方法があります。どちらの場合にも、新たな Python オブジェクトが生成されます。このことが、 __init__ の位置付けをいささか難 解にしています。
基本的には、 __init__ をいじってはなりません。 その代わりに _init メソッドを使うようにしましょう。このメソッドはデータの取り出しや挿入でオブ ジェクトが生成された後に呼び出されます。メソッドのシグネチャは _init(self, id, connection=None, selectResults=None) ですが、 _init(self, *args, **kw) を使っても構いません。 注意: メソッドをオーバライドするのなら、 SQLObject._init(self, *args, **kw) を呼び出し忘れないようにしましょう!
新しい形式のクラスでは、 classmethod, staticmethod, そして property といった、メソッドを定義する際の通常のテクニックを全て使えますが、近道も 用意しています。 _set_, _get_, _del_, あるいは _doc_ から はじまる名前のメソッドがあると、メソッドはプロパティの生成に使われます。従っ て、たとえば /var/people/images ディレクトリの下に Person の id で画像 ファイルが保存されているとしましょう:
class Person(SQLObject):
    # ...
    def imageFilename(self):
        return 'images/person-%s.jpg' % self.id
    def _get_image(self):
        if not os.path.exists(self.imageFilename()):
            return None
        f = open(self.imageFilename())
        v = f.read()
        f.close()
        return v
    def _set_image(self, value):
        # assume we get a string for the image
        f = open(self.imageFilename(), 'w')
        f.write(value)
        f.close()
    def _del_image(self, value):
        # I usually wouldn't include a method like this, but for
        # instructional purposes...
        os.unlink(self.imageFilename())
こうすれば、 .image プロパティは一つの属性のように扱われ、属性に対する 変更は対応するメソッドの呼び出しによってファイルシステム上に反映されます。 このテクニックは、データベースではなくファイルに保存しておいた方がよい情報 (例えば画像のように、巨大で不透明なデータ) の場合に有効です。
image キーワード引数はまた、 Person(..., image=imageText) のように、 コンストラクタや set メソッドに渡したりできます。
こうしたメソッド (_get_, _set_ など) はいずれもオプションです。どれ か一つを定義して、その他は使わないようにできます。従って、 _get_attr だ けを定義して attr を読み出し専用の属性にしたりできます。
データベースの列属性に関する振舞いのオーバライドは、やや厄介です。 例えば、誰かの名前が変更されたときに常に実行されるような特殊なコードを考え てみましょう。多くのシステムでは、何らかの自作のコードを書いておき、そのスー パークラスのコードを呼び出すことになるでしょう。しかしながら、スーパークラ ス (SQLObject) は自作のサブクラスにどんなカラムが定義されているか全く 関知していないのです。プロパティの場合にはなおさらです。
SQLObject は各列に対して _set_lastName のようなメソッドを生成しますが、 参照すべきスーパークラスは存在しないので、このメソッドは利用できません。(そ して、SQLObject クラスはあなたの作ったクラスにどんな列があるのかを知らない ので、 SQLObject._set_lastName(...) のようにも書けません) 結局、 _set_lastName を自分でオーバライドすることになるでしょう。
この問題を解決するために、 SQLObject では各 getter および setter に対して、 例えば _set_lastName と _SO_set_lastName のように二つのメソッドを 生成します。 lastName への全ての変更に割り込むには、以下のようにします:
class Person(SQLObject):
    lastName = StringCol()
    firstName = StringCol()
    def _set_lastName(self, value):
        self.notifyLastNameChange(value)
        self._SO_set_lastName(value)
あるいは、例えば電話番号が正しい番号表記で適切な長さであるように制限し、 綺麗にフォーマットされるようにしたければ、以下のようになるでしょう:
import re
class PhoneNumber(SQLObject):
    phoneNumber = StringCol(length=30)
    _garbageCharactersRE = re.compile(r'[\-\.\(\) ]')
    _phoneNumberRE = re.compile(r'^[0-9]+$')
    def _set_phoneNumber(self, value):
        value = self._garbageCharactersRE.sub('', value)
        if not len(value) >= 10:
            raise ValueError(
                'Phone numbers must be at least 10 digits long')
        if not self._phoneNumberRE.match(value):
            raise ValueError, 'Phone numbers can contain only digits'
        self._SO_set_phoneNumber(value)
    def _get_phoneNumber(self):
        value = self._SO_get_phoneNumber()
        number = '(%s) %s-%s' % (value[0:3], value[3:6], value[6:10])
        if len(value) > 10:
            number += ' ext.%s' % value[10:]
        return number
Note
属性値に設定するデータを加工する場合には気を付けてください。ユーザは普通、 あなたの作成したクラスを使うときに、自分がある属性に設定した値を取り出す と、同じ値が返って来るはずと期待しています。上の例では、いくつか文字を削っ てからデータベースにデータを入れておき、取り出すときに再度フォーマットし ています。こうした手法の (属性アクセスに対する) 利点は、プログラマにとっ てこうした分離の方が望ましいから、ということにすぎません。
Also note while these conversions will take place when getting and setting the column, in queries the conversions will not take place. So if you convert the value from a "Pythonic" representation to a "SQLish" representation, your queries (when using .select() and .selectBy()) will have to be in terms of the SQL/Database representation (as those commands generate SQL that is run on the database).
これまでの解説はSQL Objectを使い始めるのに十分でしょうし,多くの状況で役に 立つでしょう。次にSQL Objectのクラスをより細かく定義するための方法について 触れます。
一連のカラムは Col オブジェクトのリストとして記述します。 このオブジェク トは機能は持たず、カラムを定義するために利用します。
この boolean (デフォルトは False) で,カラムをプライマリ・キーでない ID (たとえばユーザ名のような) として利用できるかどうかを指定します。この値 が True の場合,``byUsername`` のようなクラス関数が追加されます。この関 数はカラムのオブジェクトを返します。もし by* 意外の関数名を利用した い場合は、 alternateMethodName を利用して下さい( alternateMethodName="username" のように指定します )。
この値が True の場合,カラムはスキーマで UNIQUE属性 を伴って定義する 必要があります。
カラムが他のクラスやテーブルの外部キーになっている場合に Col クラスの変わ りに ForeignKey クラスを使います。このクラスは ForeignKey('Role') の ように定義します。この場合カラムを Role テーブルの外部キーとして定義して います。 Col(foreignKey='Role',sqlType='INT') と定義するのとほぼ同じで す。このクラスでは2つのアトリビュートを作ります。``role`` は Role インス タンスを返します。 roleID は関連する role に対する整数のIDを返します。
Col にはその他にもサブクラスがあります。様々な型を定義するためのクラスで, SQLObject がテーブルを作成する際に利用します。
DecimalCol:
10 進法の数値です。 size で桁数を指定し, precision で小数点以下の 桁数を指定します。
文字列型のカラムです。オプションで以下のキーワードを指定します:
StringCol のサブクラスです。dbEncoding 引数を指定できます。デフォルト は "UTF-8" です。データベース経由で入出力する値のエンコード/デコー ドを行います。
注意 : クエリにあるパラメータは自動的にエンコードされません。このため UnicodeCol に対してクエリ上で比較を行う場合,エンコード変換を行う必要が あります。
ForeignKey を使うと,テーブル間のリレーションを定義できます。逆参照や多対 多のリレーションには Join を使って下さい。
例については 一対多のリレーション をご覧下さい。
MultipleJoin の生成時にはいくつかのキーワード引数が利用できます。
MultipleJoin と同様ですが、リストではなく単一のオブジェクトを返します。
SQLObject のトランザクションサポートはデータベース依存となります。トランザ クションは以下のように使います:
conn = DBConnection.PostgresConnection('yada')
trans = conn.transaction()
p = Person.get(1, trans)
p.firstName = 'Bob'
trans.commit()
p.firstName = 'Billy'
trans.rollback()
ここにある trans オブジェクトは個別のデータベースコネクションオブジェク トのラッバで, commit と rollback は単に低水準のコネクションオブジェク トにメッセージを渡しているだけです。
.rollback() の後で新たなトランザクションを開始したければ、 .begin() を呼び 出してください。
トランザクションを利用する場合,`_cacheValues` を以下のように無効にする必要 があります:
class Person(SQLObject):
    _cacheValue = False
    # ...
すべてのデータベース接続で,クラス定義からテーブルを作成したり削除する機能 をサポートしています。最初に,カラムに対する型の定義を含んだクラスを定義し てください。
テーブルにインデックスを定義することもできます。 SQLObject でテーブルを作成 した場合に利用すると便利でしょう(インデックスの実装はデータベースに依存しま す)。以下のようにして,インデックスを再定義できます:
firstLastIndex = DatabaseIndex('firstName', 'lastName')
この行は2つのカラムにインデックスを生成します。特定の列のみインデックスを生 成したい場合に便利でしょう。もちろん,カラムをひとつだけ与えることも可能で すし,カラムのオブジェクト (firstName) を文字列のカラム名の代わりに利用 できます。 unique や alternateID (unique と同じです) を使うとデー タベースは自動的にインデックスを作成することに注意して下さい。また,主キー も常にインデックスを持ちます。
DatabaseIndex に unique というキーワードを与えた場合, unique 属性を 持つインデックスが作成されます -- カラムの値を必ず一意でなければなりません。
追加のオプションを指定するために,カラム名の代わりに辞書を使えます。たとえ ば:
lastNameIndex = DatabaseIndex({'expression': 'lower(last_name)'})
この例では,インデックスはカラム名を小文字化したものになります。 PostgreSQL のみがこの機能をサポートしています。次のようにすることも出来ます:
lastNameIndex = DatabaseIndex({'column': lastName, 'length': 10})
この例では,データベースに対し最初の10文字のみをインデックスに含めるように 指示しています。これは MySQL のみの機能で,他のデータベースでは無視されます。
テーブルを作成するためには createTable を呼びます。この関数は2つの引数を 伴います:
dropTable は ifExists と dropJoinTables という引数を伴います。引数の 意味は名前の通りです。
SQLObject ではクラスを動的に定義できます。 この機能を使うと,データベースの 定義出力や GUI インターフェースから書き出された XML ファイルを使って SQL Object のクラスを生成出来るようになるかも知れません。
SQLObject はデータベースからテーブル定義を読み込み,クラスのカラムを自動生 成できます。(_columns というアトリビュートに定義を記述してゆきます)
以下のようにします:
class Person(SQLObject):
    _fromDatabase = True
ここからさらにカラムを指定 ( _columns を使って) でき,存在しないカラムは, 追加されます。
実行時間中に,クラスを追加したり削除したりできます。クラス定義が変更される ため,変更は全部クラスインスタンスに影響を与えます。 addColumn と delColumn という 2 つの関数があり,どちらも Col オブジェクト (またはサ ブクラス) を引数として取ります。オプションの引数として, changeSchema が あります。 True にすると,カラムを追加したり削除したりします (たいていは ALTER コマンドを利用します)。
カラムを追加する場合, StringCol("username", length=20) のようにしてカ ラム名をカラムのコンストラクタに渡す必要があります。カラムを削除する場合も, Col オブジェクト ( _columns`に定義されているか, `addColumn で追加したも の) を利用できます。また,カラム名も使えます ( MyClass.delColumn("username") のようにします)。
MyClass.addJoin(MultipleJoin("MyOtherClass")) のようにして Joins を追 加することも出来ます。 delJoin を使うとJOINを削除できます。 delJoin に は文字列を渡さず, _joins アトリビュートにあるオブジェクトを指定してます。
たいていの場合,すでにデータベースが存在していて, SQLObject が要求する名前 の変換を利用したくないか,名前をまったく変えたくないことがあるでしょう。
SQLObject はスキーマに対して多くの制限を設けないように作られていますが,い くつか前提条件があります。前提条件のうちいくつかは将来的に取り除かれるかも 知れません。
クラスに変換するテーブルはすべて整数の主キーを持っている必要があります。主 キーは以下のように定義する必要があります:
SQLObject は複数カラムからなる主キーをサポートしません (この仕様は恐らく今 後も変わらないでしょう)。また、基本的には、主キーが何らかの実用的な意味 (business meaning) を持つようなテーブルもサポートしません -- すなわち、主キー は不変のものと想定しています (この仕様は今後決して変更されません)。
今の時点では,外部キーのカラム名は "ID" で終わっている必要があります (必ず大文字で記述します)。この制限は次のリリースで取り除く予定です。
デフォルトでは,SQLObject の名前付けは Python 風に大文字小文字が混在した表 記 (たとえば mixedCase ) か,アンダースコアで区切られた SQL 風の表記 (たとえば mixed_case )となります。このルールはテーブル名とカラム名に適 用されます。主キーは単純に id として扱います。
別のスタイルも存在します。典型的な例は大文字小文字混在のカラム名,それに主 キーにテーブル名が含まれているスタイルでしょう。別の "Style" オブジェクトを 使って,命名規則を変更できます。例えば以下のようにします:
class Person(SQLObject):
    _style = MixedCaseStyle(longID=True)
    firstName = StringCol()
    lastName = StringCol()
Person.createTable() を使う場合,以下のようにします:
CREATE TABLE Person (
    PersonID INT PRIMARY KEY,
    FirstName Text,
    LastName Text
)
MixedCaseStyle オブジェクトが単語の最初の文字を大文字にし,それ以外はその ままにします。 longID=True とすることで,主キーをよく見るスタイルに変更 します。(MixedCaseStyle を使うと PersonID になりますが,デフォルトで は person_id のようになります)
スタイルを全体に渡って変更したい場合には,コネクションオブジェクト (connection) に適用します。以下のようになります:
__connection__.style = MixedCaseStyle(longID=True)
命名規則は便利ですが,常に利用できるとは限りません。Pythonの利用する名前意 外であれば,SQLObject が利用する名前のほとんどをコントロールできます。
以下が簡単な例です:
class User(SQLObject):
    _table = "user_table"
    _idName = "userid"
    username = StringCol(length=20, dbName='name')
_table というアトリビュートはテーブル名をオーバーライドします。 _idName を定義すると id の代わりに利用する文字列を指定できます。 dbName と いうキーワード引数はカラム名を与えます。
キーとして整数意外を利用している場合,主キーの扱いをすべて別途行う必要があ ります。テーブルを自分自身で作成する必要がありますし,コンストラクタに常に id というキーワード引数を与える必要があります ( Person(id='555-55-5555', ...) のようにします)。
DBConnection モジュールは現在外部に4つのクラスを持っています。 MySQLConnection , PostgresConnection , SQLiteConnection , SybaseConnection ,そして MaxdbConnection です。
上記4つのクラスには debug というキーワード引数を渡せます。引数が True の 場合,データベースに送られる SQL をコンソールに表示します。
MySQLConnection は host , port , db , user , passwd という キーワード引数を取ります。引数の意味は MySQLdb.connect と同じです。
MySQLConnection は SQLObject のすべての機能をサポートします。InnoDB を利用 している場合にのみ トランザクション をサポートします。 SQLObject は現状 createTable を使った明示的なテーブル定義をサポートしていません。
PostgresConnection には "dbname=something user=some_user" のような接 続文字列をひとつだけ渡します。 psycopg.connect のとる引数と同様です。 MySQLConnection が取るキーワード引数を利用することもでき, dns を生成しま す。
PostgresConnection はトランザクションを含めすべての機能をサポートします。
SQLiteConnection は単一のキーワード引数をとり,データベースファイルのパス を渡します。
SQLite は全データをひとつのファイルに書き込み,また処理中は記録用のファイル をデータファイルと同一のフォルダに作成します(プログラムが終了するとファイル は消されます)。 SQLite ではカラムに記入するタイプの制限はありません -- 整数 型のカラムに文字列を記入することもできますし,日付を記入することなどもでき ます。
SQLiteConnection は クラスの自動生成 をサポートしません。また, 実行時間中にカラムを変更する 機能もサポートしません。
マルチスレッドで利用する場合,SQLite では同時実行の問題が発生するかも知れま せん。
FirebirdConnection は host , db , user (デフォルトは "sysdba" ), passwd (デフォルトは "masterkey") の引数をサポートします。 Firebird は SQLObject の全機能をサポートします。全機能をサポートするように なって日が浅く,不具合が潜んでいるかも知れません。特にマルチスレッド稼働時 や lazy select を利用する際には注意してください。マルチスレッドでカーソルを 利用中,問題が発生したら list(MyClass.select()) を試してみて下さい ( list() を利用すると選択結果を事前にすべて取得します)。
Firebird は Pythonライブラリ kinterbasedb の利用をサポートしています。
インデックスを利用していて, key size exceeds implementation restriction for index (キーのサイズがインデックスに実装されている値より大きい) というエラーが発 生したら, このページ を見てインデックスについての制限を理解して下さい。
SybaseConnection は host , db , user ,と passwd という引数を取 ります。 locking というBooleanの引数を加えることができ,(デフォルトは True) 接続を確立する際に利用します。マルチスレッドで利用しない場合,False を指定すると少しだけ実行が早くなります。
SybaseConnection は Sybase モジュールを使います。
from sqlobject import * を使うことも可能ですが、そうする必要はありません。 sqlobjeect では、以下に示すようにほんのわずかな数のシンボルしか公開していま せん:
sqlobject.main のシンボル:
sqlobject.col のシンボル: * Col * StringCol * IntCol * FloatCol * KeyCol * ForeignKey * EnumCol * DateTimeCol * DecimalCol * CurrencyCol
sqlobject.joins のシンボル: * MultipleJoin * RelatedJoin
sqlobject.styles のシンボル: * Style * MixedCaseUnderscoreStyle * DefaultStyle * MixedCaseStyle
sqlobject.sqlbuilder のシンボル:
First look in the FAQ, question "How can I do a LEFT JOIN?"
Still here? Well. To perform a JOIN use one of the JOIN helpers from SQLBuilder. Pass an instance of the helper to .select() method. For example:
from sqlobject.sqlbuilder import LEFTJOINOn
MyTable.select(
    join=LEFTJOINOn(Table1, Table2,
                    Table1.q.name == Table2.q.value))
will generate the query:
SELECT * FROM my_table, table1 LEFT JOIN table2 ON table1.name = table2.value;
If you want to join with the primary table - leave the first table None:
MyTable.select(
    join=LEFTJOINOn(None, Table1,
                    MyTable.q.name == Table1.q.value))
will generate the query:
SELECT * FROM my_table LEFT JOIN table2 ON my_table.name = table1.value;
The join argument for .select() can be a JOIN() or a sequence (list/tuple) of JOIN()s.
Available joins are JOIN, INNERJOIN, CROSSJOIN, STRAIGHTJOIN, LEFTJOIN, LEFTOUTERJOIN, NATURALJOIN, NATURALLEFTJOIN, NATURALLEFTOUTERJOIN, RIGHTJOIN, RIGHTOUTERJOIN, NATURALRIGHTJOIN, NATURALRIGHTOUTERJOIN, FULLJOIN, FULLOUTERJOIN, NATURALFULLJOIN, NATURALFULLOUTERJOIN, INNERJOINOn, LEFTJOINOn, LEFTOUTERJOINOn, RIGHTJOINOn, RIGHTOUTERJOINOn, FULLJOINOn, FULLOUTERJOINOn, INNERJOINUsing, LEFTJOINUsing, LEFTOUTERJOINUsing, RIGHTJOINUsing, RIGHTOUTERJOINUsing, FULLJOINUsing, FULLOUTERJOINUsing.
Use Alias from SQLBuilder. Example:
from sqlobject.sqlbuilder import Alias alias = Alias(MyTable, "my_table_alias") MyTable.select(MyTable.q.name == alias.q.value)
will generate the query:
SELECT * FROM my_table, my_table AS my_table_alias WHERE my_table.name = my_table_alias.value;
Sure! That's a situation the JOINs and aliases were primary developed for. Code:
from sqlobject.sqlbuilder import LEFTJOINOn, Alias
alias = Alias(OtherTable, "other_table_alias")
MyTable.select(MyTable.q.name == OtherTable.q.value,
    join=LEFTJOINOn(MyTable, alias, MyTable.col1 == alias.q.col2))
will result in the query:
SELECT * FROM other_table,
    my_table LEFT JOIN other_table AS other_table_alias
WHERE my_table.name == other_table.value AND
    my_table.col1 = other_table_alias.col2.
You can run queries with subqueries (subselects) on those DBMS that can do subqueries (MySQL supports subqueries from version 4.1).
Use corresponding classes and functions from SQLBuilder:
from sqlobject.sqlbuilder import EXISTS, Select select = Test1.select(EXISTS(Select(Test2.q.col2, where=(Outer(Test1).q.col1 \ == Test2.q.col2))))
generates the query:
SELECT test1.id, test1.col1 FROM test1 WHERE EXISTS (SELECT test2.col2 FROM test2 WHERE (test1.col1 = test2.col2))
Note the usage of Outer - it is a helper to allow referring to a table in the outer query.
Select() is used instead of .select() because you need to control what columns the inner query returns.
Available queries are IN(), NOTIN(), EXISTS(), NOTEXISTS(), SOME(), ANY() and ALL(). The last 3 are used with comparison operators, like this: "somevalue = ANY(Select(...))".
SQLBuilder の詳細については SQLBuilder ドキュメント を参照してください。