テンプレートからプロジェクトを生成できるcookiecutterを試す

cookiecutterは、テンプレート(雛形となるファイル群)を元に、プロジェクトファイル(ディレクトリやファイル)を生成できるコマンドラインツール。
Pythonで作られている。
GitHub - audreyr/cookiecutter: A command-line utility that creates projects from cookiecutters (project templates). E.g. Python package projects, jQuery plugin projects.
ローカルディレクトリや、github上のリポジトリにあるcookiecutterテンプレートを指定すると、対話的に必要な項目を入力してファイルを生成できる。
テンプレートは、対話的に入力させたい変数を定義したJSONファイルと、雛形となるファイル、ディレクトリで構成される。
雛形のファイルは、Jinja2テンプレートになっているので、制御構造やフィルタなども使える。
また、生成の過程でフックポイントが用意されており、Pythonコードを実行することもできる。

インストール

インストールにはpipコマンドを使う。virtualenv環境でも問題なし。

pip install cookiecutter

利用する

インストールするとcookiecutterコマンドが有効になる。
引数にテンプレートとなるディレクトリやリポジトリのパスを指定するとプロジェクトを作成できる。
github上のリポジトリの場合は、「gh:」のように簡易的な指定もできる。

cookiecutter gh:audreyr/cookiecutter-pypackage

cookiecutterで利用可能なテンプレートは、cookiecutterのドキュメントで紹介されている。
Available Cookiecutters

unittest.mock.patchの使用例

Pythonの標準ライブラリに含まれるunittest.mockモジュールの使い方メモ。patchのみ。
試したバージョンは、Python 3.5

テスト対象

main.py

import subprocess


def say(message):
    """echoコマンドでmessageを実行する
    """
    return subprocess.call(['echo', message])


def say_hello(somebody):
    """「Hello, {somebody}!」と画面に表示する関数
    """
    message = "Hello, {}!".format(somebody)
    return say(message)


if __name__ == '__main__':
    say_hello("tokibito")

実行すると次のようになる。

$ python main.py
Hello, tokibito!

テストコード

test_main.py

from unittest import TestCase, mock


class SayHelloTest(TestCase):
    """say_hello関数のテスト
    """
    def _get_target(self):
        from main import say_hello as target
        return target

    def _callFUT(self, *args, **kwargs):
        return self._get_target()(*args, **kwargs)

    def test_somebody(self):
        """somebody引数が使われていることを確認するテスト
        """
        with mock.patch('main.say', return_value=0) as patcher:
            result = self._callFUT("Spam")
            self.assertEqual(result, 0, "正常な終了コードになること")
            # somebody引数が使われたメッセージを指定して
            # say関数を実行していること
            patcher.assert_called_with("Hello, Spam!")

Pythonコードを3-way mergeする場合にKDiff3とP4Mergeのどちらを使うか検討

もう何年もKDiff3ユーザーですが、無料のマージツールならP4Mergeも良いよと聞いたので、少し試してみました。
MercurialやGitのマージツールとして使うため、3-way mergeが前提です。
できるだけ自動解決したいので、うまく自動解決する手段があればそちらを優先します。

マージ対象のコード

今回試したマージ対象のコードは以下の3つです。意図的にコンフリクトが発生するような内容にはしていますが、多人数で同じモジュールを変更するような開発をしていると、しばしばこのような状況は起こるかと思います。

base.py
class User:
    """ユーザークラス
    """
    def __init__(self, name):
        self.name = name
local.py

base.pyに対してGroupクラスをファイルの末尾に追記しています。

class User:
    """ユーザークラス
    """
    def __init__(self, name):
        self.name = name


class Group:
    """グループクラス
    """
    def __init__(self, name):
        self.name = name
        self.users = []
other.py

base.pyに対してPermissionクラスをファイルの末尾に追記しています。

class User:
    """ユーザークラス
    """
    def __init__(self, name):
        self.name = name


class Permission:
    """パーミッションクラス
    """
    def __init__(self, name):
        self.name = name

3-way mergeの比較

KDiff3とP4Mergeで前述の3つのファイルをマージしてみます。

KDiff3の場合

KDiff3は0.9.96のcjk対応のものを使っています。
yuja / kdiff3-mq-cjk — Bitbucket

