【Python】cronを生成するモジュールを作った

Code

はじまり

リサちゃん
リサちゃん

うわ〜、cronをいちいち設定するのがめんどくせええ。

135ml
135ml

あー、cronって時差を考慮したり、どの*が分だっけ、曜日だっけ

って感じで、めんどいよね。

リサちゃん
リサちゃん

よし、今回はcronを生成するモジュールを作ってしまおうじゃないか!

135ml
135ml

お!作ってくれるか!いけいけ!

今回作ったモジュール

今回作ったモジュールは自分用ライブラリの中に作りました。ちゃんと見たい方は、このリポジトリにあります。

GitHub - landmaster135/landmasterlibrary
Contribute to landmaster135/landmasterlibrary development by creating an account on GitHub.

ソースはこちらになります。

generaltool.py

import datetime

def generate_cron_from_datetime_now(minutes_scheduled_later : int, time_difference : int = 0) -> str:
    """
    e.g.
    '0 19 * * *'  # At 04:00. – https://crontab.guru"
    """
    if type(minutes_scheduled_later) != int:
        raise TypeError("TypeError: minutes_scheduled_later must be int type.")
    if type(time_difference) != int:
        raise TypeError("TypeError: time_difference must be int type.")
    cron_minute = 0
    cron_hour = 0
    dt_now = datetime.datetime.now(
        datetime.timezone(datetime.timedelta(hours=time_difference))
    )
    minutes_from_an_hour = 60
    if dt_now.minute + minutes_scheduled_later >= minutes_from_an_hour:
        cron_minute = dt_now.minute + minutes_scheduled_later - minutes_from_an_hour
        cron_hour = dt_now.hour + 1
    else:
        cron_minute = dt_now.minute + minutes_scheduled_later
        cron_hour = dt_now.hour
    hours_from_a_day = 24
    if cron_hour >= hours_from_a_day:
        cron_hour = cron_hour - hours_from_a_day

    cron = f"{cron_minute} {cron_hour} * * *"
    return cron

実は、GitHub Actions内でcronを設定する際、時差の考慮が要らない?

cronを設定する時に、時差の考慮は必須です。

例えば、日本時間で生活している方は、時差を+9:00としてcronを生成するかどうかを考慮すべきでしょう。本モジュールですと、例えば、こんな風に時差を設けます。

minutes_scheduled_later = 10
time_difference = 9
cron = generate_cron_from_datetime_now(minutes_scheduled_later, time_difference)

しかしながら、サーバ時間が”Asia/Tokyo”に設定してあれば、おそらく必要ないでしょう。

そして、そこに加えて、GitHub Actions内でcronを設定する際は、時差の考慮が不要のようです。

例えば、この記事で紹介しているツールは、GitHub Actions内でPythonファイルを実行して、本モジュールから時差0:00(UTC)でcronを設定しています。

しかし、GitHub ActionsもUTCで実行されているみたいなので、GitHub Actions内のコンテナ内のOS時間をUTCにしていれば、cronのずれは発生しないということです。

なので、こんな感じで、time_difference(時差)を0にしてcronを設定します。

minutes_scheduled_later = 10
time_difference = 0
cron = generate_cron_from_datetime_now(minutes_scheduled_later, time_difference)

テストコード

今回のテストは、pytest-freezegunを使用しました。とても使いやすかったので、おすすめです。

shell session

pip install pytest-freezegun

テストコードはこちらになります。

test_generaltool.py

import pytest

from src.landmasterlibrary.generaltool import generate_cron_from_datetime_now

