SQLAlchemy で Firebird その0
- その0 準備(いまここ)
- その1 テーブル定義関連
- その2 接続関連
- その3 クエリー関連(ORM 版)
まる 1 日かかって Python, SQLAlchemy 経由で Firebird データベースが扱えるようになったけれど、なにを行ってきたのか忘れそうなので覚書を残す。記録をとっておかないと名前に惹かれてつかってみようと思い至ったもののあっさり挫折した 3 年前の自分にすぐ戻りそうだ。
今回は環境の準備について。タイトルに反して SQLAlchemy はまだでてこない。
環境
- Ubuntu 10.04 (Windows XP Pro の VirtualBox 3.1.4 上)
- Firebird 2.1 SuperServer
- Python 2.6.5 (Ubuntu 10.04 デフォルト)
- kinterbasdb 3.3.0
- SQLAlchemy 0.6.3
とりあえず Firebird 用意しないと始まらない
firebird2.1-super (version 2.1.3.18185-0.ds1-6build1(lucid)) パッケージがあるので入れるだけ。 configure; make; make install; よりも簡単ってどうなのよ?
ちなみに Python からアクセスするための kinterbasdb も python-kinterbasdb (version 3.3.0-2) という名でパッケージが用意されている。ついでに SQLAlchemy もあるけれど python-sqlalchemy(version 0.5.8-1) とバージョン 0.5.8 だったのでこちらは easy_install sqlalchemy で 0.6.3 にした。
パッケージから入れ終わったら、 SYSDBA のパスワードの設定。
$ sudo dpkg-reconfigure firebird2.1-super
適当に変更しておく(最大8文字)。以下、記事内でのパスワードは masterkey としておく。
その後は firebird のユーザーを作成する。
$ gsec -user SYSDBA -password masterkey
gsec のインタラクティブモードに入る。ここでは display で既存ユーザーの確認、 add 'spam' -pw 'spamspam' で パスワード spamspam のユーザー spam の作成、 delete 'spam' で ユーザー spam の削除となる。
最後にデータベースファイルの置き場所の用意。どこでもいいけれど Firebird 専用とするため既存のディレクトリは使わず mkdir する。できたらアクセス権限の設定。 sudo chgrp firebird .; sudo chmod 770 . して、 firebird グループがアクセスできるようにすれば OK 。
起動と停止
まずは Firebird の起動、停止、再起動を覚えねば…。
$ sudo /etc/init.d/firebird2.1-super start $ sudo /etc/init.d/firebird2.1-super stop $ sudo /etc/init.d/firebird2.1-super restart
isql-fb で初めての Firebird
次にデータベースの作成。 Firebird だと 1 データベースは 1 ファイルとなる。
データベースを作るときはファイルパスを指定することになる(エイリアスをつけておけばこれでも可能、後述)。
カレントディレクトリに spam.fdb というデータベースファイルを作るには次のようにする。
$ isql-fb -u spam -p spamspam Use CONNECT or CREATE DATABASE to specify a database SQL> CREATE DATABASE 'spam.fdb' DEFAULT CHARACTER SET UTF8; SQL> COMMIT;
特別な意図がなければデータベースのデフォルト文字コードの指定をしておくと吉。今回はふつうに UTF8 で。
データベースができたので、早速接続してみる。
SQL> CONNECT 'spam.fdb'; Database: 'spam.fdb', User: spam
なにかテーブル作ってみる
SQL> CREATE TABLE abc( CON> a INTEGER PRIMARY KEY, CON> b INTEGER, CON> c INTEGER NOT NULL CON> );
なにか INSERT してみる。
SQL> INSERT INTO abc(a, b, c) VALUES (1, 10, 100); SQL> INSERT INTO abc VALUES (2, 20, 200); SQL> INSERT INTO abc(a, c) VALUES (3, 300); SQL> COMMIT;
カラムの指定をしなかった場合は全部の列を定義順に指定したものとして扱われる。カラムの値を指定していないものがある場合 DEFAULT の値が入る。 DEFAULT がなければ null が入る。NOT NULL 制約が入っているカラムの値を省略した場合、 null を入れようとするができないのでエラーとなる、とまぁイメージどおりの挙動。
とりあえず SELECT してみる。
SQL> SELECT * FROM abc; A B C ============ ============ ============ 1 10 100 2 20 200 3 <null> 300
うん、データベース作ってテーブル作って値入れることができることを確認。
Python, kinterbasdb で初めての Firebird
kinterbasdb は DB-API 2.0 準拠。 connect するさいは dsn にファイルパス(or エイリアス)、 user にユーザー名、 password にパスワードを指定するようにする。
$ python Python 2.6.5 (r265:79063, Apr 16 2010, 13:09:56) [GCC 4.4.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import kinterbasdb >>> conn = kinterbasdb.connect(dsn='spam.fdb', user='spam', password='spamspam') >>> cur = conn.cursor() >>> cur.execute('SELECT * FROM abc') >>> for row in cur: ... print row ... (1, 10, 100) (2, 20, 200) (3, None, 300)
問題なく。
でもデータベースにアクセスするのにファイルパスでってどうなのよ?
ファイルの場所が明らかになってしまっていたり、(Firebird がアクセスできる場所に)データベース作り放題だったり。そんな疑問をもったらエイリアスとアクセス制限を行う。 aliases.conf と firebird.conf の DetabaseAccess で設定できる。これらの設定ファイルは /etc/firebird/2.1 ディレクトリにある。
エイリアスを設定すればファイルパスを直接扱わなくてもよくなる。
DatabaseAccess に None を指定すればファイルパスでのアクセスを禁止できる。 Restrict を指定すればファイルパス指定でアクセスできるディレクトリを限定できる。
インストール直後の aliases.conf, firebird.conf には設定例が満載されているので一目見ればなんとなくわかるようにできている。
aliases.conf
# ------------------------------ # List of known database aliases # ------------------------------ # # Example Database: # employee.fdb = /var/lib/firebird/2.1/data/employee.fdb employee = /var/lib/firebird/2.1/data/employee.fdb # # Live Databases: #
firebird.conf(一部抜粋)
# ---------------------------- # Database Paths/Directories # # DatabaseAccess may be None, Full or Restrict. If you choose Restrict, # provide ';'-separated trees list, where database files are stored. # Relative paths are treated relative to RootDirectory entry # (see above). Default value 'Full' gives full access to all files # on your site. To specify access to specific trees, enum all required # paths (for Win32 this may be something like 'C:\DataBase;D:\Mirror', # for unix - '/db;/mnt/mirrordb'). If you choose 'None', then only # databases listed in aliases.conf can be attached. # # Note: simple quotation marks shown above should *NOT* be used when # specifying values and directory path names. Examples: # # DatabaseAccess = None # DatabaseAccess = Restrict C:\DataBase # DatabaseAccess = Restrict C:\DataBase;D:\Mirror # DatabaseAccess = Restrict /db # DatabaseAccess = Restrict /db;/mnt/mirrordb # DatabaseAccess = Full # # UNCONTROLLED DATABASE ACCESS MAY COMPROMISE YOUR SYSTEM! # IT IS STRONGLY RECOMMENDED THAT THIS SETTING BE USED TO LIMIT # DATABASE LOCATIONS! # # Type: string (special format) # #DatabaseAccess = Full # ----------------------------
これで準備 OK 。次は SQLAlchemy を、といったところで時間切れ。続く…。
おまけ Windows からも Python, kinterbasdb でアクセス
Firebird 2.1.3 のインストーラーを公式サイトより持ってきてインストール。途中でインストール内容を聞かれるけれども、クライアントだけで OK 。そして Python, kinterbasdb を用意したら準備完了。
リモートのデータベースにアクセスするには dsn に ipアドレス or ホスト名:パス名 or エイリアス と指定するようにする。
Python 2.6.4 (r264:75708, Oct 26 2009, 08:23:19) [MSC v.1500 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import kinterbasdb >>> conn = kinterbasdb.connect(dsn='192.168.xxx.xxx:spam', user='spam', password= 'spamspam')
接続できたらあとは同じ。
おまけ VirtualBox のホストからゲストへのアクセス
VirtualBox のホストからゲストへアクセスできるようにするには設定、ネットワークアダプタからホストオンリーアダプタを追加する。
Python レシピ更新 ハッシュの要素を挿入した順に取り出す
『118:ハッシュの要素を挿入した順に取り出す』を 「PEP 372 で順序つき辞書提案中」という内容から、「標準モジュール collections に OrderedDict があるよ」という内容に更新!
collections.OrderedDict 、その実装
collections.OrderedDict の実装は「順序の保持に双方向循環リストを用いる」というものだった。すべてのメソッドの計算量が通常の dict と同じオーダーとなっている*1とのこと。
なので「順序の保持に Python 組み込みの list*2 を用いる」実装のものとは特性が異なる操作があったりする。たとえば OrderedDict だと値のセット(__setitem__)は O(1)。 http://dev.pocoo.org/hg/sandbox/raw-file/tip/odict.py だと既存のキーがすでに存在しないかどうか線形探索するので O(n) 。代わりにインデックスアクセスは OrderedDict には用意されていなくて、無理に行えば O(n) (コード例は以下)。 odict だと byindex メソッドで O(1) 。
OrderedDict に無理やりインデックスアクセスする例1
>>> from collections import OrderedDict >>> a = OrderedDict() >>> a['x'] = 'X' >>> a['y'] = 'Y' >>> keys = list(a) >>> keys[0] 'x' >>> a[_] 'X'
OrderedDict に無理やりインデックスアクセスする例2
メモリ消費量および先頭に近いほうの値を取り出す際の速度が「例1」のものより有利ではあるものの、負の値のインデックスをつかうことはできない。
>>> from collections import OrderedDict >>> from itertools import islice >>> def nth(iterable, n, default=None): ... return next(islice(iterable, n, None), default) ... >>> a = OrderedDict() >>> a['x'] = 'X' >>> a['y'] = 'Y' >>> nth(a, 0) 'x' >>> a[_] 'X' >>> >>> nth(a, -1) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in nth ValueError: Indices for islice() must be None or an integer: 0 <= x <= maxint.
*1:原文:「Big-O running times for all methods are the same as for regular dictionaries.」
*2:CPython の list は PyObject へのポインタを保持する配列。 PyListObject 構造体は PyObject **ob_item; Py_ssize_t allocated; というメンバを持っていて、そのコメントには Vector of pointers to list elements. list[0] is ob_item[0], etc. とか ob_item contains space for 'allocated' elements. The number currently in use is ob_size. とある。
Windows Python 2.7 でも MeCab 0.98
先週土曜 2010/8/7 に Windows で MeCab 0.98 の Python 2.7 バインディングをビルドしました。方法はやっぱり以前と変わりなしです。
- 前々回
- 形態素解析エンジン MeCab 0.97 とその Python バインディングを MinGW でビルドする - 銀月の符号
- 前回
- 形態素解析エンジン MeCab 0.98pre3 野良ビルド - 銀月の符号
つまり、使い方も変わりなしです。そして、『ナイーブベイズを用いたブログ記事の自動分類 - 人工知能に関する断創録』によるとMeCabの本サイトの mecab-0.98.exe と問題なく共存できるみたいです。これを入れてしまえば mecabrc の設定・指定が不要になり、また辞書を個別に落とす必要もないみたいです。id:aidiary さん、ありがとうございます。後日、自分でも確かめてみます。
ちなみに MinGW をつかっているけれど、理由は Python 2.5 公式版が VisualStudio.NET 2003 でコンパイルされていて入手できていなかったからだった。しかし Python 2.6, 2.7 の公式版は VisualStudio 2008 。 Express Edition なら手に入る、というかもう入っている。 MinGW でなければならない理由はなくなってしまってるんだよね。
次の休みには VS でビルドしてみるかな。こっちのほうがパッチ当てしなくても簡単にできてしまうかもしれない。
しかし最近の自分は仮想マシンで Ubuntu、 みたいなのにも慣れてきたので Windows だとビルドがめんどくさいライブラリを無理に Windows でビルドする意義も薄れつつある。今回も MeCab が欲しいというよりも、トラブルのありそうなところに突っ込んでみて勉強が目的だったり。そして何事もなくビルドできてしまい、得たものは少なめ。
う〜ん、せっかくビルドしたんだから MeCab をつかって何か作れないかな? 考えてみる
絵文字にマッチする正規表現 Unicode 版
携帯電話の絵文字に触れる機会があったので、絵文字にマッチする正規表現を。
絵文字は Unicode の外字領域にある。なので絵文字以外の外字が使われていないという前提があるならば、こうしてしまえばよい。
import re private_use = re.compile(ur'[\uE000-\uF8FF]')
これだとあまりにも乱暴なので、以下の Wikipedia 記事を参考に範囲を狭めると、こうなる。
import re _au = ur'\ue468-\ue5df\uea80-\ueb88' _docomo = ur'\ue63e-\ue6a5\ue6ac-\ue6ae\ue6b1-\ue6ba\ue6ce-\ue757' _softbank = ur'\ue001-\ue05a\ue101-\ue15a\ue201-\ue253\ue301-\ue34d\ue401-\ue44c\ue501-\ue537' _emobile = ur'%s\ue600-\ue619' % _docomo au = re.compile(u'[%s]' % _au) docomo = re.compile(u'[%s]' % _docomo) softbanck = re.compile(u'[%s]' % _softbank) emobile = re.compile(u'[%s]' % _emobile)
== 文字コード ==
使用される文字コードは以下の通りである。=== au ===
; Shift_JIS: 第1バイトがF3,F6,F7で始まる2バイト文字すべてと、F440-F47E, F480-F48D
; Unicode: E468-E5DF, EA80-EB88=== DoCoMo ===
; Shift_JIS: F89F-F8FC, F940-F949, F950-F957, F95B-F95E, F972-F97E, F980-F9FC
; Unicode: E63E-E6A5, E6AC-E6AE, E6B1-E6BA, E6CE-E757=== SoftBank ===
; Shift_JIS・EUC・JIS: ESC $ [G/E/F/O/P/Q] + [0x21-0x7E] + 0x0F
; Unicode: E001-E05A, E101-E15A, E201-E253, E301-E34D, E401-E44C, E501-E537=== emobile ===
※F860-F879(E600-E619)の領域以外は、基本的にDoCoMoと同じコード体系である(一部の商標を除く)。; Shift_JIS: F89F-F8FC, F940-F949, F950-F957, F95B-F95E, F972-F97E, F980-F9FC, F860-F879
http://ja.wikipedia.org/wiki/%E6%90%BA%E5%B8%AF%E9%9B%BB%E8%A9%B1%E3%81%AE%E7%B5%B5%E6%96%87%E5%AD%97
; Unicode: E63E-E6A5, E6AC-E6AE, E6B1-E6BA, E6CE-E757, E600-E619
MessagePack メモ
Python
help(msgpack) するとファイルライクオブジェクトに対して入出力する pack, unpack と、 Python 文字列を対象とする packb (= packs), unpackb (= unpacks) の説明あり。
import sys import msgpack msgpack.pack([1, True, 'example'], sys.stdout)
import sys import msgpack print msgpack.unpack(sys.stdin)
$ python write.py | python read.py [1, True, 'example']
しかしコードを斜め読みすると Packer, Unpacker というのもあった。こちらのほうが本命で pack, unpack はショートカット用かな。
import sys import msgpack packer = msgpack.Packer() sys.stdout.write(packer.pack([1, True, 'example'])) sys.stdout.write(packer.pack('spam')) sys.stdout.write(packer.pack(range(5))) sys.stdout.write(packer.pack({'a': 'b', 'c': 'd'}))
import sys import msgpack unpacker = msgpack.Unpacker() while True: buf = sys.stdin.read() if not buf: break unpacker.feed(buf) for obj in unpacker: print obj
$ python write2.py | python read2.py [1, True, 'example'] spam [0, 1, 2, 3, 4] {'a': 'b', 'c': 'd'}
Ruby
http://msgpack.sourceforge.net/ruby:snippets 。明日試してみる。 http://d.hatena.ne.jp/viver/20100324/p1 も読む。 Copy-on-Write によるゼロ・コピーデシリアライズ対応。
C
シリアライズ。 http://msgpack.sourceforge.net/c:snippets のまま。
#include <msgpack.h> #include <stdio.h> int main(void) { msgpack_sbuffer sbuf; msgpack_packer pk; msgpack_sbuffer_init(&sbuf); msgpack_packer_init(&pk, &sbuf, msgpack_sbuffer_write); msgpack_pack_array(&pk, 3); msgpack_pack_int(&pk, 1); msgpack_pack_true(&pk); msgpack_pack_raw(&pk, 7); msgpack_pack_raw_body(&pk, "example", 7); fwrite(sbuf.data, sbuf.size, 1, stdout); msgpack_sbuffer_destroy(&sbuf); return 0; }
デシリアライズ。 http://msgpack.sourceforge.net/c:snippets の改悪。 Advanced streaming deserialization も読む必要がありそうだがこれは明日に。
#include <msgpack.h> #include <stdio.h> int main(void) { msgpack_zone *mempool; msgpack_object obj; msgpack_unpack_return ret; size_t offset = 0; char buf[8192]; size_t size; size = fread(buf, 1, 8192, stdin); while (1) { mempool = msgpack_zone_new(MSGPACK_ZONE_CHUNK_SIZE); ret = msgpack_unpack(buf, size, &offset, mempool, &obj); switch(ret) { case MSGPACK_UNPACK_SUCCESS: printf("a object is unpacked and no buffer remain: "); msgpack_object_print(stdout, obj); printf("\n"); msgpack_zone_free(mempool); return 0; case MSGPACK_UNPACK_EXTRA_BYTES: printf("a object is unpacked: "); msgpack_object_print(stdout, obj); printf("\n"); msgpack_zone_free(mempool); break; case MSGPACK_UNPACK_CONTINUE: printf("buffer is to insufficient to unpack a object\n"); return 1; case MSGPACK_UNPACK_PARSE_ERROR: printf("parse error\n"); return 1; } } }
$ ./write | ./read a object is unpacked and no buffer remain: [1, true, "example"]
えっと、 msgpack_unpack の戻り値 MSGPACK_UNPACK_SUCCESS はぴったり読みきったことを示し MSGPACK_UNPACK_EXTRA_BYTES はまだデータが余っていることを示すのか。これは Python 版の Unpacker 同様、複数オブジェクトを詰め込んだファイルを綺麗に処理できる予感。 試してみる。
$ python write2.py | ./read a object is unpacked: [1, true, "example"] a object is unpacked: "spam" a object is unpacked: [0, 1, 2, 3, 4] a object is unpacked and no buffer remain: {"a"=>"b", "c"=>"d"}
うん、読めた。