Studio3104::BLOG.new

uninitialized constant Studio3104 (NameError)

自動的に Annotation を追加してくれる pyannotate が便利そう

TL; DR

こいつを使うと、Annotation を追加していない部分に自動的に適用してくれます。

github.com

もう少し詳しく

詳しい使い方はリポジトリを見ていただくとして、要はこの実装に対して実行すると、

gist.github.com

こういう風になります。

gist.github.com

残念ながらこのエントリを書いている本日現在では python3 形式での変換には対応していないようですが、README の TODO に Python 3 code generation.  と書いてあるのでそのうち対応されるんではないでしょうか。
ちなみに手動で書き換えるとこういう風になるんだと思います。

gist.github.com

pytest と組み合わせるとさらに便利

pyannotate は実際に実行してみた結果を収集しながら型情報を適用するための JSON ファイルを生成してから、それをベースにして適用を行うという仕組みです。

pyannotate/example at master · dropbox/pyannotate · GitHub を覗いてみると、以下のような説明があり、pytest で実際に走破された箇所に対しての JSON ファイルを生成するためのヘルパーを含んだ conftest.py のサンプルがあります。

Alternative, using pytest
For pytest users, the example_conftest.py file shows how to automatically configures pytest to collect types when running tests. The test_gcd.py file contains a simple test to demonstrate this. Copy the contents of example_conftest.py to your conftest.py file and run pytest; it will then generate a type_info.json file like the one above.

どうせテストは書くんだからこれはかなり便利なんじゃないでしょうか。
今回の例を出すために使用した実装は以下にアップしていますが、実際にその conftest.py を使用してみています。

github.com

雑感

このライブラリから得られた結果をそのままで使うというよりもあくまで参考にする程度の気持ちでいたほうが良さそう。
上に例示した内容の場合もそうですが、たとえばネストされた dict だと Dict[str, Any] になりがちです。しかしながら実際の実装では本当は構造がちゃんと決まっているかもしれないので、その場合は適切に書き換えてあげる必要があるでしょう。極力 Any は使いたくない。
また、これも例示した内容に含まれていますが、requests のようなサードパーティライブラリであっても適切にどのクラスのオブジェクトインスタンスなのかということを判断して import も自動的に挿入してくれるのはとても便利ですね。
なんにしても、Python で type hinting を始めてみようという段階ではとんでもなく便利だと思います。

突然 ERD を要求されたときに便利な eralchemy

開発してるプロダクトで突然 ERD 出せと言われることがあります。
そんなときには eralchemy を使うと便利です。
実際の Schema から ERD を出力してくれます。

使いからは上記のリンク先の README を見るだけでイナフですがこんな感じ。

brew install eralchemy
eralchemy -i sqlite:///db.sqlite -o erd_from_sqlite.pdf

スーパーイージーですね。pip からもインストール出来ます。

Django の Abstract Model を py.test でテストする

※これは Python Advent Calendar 2015 2日目のエントリです。


各モデルが共通のカラムや振る舞いを持つようになってきたら

Django でアプリケーションを開発していると、各モデルに共通のカラムを持たせたり、共通の振る舞いをさせたりしたいということが起こると思います。
そのような場合は、各モデルに対して同じような実装を施すのではなく、Abstract Model を定義して各モデルがそれを継承するようにするのが自然でしょう。

例えば以下の様な感じです。

from django.db import models


class CommonAbstractModel(models.Model):
    created_datetime = models.DateTimeField(auto_now_add=True)
    updated_datetime = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True
from common.models import CommonAbstractModel


class User(CommonAbstractModel):
    name = models.models.CharField(max_length=100)

このように実装することによって、User モデルに直接 created_datetime, updated_datetime を定義する必要がなくなるので便利です。

ではテストをどうするか

CommonAbstractModel を継承したモデルが確実に created_datetime, updated_datetime を持っているかということをテストした気持ちになるのが自然ですよね。
ただ abstract = True なモデルは manage.py makemigrationsマイグレーションが作成されないため、通常のモデルのように普通にテストを書くことがかないません。
とはいえ、子クラスである User に対してそれらテストを行うのは、あくまで User のテストであって CommonAbstractModel をテストするということにはならないです(なるかも知れないけど、例えば CommonAbstractModel に論理削除の実装が入ったりしたらそれも子クラスでテストするんですか?という話になりますね) 。