未解決のコンフリクトは、クラス定義とその次の行になります。また、__init__コンストラクタとself.nameへの代入の行は同一とみなされて、自動解決されています。
この場合、未解決のコンフリクト部分でB(local.py)とC(other.py)の内容を単純に選択しても、正しくマージできないため、コピー&ペーストで編集することになります。

P4Mergeの場合

P4Mergeは2015.2を使っています。
Visual Merge and Diff Tools | Perforce

P4Mergeの場合も、未解決のコンフリクト位置と同一な位置の検出はKDiff3と同様でした。
P4Mergeでもこの場合はコピー&ペーストで編集することになります。

ベースを少し工夫した場合の3-way mergeの比較

前述の通り、マージ対象のコードのままではどちらを使っても自動解決できませんでした。
あらかじめ同じモジュールを編集することがわかっているのであれば、ベースのファイル(base.py)に少し手を加えることで、マージツールの差分検出にヒントを与えられます。

base.py(工夫したやつ)

分岐前にあらかじめ、追記する部分にコメントを入れておきます。

class User:
    """ユーザークラス
    """
    def __init__(self, name):
        self.name = name

# ここにGroupクラス

# ここにPermissionクラス
local.py(工夫したbase.pyからの編集)

編集する場合には、base.pyの該当箇所のコメントを削除した上でコードを記述します。この際、追記しない部分のコメントは、残しておくことに注意します。

class User:
    """ユーザークラス
    """
    def __init__(self, name):
        self.name = name

class Group:
    """グループクラス
    """
    def __init__(self, name):
        self.name = name
        self.users = []

# ここにPermissionクラス
other.py(工夫したbase.pyからの編集)
class User:
    """ユーザークラス
    """
    def __init__(self, name):
        self.name = name

# ここにGroupクラス

class Permission:
    """パーミッションクラス
    """
    def __init__(self, name):
        self.name = name
KDiff3の場合(対象を工夫した場合の3-way merge)

KDiff3では、この場合、変更がうまく検出されてコンフリクト部分は自動解決されました。

P4Mergeの場合(対象を工夫した場合の3-way merge)

一方P4Mergeでは、大変残念な結果になりました。マージ結果にコメントが残ったり、チグハグな状態です。

P4Mergeだと、同じ内容の行が衝突の前後位置に来ると差分検出で同一と判定されてしまうのでしょうか?

結論

  • KDiff3とP4Mergeのどちらでも自動解決できないパターンはある。
  • KDiff3の場合、コメント等でマージのヒントになるキーワードを事前に入れておけば、自動解決できることがある。

やっぱりKDiff3を使い続けることになりそう。

Djangoで複数のデータベースを使う際に、モデルごとに使われるデータベース名を取得する

Djangoで複数のデータベースを使う際に、データベースルーター(Database router)を設定すると、モデル毎にデータベースを切り替えたりできます。
Multiple databases | Django documentation | Django
データベースルーターが設定されている場合に、アプリケーション側で任意のモデルがどのデータベースを使うのかを取得するには、django.db.routerを使います。
試したバージョンは、Python3.5、Django1.9。

コード

myapp/models.py
from django.db import models

class Item(models.Model):
    name = models.CharField(max_length=20)
myproject/router.py

myappという名前のアプリケーションの場合はspamdbを使うルールのデータベースルーター

class MyAppRouter:
    def db_for_read(self, model, **hints):
        if model._meta.app_label == 'myapp':
            return 'spamdb'

    def db_for_write(self, model, **hints):
        if model._meta.app_label == 'myapp':
            return 'spamdb'

    def allow_relation(self, obj1, obj2, **hints):
        if obj1._meta.app_label == 'myapp' or \
           obj2._meta.app_label == 'myapp':
           return True

    def allow_migrate(self, db, app_label, model=None, **hints):
        if app_label == 'myapp':
            return db == 'spamdb'
myproject/settings.py(抜粋)
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    },
    'spamdb': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'spamdb.sqlite3'),
    }
}

DATABASE_ROUTERS = ['myproject.router.MyAppRouter']

django.db.routerを使って、データベース名を取得してみる

django.dbモジュールのrouter変数は、django.db.utils.ConnectionRouterクラスのインスタンスです。

