Azure AutomationでPython3
- ccf代表
- 2023年2月11日
- 読了時間: 5分
更新日:4月2日
Azure AutomationでPython3を動かす手順をご紹介します。難しい手順ではないですが、MSサイトは初学者には敷居が高いので。
はじめに
公式サイトは、こちらです。
https://learn.microsoft.com/ja-jp/azure/automation/python-3-packages?tabs=py3
2023年2月時点では、3.8がプレビュー版として利用可能ですが、5 つのリージョン (米国中西部、米国東部、南アフリカ北部、北ヨーロッパ、オーストラリア南東部) では3.10(プレビュー版)が利用できるようです。
利用にあたっての注意点は、以下の2つです。
パッケージ(モジュール)のインストールにおいて、依存関係を自動的に解消できない
バイナリーパッケージは適切に選ばないとインストールエラーになる
パッケージ(モジュール)の準備
通常はpipを使用して、Python Package Indexのレポジトリーから、パッケージをインストールしますが、pipコマンドを直接実行できないAutomationでは、事前にパッケージのインストールを行っておく必要があります。
ざっくりとした手順は以下のとおりです。
必要なパッケージをリストアップする
依存関係を調べるスクリプトを実行し、利用したいパッケージの依存関係も含めて、必要なパッケージを洗い出す
ソースパッケージやホイールパッケージを選択して、ダウンロードする
AutomationにPythonをパッケージを追加する
必要なパッケージのリストアップ
例えば、「Pandas」が必要だったとします。
以下のサイトから、リンクをたどって、必要なスクリプトをダウンロードします。
https://learn.microsoft.com/ja-jp/azure/automation/python-3-packages?tabs=py3#import-a-package-with-dependencies
このスクリプトは以下のようなコードです。
import_py3package_from_pypi.py
#!/usr/bin/env python3
"""
Imports python 3 packages from pypi.org
This Azure Automation runbook runs in Azure to import a package and its dependencies from pypi.org.
It requires the subscription id, resource group of the Automation account, Automation name, and package name as arguments.
Args:
subscription_id (-s) - Subscription id of the Automation account
resource_group (-g) - Resource group name of the Automation account
automation_account (-a) - Automation account name
module_name (-m) - Name of module to import from pypi.org
version (-v) - Version of module to be imported.
Imports module
Example:
import_python3package_from_pypi.py -s xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx -g contosogroup -a contosoaccount -m pytz -v 1.0.0
Changelog:
2020-12-29 AutomationTeam:
-Import Python 3 package with dependencies
"""
import requests
import subprocess
import json
import sys
import pip
import os
import re
import shutil
import json
import time
import getopt
import re
from pkg_resources import packaging
#region Constants
PYPI_ENDPOINT = "https://pypi.org/simple"
FILENAME_PATTERN = "[\\w]+"
def extract_and_compare_version(url, min_req_version):
try:
re.search('\d+(\.\d+)+', url).group(0)
except :
print ("Failed to extract and compare version URL %s min_req_versionor %s" % (url, min_req_version))
extracted_ver = re.search('\d+(\.\d+)+', url).group(0)
print ("Extracted version %s min_req_versionor %s" % (extracted_ver, min_req_version))
return packaging.version.parse(extracted_ver) >= packaging.version.parse(min_req_version)
def resolve_download_url(packagename, version):
response = requests.get("%s/%s" % (PYPI_ENDPOINT, packagename))
urls = re.findall(r'href=[\'"]?([^\'" >]+)', str(response.content))
for url in urls:
if 'cp38-win_amd64.whl' in url and version in url:
print ("Detected download uri %s for %s" % (url, packagename))
return(url)
for url in urls:
if 'py3-none-any.whl' in url and version in url:
print ("Detected download uri %s for %s" % (url, packagename))
return(url)
for url in urls:
if 'abi3-win_amd64.whl' in url and 'cp36' in url and version in url:
print ("Detected download uri %s for %s" % (url, packagename))
return(url)
for url in urls:
if 'cp38-win_amd64.whl' in url and extract_and_compare_version(url,version):
print ("Detected download uri %s for %s" % (url, packagename))
return(url)
for url in urls:
if 'py3-none-any.whl' in url and extract_and_compare_version(url,version):
print ("Detected download uri %s for %s" % (url, packagename))
return(url)
print("Could not find WHL from PIPI for package %s and version %s" % (packagename, version))
def send_webservice_import_module_request(packagename, download_uri_for_file):
request_url = "https://management.azure.com/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Automation/automationAccounts/%s/python3Packages/%s?api-version=2018-06-30" \
% (subscription_id, resource_group, automation_account, packagename)
requestbody = { 'properties': { 'description': 'uploaded via automation', 'contentLink': {'uri': "%s" % download_uri_for_file} } }
headers = {'Content-Type' : 'application/json', 'Authorization' : 'Bearer %s' % token}
r = requests.put(request_url, data=json.dumps(requestbody), headers=headers)
if str(r.status_code) not in ["200", "201"]:
raise Exception("Error importing package {0} into Automation account. Error code is {1}".format(packagename, str(r.status_code)))
def find_and_dependencies(packagename, version, dep_graph, dep_map):
dep_map.update({packagename: version})
for child in dep_graph:
if child['package']['key'].casefold() == packagename.casefold():
for dep in child['dependencies']:
version = dep['installed_version']
if version == '?' :
version = dep['required_version'][2:]
if "!" in version :
version = version .split('!')[0]
find_and_dependencies(dep['package_name'],version,dep_graph, dep_map)
subprocess.check_call([sys.executable, '-m', 'pip', 'install','pipdeptree'])
subprocess.check_call([sys.executable, '-m', 'pip', 'install','packaging'])
if __name__ == '__main__':
if len(sys.argv) < 9:
raise Exception("Requires Subscription id -s, Automation resource group name -g, account name -a, and module name -g as arguments. \
Example: -s xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx -g contosogroup -a contosoaccount -m pytz -v version")
# Process any arguments sent in
subscription_id = None
resource_group = None
automation_account = None
module_name = None
version_name = None
opts, args = getopt.getopt(sys.argv[1:], "s:g:a:m:v:")
for o, i in opts:
if o == '-s':
subscription_id = i
elif o == '-g':
resource_group = i
elif o == '-a':
automation_account = i
elif o == '-m':
module_name = i
elif o == '-v':
version_name = i
module_with_version = module_name + "==" + version_name
# Install the given module first
for i in (1,10):
try:
subprocess.check_call([sys.executable, '-m', 'pip', 'install', module_with_version])
break
except subprocess.CalledProcessError as e:
continue
result = subprocess.run(
[sys.executable, "-m", "pipdeptree","-j"], capture_output=True, text=True
)
dep_graph = json.loads(result.stdout)
dep_map = {}
find_and_dependencies(module_name,version_name,dep_graph,dep_map)
# Import package with dependencies from pypi.org
for module_name,version in dep_map.items():
download_uri_for_file = resolve_download_url(module_name, version)
依存関係の調査
このコードを以下のように実行して、パッケージの依存関係を調べます。
python import_py3package_from_pypi.py -s "AutomationのサブスクリプションID" -g "リソースグループ名" -a "Automationアカウント名" -m pandas -v 1.5.3
〜前半省略〜
Detected download uri https://files.pythonhosted.org/packages/ca/4e/d18db7d5ff9d28264cd2a7e2499b8701108f0e6c698e382cfd5d20685c21/pandas-1.5.3-cp38-cp38-win_amd64.whl#sha256=41179ce559943d83a9b4bbacb736b04c928b095b5f25dd2b7389eda08f46f373 for pandas
Detected download uri https://files.pythonhosted.org/packages/bf/8c/3d36cef521739bd481e9a5b30e5c0f9faf8b7fe7b904238368908a9d149d/numpy-1.24.2-cp38-cp38-win_amd64.whl#sha256=76807b4063f0002c8532cfeac47a3068a69561e9c8715efdad3c642eb27c0756 for numpy
Detected download uri https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl#sha256=961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 for python-dateutil
Detected download uri https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl#sha256=8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 for six
Detected download uri https://files.pythonhosted.org/packages/2e/09/fbd3c46dce130958ee8e0090f910f1fe39e502cc5ba0aadca1e8a2b932e5/pytz-2022.7.1-py2.py3-none-any.whl#sha256=78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a for pytz
パッケージの収集
上記のURLをダウンロードします。
wget
https://files.pythonhosted.org/packages/bf/8c/3d36cef521739bd481e9a5b30e5c0f9faf8b7fe7b904238368908a9d149d/numpy-1.24.2-cp38-cp38-win_amd64.whl#sha256=76807b4063f0002c8532cfeac47a3068a69561e9c8715efdad3c642eb27c0756;2D
--2023-02-11 10:55:39-- https://files.pythonhosted.org/packages/bf/8c/3d36cef521739bd481e9a5b30e5c0f9faf8b7fe7b904238368908a9d149d/numpy-1.24.2-cp38-cp38-win_amd64.whl
files.pythonhosted.org (files.pythonhosted.org) をDNSに問いあわせています... 151.101.89.63
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 14866419 (14M) [application/octet-stream]
numpy-1.24.2-cp38-cp38-win_amd64.w 100%[===============================================================>] 14.18M 38.4MB/s 時間 0.4s
2023-02-11 10:55:40 (38.4 MB/s) - `numpy-1.24.2-cp38-cp38-win_amd64.whl' へ保存完了 [14866419/14866419]
これを必要なパッケージ数分、実行します。
パッケージの追加
メニュー画面から以下のようにパッケージをインストールします。

これを必要な回数繰り返せば、以下のようにパッケージの追加が完了します。

Comments