もうちょっとだけがんばってみた
分かったのは
root.py の RootController にexposeで修飾したメソッドを追加すると、そのメソッドをurlで指定できる。
クラス変数で他のコントローラを設定しておくと、その名前とメソッドをurlで指定できる。
文で分かりやすく書けないのだが、
class RootController(TurboGearsController): hello = Tg2HelloController() @expose('tutorial.templates.index') def index(self): from datetime import datetime return dict(now=datetime.now())
こうすると、/hello/ でアクセスできる。
...CherryPyをエミュレートしている?
とりあえず以前のTGと同じようにコントローラーのツリーを構成できるようだ。
TurboGears2を予習してみる。
以下の内容は、Subversionのtrunkから取得したコードで試しています。
http://svn.turbogears.org/trunk/INSTALL.txt を見ると、PylonsとTurboGearsをそれぞれSuversionのtrunkからとってくるように書いてある。
$ svn co http://pylonshq.com/svn/Pylons/trunk pylons $ svn co http://svn.turbogears.org/trunk tg2 and install TurboGears 2 in development mode:: $ python ./pylons/setup.py develop $ python ./tg2/setup.py develop
でもこのままやるとPasteScriptでVersionConflictしますから。
Pylonsが0.9.6っていうバージョンで入るけど、tg2のcommandが、'Pylons==dev,<0.9.6' こんな指定の仕方。
直接Subversionから入れるとdevになるようなので、そっちの方法で。
$ easy_install http://pylonshq.com/svn/Pylons/trunk pylons
さて、ベースのMVCフレームワークがCherryPyからPylonsに移行することになるらしいTurboGears2.0。
quickstartはPasteScriptのコマンドになります。
$ paster quickstart tutorial
ez_setup がねーぞとエラーになります。orz
まあ、終盤でエラーになるし、プロジェクトディレクトリ内はそれなりなので、気にしないことにします。
とりあえずidentityオプションを有効にしときました。
が、modelsパッケージ内には特にそんなものは生成されていない模様。
paste のテンプレートを除いてもそれらしきテンプレートは見つからないので、まだ実装されていないのでしょう。
さて、フレームワークをまとめあげるメガフレームワークであるところのTurboGearsですが2.0になってどんなフレームワークの選択になったのでしょう。
paster tginfo で依存するモジュール一覧と現在インストールされているバージョンを確認できました。
requiresだけだと以下のようなもの。
* TurboGears2 2.0a1dev-r3239 * SQLAlchemy 0.3.7 * Genshi 0.4.2 * Pylons 0.9.6dev-r2126 * Mako 0.1.8 * nose 0.9.3 * decorator 2.0.1 * simplejson 1.7.1 * FormEncode 0.7.1 * PasteScript 1.3.5 * PasteDeploy 1.3.1 * Paste 1.4 * Beaker 0.7.4 * WebHelpers 0.3.1dev-r2064 * Routes 1.7 * Beaker 0.7.4 * PasteDeploy 1.3.1 * simplejson 1.7.1 * Routes 1.7
うむ。いかにもon Pylons。
SQLAlchemyとGenshiは前評判どおりの内容。
FormEncodeは続投。
Pylonsがベースになったので、CherryPyが脱落。
って、Makoはrequiresなの?
ユニットテストはnoseになっていくのかなぁ
と、いろいろ思いをはせることはできますが、不思議なのはElixirがrequiresじゃない?
template内にはElixirの文字が見えるのだが...
まだ本採用ではないのかな。
じゃあ、動かしてみるか。
$ paster serve development.ini
PylonsもTurboGearsもtrunk版なのが原因なのか、がんがんWarningが出る。
でも、とりあえず動いている。
デフォルトのトップページはTurboGearsを使っている人には、見慣れた画面。
さて、コントロールを追加してみるか。
controllers以下にそれぞれモジュールになっている。
pylonsと同じように、paster controllerでいいのだろうか。
root.pyを覗いてみると、RootControllerはTurboGearsControllerを継承している。
pasterのヘルプには、TurboGears2でコントローラー用のコマンドはないようなのだ。
とりあえず、Pylons的にコントローラーを作ってみると、BaseControllerを継承したものになった。
ところで、TurboGearsControllerって普通のとどう違うんだろう。
WSGIControllerを継承している。
出所はpylonsのcontrollers。
要するに、WSGI spec の__call__を実装したコントローラーのようだ。
BaseControllerもWSGIControllerを継承しているから正しい?
まあ、これはPylonsのまんまでやるってことかなー。
コントローラーはデフォルトのままにしておいて、ディスパッチャに設定してみる。
途中、Elixirインストールしてないせいでエラーになる。
templateに書くなら、requiresに入れてください。orz
この辺でやめとこう。
setuptoolsを使ってコマンドツールを作る
pasterでsetuptoolsを使った最小プロジェクトを作成する。
paster create hello
setup.pyを編集。
コマンドツールのスクリプトを追加する。
ここでは、helloモジュールのhello関数をpyhelloという名前のコマンドにする。
entory_pointsにconsole_scriptsセクションを作りその中に追加する。
from setuptools import setup, find_packages import sys, os version = '0.0' setup(name='hello', version=version, description="", long_description="""\ """, classifiers=[], # Get strings from http://www.python.org/pypi?%3Aaction=list_classifiers keywords='', author='', author_email='', url='', license='', packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), include_package_data=True, zip_safe=False, install_requires=[ # -*- Extra requirements: -*- ], entry_points=""" # -*- Entry points: -*- [console_scripts] pyhello = hello:hello """, )
setup.pyを変更したら、EGG_INFOを更新する。
python setup.py egg_info
# import sys def hello(): if len(sys.argv) > 1: print "Hello, %s!" % sys.argv[1] else: print "Hello, world!"
installしてもいいけど、developを使えば今編集しているソースツリーをsite-packagesから参照するようになる。
python setup.py develop
コマンドはプラットフォームごとに適当な方法で作成される。
Windowsなら、c:\Python25\Scripts 以下にpyhello.exeができているはず。
c:\Python25\Scripts にPATHを通してあるならそのまま pyhello と入力すれば実行できる。
いろんなもの使ってSBM その2 データアクセス
さて、ブックマークを保存する部分を考えよう。
深く考えずに、ひとまずDBに入れることにする。
データアクセスにはSQLAlchemyを使う。
オブジェクトをテーブルをまたいでマッピングできたり、継承構造をマッピングする方法を選択できたりと柔軟なO/Rマッパーだ。
DB情報保持のためのモジュールを先に作っておこう。
# database.py from sqlalchemy import * from sqlalchemy.ext.sessioncontext import SessionContext meta = DynamicMetaData() context = SessionContext(create_session)
metaはDBに関する情報を持っている。
contextはスレッドローカルなセッションを保持していて、実際のセッションにはcontex.current でアクセスできる。
どちらもグローバルに使用できるように作られていて、プログラム中では、直接この2つを扱えばいい。
ここで、モデルを考えよう。
主なモデルは2つブックマークとユーザーだ。
ユーザーはブックマークを複数持っている。
ここで、ブックマークはユーザーが自由に入力したものとして扱い、複数ユーザーで共有することはないものとする。
つまり1:N関連となるわけだ。
あとは最近のソーシャルブックマークから適当に引っ張ってこよう。
タグ付けは必須機能と言っていいほど、どのSBMも持っている機能だ。
また、ブックマークにメモ書きできる。
ラベルはユーザー共通としておこう。
u = User() b = Bookmark() l = Label() u.name u.bookmarks u.labels u.password b.title b.url b.memo b.user b.labels l.name l.bookmarks
ラベルの識別子はラベル名(name)、bookmarkはユーザーごとにurlが識別子、ユーザーはユーザー名が識別子となる。
label = Label.byName(labelname) user = User.byName(username) bookmark = user.bookmarkByUrl(url) bookmarks = user.selectBookmarkByLabel(label)
Label.byNameでは該当するラベルがなかった場合は、その名前で作成したラベルを作ってしまえばいい。
user.bookmarkByUrlも同様に考えることにしよう
としても、既存のものかどうかは知りたいと思うので、それを調べるメソッドも考えよう。
Label.exists(labelname) User.exists(username) user.hasBookmark(url)
さてこいつらをテーブルにマッピングしよう。
基盤として、クラスをそのままテーブルに割り当ててみる
from database import meta user_table = Table("User", meta) bookmark_table = Table("Bookmark", meta) label_table = Table("Label", meta)
それぞれに機械的なIDをつける
user_table = Table("User", meta, Column("user_id", Integer, primary_key=True)) bookmark_table = Table("Bookmark", meta, Column("bookmark_id", Integer, primary_key=True)) label_table = Table("Label", meta) Column("label_id", Integer, primary_key=True))
関連付けのために外部キーを付ける
user_table = Table("User", meta, Column("user_id", Integer, primary_key=True)) bookmark_table = Table("Bookmark", meta, Column("bookmark_id", Integer, primary_key=True), Column("user_id", Integer, ForeignKey("user.user_id"))) label_table = Table("Label", meta, Column("label_id", Integer, primary_key=True))
N:M関連のためにブリッジテーブルを追加する
label_bookmark_table = Table("label_bookmark", meta, Column("label_id", Integer, ForeignKey("label.label_id")), Column("bookmark_id", Integer, ForeignKey("bookmark.bookmark_id")))
識別子の列を追加する
user_table = Table("User", meta, Column("user_id", Integer, primary_key=True), Column("name", String, unique=True)) bookmark_table = Table("Bookmark", meta, Column("bookmark_id", Integer, primary_key=True), Column("user_id", Integer, ForeignKey("user.user_id")), Column("url", String)) bookmark_table.append_constraint(UniqueConstraints("user_id", "url")) label_table = Table("Label", meta, Column("label_id", Integer, primary_key=True), Column("name", Unicode, unique=True))
追加の情報について列を追加
bookmark_table = Table("Bookmark", meta, Column("bookmark_id", Integer, primary_key=True), Column("user_id", Integer, ForeignKey("user.user_id")), Column("url", String), Column("title", Unicode), Column("memo", Unicode)) bookmark_table.append_constraint(UniqueConstraints("user_id", "url"))
マッピング
assign_mapperを使って少し楽に使えるようにしておこう。
#database.py from sqlalchemy import * from sqlalchemy.ext.sessioncontext import SessionContext from sqlalchemy.ext.assignmapper import assign_mapper meta = DynamicMetaData() context = SessionContext(create_session) def assgin(*args, **kwargs): return assign_mapper(context, *args, **kwargs)
とりあえずそれぞれのクラス定義をしておく
class Bookmark(object): pass class Label(object): pass class User(object): pass
クラスとテーブルをマッピング
assgin(User, user_table) assign(Bookmark, bookmark_table) assign(label, label_table)
関連をマッピング
assign(Bookmark, bookmark_table, properties=dict(user=relation(User, backref="bookmarks"), labels=relation(Label, secondary=label_bookmark_table, backref="bookmarks"))
Paste 1.4リリース
http://pythonpaste.org/news.html#id1
WSGIベースのWebアプリケーションを作るのに便利なツール集Pasteの最新バージョン1.4が昨日リリースされたようです。
PasteDeploy, PasteScriptは変わっていない模様。
webstringのlxmlバージョン縛り
webstring-0.5-py2.5.egg/EGG-INFO/requires.txt
lxml==1.1.1
こんなピンポイントで指定しなくても....