>>> from django.db import router
>>> from myapp.models import Item
>>> router.db_for_read(Item)
'spamdb'
>>> router.db_for_write(Item)
'spamdb'
>>> from django.contrib.auth.models import User
>>> router.db_for_read(User)
'default'
>>> router.db_for_write(User)
'default'

モデルから、使用するデータベース名を取得できました。
ちなみに、Manager.db_managerによって取得したMangerインスタンスの場合は、dbプロパティにてアクセスできます。

>>> User.objects.db_manager('spamdb').db
'spamdb'

複数のレコードをまとめて更新する際にdjango-bulk-updateが便利

既存の複数のレコードをまとめて更新する際にdjango-bulk-updateというパッケージが便利だったので紹介します。
GitHub - aykut/django-bulk-update: Bulk update using one query over Django ORM

通常のDjangoのORMを使ったレコードの更新

Djangoで既存のレコードを更新するには、モデルインスタンスのsaveメソッドを呼ぶか、クエリセットのupdateを使うのが通常の方法です(Django1.9時点)

# Itemモデル
class Item(models.Model):
    value = models.CharField(max_length=20)
    class Meta:
        db_table = 'item'

# 新規作成(id=1で保存)
Item.objects.create(pk=1, value="spam")

# id=1のレコードの更新(saveメソッド)
item = Item.objects.get(pk=1)
item.value = "更新された値"
item.save()

# id=1のレコードの更新(QuerySetのupdateメソッド)
Item.objects.filter(pk=1).update(value="更新された値1")

これは、バックエンドにもよりますが、例えばMySQLの場合だとUPDATEクエリが実行されます。
複数件をまとめて更新する場合に、更新後の値がすべて同じであれば、クエリは単純でDjangoの標準ORMでも対応できます。

# 新規作成(id=1, 2, 3で保存)
Item.objects.create(pk=1, value="spam")
Item.objects.create(pk=2, value="egg")
Item.objects.create(pk=3, value="ham")

# id=1, 2, 3のレコードの更新(すべて同じ値にする)
Item.objects.filter(pk__in=[1, 2, 3]).update(value="更新された値")

しかし、一件ごとに違う内容で保存したい場合には、モデルインスタンスのsaveメソッドか、前述のupdateメソッドで何度もUPDATEクエリを実行することになります。
大量に更新する場合には、これだと時間がかかってしまいます。

raw SQLで対応するならどうやるか

例えばMySQLの場合、レコードの更新方法だと、UPDATE文以外にREPLACE文などもあります。またLOAD DATA INFILEでREPLACEを行う方法もあります。
ただしこれは、既存のレコードを削除した上で更新となるため、一部の列の内容だけを更新したい場合には、事前にSELECTした値を使う必要があったりして少し使いづらいことがあります。
更新対象のテーブルに主キー、または一意なキーがある前提の場合、UPDATE文のSETの値部分にCASE演算子を指定することで、一回のクエリの発行でそれぞれ別の値を更新できます。

mysql> SELECT * FROM item;
+----+-------+
| id | value |
+----+-------+
|  1 | spam  |
|  2 | egg   |
|  3 | ham   |
+----+-------+
3 rows in set (0.00 sec)

mysql> UPDATE item SET value=(
    ->  CASE id
    ->   WHEN 1 THEN 'updated_spam'
    ->   WHEN 2 THEN 'updated_egg'
    ->   WHEN 3 THEN 'updated_ham'
    ->  END)
    ->  WHERE id in (1, 2, 3);
Query OK, 3 rows affected (0.01 sec)
Rows matched: 3  Changed: 3  Warnings: 0

mysql> SELECT * FROM item;
+----+--------------+
| id | value        |
+----+--------------+
|  1 | updated_spam |
|  2 | updated_egg  |
|  3 | updated_ham  |
+----+--------------+
3 rows in set (0.00 sec)

他にもまとめて更新する方法はあるかと思いますが、ここでは言及しません。

django-bulk-updateを使う

django-bulk-updateを使うと、前述のUPDATEのSET部分にCASE演算子を使うクエリの発行をSQLの記述無しでできます。
PyPI上のパッケージ名はdjango-bulk-updateです。
django-bulk-update · PyPI

(venv)$ pip install django-bulk-update

使い方は、Djangoの標準ORMのbulk_createに似ています。
Django1.9、Python3.5、django-bulk-update1.1.8で試しました。

from bulk_update.helper import bulk_update

