Hatena::ブログ(Diary)

SPEAKER BREAKA このページをアンテナに追加 RSSフィード Twitter

2011-10-04

factory_boy README 和訳

# 必要に迫られたので訳したよ。更新が久しぶりすぎて、はてダの使いかた忘れてる…


factory_boy は thoughtbot の factory_girl をベースにした fixture replacement です。 factory_girl のように、わかりやすい定義構文、 複数のビルドストラテジ(インスタンスの保存、非保存、属性辞書、スタブオブジェクト)の対応、 ファクトリの継承を含め、同一クラスに対する複数ファクトリの対応などがあります。 Django に対応していますが、他の ORM の対応も簡単に追加することができます。

クレジット

この README はできるだけ factory_girl の README に対応させています。 文章や例は比較しやすいように同じ内容にしています。 factory_girl を使っている Ruby ユーザーなら、すぐに Python の factory_boy を使えるでしょう。

factory_boy は Mark Sandstrom によって書かれました。

Joe Ferris と factory_girl を作ってくれた thoughtbot に感謝します。

ダウンロード

Github: http://github.com/dnerdy/factory_boy/tree/master

easy_install:

easy_install factory_boy

ソース:

# ソースをダウンロードして実行
python setup.py install

ファクトリの定義

ファクトリは、オブジェクトインスタンス化するのに使う属性のまとまりを宣言したものです。 ファクトリ名はデフォルトではオブジェクトのクラスを推測するのに使われますが、 明示的に指定することも可能です:

import factory
from models import User

# これは User クラスを推測します
class UserFactory(factory.Factory):
    first_name = 'John'
    last_name = 'Doe'
    admin = False

# これは(Admin クラスを推測しますが) User クラスを使います
class AdminFactory(factory.Factory):
    FACTORY_FOR = User

    first_name = 'Admin'
    last_name = 'User'
    admin = True

ファクトリの使用

factory_boy はいくつかの異なったビルドストラテジに対応しています: build, create, attributes そして stub です:

# save されていない User インスタンスを返します
user = UserFactory.build()

# save された User インスタンスを返します
user = UserFactory.create()

# User インスンタンスの生成に使える属性の辞書を返します
attributes = UserFactory.attributes()

# 全ての属性がスタブ化されたオブジェクトを返します(?)
stub = UserFactory.stub()

ファクトリクラスをデフォルトビルドストラテジのショートカットとして使うことができます:

# UserFactory.create() と同じ
user = UserFactory()

デフォルトのストラテジを上書きすることも可能です:

UserFactory.default_strategy = factory.BUILD_STRATEGY
user = UserFactory()

全ファクトリに対するデフォルトストラテジを上書きすることもできます:

# デフォルトのビルドストラテジが定義されていない全ファクトリのデフォルトストラテジを設定
factory.Factory.default_strategy = factory.BUILD_STRATEGY

どのストラテジが使われていても、 キーワード引数を渡すことで既に定義済の属性を上書きすることが可能です:

# User インスタンスを生成して first_name を上書きする
user = UserFactory.build(first_name='Joe')
user.first_name
# => 'Joe'

遅延評価属性

ほとんどのファクトリ属性は、ファクトリを定義する際に評価される 静的な値を使ってファクトリに追加することができますが、 いくつかの属性(関連や動的に生成されるべき属性など)は インスタンスが生成される度に値が設定される必要があります。 そのような "lazy" な属性は、次のように追加することができます:

class UserFactory(factory.Factory):
    first_name = 'Joe'
    last_name = 'Blow'
    email = factory.LazyAttribute(lambda a: '{0}.{1}@example.com'.format(a.first_name, a.last_name).lower())

UserFactory().email
# => 'joe.blow@example.com'

LazyAttribute に渡された関数には、その宣言までにファクトリに定義された属性が与えられます。

ラムダ式では具合が悪い場合は lazy_attribute デコレータ関数を装飾することができます:

