明日ってゴミ出す日だっけ?、不燃ごみっていつだっけ?などいちいち確認がめんどくさく思い、LINEで、明日がゴミを出す日か、出すとしたら何のゴミかを通知するだけのものを作りました。
環境
macOS Big Sur Python 3.9.6 pytest 6.2.2 requests 2.25.1 LINE Notify Heroku
PythonでLINE Notifyで通知
今回はただメッセージを送るだけなので、LINE Notifyで通知をしたいと思います。
参考にした記事は以下です。
アクセストークンを取得するため、公式サイトから自分のLINEアカウントでログインし、マイページ > トークンを発行するでアクセストークンを取得します(下記画像)。発行したアクセストークンはどこかに保存しておきましょう。
ソースコード概要
自分の用意したファイル構成は以下のような感じです。dockerとかを用意していますが、結局使いませんでした。何となく、自分のローカルで試してみたかっただけなので、docker配下の説明は省きます。
NotificationGomidashi/ ├── Application │ ├── __init__.py │ ├── entity │ │ ├── __init__.py │ │ ├── calendar_entity.py │ │ └── garbage_removal_entity.py │ ├── main.py │ └── service │ ├── __init__.py │ └── notify_service.py ├── Procfile ├── docker │ ├── Dockerfile │ └── docker-compose.yml ├── requirements.txt ├── runtime.txt └── tests ├── __init__.py ├── entity │ ├── __init__.py │ ├── test_Calendar.py │ └── test_garbage_removal.py └── service └── __init__.py
main.pyについて
#coding:UTF-8 from service.notify_service import NotifyService as sv import requests def main(message): url = "https://notify-api.line.me/api/notify" token = "YOUR ACCESS TOKEN" headers = {"Authorization" : "Bearer "+ token} payload = {"message" : message} r = requests.post(url ,headers = headers ,params=payload) if __name__ == '__main__': sv = sv() n_th_week, week_day = sv.get_time() message = '\n明日は、第' + str(n_th_week) + week_day + '曜日\n' + sv.get_judge_case(n_th_week, week_day) main(message)
token
部分で先ほど発行したアクセストークンを貼り付けます。sv.get_time()
で明日がその月の第何週目か、何曜日かを取得してきてくれます。
notify_service.pyについて
from entity.calendar_entity import CalendarEntity as cal from entity.garbage_removal_entity import GarbageRemovalEntity as remo class NotifyService(object): # 現在日時を取得 def get_time(self): date = cal().get_now_time() week_day = self.get_week_day(date) n_th_week = self.get_n_th_week(date.day) return n_th_week, week_day # 曜日の取得 def get_week_day(self, date): return cal().get_week_day(date) # 月の第n週目を取得 def get_n_th_week(self, day): return cal().get_nth_week(day) def get_judge_case(self, n_th_week, week_day): return remo().judge_case(n_th_week, week_day)
notify_service.py
はentity
から必要な処理を呼び出し、値を受け取るだけの仕事を行なっています。データの加工などについてはentity
側だけの仕事にするようにしました。
calendar_entity.pyについて
from datetime import datetime, timedelta, timezone import calendar class CalendarEntity(object): def get_now_time(self): # タイムゾーンの生成 JST = timezone(timedelta(hours=+9), 'JST') # 現在日時 today = datetime.now(JST) # 次の日時 tomorrow = today + timedelta(days=1) return tomorrow # 月の第何週目かを返却 def get_nth_week(self, day): return (day - 1) // 7 + 1 def get_nth_dow(self): date = get_now_time() # 曜日(数値)を取得 week_day_number = calendar.weekday(date.year, date.month, date.day) return get_nth_week(date.day), get_week_day(week_day_number) def get_week_day(self, date): week_day = ['月', '火', '水', '木', '金', '土', '日'] week_day_number = calendar.weekday(date.year, date.month, date.day) return week_day[week_day_number]
calendar_entity.py
で明日が何週目か、何曜日かということを処理し、値を返しています。
garbage_removal_entity.pyについて
class GarbageRemovalEntity(object): def judge_case(self, n_th_week, week_day): garbage = ['可燃ごみ', '資源ごみ', '不燃ごみ', 'ゴミ出しなし'] if week_day == '水' or week_day == '土': return garbage[0] elif week_day == '火' : return garbage[1] elif (n_th_week == 2 or n_th_week == 4) and week_day == '月': return garbage[2] else: return garbage[3]
garbage_removal_entity.py
でserviceからjudge_case
のメソッドが呼び出された時にどのゴミを出すか、はたまた出さないのかという処理を行なっています。
Herokuにデプロイ
今回は作ったものをHerokuにデプロイし、定期的に実行するようにします。参考にした記事は以下です。
ログイン
まずはHerokuにログインしますが、HerokuCLIがインストールされていなければ、HerokuCLIをインストールしましょう。
Herokuでアカウントを作れば、brew
コマンドを使ってHerokuCLIをインストールする方法が出てくるはずです。
heroku login
Herokuへログインできたら、
heroku create アプリ名
でアプリを作ります。私は
heroku create garbage-notify
としました。
デプロイ前の準備
Herokuへデプロイする前にrequirements.txt
に必要なモジュールを、Procfile
にHerokuサーバーで実行する方法を、runtime.txt
でpythonのバージョンを書いておきます。
requirements.txt
requests
Procfile
# Procfile web: python Application/main.py
runtime.txt
python-3.9.6
Herokuへデプロイ
次にgit
を使ってHerokuへデプロイしていきます。
git add . git commit -m "make it better" git push heroku main
上記コマンドを上から順に実行していってください。最後のgit push heroku main
で人によってはgit push heroku master
などの可能性もあるので、どのブランチをHerokuにプッシュしたいかをしっかり把握しておいてください。
Herokuで動作確認
プッシュが終わったら、動作確認をしてみましょう。
heroku run python Application/main.py
として、LINE Notifyから通知メッセージが来ればOKです。
定期実行の設定
Herokuから定期的に実行してもらうために、Herokuにアドオンを追加します。この時、アドオンを使用するため、クレジットカードの登録が必要です。登録したところで勝手に有料プランになったり、チャージされることもありません。
クレジットカードを登録したら、ターミナルから
heroku addons:add scheduler:standard
と打ちます。そのあとはHerokuに移動し、下記画像のHeroku Schedulerを開きます。
ここで、Add new Job
を押下し、設定を行ってSave
を押せば定期実行になります。ここで、時間はUTCなので、もし日本時間で22時に実行して欲しい場合は、UTCでPM1時を設定します。
これで毎日夜の22時に明日は何のゴミを出すかを通知してくるアプリができました。
おまけ:pytestでテストする
以前業務でpytestを使っており、テストを書かないと和田卓人さんに怒られるので、せっかくなら使ってみようと思い、テストを書いてみました。
test_Calendar.pyについて
import pytest from datetime import datetime, timedelta, timezone from Application.entity.calendar_entity import CalendarEntity class TestCalendar(object): @classmethod def setup_class(cls): print('Test start') cls.entity = CalendarEntity() @classmethod def teardown_class(cls): print('Test finish') del cls.entity def setup_method(self, method): print('method={}'.format(method.__name__)) def teardown_method(self, method): print('method={}'.format(method.__name__)) def test_get_n_th_week(self): date = datetime(2021, 8, 1) actual = self.entity.get_nth_week(date.day) assert actual == 1 def test_get_week_day(self): date = datetime(2021, 8, 1) actual = self.entity.get_week_day(date) assert actual == '日' def test_get_now_time(self): # タイムゾーンの生成 JST = timezone(timedelta(hours=+9), 'JST') # 現在日時 today = datetime.now(JST) actualDate = self.entity.get_now_time() assert today < actualDate
大したことはやっていません。
test_get_n_th_week
で指定した日の週がその月の第何週目なのかを、
test_get_week_day
で指定した日の週の曜日が合っているかを、
test_get_now_time
でちゃんと明日の日付が取れているかを確認しています。
test_garbage_removal.pyについて
import pytest from Application.entity.garbage_removal_entity import GarbageRemovalEntity class TestGarbageRemoval(object): @classmethod def setup_class(cls): print('Test start') cls.entity = GarbageRemovalEntity() @classmethod def teardown_class(cls): print('Test finish') del cls.entity def setup_method(self, method): print('method={}'.format(method.__name__)) def teardown_method(self, method): print('method={}'.format(method.__name__)) def test_judge_case(self): actual1 = self.entity.judge_case(1, '水') actual2 = self.entity.judge_case(4, '土') actual3 = self.entity.judge_case(2, '火') actual4 = self.entity.judge_case(3, '火') actual5 = self.entity.judge_case(2, '月') actual6 = self.entity.judge_case(4, '月') actual7 = self.entity.judge_case(2, '木') actual8 = self.entity.judge_case(4, '日') assert actual1 == '可燃ごみ' assert actual2 == '可燃ごみ' assert actual3 == '資源ごみ' assert actual4 == '資源ごみ' assert actual5 == '不燃ごみ' assert actual6 == '不燃ごみ' assert actual7 == 'ゴミ出しなし' assert actual8 == 'ゴミ出しなし'
こちらも大したことはやっていません。月の何週目かと曜日を与えて、帰ってくる値が正しいかを判定しています。
実行方法はNotificationGomidashi
のところで、
pytest
と打てば、tests
配下のテストをしてくれます。