Studio3104::BLOG.new

uninitialized constant Studio3104 (NameError)

ミスって 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, @!

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

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

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

自作ライブラリを GitHub に置いて、そこから pip インストール出来るようにするまで

Rubyist でしたが Python もはじめました。
パーフェクト PythonKindle で購入しましたが、検索出来ないし文字を選択できないのでコピペ出来なくて大変不便なので、物理版を購入すれば良かったと後悔しております。

話が逸れました。本題に。
これを書いている現在、当方 Python 歴数日 なので温かいアドバイスなどあれば是非ともよろしくお願いいたしますm(__)m

雛形を作る

paster create を実行し、プロジェクトの設定を対話的に入力していきます。

$ pip install python_boilerplate_template
$ paster create

こんな感じになる。

$ tree -C hoge
hoge
├── hoge
│   └── __init__.py
├── hoge.egg-info
│   ├── PKG-INFO
│   ├── SOURCES.txt
│   ├── dependency_links.txt
│   ├── entry_points.txt
│   ├── not-zip-safe
│   └── top_level.txt
├── setup.cfg
└── setup.py

2 directories, 9 files

実装

コードを書きます。

依存ライブラリを書いておく

requirements.txt に依存ライブラリを書いていくのが普通っぽい。
んだけど、上述の雛形の作成方法だと requirements.txt は作られないし、作成された setup.py からは当然参照されなくて不便。
なので、setup.py から requirements.txt を参照させるようにして、作成したライブラリがインストールされるときにちゃんと依存ライブラリも一緒にインストールされるようにする。

--- a/setup.py
+++ b/setup.py
@@ -1,8 +1,14 @@
 from setuptools import setup, find_packages
-import sys, os
+from pip.req import parse_requirements
+import sys, os, pip

 version = '0.1'

+requirements = [
+    str(requirement.req)
+    for requirement in parse_requirements('requirements.txt', session = pip.download.PipSession())
+]
+
 setup(name='hoge',
       version=version,
       description="test project",
@@ -17,9 +23,7 @@ setup(name='hoge',
       packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
       include_package_data=True,
       zip_safe=False,
-      install_requires=[
-          # -*- Extra requirements: -*-
-      ],
+      install_requires=requirements,
       entry_points="""
       # -*- Entry points: -*-
       """,

あとは

普通に実装が終わったら普通に GitHub にコードを上げる。
で、使うときはこんな感じで。

  • 作成するアプリケーションの依存に突っ込みたい場合

    • requirements.txt に追記

      -e git://github.com/youraccount/youreggname#egg=eggname

    • インストール

      $ pip install -r requirements.txt

  • 普通にインストールする場合

    $ pip install -e git://github.com/youraccount/youreggname#egg=eggname

この手順で作ったやつ

github.com

大物 alter table の途中で Lost connection to MySQL server during query が発生したときに確認したいこと

とりあえず show processlist してまだ動いてるっぽかったら、datadir 配下で #sql-xxxx_xxxxx.ibd な名前のファイルが育っているかどうか確認する。
タイムスタンプが更新されていればテンポラリテーブルへのデータコピーが生きてるということになるので、あとは show processlist を監視して終わるのを待てば良い。
で、終わったっぽかったら show create table を確認して変更が反映されてれば完了。

って @ さんに教えてもらった。ありがとうございます!