[Python] luigiのTask#outputをopenで書き込まない場合

[Python] luigiのTask#outputをopenで書き込まない場合

ちょっとニッチな状況でハマったのでメモ

runでファイルコピーするだけのタスクを作った時、ファイルコピーを以下のようにやってしまった。


import shutil from luigi import Task class UserTask(Task): def run(self): shutil.copy('<source path>', self.output().path) def output(self): return LocalTarget('<output path>')

ディレクトリを自動で作ってくれない

shutil.copyでコピーしているので当然と言えば当然だが、outputの親ディレクトリを自動で作成はしてくれない。
self.output().makedirs()を明示的に実行する必要がある。

    def run(self):
        self.output().makedirs()
        shutil.copy('<source path>', self.output().path)

self.output().makedirs()self.output().open('w')で書き込む時は内部的に自動で呼んでくれる。なのでshutil.copyではなく直接書き込めばmakedirs()を明示的に呼ぶ必要はない。

    def run(self):
        with self.output.oepn('w') as output, open('<source path>', 'r') as input:
            output.write(input.read())

ユニットテスト時にMockTargetでパッチできない

パッチできないわけではないけど、意図した動きにならなかった。

# !/usr/bin/env python3
# coding: utf-8

import unittest
from unittest import mock
from luigi import Task
from luigi import build, LocalTarget
from luigi.mock import MockTarget

class UserTask(Task):

    def run(self):
        self.output().makedirs()
        shutil.copy('<source path>', self.output().path)

    def output(self):
        return LocalTarget('<output path>')


def mocked_output(*args, **kwargs):
    m = MockTarget('mock_target')
    m.makedirs = lambda: True
    return m


class TestUserTask(unittest.TestCase):

    @mock.patch('__main__.UserTask.output', side_effect=mocked_output)
    def test_method(self, m_mock):
        """ test file copy """
        result = build([UserTask()], local_scheduler=True)
        self.assertTrue(result)


if __name__ == "__main__":
    unittest.main()

LocalTargetMockTargetに置き換えるようoutputをモックでパッチしたかった。

しかしoutputファイルの書き出しはself.output().openでやっているわけではないので、MockTargetにしたにも関わらずファイルの実体が書き出されてしまう。

ここもshutil.copyではなくself.output().open('w')でやれば問題なかった。

パッチはするけどLocalTargetで

モックでパッチするけど、MockTargetではなくLocalTargetis_tmpする形にしている。

def mocked_output(*args, **kwargs):
    m = LocalTarget(is_tmp=True)
    m.makedirs = lambda: True
    return m

また、makedirsが呼ばれるとディレクトリを作ったしまうので、適当なメソッド(上記ではラムダ)で上書きしておく

まとめ

  • outputのターゲットは、ちゃんとself.output().open('w')で書かないと意図しない動作することがあるよ!

実行環境

  • Windows 10
  • Python 3.6.3
  • luigi 2.7.2
%d人のブロガーが「いいね」をつけました。