Pyramidでzope.interfaceを使う

Pyramidzope.interface を使うときの話

zope.intreface

zope.intreface が面白いと最近知った。

実装を持たない「インターフェース」だけ定義して、 そこに後で実装をもたせる、というかんじ(超ざっくり)

折れ線グラフとしてのインターフェースをILinechart、 なにやら描画してくれるインターフェースをIRendererとして考えてみる。:

import zope.interface


class IRenderer(zope.interface.Interface):
    def render():
        """なんか描画する的"""


class ILinechart(zope.interface.Interface):
    series = zope.interface.Attribute("""Y軸の値みたいな""")
    category = zope.interface.Attribute("""X軸の値みたいな""")


@zope.interface.implementer(IRenderer)
class LinechartRenderer(object):

    __used_for__ = ILinechart

    def __init__(self, context):
        self.context = context

    def render(self):
        return str(zip(self.context.series, self.context.category))

こんなかんじか。 嬉しいのは:

  • インターフェースだけ先に書いといて実装はあとから持たせられる。
  • 継承みたいに書いた時点で関係が固定されるわけじゃない。

とかかな。正直面白半分で使い始めてる節あるので何とも。

zope.interface のドキュメント読んでこのへん読んどけば捗りそう:

Pyramidにおけるzope.interfaceの使い方

zope.interface とか zope.component でもアダプターの登録ができますが、 それは Pyramid でやりましょう。

Registry というものに登録する。 まぁconfigとかrequestにひっついてるから:

# config 経由で登録して
config.registry.registerAdapter(LinechartRenderer,
                                (ILinechart,), IRenderer, '')

# request 経由で取ると
adapted = request.registry.getAdapter(linechart, IRenderer, '')

# 良い感じ
adapted.render() # '[(0, 0), (1, 1), (2, 4)]' とか

でまぁ:

config.registry.registerAdapter(LinechartRendererJSON,
                                (ILinechart,), IRenderer, 'json')
config.registry.registerAdapter(LinechartRendererHTML,
                                (ILinechart,), IRenderer, 'html')

とかname変えて登録していくとさらに良い (この場合rendererのメソッドにもたせたほうがいいかもだけど)。

お作法

そんな感じで、アプリケーション書くときに

  • __init__.pyでconfig.registry.registerAdapter
  • view_callable内でrequest.registry.getAdapter

とかやっちゃうわけだけど、これはまぁ行儀良くないらしい。 zope.interface を意識しないといけないのはライブラリやフレームワークの開発者であって ユーザー(ライブラリ等を使って開発する人)には見せたくないとのこと[要出典]。

まあたしかに、もっとわかりやすい書き方してよって気になるしね。

そのお行儀の良い書き方というのは簡単で:

  • config.add_directiveでdirectiveを追加
  • そのdirective経由でアダプター登録
  • requestを受け取るAPIとしての関数から、アダプトされたオブジェクトを返す

というもの。 さらに venusian を使って、directive経由でアダプター登録してたのを デコレーターで書いてやることができる。

まぁこんなかんじになるのか(renderer_configとto_rendererは自分で書くのよ):

@renderer_config('',
                 chart_type='linechart')
def str_linechart(linechart):
    return str(zip(linechart.series, linechart.category))

renderer = to_renderer(linechart, '')
renderer.render()

このrenderer_configのなかではconfig.set_rendererとか呼び出して登録してやるといい。 さながらPyramidのview_configとconfig.add_viewみたいなもんである。

まあ一見に如かずなのでrebecca.todictを読めばいいと思う:

これを参考に私もpyramid_tochartというのを書いてるので、こっちも参考になるかも:

ただまあ良い書き方を模索してるところ

Southで複合インデックスを貼る

Southで複合インデックスを貼る。 複合インデックスを使いたい場合、Djangoだと1.5からしか対応してない。

でも複合インデックスを手動で貼るのはツライ。 そこでこの記事では

  • Southから複合インデックスを貼る方法

あと

  • Southで吐いたインデックス名を取得する方法

を書く。

名前を取得する理由としては 「生クエリを吐かせて、そこでインデックスのヒントとして与えたい」なんていうときに 使う。

この複合インデックスを貼る機能はSouthの0.4から使えるそう。 今回はDjango1.3、South0.7で動作確認している。

Southで複合インデックスを貼る

自前でmigrationファイルを書いてやる。 0001_add_idx_table_name_column1_column2.py とか名前つけてあげて app/migrations/ 以下においてあげるだけ。

create_index を使えばできる。

from south.db import db
from south.v2 import SchemaMigration

class Migration(SchemaMigration):

    def forwards(self, orm):
        db.create_index('table_name', ['column1', 'column2'])

    def backwards(self, orm):
        db.delete_index('table_name', ['column1', 'column2'])

まぁそれ以上でも以下でもない感じ。テーブルとカラムを指定してあげるだけ。 もちろん単一カラムへのインデックスも貼れるよう。

これでマイグレーションすると ‘column1’, ‘column2’ への複合インデックスが 貼られる。

手で書くなら

ALTER TABLE table_name ADD INDEX idx_table_name_column1_column2 (column1, column2);

って感じかな。

で、インデックスヒントとか使ってやりたい場合にはインデックスの名前が必要なわけだけど、 Southにインデックス貼らせるとインデックスの名前が自動で生成される。 この名前を取ってやりたい。

Southが貼ったインデックスの名前をとる

意外と簡単。

from south.db import db

index_name = db.create_index_name('table_name', ['column1', 'column2'])

create_index_name にインデックス作ったときと同じ条件を渡してやるだけ。 まぁ constracts.py にでも書いておくのがいいかな。

ただドキュメントに書いてないからソースコード読んでみたほうがいい。

ちょっとインデックス名指定して作れたらいいのに、と思ったけどそれほど需要もなさそう。 まぁ取れるならいい。