自動的に Annotation を追加してくれる pyannotate が便利そう
TL; DR
こいつを使うと、Annotation を追加していない部分に自動的に適用してくれます。
もう少し詳しく
詳しい使い方はリポジトリを見ていただくとして、要はこの実装に対して実行すると、
こういう風になります。
残念ながらこのエントリを書いている本日現在では python3 形式での変換には対応していないようですが、README の TODO に Python 3 code generation.
と書いてあるのでそのうち対応されるんではないでしょうか。
ちなみに手動で書き換えるとこういう風になるんだと思います。
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
を使用してみています。
雑感
このライブラリから得られた結果をそのままで使うというよりもあくまで参考にする程度の気持ちでいたほうが良さそう。
上に例示した内容の場合もそうですが、たとえばネストされた dict
だと Dict[str, Any]
になりがちです。しかしながら実際の実装では本当は構造がちゃんと決まっているかもしれないので、その場合は適切に書き換えてあげる必要があるでしょう。極力 Any
は使いたくない。
また、これも例示した内容に含まれていますが、requests
のようなサードパーティライブラリであっても適切にどのクラスのオブジェクトインスタンスなのかということを判断して import
も自動的に挿入してくれるのはとても便利ですね。
なんにしても、Python で type hinting を始めてみようという段階ではとんでもなく便利だと思います。
GPG encrypted なファイルを直接編集したいときは
これを使えば完全に解決します。vim plugin です。
ミスって Cmd + q で Chrome を落としてしまったら
Chrome を起動し直してから Cmd + Shift + t で全部生き返るよ。
意外と知らない人が多いようなのでしたためておこう。
突然 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, @rrreeeyyy!
n 種類のカラーコードのちょうど平均のカラーコードが知りたいとき
2 つのカラーコードのちょうど中間のカラーコードを教えてくれる便利なやつないかな
— Satoshi SUZUKI (@studio3104) 2015, 4月 21
R,G,B でそれぞれの平均値で再構成したらちょうど真ん中になるかな?
— Satoshi SUZUKI (@studio3104) 2015, 4月 21
R,G,B でそれぞれの平均値で再構成したらちょうど真ん中になるのでは?
という感じで冗長だけど書いた