class Test_Generaltool:

    @pytest.mark.freeze_time("2022-01-12 13:23:43")
    def test_generate_cron_from_datetime_now_1_1(self):
        minutes_scheduled_later = 10
        time_difference = 0
        actual = generate_cron_from_datetime_now(minutes_scheduled_later, time_difference)
        expected_hours = 13
        expected_minutes = 33
        expected = f"{expected_minutes} {expected_hours} * * *"
        assert actual == expected

    @pytest.mark.freeze_time("2022-01-12 13:53:43")
    def test_generate_cron_from_datetime_now_1_2(self):
        minutes_scheduled_later = 10
        time_difference = 0
        actual = generate_cron_from_datetime_now(minutes_scheduled_later, time_difference)
        expected_hours = 14
        expected_minutes = 3
        expected = f"{expected_minutes} {expected_hours} * * *"
        assert actual == expected

    @pytest.mark.freeze_time("2022-01-12 23:53:43")
    def test_generate_cron_from_datetime_now_1_3(self):
        minutes_scheduled_later = 10
        time_difference = 0
        actual = generate_cron_from_datetime_now(minutes_scheduled_later, time_difference)
        expected_hours = 0
        expected_minutes = 3
        expected = f"{expected_minutes} {expected_hours} * * *"
        assert actual == expected

    @pytest.mark.freeze_time("2022-01-12 13:23:43")
    def test_generate_cron_from_datetime_now_2_1(self):
        minutes_scheduled_later = 10
        time_difference = 2
        actual = generate_cron_from_datetime_now(minutes_scheduled_later, time_difference)
        expected_hours = 15
        expected_minutes = 33
        expected = f"{expected_minutes} {expected_hours} * * *"
        assert actual == expected

    @pytest.mark.freeze_time("2022-01-12 13:23:43")
    def test_generate_cron_from_datetime_now_2_2(self):
        minutes_scheduled_later = 10
        time_difference = -2
        actual = generate_cron_from_datetime_now(minutes_scheduled_later, time_difference)
        expected_hours = 11
        expected_minutes = 33
        expected = f"{expected_minutes} {expected_hours} * * *"
        assert actual == expected

    @pytest.mark.freeze_time("2022-01-12 13:23:43")
    def test_generate_cron_from_datetime_now_3_1(self):
        minutes_scheduled_later = 10
        actual = generate_cron_from_datetime_now(minutes_scheduled_later)
        expected_hours = 13
        expected_minutes = 33
        expected = f"{expected_minutes} {expected_hours} * * *"
        assert actual == expected

    @pytest.mark.freeze_time("2022-01-12 23:53:43")
    def test_generate_cron_from_datetime_now_4_1(self):
        minutes_scheduled_later = "10"
        time_difference = 0
        with pytest.raises(TypeError) as e:
            actual = generate_cron_from_datetime_now(minutes_scheduled_later, time_difference)

    @pytest.mark.freeze_time("2022-01-12 23:53:43")
    def test_generate_cron_from_datetime_now_4_2(self):
        minutes_scheduled_later = 10
        time_difference = "0"
        with pytest.raises(TypeError) as e:
            actual = generate_cron_from_datetime_now(minutes_scheduled_later, time_difference)

    @pytest.mark.freeze_time("2022-01-12 23:53:43")
    def test_generate_cron_from_datetime_now_4_3(self):
        minutes_scheduled_later = None
        time_difference = 0
        with pytest.raises(TypeError) as e:
            actual = generate_cron_from_datetime_now(minutes_scheduled_later, time_difference)

    @pytest.mark.freeze_time("2022-01-12 23:53:43")
    def test_generate_cron_from_datetime_now_4_4(self):
        minutes_scheduled_later = 10
        time_difference = None
        with pytest.raises(TypeError) as e:
            actual = generate_cron_from_datetime_now(minutes_scheduled_later, time_difference)

    @pytest.mark.freeze_time("2022-01-12 23:53:43")
    def test_generate_cron_from_datetime_now_5_1(self):
        with pytest.raises(TypeError) as e:
            actual = generate_cron_from_datetime_now()

おしまい

135ml
135ml

pytest-freezegunとは、また良いモジュールを見つけたね。

楽に、datetimeをモック出来るよね。

リサちゃん
リサちゃん

うん。スゴイ楽! 簡単だからテストコードも見やすいし。

これからもドンドン使っていこー!

135ml
135ml

ちなみに、cronの行でチラホラ出てきてたURLだけど、cronの設定方法を忘れてた時に役立つので、よければご参考ください。

Crontab.guru - The cron schedule expression editor
An easy to use editor for crontab schedules.

あと、このページにあるcronチートシートもコメントアウトすれば、ソース内で使えると思うので、それもアリかも。

Github Actions チートシート

以上になります!

コメント

タイトルとURLをコピーしました