# Stub ファクトリは特定のクラスとのひもづきを持たない
class SumFactory(factory.StubFactory):
    lhs = 1
    rhs = 1

    @lazy_attribute
    def sum(a):
        result = a.lhs + a.rhs  # などのなんらかの計算
        return result

関連

LazyAttribute を使うことで、ひもづいたオブジェクトインスタンスを生成することもできます:

from models import Post

class PostFactory(factory.Factory):
    author = factory.LazyAttribute(lambda a: UserFactory())

ひもづいたオブジェクトでは、常にデフォルトストラテジが使われます:

# User と Post をビルドして保存する
post = PostFactory()
post.id == None           # => False
post.author.id == None    # => False

# User をビルドして保存してから Post をビルドするが保存はしない
post = PostFactory.build()
post.id == None           # => True
post.author.id == None    # => False

継承

継承を使えば、何度も共通の属性の宣言をしなくても、簡単に同じクラスのファクトリを複数作ることができます:

class PostFactory(factory.Factory):
    title = 'A title'

class ApprovedPost(PostFactory):
    approved = True
    approver = factory.LazyAttribute(lambda a: UserFactory())

シーケンス

特定のフォーマット(Eメールアドレスなど)のユニークな値はシーケンスを使って生成できます。 シーケンスは Sequence で定義するか sequence デコレータを使います:

class UserFactory(factory.Factory):
    email = factory.Sequence(lambda n: 'person{0}@example.com'.format(n))

UserFactory().email  # => 'person0@example.com'
UserFactory().email  # => 'person1@example.com'

シーケンスは遅延属性と組み合わせて使うこともできます:

class UserFactory(factory.Factory):
    name = 'Mark'
    email = factory.LazyAttributeSequence(lambda a, n: '{0}+{1}@example.com'.format(a.name, n).lower())

UserFactory().email  # => mark+0@example.com

2010-04-13

Jogging の DatabaseHandler だとロールバックしちゃって例外ロギングできなくね?と思ったら

TransactionMiddleware

Djangoトランザクションにおいて、TransactionMiddleware を使えば、 リクエストを受けてからレスポンスを返すまでをひとつのトランザクションとして 処理してくれるので楽ちんですよね:


# settings.py
MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.CacheMiddleware',
    'django.middleware.transaction.TransactionMiddleware',  # これ
)

Jogging の設定

さて、以前のポストで README を和訳したように、「Django でもログを取りたい! できれば管理画面で閲覧できるように、モデル化されてると使い勝手がいいな〜」 ということで Jogging の DatabaseHandler を使ってロギングしたりしています:

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.CacheMiddleware',
    'django.middleware.transaction.TransactionMiddleware',
    'jogging.middleware.LoggingMiddleware',  # ここと
)

...

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions.backends.cache',
    'django.contrib.sites',
    'django.contrib.admin',
    # ...
    'jogging',  # ここを追加
    # ...
)

正しくは

ここで、「500エラーなんかは TransactionMiddleware でロールバックされちゃうから、 DB にログが残んないじゃん!」という状態になったのですが、まあ落ちつけと:

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.CacheMiddleware',
    'jogging.middleware.LoggingMiddleware',  # TransactionMiddleware の前に移動
    'django.middleware.transaction.TransactionMiddleware',
)

イアン ありがとう。ian++


ところで Jogging と django-db-log の比較

Jogging と django-db-log を比較した場合、django-db-log のモデルの方が例外向けに 洗練されてる印象なのですが、例外以外のロギングを考えると、Python の logging を Django 向けにラップしてる Jogging の方が使い易さ的には上かな、と感じました。


そもそも目指してるところが違うっぽいので、単に比較するのもおかしいのかも。


なので、ADMINS にメールで飛んでくるような例外レポートをモデルで管理したければ django-db-log を、例外以外にも Django でも Python コードのようにログを取りたければ Jogging を、という感じ。


別に両方使ってもいいのかもしれないけど。

2010-04-12

Django クエリセットの defer / only メソッド 直訳

Django 1.2 目前ですが、1.1 からクエリセットのメソッドに、クエリ最適化に利用できる defer()only() メソッドが追加されています。


