これまでのお話
Azure FunctionsのTimerTriggerの実行・開発環境を作りました。いよいよコードを書きます。
どんなTwitter botを作るのか
開発する場合、まずは全体を設計してから詳細の設計をし、次いで実装するものですが、今回は技術調査もかねて作りながら考えていこうと思います。当方はAzure初心者ですし。Azureのことを理解しつつ全体像を考えることにします。
まずはツィートしてみる
まずはツィートしてみましょう。そのほうが楽しいでしょうし。
認証のための情報を保存する
Twitter APIを使用するための認証情報を保存します。保存する場所はAzure Functionsが提供してくれます。
デバッグ実行の場合
local.settings.json
にTwitter APIへの認証情報を保存しましょう。json
のキーは当方で適当に決めています。
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": <Azure Storage Accountの接続文字列>,
"FUNCTIONS_WORKER_RUNTIME": "python",
"TWITTER_ACCESS_TOKEN": <アクセストークン>,
"TWITTER_TOKEN_SECRET": <アクセストークンのシークレット>,
"TWITTER_API_KEY": <APIキー>,
"TWITTER_API_SECRET_KEY": <APIキーのシークレット>
}
}
余談ですが、.gitignore
で誤ってGitリポジトリにコミットされないように記載があります。
# Azure Functions artifacts
bin
obj
appsettings.json
local.settings.json
.python_packages
本番環境の場合
Azure Portalで該当Azure Functionsを選択します。ナビゲーションバーから構成
を選択してください。次のような表示がされると思います。
新しいアプリケーション設定
を押下して4つのキーと値を入力してください。その後、保存
ボタンを押下するとAzure Functionsが再起動して環境変数として扱えるようになります。
Twitter APIの呼び出しを共通化する
一般的に言って、共通化は必要になったときに共通にしたほうが良いと思います。今回は別途開発していたTwitter APIのHelper関数を持っているのでそれを使いたいというのと、OAuth
による認証は共通化したくなることが見えているので先にやってしまします。(単純に興味があるというのが本音です。好奇心には勝てない。遊びですしね。笑)
共通化のやり方はこちらのページに書いてあるのでそれに従います。Azure Functionsの流儀に従い共通化は関数で行い、1ファイル1関数にするのがよさそうです。
まずはルートにshared_code
というフォルダを追加します。
shared_code/ フォルダーには、絶対インポート構文を使用するときに Python パッケージとしてマークするための __init__.py ファイルが含まれている必要があります。
とのことです。日本語がおかしいので意味がよくわからないですが、__init__.py
ファイルが必要のようです。上記のページに従って次の内容の__init__.py
ファイルを作りましょう。
# <project_root>/shared_code/__init__.py
# Empty __init__.py file marks shared_code folder as a Python package
次にTwitter APIを使用するためのOAuthセッションを生成するヘルパースクリプトを作成しました。先ほどlocal.settings.jsonやAzure Functionsの構成で設定した環境変数から値を読み込んでいるのがポイントです。
# <project_root>/shared_code/twitter_oauth_helper.py
from requests_oauthlib import OAuth1Session
from os import environ
def create_session():
token = environ['TWITTER_ACCESS_TOKEN']
token_secret = environ['TWITTER_TOKEN_SECRET']
consumer_key = environ['TWITTER_API_KEY']
consumer_secret = environ['TWITTER_API_SECRET_KEY']
return OAuth1Session(consumer_key, consumer_secret, token, token_secret)
requests_oauthlib
が見つからないと思うのでrequirements.txt
に追加します。ライブラリのバージョンは読者の環境に合わせて設定してください。今回は1.3.0
にしていますが執筆時の最新バージョンです。読者の環境に応じてバージョンをご指定ください。
# DO NOT include azure-functions-worker in this file
# The Python Worker is managed by Azure Functions platform
# Manually managing azure-functions-worker may cause unexpected issues
azure-functions
requests_oauthlib==1.3.0
次にツィートするヘルパースクリプトを作成します。Twitter APIのドキュメントは公式ページをご覧ください。
# <project_root>/shared_code/twitter_update_helper.py
import json
from shared_code import twitter_oauth_helper
class Param:
def __init__(self, status):
self._status= status
def get_status(self):
return self._status
def request(param):
endpoint_url = 'https://api.twitter.com/1.1/statuses/update.json'
client = twitter_oauth_helper.create_session()
params = {}
params['status'] = param.get_status()
res = client.post(endpoint_url, params=params)
if res.status_code == 200:
res = json.loads(res.text)
else:
res = json.loads(res.text)
return res
共通化した関数を呼び出してみる
TimerTriggerTweet
関数から共通化した関数を呼び出してみます。
# <project_root>/TimerTriggerTweet/__init__.py
import datetime
import logging
import azure.functions as func
from shared_code import twitter_update_helper
def main(mytimer: func.TimerRequest) -> None:
utc_timestamp = datetime.datetime.utcnow().replace(
tzinfo=datetime.timezone.utc).isoformat()
if mytimer.past_due:
logging.info('The timer is past due!')
logging.info('Python timer trigger function ran at %s', utc_timestamp)
param = twitter_update_helper.Param('ほげ')
twitter_update_helper.request(param)
logging.info('Python timer trigger function exit at %s', utc_timestamp)
デバッグ実行したらModuleNotFoundError発生
F5を押下してデバッグ実行したところ、ModuleNotFoundError
エラーが発生しました。requests_oauthlib
が見つからないようです。
> Executing task: .venv\Scripts\python -m pip install -r requirements.txt <
Requirement already satisfied: azure-functions in simple-twitter-bot\.venv\lib\site-packages (from -r requirements.txt (line 5)) (1.7.0)
Collecting requests-oauthlib
Downloading requests_oauthlib-1.3.0-py2.py3-none-any.whl (23 kB)
Collecting requests>=2.0.0
Downloading requests-2.25.1-py2.py3-none-any.whl (61 kB)
|████████████████████████████████| 61 kB 2.0 MB/s
Collecting oauthlib>=3.0.0
Downloading oauthlib-3.1.1-py2.py3-none-any.whl (146 kB)
|████████████████████████████████| 146 kB 6.4 MB/s
Collecting certifi>=2017.4.17
Downloading certifi-2021.5.30-py2.py3-none-any.whl (145 kB)
|████████████████████████████████| 145 kB 3.2 MB/s
Collecting idna<3,>=2.5
Downloading idna-2.10-py2.py3-none-any.whl (58 kB)
|████████████████████████████████| 58 kB 2.9 MB/s
Collecting chardet<5,>=3.0.2
Downloading chardet-4.0.0-py2.py3-none-any.whl (178 kB)
|████████████████████████████████| 178 kB 3.3 MB/s
Collecting urllib3<1.27,>=1.21.1
Downloading urllib3-1.26.5-py2.py3-none-any.whl (138 kB)
|████████████████████████████████| 138 kB 3.3 MB/s
Installing collected packages: certifi, idna, chardet, urllib3, requests, oauthlib, requests-oauthlib
Successfully installed certifi-2021.5.30 chardet-4.0.0 idna-2.10 oauthlib-3.1.1 requests-2.25.1 requests-oauthlib-1.3.0 urllib3-1.26.5
WARNING: You are using pip version 20.1.1; however, version 21.1.2 is available.
You should consider upgrading via the 'simple-twitter-bot\.venv\Scripts\python.exe -m pip install --upgrade pip' command.
Terminal will be reused by tasks, press any key to close it.
> Executing task: .venv\Scripts\activate ; func host start <
Found Python version 3.7.9 (python3).
Azure Functions Core Tools
Core Tools Version: 3.0.3477 Commit hash: 5fbb9a76fc00e4168f2cc90d6ff0afe5373afc6d (64-bit)
Function Runtime Version: 3.0.15584.0
Functions:
TimerTriggerTweet: timerTrigger
For detailed output, run func with --verbose flag.
[2021-06-24T13:22:07.244Z] Executing 'Functions.TimerTriggerTweet' (Reason='Timer fired at 2021-06-24T22:22:07.2118593+09:00', Id=e80707da-4241-46e3-9fb4-62ab4d927465)
[2021-06-24T13:22:07.247Z] Trigger Details: UnscheduledInvocationReason: IsPastDue, OriginalSchedule: 2021-06-24T22:00:00.0000000+09:00
[2021-06-24T13:22:08.091Z] Worker process started and initialized.
[2021-06-24T13:22:08.454Z] The timer is past due!
[2021-06-24T13:22:08.459Z] Python timer trigger function ran at 2021-06-24T13:22:08.451326+00:00
[2021-06-24T13:22:08.533Z] Executed 'Functions.TimerTriggerTweet' (Failed, Id=e80707da-4241-46e3-9fb4-62ab4d927465, Duration=1308ms)
[2021-06-24T13:22:08.535Z] System.Private.CoreLib: Exception while executing function: Functions.TimerTriggerTweet. System.Private.CoreLib: Result: Failure
Exception: ModuleNotFoundError: No module named 'requests_oauthlib'
Stack: File "C:\Program Files\Microsoft\Azure Functions Core Tools\workers\python\3.7/WINDOWS/X64\azure_functions_worker\dispatcher.py", line 402, in _handle__invocation_request
invocation_id, fi_context, fi.func, args)
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.7_3.7.2544.0_x64__qbz5n2kfra8p0\lib\concurrent\futures\thread.py", line 57, in run
result = self.fn(*self.args, **self.kwargs)
File "C:\Program Files\Microsoft\Azure Functions Core Tools\workers\python\3.7/WINDOWS/X64\azure_functions_worker\dispatcher.py", line 604, in _run_sync_func
func)(params)
File "C:\Program Files\Microsoft\Azure Functions Core Tools\workers\python\3.7/WINDOWS/X64\azure_functions_worker\extension.py", line 215, in _raw_invocation_wrapper
result = function(**args)
File "simple-twitter-bot\TimerTriggerTweet\__init__.py", line 19, in main
twitter_update_helper.request(param)
File "simple-twitter-bot\shared_code\twitter_update_helper.py", line 16, in request
client = twitter_oauth_helper.create_session()
File "simple-twitter-bot\shared_code\twitter_oauth_helper.py", line 5, in create_session
from requests_oauthlib import OAuth1Session
Microsoftがトラブル・シューティングのページを用意してくれていました。しかしながら、今回の問題の解決には至りませんでした。
問題の切り分け、venv環境で実行
Power Shellからvenv環境を立ち上げ、Pythonインタプリタからrequests_oauthlib
をインクルードしてオブジェクトを作ったところ、生成できました。venv環境ではライブラリrequests_oauthlib
は見えているようです。
PS simple-twitter-bot> .\.venv\Scripts\Activate.ps1
(.venv) PS simple-twitter-bot> python
Python 3.7.9 (tags/v3.7.9:13c94747c7, Aug 17 2020, 16:30:00) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests_oauthlib
>>> requests_oauthlib.OAuth1Session('', '', '', '')
<requests_oauthlib.oauth1_session.OAuth1Session object at 0x0000020F7D41A248>
>>>
問題の切り分け、Power ShellからAzure Core Toolsを直接実行
Power Shellからvenvを起動したのちに、func host start
コマンドでAzure Functions Core Toolsを直接たたいてみたところ同じエラーが発生しました。Azure Core Toolsが pip
でインストールしたライブラリを見つけられていないようです。
PS simple-twitter-bot> .\.venv\Scripts\Activate.ps1
(.venv) PS simple-twitter-bot> func host start
Found Python version 3.7.9 (python3).
Azure Functions Core Tools
Core Tools Version: 3.0.3477 Commit hash: 5fbb9a76fc00e4168f2cc90d6ff0afe5373afc6d (64-bit)
Function Runtime Version: 3.0.15584.0
Functions:
TimerTriggerTweet: timerTrigger
For detailed output, run func with --verbose flag.
[2021-06-27T15:49:36.349Z] Worker process started and initialized.
[2021-06-27T15:49:41.812Z] Host lock lease acquired by instance ID '000000000000000000000000919C9F3E'.
[2021-06-27T16:00:00.039Z] Executing 'Functions.TimerTriggerTweet' (Reason='Timer fired at 2021-06-28T01:00:00.0200726+09:00', Id=5b23d7b2-8578-422c-9a76-39369e14c7bc)
[2021-06-27T16:00:00.071Z] Python timer trigger function ran at 2021-06-27T16:00:00.070155+00:00
[2021-06-27T16:00:00.129Z] Executed 'Functions.TimerTriggerTweet' (Failed, Id=5b23d7b2-8578-422c-9a76-39369e14c7bc, Duration=99ms)
[2021-06-27T16:00:00.132Z] System.Private.CoreLib: Exception while executing function: Functions.TimerTriggerTweet. System.Private.CoreLib: Result: Failure
Exception: ModuleNotFoundError: No module named 'requests_oauthlib'
Stack: File "C:\Program Files\Microsoft\Azure Functions Core Tools\workers\python\3.7\WINDOWS\X64\azure_functions_worker\dispatcher.py", line 402, in _handle__invocation_request
invocation_id, fi_context, fi.func, args)
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.7_3.7.2544.0_x64__qbz5n2kfra8p0\lib\concurrent\futures\thread.py", line 57, in run
result = self.fn(*self.args, **self.kwargs)
File "C:\Program Files\Microsoft\Azure Functions Core Tools\workers\python\3.7\WINDOWS\X64\azure_functions_worker\dispatcher.py", line 604, in _run_sync_func
func)(params)
File "C:\Program Files\Microsoft\Azure Functions Core Tools\workers\python\3.7\WINDOWS\X64\azure_functions_worker\extension.py", line 215, in _raw_invocation_wrapper
result = function(**args)
File "simple-twitter-bot\TimerTriggerTweet\__init__.py", line 19, in main
twitter_update_helper.request(param)
File "simple-twitter-bot\shared_code\twitter_update_helper.py", line 16, in request
client = twitter_oauth_helper.create_session()
File "simple-twitter-bot\shared_code\twitter_oauth_helper.py", line 5, in create_session
from requests_oauthlib import OAuth1Session
.
Azure Functions Core Toolsの使い方はこちらをご覧ください。
エラーをよく見ると .venv
のPythonではなく、当方の開発PCにインストールしているPythonを参照しているように見えます。 .venv
環境には requests_oauthlib
をインストールしていないので見つからないのは当然ですが、 .venv
環境を作成して Activate.ps1
スクリプトで仮想環境を有効にしているのになぜにホストPCの環境を参照するのかはよくわかりません。
公式の開発者ガイドの パッケージの管理
には Azure Functions Core Tools または Visual Studio Code を使用してローカルで開発を行う場合は、必要なパッケージの名前とバージョンを requirements.txt ファイルに追加し、pip を使用してそれらをインストールしてください。
と記載があるのでそういうもののようです。
次のコマンドで必要なライブラリをインストールします。
PS simple-twitter-bot> pip install -r .\requirements.txt
Collecting azure-functions==1.7.0
Using cached azure_functions-1.7.0-py3-none-any.whl (135 kB)
Collecting requests-oauthlib==1.3.0
Using cached requests_oauthlib-1.3.0-py2.py3-none-any.whl (23 kB)
Collecting requests>=2.0.0
Using cached requests-2.27.0-py2.py3-none-any.whl (63 kB)
Collecting oauthlib>=3.0.0
Using cached oauthlib-3.1.1-py2.py3-none-any.whl (146 kB)
Collecting charset-normalizer~=2.0.0
Using cached charset_normalizer-2.0.10-py3-none-any.whl (39 kB)
Collecting certifi>=2017.4.17
Downloading certifi-2021.10.8-py2.py3-none-any.whl (149 kB)
|████████████████████████████████| 149 kB 3.3 MB/s
Collecting idna<4,>=2.5
Downloading idna-3.3-py3-none-any.whl (61 kB)
|████████████████████████████████| 61 kB 4.0 MB/s
Collecting urllib3<1.27,>=1.21.1
Downloading urllib3-1.26.7-py2.py3-none-any.whl (138 kB)
|████████████████████████████████| 138 kB ...
Installing collected packages: urllib3, idna, charset-normalizer, certifi, requests, oauthlib, requests-oauthlib, azure-functions
WARNING: The script normalizer.exe is installed in 'C:\Users\XXX\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0\LocalCache\local-packages\Python37\Scripts' which is not on PATH.
Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
Successfully installed azure-functions-1.7.0 certifi-2021.10.8 charset-normalizer-2.0.10 idna-3.3 oauthlib-3.1.1 requests-2.27.0 requests-oauthlib-1.3.0 urllib3-1.26.7
WARNING: You are using pip version 21.1.2; however, version 21.3.1 is available.
You should consider upgrading via the 'C:\Users\XXX\AppData\Local\Microsoft\WindowsApps\PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0\python.exe -m pip install --upgrade pip' command.
PS simple-twitter-bot>
> Executing task: .venv\Scripts\python -m pip install -r requirements.txt <
Requirement already satisfied: azure-functions==1.7.0 in simple-twitter-bot\.venv\lib\site-packages (from -r requirements.txt (line 5)) (1.7.0)
Requirement already satisfied: requests-oauthlib==1.3.0 in simple-twitter-bot\.venv\lib\site-packages (from -r requirements.txt (line 6)) (1.3.0)
Requirement already satisfied: requests>=2.0.0 in simple-twitter-bot\.venv\lib\site-packages (from requests-oauthlib==1.3.0->-r requirements.txt (line 6)) (2.27.0)
Requirement already satisfied: oauthlib>=3.0.0 in simple-twitter-bot\.venv\lib\site-packages (from requests-oauthlib==1.3.0->-r requirements.txt (line 6)) (3.1.1)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in simple-twitter-bot\.venv\lib\site-packages (from requests>=2.0.0->requests-oauthlib==1.3.0->-r requirements.txt (line 6)) (1.26.5)
Requirement already satisfied: charset-normalizer~=2.0.0; python_version >= "3" in simple-twitter-bot\.venv\lib\site-packages (from requests>=2.0.0->requests-oauthlib==1.3.0->-r requirements.txt (line 6)) (2.0.10)
Requirement already satisfied: idna<4,>=2.5; python_version >= "3" in simple-twitter-bot\.venv\lib\site-packages (from requests>=2.0.0->requests-oauthlib==1.3.0->-r requirements.txt (line 6)) (2.10)
Requirement already satisfied: certifi>=2017.4.17 in simple-twitter-bot\.venv\lib\site-packages (from requests>=2.0.0->requests-oauthlib==1.3.0->-r requirements.txt (line 6)) (2021.5.30)
WARNING: You are using pip version 20.1.1; however, version 21.3.1 is available.
You should consider upgrading via the 'simple-twitter-bot\.venv\Scripts\python.exe -m pip install --upgrade pip' command.
Terminal will be reused by tasks, press any key to close it.
> Executing task: .venv\Scripts\activate ; func host start <
Found Python version 3.7.9 (python3).
Azure Functions Core Tools
Core Tools Version: 4.0.3971 Commit hash: d0775d487c93ebd49e9c1166d5c3c01f3c76eaaf (64-bit)
Function Runtime Version: 4.0.1.16815
Functions:
TimerTriggerTweet: timerTrigger
For detailed output, run func with --verbose flag.
[2022-01-05T12:44:22.529Z] Executing 'Functions.TimerTriggerTweet' (Reason='Timer fired at 2022-01-05T21:44:22.4760677+09:00', Id=57b2b437-f64b-4e81-843f-a6657456c2d5)
[2022-01-05T12:44:22.534Z] Trigger Details: UnscheduledInvocationReason: RunOnStartup
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/2 POST http://127.0.0.1:56818/AzureFunctionsRpcMessages.FunctionRpc/EventStream application/grpc -
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'gRPC - /AzureFunctionsRpcMessages.FunctionRpc/EventStream'
[2022-01-05T12:44:23.825Z] Worker process started and initialized.
[2022-01-05T12:44:24.526Z] Python timer trigger function ran at 2022-01-05T12:44:24.497660+00:00
[2022-01-05T12:44:24.830Z] Python timer trigger function exit at 2022-01-05T12:44:24.497660+00:00
[2022-01-05T12:44:24.880Z] Executed 'Functions.TimerTriggerTweet' (Succeeded, Id=57b2b437-f64b-4e81-843f-a6657456c2d5, Duration=2381ms)
[2022-01-05T12:44:26.706Z] Host lock lease acquired by instance ID '000000000000000000000000919C9F3E'.
無事ツィートが成功しました。
しかし、実行ログを見ると冒頭 .venv
環境で requirements.txt
のライブラリをインストールしているように見えますが、あれはいったい何なのでしょうか?開設できる方がいらっしゃいましたらぜひともご教授ください。
まとめ
かなり納得がいかないですが、何とかローカル環境でツィートできるようになりました。次回はいよいよAzure Functionsにデプロイしてみようと思います。
おまけ
.python_packagesの作り方
.gitignore
ファイルに.python_packages
の記載があったので、.python_packages
を作ればうまくいくかと思い試しましたが、ダメでした。忘備録もかねてどんなことをしたかを残します。こんな感じのコマンドで作成できるようです。
PS simple-twitter-bot> pip install --target=".python_packages/lib/site-packages" -r requirements.txt
ERROR: Can not combine '--user' and '--target'
PS C:\Users\Yusuk\Development\simple-twitter-bot> pip install --target=".python_packages/lib/site-packages" -r requirements.txt --no-user
TimerTriggerのデバッグTips
タイマートリガーはその時間にならないと動作しません。TimerTriggerの関数が実装されているフォルダに保存されている function.json
に次のキーと値を記述すると起動直後に動作してくれます。誤ってコミットしないようにご注意ください。
"runOnStartup": true