py.test を使った例

実はこの例(継承したモデルが確実に created_datetime, updated_datetime を持っているかというテスト)だと、擬似的なマイグレーションを行う必要はまったくなく、擬似的な子クラスだけがあれば良かったりします。。。
Abstract Model に論理削除とかを実装したくなったときには必要ですが。

import pytest
import datetime

from common.models import CommonAbstractModel
from django.db import connection
from django.core.management.color import no_style


@pytest.fixture(scope='module')
def fx_TestInheritedModel():
    class TestInheritedModel(CommonAbstractModel):
        class Meta:
            app_label = 'common'

    return TestInheritedModel


@pytest.yield_fixture(scope='function')
@pytest.mark.django_db
def fx_test_model_cls(request, fx_TestInheritedModel):
    cursor = connection.cursor()
    statements, pending = connection.creation.sql_create_model(fx_TestInheritedModel, no_style())
    for sql in statements:
        cursor.execute(sql)

    yield fx_TestInheritedModel

    statements = connection.creation.sql_destroy_model(fx_TestInheritedModel, (), no_style())
    for sql in statements:
        cursor.execute(sql)


@pytest.mark.django_db
class TestCommonAbstractModel:
    @pytest.mark.parametrize(('attr_name'), [
        'created_datetime',
        'updated_datetime',
    ])
    def test_inherited_model_has_proper_fields(self, fx_TestInheritedModel, attr_name):
        inherited_model = fx_TestInheritedModel()
        assert hasattr(inherited_model, attr_name), 'the inherited model must have %s field' % (attr_name, )

ちょっとだけ解説

テスト内部で擬似クラスを作成してそれに CommonAbstractModel を継承させて、それを擬似マイグレーションをさせてテストしてしまおうという戦略です。

擬似クラス

フィクスチャをこのように定義します。
app_label = 'common' がめっちゃ重要で、これを定義しておかないと擬似マイグレーションさせることが出来ません。

@pytest.fixture(scope='module')
def fx_TestInheritedModel():
    class TestInheritedModel(CommonAbstractModel):
        class Meta:
            app_label = 'common'

    return TestInheritedModel
擬似マイグレーション

各テストメソッドが実行される前にテーブルが作成され、終わったら消されます。
yield_fixture を使うのがポイントです(scope='function' はデフォルトなのであえて指定する必要はないかもしれませんが、Explicit is better than implicit. です。)

@pytest.yield_fixture(scope='function')
@pytest.mark.django_db
def fx_test_model_cls(request, fx_TestInheritedModel):
    cursor = connection.cursor()
    statements, pending = connection.creation.sql_create_model(fx_TestInheritedModel, no_style())
    for sql in statements:
        cursor.execute(sql)

    yield fx_TestInheritedModel

    statements = connection.creation.sql_destroy_model(fx_TestInheritedModel, (), no_style())
    for sql in statements:
        cursor.execute(sql)
あとは...

実際に fx_test_model_cls を利用するテストをガシガシ書いていくだけです!

django.test.TestCase を使いたい場合

普通に setUp, tearDown を使って同じようなことすれば良さそう。


EOF

Python でオブジェクト内部の dict に直接アクセスする

  • Ruby でいうとこのこういうやつ。
class Hage
  def initialize()
    @hage = { bozu: 1, hage: 2 }
  end

  def [](key)
    @hage[key]
  end

  def []=(key, value)
    @hage[key] = value
  end
end

h = Hage.new()
p h                    #=> #<Hage:0x007f94428a2c20 @hage={:bozu=>1, :hage=>2}>
p h[:bozu]         #=> 1
h[:fusa] = 3
p h[:fusa]          #=> 3
  • Python でやるとこう。
class Hage:
    def __init__(self):
        self.hage = { 'bozu': 1, 'hage': 2 }

    def __getitem__(self, key):
        return self.hage[key]

    def __setitem__(self, key, value):
        self.hage[key] = value

h = Hage()
print h            #=> <__main__.Hage instance at 0x109f6aef0>
print h['hage'] #=> 2
h['fusa'] = 3
print h['fusa']  #=> 3

thank you for letting me, @!

n 種類のカラーコードのちょうど平均のカラーコードが知りたいとき

R,G,B でそれぞれの平均値で再構成したらちょうど真ん中になるのでは?

という感じで冗長だけど書いた