no title


まあいつものように、自分のために直訳。


defer(*fields)

複雑なデータモデリングが必要な状況では、モデルに大量のフィールドを持っていたり、 フィールドに(テキストフィールドなどで)大量のデータを持っていたり、 Python オブジェクトに変換するのに重い処理が必要だったりするだろう。 クエリセットの結果のうち、必要のないフィールドがわかっている場合、 Django にそれらのフィールドをデータベースから取得しないように指定することができる。


これは defer() にロードしないフィールド名を渡せばいい:

Entry.objects.defer("lede", "body")

この遅延クエリセットもまたモデルインスタンスを返す。 defer で指定された各フィールドは、そのフィールドにアクセスした時にデータベースから取得される。 (一度に全ての遅延フィールドを取得するのではなく、1フィールドだけ)。


defer() の複数呼び出しも可能。 各呼び出しは新しいフィールドを遅延セットに追加する:

# body と lede フィールドの取得を延期する
Entry.objects.defer("body").filter(headline="Lennon").defer("lede")

遅延セットへの追加順は関係ない。 既に遅延指定されたフィールド名の defer() 再呼び出しも問題ない(そのフィールドは遅延指定されたまま)。


(select_related() でロードされていれば)関連モデルのフィールドに対する遅延ロードも、 関連フィールドを標準的な二重アンダースコアの記述する方法で指定可能:

Blog.objects.select_related().defer("entry__lede", "entry__body")

遅延フィールドの解除は defer()パラメータとして None を渡せばいい:

# 遅延せずに全フィールドを読み込む
my_queryset.defer(None)

いくつかのフィールドは、指定したとしても遅延処理されない。 主キーは決して遅延読み込みはできない。 select_related() を使って同時に他のモデルを取得する場合、 主モデルから接続される関連モデルフィールドの遅延読み込みをするべきではない (その場ではエラーにはならないが、結果としてエラーが発生する)。


note:

defer() メソッド(と兄弟の only() メソッド)は、高度なユースケースのために存在している。これらはクエリ分析時の最適化や、どの情報が必要かを 厳密に 把握している場合や、 モデルにおいて、必要なフィールドだけ読み込んだ場合と全てのフィールドを返した場合で大きな違いがあることが測定されている場合のために提供されている。 アプリケーション開発の初期の段階から、わざわざ defer() を使う必要はない; クエリ設計が落ち着いて、どこが問題点なのか理解できるまで触るべきではない。


only(*fields)

only() メソッドは defer() メソッドとほぼ反対のメソッドだ。 モデルを取得する際に、遅延取得 しない フィールドを指定して呼び出す。 ほとんどのフィールドを遅延読み込みするようなモデルの場合に、コードを簡潔にするために 遅延処理しないフィールドを指定するために only() を使用する。


name, age, biography フィールドを持つモデルの場合、次の2つのクエリセットは 遅延フィールドとして同じものになる:

Person.objects.defer("age", "biography")
Person.objects.only("name")

only() 呼び出しはいつでも、即時ロードするフィールドセットを 置換 する。 このメソッド名は覚えやすい用につけたもので: 指定したフィールド だけ 即時ロードされ; 残りのフィールドは遅延ロードされる。 このため、連続した only() 呼び出しは、最後の指定されたフィールドだけが考慮される:

# これは headline 以外のフィールドが遅延処理される
Entry.objects.only("body", "lede").only("headline")

defer() は追加処理される(遅延処理リストにフィールドを追加していく)ため、 only()defer() の結合呼び出しが可能であり、これらは論理的にふるまう:

# 最終的に "headline" 以外が遅延処理される。
Entry.objects.only("headline", "body").defer("body")

# 最終的に headline と body が即時処理される
# (only() が全フィールドセットを置換するため)
Entry.objects.defer("body").only("headline", "body")

2010-04-06

Jogging README 翻訳

Django アプリのログを取りたい場合に Jogging が便利そうなので、いつもの直訳です。


Jogging README