def my_update_task():
    # 更新対象のインスタンスを用意(明示的に生成せずに、QuerySetで取得したインスタンスでも可)
    updates = [
        Item(pk=1, value="updated_spam"),
        Item(pk=2, value="updated_egg"),
        Item(pk=3, value="updated_ham"),
    ]
    # value列だけを更新
    bulk_update(updates, update_fields=['value'])
    # 結果を表示
    print(Item.objects.values_list())

実行結果は次の通り。

>>> my_update_task()
[(1, 'updated_spam'), (2, 'updated_egg'), (3, 'updated_ham')]

MySQLの場合、実行されたクエリは次の通りです。

UPDATE `item` SET `value` = (CASE `id` WHEN 1 THEN 'updated_spam' WHEN 2 THEN 'updated_egg' WHEN 3 THEN 'updated_ham' END) WHERE id in (1, 2, 3)

このようにbulk_updateを使うことで、1クエリで複数件の更新を簡単にできるようになりました。便利。

2019/2/28追記

Django 2.2で同等の機能となるbulk_updateメソッドが実装されます。

django.db.models.query.QuerySet.bulk_update

jedi-vimのrpcフォークをWindowsで使ってみた

メモ書き。jedi-vimのjedi部分をRPCで動かすforkを@nakamurayが作ってくれたのでWindowsで動かしてみてた。
https://github.com/nakamuray/jedi-vim/tree/rpc
https://github.com/nakamuray/jedi-vim/tree/rpc2
そのままではWindowsで動かなかったので、手元で少し修正して使った。

virtualenvでも問題なく動作しているのでありがたい。

Windowsで動かすために修正したところ

  • Windowsだとstderrを指定しないと例外が発生していたので指定した(os.devnull)
  • サブプロセスの起動時にコマンドプロンプトのウィンドウが表示されてしまうのでstartupinfoを指定

作業のお供にノンアルコールビール(主にドライゼロ)

この記事はpyspa Advent Calendar 2015の2日目の記事です。
Python温泉は主催者の意向により、酒を飲むのは禁止となってます。
ノンアルコールビールは、酒としてカウントされないのでOKとなっています 。
前回のPython温泉で、「ノンアルコールビールでもドライゼロはうまい」とのタレコミがあり、どれどれと飲んでみたところ、確かにうまい。
参加者たちの間で人気となり、熱海周辺のスーパーマーケットでドライゼロを買い占める集団となってしまいました。

自分も飲んでみて、「これは新しい選択肢だ」と思ったので、この記事で少し紹介したいと思います。

ビールと比べてどうなのか?

ドライゼロの成分表はウェブサイトで確認できます。
アサヒ ドライゼロ | アサヒビール
まず、ドライゼロについては、アルコール分が0.00%でノンアルコールです(0%じゃないノンアルコールもあるので注意しよう)
飲んでも酔いませんし、自動車の運転もできます。
カロリー、糖質も0gとなっています。
後味はすっきりです。ブラックのほうは少し癖がありました。
うまみについては、生ビールと比べると少し物足りなさを感じます。

清涼飲料水やいわゆるノンアルコールの飲み物としてはどうなのか?

では酒ではなく、コーヒーや紅茶、ジュースなどと比べるとどうなのか。
カロリー、糖質が0gとなっている飲み物はノンアルコールビール以外にもありますね。
ノンアルコールビールは、 甘くない(むしろ苦い) カテゴリの飲み物ですね。
コーヒーなどと比べると、 ノンカフェイン の飲み物でもあります。
また、炭酸飲料でもあります。

価格

酒ではないので酒税がかからないため、ビールよりは安いですが、同容量のジュースよりは高めになっているようです。
2015年現在、350ml缶で120円前後で販売されているようです。

飲み物の選択肢として

ノンカロリー、ノンカフェイン、甘くない、炭酸を含む、だとウィルキンソンの炭酸水などが該当しますが、ノンアルコールビールも同様に選択肢としてはありだと思います。

作業のお供に試してみてはいかがでしょうか?

プログラマーは飲み物をつい過剰摂取してしまいがちなデスクワークですし、この機会に飲み物について見なおしてみてはどうでしょうか?
炭酸水のような甘くない炭酸飲料に抵抗がないのであれば、是非ドライゼロを試してみることをおすすめします。