top of page
  • ccf代表

Azure AutomationでPython3

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つです。

  1. パッケージ(モジュール)のインストールにおいて、依存関係を自動的に解消できない

  2. バイナリーパッケージは適切に選ばないとインストールエラーになる

 

パッケージ(モジュール)の準備

 通常はpipを使用して、Python Package Indexのレポジトリーから、パッケージをインストールしますが、pipコマンドを直接実行できないAutomationでは、事前にパッケージのインストールを行っておく必要があります。

 ざっくりとした手順は以下のとおりです。

  1. 必要なパッケージをリストアップする

  2. 依存関係を調べるスクリプトを実行し、利用したいパッケージの依存関係も含めて、必要なパッケージを洗い出す

  3. ソースパッケージやホイールパッケージを選択して、ダウンロードする

  4. 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]+"

#endregion


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]   

これを必要なパッケージ数分、実行します。


パッケージの追加

メニュー画面から以下のようにパッケージをインストールします。


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







閲覧数:152回0件のコメント

最新記事

すべて表示

Pythonにハマった件

ここ数年流行っているPythonですが、僕もご多分に漏れず4年ほど前からハマりました!! それまではインフラエンジニアらしく、AzureやMicrosoft 365向けにはPowerShell、データ解析にはbash/zshなどを使ってきました。しかし、5年前からはデータ解析にはほぼPythonで、ここ2年ぐらいはAzureでもPythonになりました。 今回はなぜ、PowerShellやBash

Comments


bottom of page