Jogging は Django でのロギングを簡単にするための、Python の logging の薄いラッパです。 これはすべてのロガーの設定を一箇所でできるようにし、ロガーをインポートする標準の場所の提供や、一般的なロギングユースケースを簡潔にすることができます。


Jogging を使えば、保存場所やフォーマット、適切な粒度の詳細ログの管理が可能です。 モジュールレベルのロギング設定も、特定の関数のロギング設定のように簡単に実現できます。


使用するには、settings.py に数行設定を追加し、Python のログ関数の変りに Jogging のログ関数インポートします。そしていつものようにログを取るだけです。


Python の logging モジュールが全ての重要な処理を担っているため、Jogging から既に存在するコードのロギングの設定を行なうことができます。 また抽象化によって logging の威力が損なわれないようにしています。


ダウンロード

最新リリース版 (2009/10/27 リリースの v0.1)を ダウンロード セクションから取得できます。


インストール

  1. INSTALLED_APPS'jogging' を追加
  2. MIDDLEWARE_CLASSES'jogging.middleware.LoggingMiddleware' を追加

設定

言葉で説明するよりコードサンプルの方が簡単なので、いくつかのサンプルを載せます。 これらを settings.py に記述して下さい。


基本的な設定例
from jogging.handlers import DatabaseHandler
import logging

GLOBAL_LOG_LEVEL = logging.INFO
GLOBAL_LOG_HANDLERS = [DatabaseHandler()] # takes any Handler object that Python's logging takes

INFO レベル以上のログがデータベースへ保存されます。


Jogging はハンドラをラップしないことに注意して下さい; ハンドラは logging と同様のオブジェクトです。 これは logging.Hander でできることは全てできる、ということであり、Jogging にハンドラオブジェクトを渡すことができる、ということです。


中間的な設定例
from jogging.handlers import DatabaseHandler
from logging import StreamHandler
import logging

LOGGING = {
    # myapp1 の全てのログをデータベースに
    'myapp1': {
        'handler': DatabaseHandler(), # an initialized handler object
        'level': logging.DEBUG,
    },

    # ... この view だけは stderr に CRITICAL なログを吐く
    'myapp1.views.super_important_view': {
        'handler': StreamHandler(),
        'level': logging.CRITICAL,
    },
}

特定のロガーだけにマッチします(この例では super_important_view() ではデータベースにログを残しません)


高度な設定例
from jogging.handlers import DatabaseHandler
from logging import StreamHandler, FileHandler
import logging

LOGGING = {
    # myapp1 の全てのログをデータベースに
    'myapp1': {
        'handler': DatabaseHandler(),
        'level': logging.DEBUG,
    },

    # 今回も super_important_view だけ CRITICAL なログを stderr に吐くが、
    # それ以外のレベルのログはデータベースに吐く
    'myapp1.views.super_important_view': {
        'handlers': [
            { 'handler': StreamHandler(), 'level': logging.CRITICAL,
                'format': "%(asctime)-15s %(source): %(message)s %(foo)s" },
            { 'handler': DatabaseHandler(), 'level': logging.DEBUG },
        ]
    },

    # サードパーティのアプリにおいて、既にログをとっている場合のロガー名。
    # これも他の設定と同様に記述できる。
    'simple_example': {
        'handler': StreamHandler(),
        'level': logging.CRITICAL,
    }
}

ハンドラのフォーマットプロパティPython の logging の書式化文字列に加え、以下の拡張文字列があります:

  • %(source)s はロギング呼び出し元のメソッド
  • %(foo)s はロギング呼び出し時に渡されたパラメータ

Usage

from jogging import logging
logging.info("I'm an info message")
logging.debug(msg="I'm a debug message", foo="bar")

%(foo)s は上記の高度な例で言及した 'format' プロパティにあったということを覚えてるでしょうか? これはデバッグ呼び出し時には "bar" として与えられます。


カスタムハンドラ

jogging.handlers.DatabaseHandler
データベースにログを保存し、管理画面において閲覧/検索/絞り込みが可能になります。
jogging.handlers.EmailHandler
ログをEメールで送信します。
jogging.handlers.InlineOnPageHandler
作成予定。レンダーしたページの下部にログを表示します。

FAQ

Jogging と django-logging の違いは?
Djagno logging は単一のルートロガーを提供しますが、Jogging は異なるモジュールで異なるロガーを使うことができます。 上述の "基本的な設定例" のように設定することで、django-logging の同じような使い方ができます。
Jogging と django-db-log の違いは?
django-db-log は例外のログをデータベースに保存してくれるだけです。 これはデバッグや一般的なロギングの目的には合っていませんし、Python の logging モジュールで提供するような機能は何もありません。 Jogging は DatabaseHandler というハンドラを使えば、django-db-log のように例外ログ(だけでなくなんでも)データベースへ保存することができます。
logging の log 関数と Jogging を併用できるか?また Jogging の log 関数を使ったほうがいい理由は?
二つの理由があります: まずひとつめは、ロガーのフォーマッタにおいて、呼び出し関数名として source 変数を使うことができます。 ふたつめは、Jogging の log 関数は自動的に正しいロガーを選択するため、どのロガーが設定されているのか気にする必要がありません。

実装について

Django’s logging proposal から多大なインスピレーションを受けています。


Jogging は Jogging によって管理するロガーの(名前の)定義辞書 settings.LOGGING の設定が必要です。Jogging の動作は:

  1. 全てのロガーはサーバの起動時に settings.LOGGING から作成されます (初期化コードは、ちょうどいい場所がなかったので、ミドルウェア内の __init__ にしています)。 ハンドラは定義通りにロガーに追加され、ログレベルがセットされます。
  2. アプリケーションが Jogging の log 関数を呼び出す際、呼び出し元の関数settings.LOGGING で設定されたロガー名から 合ったものを探し、より具体的なロガー名が選択されます。 例えば、呼び出し元を myproj.myapp.views.func() とした場合、次の名前のロガーからマッチするロガー名を探します。 myproj.myapp.views.func, myproj.myapp.views, myproj.myapp, myproj 。 この場合、最初の(そしてより具体的な)ロガーがマッチして選択されます。
  3. log() は選択されたロガーから呼び出され、Python の logging モジュールに渡されます。

リソース

Python logging モジュールのハンドラ一覧:

http://docs.python.org/library/logging.html#handler-objects

Python loggign モジュールの書式化文字列:

http://docs.python.org/library/logging.html#formatter-objects


ToDo

  • Instantiate handlers outside of settings.py (e.g. so the ORM can be used)
  • settings.py より外でハンドラのインスタンス化を行なうようにする(例えば ORM で使えるように)
  • もっとカスタムハンドラを作成する

名前について

最初の "j" は発音せずに "ヨギング" と読みます。

2010-04-03

Macports で入れた Python で Skype4Py を使う

Mac OS X 10.6.3 で Skype4Py を使いたいんだけど、Segmentation fault が発生する。

と思ったら、ドンピシャな方が。

Skype4Pyがsegmentation fault する - mfumiの日記


なるほど、python を 32bit アプリケーションとして起動してやればいいのか。


ただ自分は Mac デフォルトPython は使わずに、Macports で複数バージョンの Python を入れて、virtualenv で必要な環境をその都度作ったり、切り替えてる。


$ port installed | grep python
python24 @2.4.6_4+darwin+darwin_10+macosx (active)
python25 @2.5.4_9+darwin+darwin_10+macosx (active)
python26 @2.6.4_0+darwin+universal (active)
python31 @3.1.1_0+darwin (active)
python_select @0.3_0+darwin_10 (active)

今回は python26+universal を 32bit アプリケーションとして動かしたいので、


$ cp python python.bak
$ lipo python -thin i386 -output python

とすればOK。Skype4Py も動いたよ。


#!/usr/bin/env python
# -*- coding:utf-8 -*-
import Skype4Py

def main():
    skype = Skype4Py.Skype()
    skype.Attach()
    print skype.CurrentUser.FullName

if __name__ == '__main__':
    main()