BioErrorLog Tech Blog

試行錯誤の記録

SageMakerでlocal training jobが実行できない時の対処法 | Unable to locate credentials

Unable to locate credentials

のエラーで、SageMaker instanceのlocal training jobが実行できないときの対処法をまとめます。

はじめに

SageMakerでは、SageMaker Python SDKを利用して簡単にtraining job/学習ジョブを実行することができます。

SageMaker SDKのtraining jobには、

  • オンデマンドに別インスタンスが立ち上がってジョブを実行する通常ジョブ
  • 作業中のNotebookインスタンス上でジョブを実行するlocalジョブ

の2種類があります。

この後者、local training jobを実行する際に、Unable to locate credentialsエラーが発生してジョブが実行できない現象に遭遇しました。

対処法を見つけたので、備忘録としてまとめておきます。

SageMakerでlocal training jobが実行できない時の対処法

事象

  • SageMaker instanceでlocal training jobを実行した際に、ジョブコンテナでUnable to locate credentialsエラーが発生してジョブが異常終了する

ただし、私はまだ確実なエラー再現手順を特定できてません。
同様の現象は下記のようにいくつか報告があがっています:

原因

  • ジョブ実行コンテナからインスタンスメタデータエンドポイント(169.254.169.254)にアクセスできていないこと

インスタンスメタデータエンドポイント(169.254.169.254)は、インスタンスのメタデータを取得するためのリンクローカルアドレスです。

Localのジョブ実行コンテナがインスタンスに付与されたIAM権限を参照/Token取得する際に、このエンドポイントにアクセスする必要があります。

しかし、このエンドポイントにアクセスできていないことで、ジョブ実行コンテナがIAMのTokenを取得できない = 権限を取得できない = Unable to locate credentials、という状況です。

対処法

  • 下記のようなスクリプトを実行し、ジョブ実行コンテナから169.254.169.254へアクセスできるように設定変更する
#!/bin/bash

# check if we need to configure our docker interface
SAGEMAKER_NETWORK=`docker network ls | grep -c sagemaker-local`
if [ $SAGEMAKER_NETWORK -eq 0 ]; then
  docker network create --driver bridge sagemaker-local
fi

# Get the Docker Network CIDR and IP for the sagemaker-local docker interface.
SAGEMAKER_INTERFACE=br-`docker network ls | grep sagemaker-local | cut -d' ' -f1`
DOCKER_NET=`ip route | grep $SAGEMAKER_INTERFACE | cut -d" " -f1`
DOCKER_IP=`ip route | grep $SAGEMAKER_INTERFACE | cut -d" " -f9`

# check if both IPTables and the Route Table are OK.
IPTABLES_PATCHED=`sudo iptables -S PREROUTING -t nat | grep -c $SAGEMAKER_INTERFACE`
ROUTE_TABLE_PATCHED=`sudo ip route show table agent | grep -c $SAGEMAKER_INTERFACE`

if [ $ROUTE_TABLE_PATCHED -eq 0 ]; then
  # fix routing
  sudo ip route add $DOCKER_NET via $DOCKER_IP dev $SAGEMAKER_INTERFACE table agent
  echo "route tables for Docker setup done"
else
  echo "SageMaker instance route table setup is ok. We are good to go."
fi

if [ $IPTABLES_PATCHED -eq 0 ]; then
  # fix ip table
  sudo iptables -t nat -A PREROUTING  -i $SAGEMAKER_INTERFACE -d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT --to-destination 169.254.0.2:9081
  echo "iptables for Docker setup done"
else
  echo "SageMaker instance routing for Docker is ok. We are good to go!"
fi

重要なのは下記2つの設定変更部分です。

# fix routing
sudo ip route add $DOCKER_NET via $DOCKER_IP dev $SAGEMAKER_INTERFACE table agent

# fix ip table
sudo iptables -t nat -A PREROUTING  -i $SAGEMAKER_INTERFACE -d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT --to-destination 169.254.0.2:9081

SageMaker SDKのlocal training jobでは、sagemaker-localという名前のdocker networkが利用されます (存在しない場合は作成する処理をスクリプト冒頭で行っています)。

このsagemaker-localを変更(ip routeの追加とiptablesのdestination変更)することによって、ジョブ実行コンテナから169.254.169.254へアクセスできるようになる -> インスタンスメタデータ取得できるようになる -> IAM権限Tokenを取得できるようになる、で、エラーを解消できました。

※上記スクリプトは、AWS公式が提供しているSageMaker examples内で見つけたセットアップスクリプトから一部を抽出/改変したものです。

おわりに

以上、Unable to locate credentialsエラーでSageMakerのlocal training jobが実行できないときの対処法をまとめました。

再現方法が特定できていないなどモヤモヤが残る部分はありますが、取り急ぎ解決にたどり着くことができました。

思わぬハマりポイントだったので、この記事が同じ目に遭ったどなたかの参考になれば幸いです。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

www.bioerrorlog.work

参考

amazon web services - Assume Sagemaker Notebook instance role from Docker container with default network mode - Stack Overflow

Local mode: role chaining/assumed role on notebook instances does not forward correct credentials · Issue #3464 · aws/sagemaker-python-sdk · GitHub

amazon-sagemaker-examples/setup.sh at main · aws/amazon-sagemaker-examples · GitHub

GitHub - aws/sagemaker-python-sdk: A library for training and deploying machine learning models on Amazon SageMaker

Retrieve instance metadata - Amazon Elastic Compute Cloud

Link-local address - Wikipedia

boto3とbotoの違い | AWS SDK for Pythonの歴史を調べる

boto3とbotoの違いや、そもそもの"boto"の由来など、AWS SDK for Pythonの歴史を調べてまとめます。

はじめに

こんにちは、@bioerrorlogです。

AWS SDK for Pythonとしてboto3をいつも使っています。

ただ、私がAWSを触り始めたときには既に"boto3"になっていたので、"3"というからには無印botoやboto2もあるんだよね..?というモヤっとした疑問を抱えていました。 以前からAWSを知る人にとっては自明かもしれませんか、自分はまだ明確な答えを知りません。

そこで今回はboto3の歴史的背景を調べ、

  • boto3とbotoの違い
  • boto2はどこに行ったのか
  • そもそも"boto"の由来は?

をまとめます。

AWS SDK for Pythonの歴史

boto3とbotoの違い

少し調べると、boto3とは別にbotoがライブラリとして存在していたことが分かります。

このbotoとboto3の違いを調べてみると、stackoverflowにbotoの作者自身が回答を寄せていました:
python - What is the difference between the AWS boto and boto3 - Stack Overflow

ざっくり意訳抜粋すると、

  • botoは2006年から使われていたAWS公式のAWS Python SDK
  • しかしその設計から、AWSサービスが増えるにしたがってメンテが困難になった
  • そこで設計を刷新したboto3が開発された
    • botocoreをベースとする設計
    • AWSとの低レベルなインターフェースは自動生成される
    • これによりクライアント層の設計を綺麗に保てるようになった

ということのようです。
なるほど私が知らなかっただけで、botoは2006年から(boto3がGAする)2015年まで10年近くも活躍していたんですね。 時間の経過とともに設計刷新の必要性が出たのも納得できます。

各GitHub starsの推移からも、この背景が透けて見えてきます:

  • botoは元々人気のあったライブラリ
  • 2015頃からboto3/botocoreが登場
  • 以後boto3の人気は直線状に上昇/botoは役目を終える

boto, boto3, botocoreのGitHub star数推移

boto2はどこに行ったのか

botoとboto3登場の歴史は納得しました。
が、"boto2はどこに行ったのか"という疑問が残ります。

これは"boto2"という言葉がどの文脈で使われているのか、を過去の情報から調べることで推し量ろうと思います。

いろいろ調べて得た結論はこちらです:

boto2 はbotoのversion2のことを指すらしい


ドキュメント類の記述からいくつか例示します:

botoのドキュメントには、"これはbotoの古いバージョン(boto2)のドキュメントだから、boto3を見ることをお勧めします"という記述があります:

You are viewing the documentation for an older version of boto (boto2).
boto: A Python interface to Amazon Web Services — boto v2.49.0


boto3のドキュメントにも、"boto2からの移行"という文脈でbotoのversion2のことを"boto2"と呼んでいる様子が見られます:

The rest of this document will describe specific common usage scenarios of Boto 2 code and how to accomplish the same tasks with Boto3.
Migrating from Boto 2.x — Boto3 Docs 1.26.13 documentation


以上、歴史的推移をまとめると、

  • 2006年よりbotoが利用されてきた
  • botoのversion2のことをboto2と呼んでいた
  • 2015年、設計刷新したboto3がリリースされた

ということになりそうです。 スッキリしました。

"boto"の由来

ちなみに"boto"という言葉の由来については、boto3のREADMEに記載があります:

Boto (pronounced boh-toh) was named after the fresh water dolphin native to the Amazon river.
GitHub - boto/boto3: AWS SDK for Python

アマゾンカワイルカ/Amazon river dolphin/Inia geoffrensis/通称boto、が由来のようです。

また下記Issueでは、botoの作者自身がその由来をコメントされています:

It was named after the fresh water dolphin native to the Amazon river. I wanted something short, unusual, and with at least some kind of connection to Amazon. Boto seemed to fit the bill 8^)
Why is the project named boto? · Issue #1023 · boto/boto3 · GitHub

(意訳)
アマゾン川に生息する川イルカから名前を付けました。 短くて、特徴的で、何かしらAmazonに関係する名を付けたかったので、Botoはぴったりの名前でした。

Amazon river dolphin (boto) の写真 | 画像はWikipediaより

おわりに

以上、boto3とbotoの違いから、AWS SDK for Pythonの歴史を調べてまとめました。

ふと思い立って発作的に始めた調べものでしたが、なかなか面白いことが知れて満足です。

同じように不思議に思ったどなたかの参考になれば幸いです。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

www.bioerrorlog.work

参考

python - What is the difference between the AWS boto and boto3 - Stack Overflow

Elastician

What is the difference between the AWS boto and boto3? - Quora

boto: A Python interface to Amazon Web Services — boto v2.49.0

Migrating from Boto 2.x — Boto3 Docs 1.26.13 documentation

GitHub - boto/boto3: AWS SDK for Python

GitHub - boto/boto: For the latest version of boto, see https://github.com/boto/boto3 -- Python interface to Amazon Web Services

GitHub - boto/botocore: The low-level, core functionality of boto3 and the AWS CLI.

Why is the project named boto? · Issue #1023 · boto/boto3 · GitHub

Amazon river dolphin - Wikipedia

Raspberry Piで複数のサーボモーターを制御する

PWM/サーボ制御モジュールを使わずに、複数のサーボモーターを制御する方法の備忘録です。

はじめに

こんにちは、@bioerrorlogです。

最近またRaspberry Piを触って遊び始めています。

複数のサーボモーターを制御したい、となったとき、世の事例を検索してみると、多くの場合はPWM/サーボ制御モジュールを取り付けることが多そうです。

ただ今回は外部モジュールを使わずに、とりあえず素のGPIOで複数サーボを制御してみました (パーツの用意やはんだ付けが面倒だったので...)。

やり方の備忘録を残します。

Raspberry Piで複数のサーボモータを制御する

OS環境の準備

モデルはRaspberry Pi 4 Model B (4GB RAM)を使いました。

Raspberry PiにはUbuntu 22.04.1 LTS (64-bit)を入れています (Raspberry Pi OSなど他の環境でも特に動作に違いはないはず)。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:    22.04
Codename:   jammy

Raspberry PiにUbuntuを入れる方法は別途記事にしているので、こちらを参照ください: www.bioerrorlog.work

回路を組む

回路は↓のように組みました。

  • サーボモーターにはSG90を使用
  • サーボモーターに供給する外部電源は単4電池4本パック/6Vを使用
    (データシートによるとギリギリSG90の動作範囲)
  • 外部電源とサーボモーターのマイナス端子はRaspberry PiのGNDに接続
  • サーボモーターのPWM制御線とRaspberry PiのGPIOを1kΩ抵抗を挟んで接続
    (今回は17, 18, 27のpinを使用)

サーボモータを動かすスクリプトの用意

では、サーボモーターの動作チェックのための簡単なスクリプトを書きます。 今回はPythonで書きました。

ソースコード: github.com

import math
import time
from gpiozero import AngularServo


def main():
    servo_17 = AngularServo(17)
    servo_18 = AngularServo(18)
    servo_27 = AngularServo(27)

    t = 0
    max_angle = 60
    gap_angle = 30

    try:
        while True:
            servo_17.angle = math.sin(math.radians(t % 360)) * max_angle
            servo_18.angle = math.sin(math.radians((t % 360) + gap_angle)) * max_angle
            servo_27.angle = math.sin(math.radians((t % 360) + (gap_angle * 2))) * max_angle

            t += 2
            time.sleep(0.01)
    except KeyboardInterrupt:
        servo_17.angle = 0
        servo_18.angle = 0
        servo_27.angle = 0


if __name__ == '__main__':
    main()

gpiozeroAngularServoを使って3つのサーボを動かす、動作確認用のちょっとしたスクリプトです。 回転を滑らかにするため、角度の計算にはsin関数を挟んでいます。

サーボモータを動かす

では用意したスクリプトをRaspberry Piで実行します。

スクリプトをcloneして、必要なライブラリをRaspberry Piにインストールします:

# Clone
git clone https://github.com/bioerrorlog/raspberry-pi-examples.git
cd raspberry-pi-examples/multi_servo_gpio/

# Install required packages
sudo apt update
sudo apt upgrade
sudo apt install python3-pip python3-dev gcc git
sudo pip install -r requirements.txt

そしたら、Pythonスクリプトを実行します:

sudo python3 main.py

# sudoで実行するのは、下記エラー回避のため:
# RuntimeError: No access to /dev/mem. Try running as root!

上手く実行できれば、↓みたいな感じでサーボモーターが動きます。

(サーボモーター同士をダンボールで固定しています)

おわりに

Raspberry Piで複数のサーボモーターを制御する方法の備忘録でした。

PWM制御モジュールも使ってないし、ソフト面での工夫もしていないので結構ジッターが入ってしまいますが、とりあえず動く、というところは見ることができます。

以上、備忘録でした。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

www.bioerrorlog.work

参考

O'Reilly Japan - Raspberry Piクックブック 第3版

マイクロサーボ9g SG-90 | 秋月電子通商

GitHub - gpiozero/gpiozero: A simple interface to GPIO devices with Raspberry Pi

GitHub - bioerrorlog/raspberry-pi-examples: Example projects for Raspberry Pi.

Athena経由でpandas DataFrameを作成する

Amazon Athena経由でpandas DataFrameを作成するやり方をまとめます。

はじめに

こんにちは、@bioerrorlogです。

Amazon Athenaクエリ経由でpandas DataFrameを作成したい、としたらどのようなやり方があるでしょうか。 

例えばいちばん愚直にやろうとすれば、boto3でAthenaクエリを発行し、クエリ結果からデータを取得して、上手いことpandasに読み込ませる...とかでしょうか。 そんな複雑なことやりたくないなぁと思って調べてみても、stackoverflowの上位answerでも似たような解決策が示されています。

しかしもう少し調べると、awswranglerを使えばかなりスッキリ書けることを知りました。

今回は、awswranglerを使ってAthena経由でpandas DataFrameを作成するやり方をメモします。


※追記:awswranglerはAWS SDK for pandasに改名されました(使い方は特に変わってない模様です)。

awswrangler (AWS SDK for pandas)とは

awswranglerは、AWSサービスとpandasを連携するツールです。

AWS公式(awslabs)が開発しており、Athenaだけでなく各AWSのDB系サービスとの連携もサポートしています。

Pandas on AWS. Easy integration with Athena, Glue, Redshift, Timestream, OpenSearch, Neptune, QuickSight, Chime, CloudWatchLogs, DynamoDB, EMR, SecretManager, PostgreSQL, MySQL, SQLServer and S3 (Parquet, CSV, JSON and EXCEL).

github.com

Athena経由でpandas DataFrameを作成する

このawswranglerを使えば、次のように簡単にAthenaのクエリ結果をそのままpandas DataFrameに読み込ませることができます。

import awswrangler as wr

df = wr.athena.read_sql_query(sql='SELECT * FROM "<table_name>"', database='<database_name>')

ちなみに上記のコードでは、Athena query resultの出力先を指定していません。 その場合はデフォルトでs3://aws-athena-query-results-ACCOUNT-REGION/にquery resultが出力されますが、任意のS3を指定したければs3_outputから指定することができます。

その他athena.read_sql_queryの各オプションはドキュメントをご覧ください

おわりに

今回は、awswranglerを使ってAthena経由でpandas DataFrameを作成するやり方をメモしました。

素のboto3で書くよりもずっとシンプルに書くことができるので、なかなか使い勝手が良いのではないでしょうか。 awslabs公式のツールであることも嬉しいポイントです。

以上、どなたかの参考になれば幸いです。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

参考

GitHub - aws/aws-sdk-pandas: Pandas on AWS - Easy integration with Athena, Glue, Redshift, Timestream, Neptune, OpenSearch, QuickSight, Chime, CloudWatchLogs, DynamoDB, EMR, SecretManager, PostgreSQL, MySQL, SQLServer and S3 (Parquet, CSV, JSON and EXCEL).

Quick Start — AWS SDK for pandas 2.17.0 documentation

awswrangler.athena.read_sql_query — AWS SDK for pandas 2.17.0 documentation

python - How to Create Dataframe from AWS Athena using Boto3 get_query_results method - Stack Overflow

boto3でエラーハンドリングする方法をまとめる | AWS SDK for Python

boto3でエラーハンドリングする方法を整理します。

はじめに

こんにちは、@bioerrorlogです。

boto3で発生するエラーをcatchしようとしたときに、少しハマったことがありました。

例えば以下のコードを実行すると、examplebucketという名前のS3バケットが既に存在しているため、BucketAlreadyExistsエラーが吐かれます。

import boto3


client = boto3.client('s3')
client.create_bucket(
    Bucket='examplebucket',
    CreateBucketConfiguration={'LocationConstraint': 'ap-northeast-1',},
)
botocore.errorfactory.BucketAlreadyExists: An error occurred (BucketAlreadyExists) when calling the CreateBucket operation: The requested bucket name is not available.
The bucket namespace is shared by all users of the system. Please select a different name and try again.

では、このエラーをcatchするにはどうすればいいでしょうか。

エラーメッセージに従って愚直にbotocore.errorfactory.BucketAlreadyExistsをcatchする例外処理を書いてしまうと、上手く動きません。

import boto3
import botocore


client = boto3.client('s3')
try:
    client.create_bucket(
        Bucket='examplebucket',
        CreateBucketConfiguration={'LocationConstraint': 'ap-northeast-1',},
    )
except botocore.errorfactory.BucketAlreadyExists as e:
    print(e)
AttributeError: module 'botocore.errorfactory' has no attribute 'BucketAlreadyExists'

今回は、このようなケースでboto3エラーをハンドリングする方法をまとめます。

boto3でエラーハンドリングする

boto3でエラーハンドリングするやり方は、大きく分けて2つあります。

  • client.exceptions
  • botocore.exceptions

client.exceptions

client.exceptionsを使えば、とてもシンプルに例外処理を書くことが出来ます。 宣言したboto3 clientから、client.exceptions.<エラー名>でcatchします。

import boto3


client = boto3.client('s3')
try:
    client.create_bucket(
        Bucket='examplebucket',
        CreateBucketConfiguration={'LocationConstraint': 'ap-northeast-1',},
    )
except client.exceptions.BucketAlreadyExists as e:
    print(e.response['Error'])

なお、boto3 clientではなくresourceを宣言している場合はresource.meta.client.exceptions.<エラー名>でcatchできます。

import boto3


resource = boto3.resource('s3')
try:
    resource.create_bucket(
        Bucket='examplebucket',
        CreateBucketConfiguration={'LocationConstraint': 'ap-northeast-1',},
    )
except resource.meta.client.exceptions.BucketAlreadyExists as e:
    print(e.response['Error'])

ただし、全てのエラーがこのやり方でcatch出来るわけではない、という点に注意が必要です。 対応できるエラーは、boto3ドキュメントの各APIごとに記載があります

一方、次に紹介するbotocore.exceptionsを使ったやり方では、全てのエラーをcatchできます。

botocore.exceptions

botocore.exceptionsを使う場合は少し冗長な書き方になってしまいますが、全ての例外を処理できるメリットがあります。

import botocore
import boto3


client = boto3.client('s3')
try:
    client.create_bucket(
        Bucket='examplebucket',
        CreateBucketConfiguration={'LocationConstraint': 'ap-northeast-1',},
    )
except botocore.exceptions.ClientError as e:
    if e.response['Error']['Code'] == 'BucketAlreadyExists':
        print(e.response['Error'])
    else:
        raise e

botocore.exceptions.ClientErrorでエラーをcatchしたのち、エラーのresponse['Error']['Code']から対応を振り分けます。

コード量が少し多くなってしまうのでなるべくclient.exceptionsを使ったやり方で書きたいところですが、やむを得ない場合はこちらで実装しましょう。

コード例

では、いくつかboto3で例外処理をするコード例を書いてみます。 xxxAlreadyExists系エラーやNoSuchEntitiy系のエラーはcatchしたいケースが多いので、これら中心にメモします。


S3 create_bucketにおけるBucketAlreadyExistsエラーのハンドリング例

import boto3


client = boto3.client('s3')
try:
    client.create_bucket(
        Bucket='examplebucket',
        CreateBucketConfiguration={'LocationConstraint': 'ap-northeast-1',},
    )
except client.exceptions.BucketAlreadyExists as e:
    print(f'S3 bucket already exists: {e.response["Error"]["BucketName"]}')


S3 get_objectにおけるNoSuchKeyエラーのハンドリング例

import boto3


client = boto3.client('s3')
try:
    client.get_object(
        Bucket='target-bucket-001',
        Key='no-such/ocject-key',
    )
except client.exceptions.NoSuchKey as e:
    print(f'No such key: {e.response["Error"]["Key"]}')


IAM get_roleにおけるNoSuchEntityExceptionエラーのハンドリング例

import boto3


client = boto3.client('iam')
try:
    client.get_role(
        RoleName='NoSuchRoleName'
    )
except client.exceptions.NoSuchEntityException as e:
    print(e.response['Error']['Message'])


Glue create_databaseにおけるAlreadyExistsExceptionエラーのハンドリング例

import boto3


client = boto3.client('glue')
try:
    client.create_database(
        DatabaseInput={
            'Name': 'test_db'
        }
    )
except client.exceptions.AlreadyExistsException as e:
    print(e.response['Error']['Message'])

おわりに

以上、boto3でエラーハンドリングする方法を整理しました。

上手にエラーハンドリングして、デバッグしやすいコードを心掛けたいものです。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

www.bioerrorlog.work

参考

Error handling — Boto3 Docs 1.26.14 documentation

python - Properly catch boto3 Errors - Stack Overflow

python - How to handle errors with boto3? - Stack Overflow

人工生命 "Lenia" を動かす | ALife

人工生命 "Lenia" を触って遊びます。

はじめに

"Lenia" という美しい人工生命モデルがあります。

このデモ動画を見てみて下さい。

顕微鏡を覗いているような、"何か"が生きているかのような動きが美しいですね。

今回は、この人工生命"Lenia"を手元で動かして遊んでみます。

作業環境

Windows 10
Python 3.7.6
Git bash

で作業しました。

人工生命 "Lenia" を動かす

Leniaとは

Lenia is a system of continuous cellular automata, a form of artificial life. It was derived from Conway's Game of Life by making everything smooth, continuous and generalized.

Lenia Portalより

(意訳) Leniaは、連続値セルオートマトンの人工生命です。 コンウェイのライフゲームから、あらゆる要素をスムーズで連続的に汎化して作られています。

とのことで、ライフゲームベースの時空間的に連続な人工生命モデルとして、Bert Chan氏が作成したものです。

論文もあるので、詳細はこちらを参照ください。

Lenia - Biology of Artificial Life

https://direct.mit.edu/isal/proceedings/isal2020/32/221/98400

Leniaを動かす

それではLeniaを動かしていきます。

github.com

Python, Matlab, Javascriptなど複数の言語がサポートされていますが、今回はPythonバージョンを動かしてみます。

# ソースコードをclone
$ git clone https://github.com/Chakazul/Lenia.git

# Pythonバージョンのディレクトリに移動
$ cd Lenia/Python

# virtualenvがインストールされていない場合はインストールする
$ pip install virtualenv

# 仮想環境作成
$ virtualenv .venv

# 仮想環境有効化
$ source .venv/Scripts/activate

# パッケージインストール
$ pip install -r requirements.txt

これで準備は完了です。

いよいよLeniaを実行してみます。

$ python LeniaND.py 

このようにLeniaのウィンドウが表示されれば成功です。

いろんなLenia

ウィンドウ上部のタブからパラメータを操作すると、いろいろな形態を表現することが出来ます。

いくつか例を紹介します。

非常に多様な形態が創発されて面白いですね。

ぜひいろいろ遊んでみてください。

おわりに

今回は人工生命 "Lenia" を動かして遊びました。

セルオートマトンは古くより研究されてきた分野ですが、このLeniaのようにいかにも"生き物っぽい"動きを目の当たりにすると、この分野の奥深さを実感します。

人工生命のモデルは面白そうなものがたくさんあるので、いろいろと触ってみたいものです。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

www.bioerrorlog.work

参考

GitHub - Chakazul/Lenia: Lenia - Mathematical Life Forms

Lenia Portal

Lenia - Biology of Artificial Life

https://direct.mit.edu/isal/proceedings/isal2020/32/221/98400

Stanford Seminar - Lenia: Biology of Artificial Life | YouTube

Bert Chan

boto3 clientのendpoint URLを確認する | AWS SDK for Python

作成済みboto3 clientのendpoint URLを確認する方法をまとめます。

はじめに

こんにちは、@bioerrorlogです。

boto3クライアントの宣言時には、endpoint URLを独自のURLに上書きすることが出来ます。

import boto3


client = boto3.client('s3', endpoint_url='http://my.endpoint:4566')

これはLocalStackなどを使う際に特に便利で、boto3がアクセスするエンドポイントを実際のAWSエンドポイントから独自のURLに振り向けることが出来ます。

しかし逆に、設定されているエンドポイントURLをclientから取得する方法はパッと思い浮かびませんでした。 もしそれが出来れば、デバッグ時に重宝しそうです。

今回は、作成済みboto3 clientから、設定されているendpoint URLを確認する方法まとめます。

環境

boto3 1.18.31 で動作確認してます。

boto3 clientのendpoint URLを確認する

作成したboto3 clientのendpoint URLを確認するには、(少なくとも)以下のふたつの方法があります。

  • client.meta.endpoint_url
  • client._endpoint.host

client.meta.endpoint_url

まず一つ目のやり方は、client.meta.endpoint_urlです。

import boto3


client = boto3.client('s3')
print(client.meta.endpoint_url)
# 出力例: https://s3.ap-northeast-1.amazonaws.com

client = boto3.client('s3', endpoint_url='http://localstack:4566')
print(client.meta.endpoint_url)
# 出力: http://localstack:4566

このようにboto3クライアントに設定されたendpoint URLを都度取得することが出来ます。

※ botocoreにおけるmeta.endpoint_url実装部分のソースコードはこちら: botocore/client.py at 6451ae1fad57f4453af97649e7ed9192b0f623be · boto/botocore · GitHub

client._endpoint.host

もう一つのやり方は、client._endpoint.hostです。

import boto3


client = boto3.client('s3')
print(client._endpoint.host)
# 出力例: https://s3.ap-northeast-1.amazonaws.com

client = boto3.client('s3', endpoint_url='http://localstack:4566')
print(client._endpoint.host)
# 出力: http://localstack:4566

client.meta.endpoint_urlと同様、boto3クライアントに設定されたendpoint URLを取得することが出来ます。

ただ、アンダースコアで始まる属性値_endpointに直接アクセスするのは憚られるので、client.meta.endpoint_urlでアクセスした方が自然に思います。

※ botocoreにおける_endpoint.host実装部分のソースコードはこちら: botocore/client.py at 6451ae1fad57f4453af97649e7ed9192b0f623be · boto/botocore · GitHub

おわりに

以上、boto3 clientのendpoint URLを確認する方法をまとめました。

なかなかboto3ドキュメントを漁っても該当のやり方を見つけることが出来ず、やり方を調べるのも一苦労でした。 (ネット上のコード例を探したうえで、boto3/botocoreソースコードを辿る必要がありました)

どなたかの参考になれば幸いです。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

www.bioerrorlog.work

参考

botocore/client.py at 6451ae1fad57f4453af97649e7ed9192b0f623be · boto/botocore · GitHub

botocore/client.py at 6451ae1fad57f4453af97649e7ed9192b0f623be · boto/botocore · GitHub

Add environment variable to override endpoint_url (#2099) by rwillmer · Pull Request #2746 · boto/boto3 · GitHub

django models - Upload to Amazon S3 using Boto3 and return public url - Stack Overflow

GitHub - localstack/localstack: 💻 A fully functional local AWS cloud stack. Develop and test your cloud & Serverless apps offline!

Pythonでユーザー定義クラスのオブジェクトを等価比較する

Pythonで独自に定義したユーザー定義クラスのオブジェクト同士を等価比較する方法を整理します。

はじめに

こんにちは、@bioerrorlogです。

Pythonでは、ユーザー定義クラスは等価演算子で比較することが出来ません。

例えば、次のようなクラス/pytestコードを定義した場合、オブジェクト比較部分のassertでエラーが発生します。

class MyClass:
    def __init__(self, amount):
        self.amount = amount


def test_equality():
    obj_1 = MyClass(5)
    obj_2 = MyClass(5)
    assert obj_1 == obj_2 # これが出来ない


pytest実行結果:

=============================== FAILURES ================================
_____________________________ test_equality _____________________________

    def test_equality():
        obj_1 = MyClass(5)
        obj_2 = MyClass(5)
>       assert obj_1 == obj_2
E       assert <equality.MyClass object at 0x0000014F05071848> == <equality.MyClass object at 0x0000014F05071C88>

equality.py:14: AssertionError
======================== short test summary info ========================
FAILED equality.py::test_equality - assert <equality.MyClass object at ...
=========================== 1 failed in 0.03s ===========================


今回は、上のように独自に定義したオブジェクト同士を比較できるようにする方法を記録します。

動作環境

Python 3.7.6 で動作確認しています。

Pythonでユーザー定義クラスのオブジェクトを等価比較する

ユーザー定義クラスのオブジェクトを等価比較できるようにするには、特殊メソッド__eq__を利用します。

__eq__メソッドは、等価演算子==が使われたときに呼び出されるものです。
例えば x==y が実行されたときには x.__eq__(y)が呼び出されます。
※参考: 3. Data model — Python 3.10.5 documentation

以下、やり方を見ていきます。

__eq__メソッドでインスタンス変数を比較する

まず、__eq__メソッドでインスタンス変数を比較することでオブジェクトを等価比較できるようになります。

実装例:

class MyClass:
    def __init__(self, amount):
        self.amount = amount

    def __eq__(self, other):
        return self.amount == other.amount # インスタンス変数を比較

def test_equality():
    obj_1 = MyClass(5)
    obj_2 = MyClass(5)
    assert obj_1 == obj_2 # このテストは通る


ただ、このやり方だとインスタンス変数が変更されるたびに__eq__メソッドを更新する必要が出てきます。 そこで次は__dict__を利用する方法を紹介します。

__eq__メソッドで__dict__を比較する

__dict__は、インスタンス変数がdictで格納されている特殊属性です。 これを__eq__メソッドで比較することで、インスタンス変数をひとつひとつ比較せずにも等価比較が可能です。

実装例:

class MyClass:
    def __init__(self, amount):
        self.amount = amount

    def __eq__(self, other):
        return self.__dict__ == other.__dict__ # インスタンス変数を比較

def test_equality():
    obj_1 = MyClass(5)
    obj_2 = MyClass(5)
    assert obj_1 == obj_2 # このテストは通る

同一クラスかを比較する

上に挙げたやり方では、インスタンス変数を比較しているのみなのでクラスの種類までは比較できていません。 なので、仮に全く同じインスタンス変数を持つ別々のクラス同士を比較した場合も、同一のものとして判定されてしまいます:

class MyClass:
    def __init__(self, amount):
        self.amount = amount

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

class DummyClass:
    def __init__(self, amount):
        self.amount = amount

    def __eq__(self, other):
        return self.__dict__ == other.__dict__


def test_equality():
    obj_1 = MyClass(5)
    obj_2 = DummyClass(5)
    assert obj_1 == obj_2 # このテストが通る


異なるクラスのオブジェクトを異なるものとして判定させるためには、__eq__メソッド内でクラス__class__を比較させます。 __class__は、クラス名が格納されている特殊属性です。

実装例:

class MyClass:
    def __init__(self, amount):
        self.amount = amount

    def __eq__(self, other):
        return (
            isinstance(other, self.__class__) and
            self.__dict__ == other.__dict__
        )


class DummyClass:
    def __init__(self, amount):
        self.amount = amount

    def __eq__(self, other):
        return (
            isinstance(other, self.__class__) and
            self.__dict__ == other.__dict__
        )


def test_equality():
    obj_1 = MyClass(5)
    obj_2 = MyClass(5)
    assert obj_1 == obj_2 # このテストは通る
    
    obj_3 = DummyClass(5)
    assert obj_1 == obj_3 # このテストは通らない

同一クラスでない時はNotImplementedを投げる

同一クラスでない時には、等価演算がサポートされていない意を示すNotImplementedを投げる実装もできます。

実装例:

class MyClass:
    def __init__(self, amount):
        self.amount = amount

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self.__dict__ == other.__dict__

class DummyClass:
    def __init__(self, amount):
        self.amount = amount

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self.__dict__ == other.__dict__


def test_equality():
    obj_1 = MyClass(5)
    obj_2 = MyClass(5)
    assert obj_1 == obj_2 # このテストは通る
    
    obj_3 = DummyClass(5)
    assert obj_1 == obj_3 # このテストは通らない

おわりに

以上、Pythonでユーザー定義クラスのオブジェクト同士を等価比較する方法をまとめました。

今回取り上げた問題は、Kent BeckのTDD本をPythonでやり直しているときに遭遇したものです。

Java/JUnitではassertEquals()ですぐオブジェクトを比較できたものが、Pythonだとちょっとした工夫が必要なことに気付き、備忘録を書きました。

どなたかの参考になれば幸いです。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

参考

3. Data model — Python 3.10.5 documentation

Built-in Constants — Python 3.10.5 documentation

python - Check if two objects have equal content in Pytest - Stack Overflow

Is there a way to check if two object contain the same values in each of their variables in python? - Stack Overflow

Assert custom objects are equal in Python unit test - Gems

aws-cli/__init__.py at develop · aws/aws-cli · GitHub

aws-cli/test_alias.py at develop · aws/aws-cli · GitHub

Pythonでメモリ/ディスク容量を確認する

Pythonを用いて、実行環境のメモリおよびディスク容量を確認する方法をメモします。

はじめに

おはよう。@bioerrorlogです。

あるとき、Pythonを用いて実行環境のメモリおよびディスク容量を確認する必要に迫られました。

そのときに使ったやり方を備忘録に残します。

環境

OS: Linux (Amazon Linux 2)
Python 3.8

ディスク/メモリ容量を取得する

ディスク容量

ディスク容量を確認するには、以下のコードを実行します。

import shutil

total, used, free = shutil.disk_usage('/')

print(f'Total: {total / (2**30)} GiB')
print(f'Used: {used / (2**30)} GiB')
print(f'Free: {free / (2**30)} GiB')

# 10の9乗をギガとする場合
# print(f'Total: {total / (10**9)} GB')
# print(f'Used: {used / (10**9)} GB')
# print(f'Free: {free / (10**9)} GB')

Pythonの標準ライブラリshutilを用いて、ディスク容量を確認します。

shutil.disk_usage(path)path部分に任意のパスを与えることで、そのパス割り当てのディスク総量/使用済み容量/空き容量を取得できます。

※shutil.disk_usageのドキュメント

Return disk usage statistics about the given path as a named tuple with the attributes total, used and free, which are the amount of total, used and free space, in bytes. path may be a file or a directory.

メモリ容量

続いて物理メモリ容量を確認するには、以下のコードを実行します。

import os
mem_bytes = os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES')
print(f'Memory total: {mem_bytes / (2**30)} GiB')

# 10の9乗をギガとする場合
# print(f'Memory total: {mem_bytes / (10**9)} GB')

メモリのページサイズos.sysconf('SC_PAGE_SIZE')と、物理メモリのページ数os.sysconf('SC_PHYS_PAGES')から、物理メモリの総容量が算出できます。

※Windows環境ではこのsysconfメソッドは利用できないのでご注意ください。

おわりに

以上、Pythonでメモリおよびディスク容量を調べる方法を記しました。

ローカルの端末あるいは普通のサーバであれば、システム情報やターミナルからメモリ/ディスク容量を確認できるかと思います。 ただ、何かの事情でPythonの実行環境しか渡されない場合、Pythonからこれら情報を取得する必要がでてきます。

そうしたケースに参考にしていただければ幸いです。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

www.bioerrorlog.work

参考

macos - Get hard disk size in Python - Stack Overflow

linux - Get total physical memory in Python - Stack Overflow

shutil — High-level file operations — Python 3.9.6 documentation

os — Miscellaneous operating system interfaces — Python 3.9.6 documentation

sysconf(3): config info at run time - Linux man page

DeletionPolicyの変更が反映されないときの対処法 | CDK/CloudFormation

CDKやCloudFormationにて、変更したDeletionPolicy (RemovalPolicy)がStackに反映されない時の対処法を残します。

はじめに

おはよう。@bioerrorlogです。

先日、CDKで作成したリソースのDeleteionPolicyを変更してdeployした際に、その変更が実環境のStackに反映されないことがありました。

その原因と対処法をメモします。

DeletionPolicyの変更が反映されないときの対処法

起こったこと

まず、起こったことをおさらいします。

例えば以下のように、S3を定義したシンプルなCDKコードがあるとします(Pythonを例とします)。

from aws_cdk import (
    aws_s3 as s3,
    core
)


class SampleS3Stack(core.Stack):

    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        
        s3_bucket = s3.Bucket(self, "TestBucket", bucket_name="deletion-policy-test-bucket")

app = core.App()
SampleS3Stack(app, "deletion-policy-test-stack")

app.synth()

上記のようにS3 BucketをCDK Constructのデフォルトで作成した場合、DeletionPolicyおよびUpdateReplacePolicyにはRetainが設定されます。

以下、生成されるCloudFormationテンプレート抜粋です。

Resources:
  TestBucket560B80BC:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: deletion-policy-test-bucket
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain

ここで、DeletionPolicy / UpdateReplacePolicyをDeleteに変更するには、以下のようにapply_removal_policy()を利用します。

from aws_cdk import (
    aws_s3 as s3,
    core
)


class SampleS3Stack(core.Stack):

    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        
        s3_bucket = s3.Bucket(self, "TestBucket", bucket_name="deletion-policy-test-bucket")
        
        # DeletionPolicy / UpdateReplacePolicyを指定
        s3_bucket.apply_removal_policy(core.RemovalPolicy.DESTROY) 

app = core.App()
SampleS3Stack(app, "deletion-policy-test-stack")

app.synth()

上記のようにapply_removal_policy()を適用することで、生成されるCloudFormationテンプレートやcdk diffの結果には変更が反映されます。

# cdk synth
Resources:
  TestBucket560B80BC:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: deletion-policy-test-bucket
    UpdateReplacePolicy: Delete
    DeletionPolicy: Delete
# cdk diff
Stack deletion-policy-test-stack
Resources
[~] AWS::S3::Bucket TestBucket TestBucket560B80BC 
 ├─ [~] DeletionPolicy
 │   ├─ [-] Retain
 │   └─ [+] Delete
 └─ [~] UpdateReplacePolicy
     ├─ [-] Retain
     └─ [+] Delete

しかし、上記変更後のCDKコードをデプロイしても、変更がStackに反映されませんでした。

以下のようにno changesと表示され、変更はデプロイされなかったのです。

# cdk deploy
deletion-policy-test-stack: deploying...
deletion-policy-test-stack: creating CloudFormation changeset...

 ✅  deletion-policy-test-stack (no changes)

原因

少し調べた結果、上記の振る舞いはCloudFormationの仕様であることが分かりました。

以下ドキュメント抜粋です。

You can't update the CreationPolicy, DeletionPolicy. or UpdatePolicy attribute by itself. You can update them only when you include changes that add, modify, or delete resources.

CreationPolicy/DeletionPolicy/UpdatePolicyのみでは変更を更新できないため、他のリソース情報と一緒に更新する必要がある、とのことです。

仕様としてイケてないとは思いますが、DeletionPolicyが更新できないこと自体は仕様として想定通りということですね。

対処法

では、どのようにしてDeletionPolicyを更新すればよいのか。

それ単体では更新できないことが仕様である以上、他のリソース情報と一緒に更新する他なさそうです。

例えば、metadataやTagの変更を同時に行えば、リソースそのものにはあまり影響を与えずにDeletionPolicyを更新することが出来ます。

ドキュメントにも以下のように、metadataの更新を一緒に行うことが言及されています。

For example, you can add or modify a metadata attribute of a resource.


例えば、以下のようにmetadataを変更して再デプロイすればDeletionPolicyの変更が反映されます。

from aws_cdk import (
    aws_s3 as s3,
    core
)


class SampleS3Stack(core.Stack):

    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        
        s3_bucket = s3.Bucket(self, "TestBucket", bucket_name="deletion-policy-test-bucket")
        
        # DeletionPolicy / UpdateReplacePolicyを指定
        s3_bucket.apply_removal_policy(core.RemovalPolicy.DESTROY)
        
        # metadataを更新
        cfn_bucket = s3_bucket.node.default_child
        cfn_bucket.cfn_options.metadata = {
            "DeletionPolicyUpdated": "DESTROY"
        }

app = core.App()
SampleS3Stack(app, "deletion-policy-test-stack")

app.synth()

※Metadataの更新方法はドキュメントGitHub issueを参考にしています。 Metadataの上書き操作になるので注意が必要です(もともとあったmetadata aws:cdk:pathが上書きされる)。


また以下のようにTagを追加して再デプロイしても、DeletionPolicyの変更が反映されます。

from aws_cdk import (
    aws_s3 as s3,
    core
)


class SampleS3Stack(core.Stack):

    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        
        s3_bucket = s3.Bucket(self, "TestBucket", bucket_name="deletion-policy-test-bucket")
        
        # DeletionPolicy / UpdateReplacePolicyを指定
        s3_bucket.apply_removal_policy(core.RemovalPolicy.DESTROY)
        
        # Tagを追加
        core.Tags.of(s3_bucket).add("DeletionPolicyUpdated", "DESTROY")

app = core.App()
SampleS3Stack(app, "deletion-policy-test-stack")

app.synth()

おわりに

今回は、CloudFormation/CDKにてDeletionPolicyの変更が反映されないときの対処法を書きました。

そもそもDeletionPolicy単体では更新できないのがCloudFormationの仕様、というのにはなかなか驚きました。

なるべくリソースそのものに影響のないTagやmetadataの更新を一緒に行うことを対処法として書きましたが、他にもっと適切なやり方があれば教えて頂けると嬉しいです。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

参考

[aws-events] `cdk diff` shows difference, but `cdk deploy` tells no changes · Issue #10219 · aws/aws-cdk · GitHub

Modifying a stack template - AWS CloudFormation

RemovalPolicy — AWS Cloud Development Kit 1.94.1 documentation

Escape hatches - AWS Cloud Development Kit (AWS CDK)

Identifiers - AWS Cloud Development Kit (AWS CDK)

Python: cfn_options.metadata cannot be mutated · Issue #6379 · aws/aws-cdk · GitHub

Add Support for adding Metadata to deal with 3rd party tools · Issue #8336 · aws/aws-cdk · GitHub

AWS CDKでTagを付与する

AWS CDKでTagを付与する方法の備忘録です。

はじめに

こんにちは、@bioerrorlogです。

CDKでリソースにTagを付与するとき、やり方を忘れてググり直すことがしばしばあります。

いちいち調べなくてもいいように、Tagの付与方法をメモします。

環境

今回は以下の環境で作業しました:
CDK version: 1.92.0
OS: Amazon Linux 2 (Cloud9)

CDKでTagを付与する

CDKでTagを付与するには、Tagsクラスを用いたやり方が推奨されています。

Tagging - AWS Cloud Development Kit (AWS CDK) v2

※他にはTagクラスを用いたやり方もありますが、非推奨となっています。

以下、Pythonを例にTag付与方法を見ていきます。

具体例

以下の形でTagを付与します。

Tags.of(SCOPE).add(key, value) 

SCOPEにはConstructやStackを指定し、tag, valueにはそれぞれタグのキーと値を指定します。

以下、具体例を挙げます。

Stack単位でTagを付与する

from aws_cdk import (
    aws_s3 as s3,
    core
)


class TagSampleStack(core.Stack):

    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        
        s3_01 = s3.Bucket(self, "TagTestBucket01", bucket_name="tag-test-bucket-001")
        s3_02 = s3.Bucket(self, "TagTestBucket02", bucket_name="tag-test-bucket-002")


app = core.App()
tag_sample_stack = TagSampleStack(app, "tag-sample")

# Stackにタグを付与する
core.Tags.of(tag_sample_stack).add("Project", "TagTest")

app.synth()

このようにStackに対してTagを付与すると、Stackに含まれるリソースすべてに対してTagが付与されます。

上記のケースですと、Stackに含まれるふたつのS3バケットtag-test-bucket-001tag-test-bucket-002の両方にProject: TagTestのTagが付与されます。

Construct単位でTagを付与する

from aws_cdk import (
    aws_s3 as s3,
    core
)


class TagSampleStack(core.Stack):

    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        
        s3_01 = s3.Bucket(self, "TagTestBucket01", bucket_name="tag-test-bucket-001")
        s3_02 = s3.Bucket(self, "TagTestBucket02", bucket_name="tag-test-bucket-002")

        # ConstructにTagを付与する
        core.Tags.of(s3_01).add("Project", "TagTest")


app = core.App()
tag_sample_stack = TagSampleStack(app, "tag-sample")

app.synth()

このように特定のConstructに対してTagを付与すると、対象のConstructに対してのみTagが付与されます。

上記のケースでは、S3バケットtag-test-bucket-001にのみProject: TagTestのTagが付与されます。

補足: 非推奨のTag付与方法

以下のようにTagクラスを用いてもTagを付与することはできますが、このやり方は非推奨となっています。

Tag.add(SCOPE, key, value)


コード例:

from aws_cdk import (
    aws_s3 as s3,
    core
)


class TagSampleStack(core.Stack):

    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        
        s3_01 = s3.Bucket(self, "TagTestBucket01", bucket_name="tag-test-bucket-001")
        s3_02 = s3.Bucket(self, "TagTestBucket02", bucket_name="tag-test-bucket-002")


app = core.App()
tag_sample_stack = TagSampleStack(app, "tag-sample")

# Tagクラスを用いてTagを付与
core.Tag.add(tag_sample_stack, "Project", "TagTest")

app.synth()

上記のコードを実行すると、以下のようにTag.addの使用は非推奨との警告が表示され、代わりTags.of(scope).add(k,v)の使用を推奨されます。

[Warning at /tag-sample] The API @aws-cdk/core.Tag.add(scope,k,v) is deprecated: Use "Tags.of(scope).add(k,v)" instead. This API will be removed in the next major release

おわりに

今回は、CDKでのTagの付与方法をメモしました。

Pythonを例にやり方を書きましたが、他の言語でも基本同じやり方でTagを付与することが出来ます。

どなたかの参考になれば幸いです。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

参考

Tagging - AWS Cloud Development Kit (AWS CDK) v2

Tags — AWS Cloud Development Kit 1.170.1 documentation

Tag — AWS Cloud Development Kit 1.170.1 documentation

Lambdaのboto3バージョンを確認する | AWS SDK for Python

Lambdaのboto3/botocoreバージョンを確認する方法の備忘録です。

はじめに

こんにちは、@bioerrorlogです。

Lambdaからboto3を用いてAWS APIを叩くのは、よくあるケースかと思います。

私もよくLambda上でboto3を使いますが、先日Lambdaのboto3バージョンではカバーされていない機能を使用し、エラーになってしまったことがありました。 その際、Lambdaのboto3バージョンを確認する必要がありましたが、ぱっとやり方が思い浮かびませんでした。

そこで今回は、Lambdaのboto3バージョンを確認する方法をメモします。

Lambdaのboto3バージョンを確認する方法

大きく分けて、以下の二つの方法があります。

  • ドキュメントからLambdaに使われているboto3バージョンを確認する
  • Lambdaを実行してboto3バージョンを確認する

それぞれ見ていきます。

ドキュメントから確認する

まず一つ目のやり方は、ドキュメントからLambdaのboto3バージョンを確認することです。

以下のドキュメントに、Lambdaランタイム毎のboto3/botocoreバージョンが記載されています。

Lambda runtimes - AWS Lambda

ドキュメントにLambdaのboto3/botocoreバージョンが記載されている(オレンジ枠)

Lambdaのランタイムを確認して、上記ドキュメントからboto3バージョンを確認するのが一番手っ取り早い確認方法かもしれません。

しかし、ドキュメントがちゃんと最新化されているか、は注意する必要があります (特に、日本語ページはしばしば最新版が反映されていないので注意が必要です)

Lambda上から確認する

二つ目のやり方は、Lambdaを実行してboto3バージョンを確認することです。

boto3/botocoreのバージョンは、boto3.__version__botocore.__version__で取得することが出来ます。

よって、例えば以下のようにboto3.__version__botocore.__version__を出力させれば、boto3/botocoreのバージョンを確認できます。

import boto3
import botocore

def lambda_handler(event, context):
    
    print(f'boto3 version: {boto3.__version__}')
    print(f'botocore version: {botocore.__version__}')
# 出力結果
boto3 version: 1.16.31
botocore version: 1.19.31

ドキュメントでの確認とは違い、こちらのやり方では確実にLambdaで使用されているboto3のバージョンを調べることが出来ます。

おわりに

今回は、Lambdaのboto3バージョンを確認する方法を簡単にメモしました。

boto3の新しいリリース機能を使いたいときは、Lambdaのboto3バージョンが追い付いていない可能性があります。 そんな時は、さっとLambdaのboto3バージョンを確認できるとよいですね。

boto3には常日頃お世話になっているので、もっと詳しくなっていきたいところです。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

www.bioerrorlog.work

参考

Lambda runtimes - AWS Lambda

Troubleshoot Python (Boto 3) Lambda function runtime errors

python - Check Boto3 Version - Stack Overflow

“Failed to execute script XX” エラー対処: PyinstallerでPygameをexe化するときの注意点

PyinstallerでPygameスクリプトをexe化する際に発生した、以下のエラーに対処するためのチェックポイントを書き残します。

Failed to execute script XX

はじめに

こんにちは、@bioerrorlogです。

以前、Pygameで作ったゲームのスクリプトをPyinstallerを用いてexe化し、itch.ioで配布してみました。


その際、何度か悩まされたエラーがこちらです。

Failed to execute script XX

今回は、このエラーが発生する状況とその対処方法を書き残します。

環境

Windows10

各バージョン情報

Python 3.7.6
pygame 2.0.1
pyinstaller 4.2

“Failed to execute script XX” エラーの対処法

私がエラーに遭遇した状況からまとめると、以下の3つの注意点があります。

  1. Pygameスクリプト内のimportパッケージはインストールされているか
  2. ゲームアセットの依存関係は保たれているか
  3. Pygame終了時にsys.exit()しているか

以下、ひとつずつ説明します。

Pygameスクリプト内のimportパッケージはインストールされているか

まず一つ目、Pygameをexe化する際には、そのスクリプト内でimportされているパッケージがインストールされている必要があります。

例えばGitHubからコードをcloneし、そのままPyinstallerによるexe化を行った場合、ゲームの実行に必要なパッケージがインストールされていないままの可能性があります(私がそうでした)。

必要なパッケージがインストールされないまま作成されたexeファイルは、実行時にエラーが発生します。

対処法:

  • Pyinstallerによるexe化を行う前に、ゲームが正常に実行できるかを確認する
  • ゲームが実行できなかった場合、必要パッケージをインストールする

ゲームアセットの依存関係は保たれているか

作成されたexeファイルとゲームアセットファイルの間には、Pygameスクリプトとゲームアセットファイルの関係が保たれている必要があります。

例えば、以下のようなファイル構成のPygameプロジェクトがあったとします。

.
├── main.py # Pygameスクリプト
└── data # ゲームアセットフォルダ

main.pyから、dataフォルダは以下に配置された画像データ/音声データなどを参照しているような形です。

こちらをPyinstallerでexe化すると、次のように各ファイルが生成されます。

.
├── build
├── main.py
├── main.spec
├── data
├── __pycache__
└── dist
    └── main.exe # 実行ファイル

ここでmain.exeをそのまま実行しても、エラーを吐かれてしまいます。 アセットフォルダdatamain.exeと同階層にないため、アセットを読み込むことが出来ないからです。

よって、アセットフォルダdataを、main.exeと同階層にコピーする必要があります。

└── dist
    ├── data # コピーしてくる
    └── main.exe

対処法:

  • exeファイルとゲームアセットフォルダの関係を、ゲームアセットフォルダとPygameスクリプトの関係で保持する
  • 例) ゲームアセットフォルダをexeファイルの同階層にコピーする

Pygame終了時にsys.exit()しているか

ゲーム終了時には、sys.exit()を実行する必要があります。 sys.exit()が実行されない場合、exeファイルから実行したゲームを終了する際、エラーを吐かれてしまいます。

例えばゲーム終了の関数を用意する場合は、次のようにsys.exit()を実行するようにします。

import pygame
import sys

def quit_game():
    pygame.quit()
    sys.exit()

対処法:

  • ゲーム終了時の処理でsys.exit()を実行する

おわりに

以上、PyinstallerでPygameスクリプトをexe化する際に発生したエラーへの対処法を3つ書きました。

エラーメッセージもあまり詳しく書かれないので、単純な原因でもエラーの対処に結構時間がかかりました。

Pygameでのゲーム開発はマイナーとは思いますが、同じ境遇のどなたかの参考になれば幸いです。

[関連記事]
www.bioerrorlog.work

www.bioerrorlog.work

参考

Making an Executable from a Pygame Game (PyInstaller) - YouTube

python - "Failed to execute script myscript" when exiting pygame window without console open after converting to .exe with pyinstaller - Stack Overflow

Pygameのexeファイルを作成する | PyInstaller

PygameスクリプトをPyinstallerを用いてexe化する方法の備忘録です。

はじめに

おはよう。@bioerrorlogです。

Pygameで簡単なゲームを自作してみました。

初心者がPythonでゼロからゲームを作ってみた | デザインから実装まで - 生物系がゼロから始めるTech Blog

せっかく作ったのだからどこかでゲーム実行ファイルを配布しようと思い、itch.ioにゲームを登録しました。

ゲームを配布するには、ソースコードからゲーム実行ファイル(exeファイル)を作成する必要があります。

そこで今回は、PygameスクリプトをPyInstallerを用いてexe化するやり方を残します。

環境

Windows10で作業しました。

各バージョン情報は以下です。

Python 3.7.6
pygame 2.0.1
pyinstaller 4.2

PyInstaller でPygameのexeファイルを作成する

PyInstaller でPygameのexeファイルを作成するには、以下の手順を踏みます。

  1. PyInstallerのインストール
  2. exeファイルの作成
  3. ゲームアセット依存関係の解決

以下、ひとつひとつ説明します。

1. Pyinstallerのインストール

まずは、Pyinstallerをpipインストールします

pip install pyinstaller

終わったら、バージョンを確認してPyinstallerが正常にインストールされていることを確認します。

$ pyinstaller --version
4.2

無事バージョンが表示されれば、インストール完了です。

2. exeファイルの作成

続いて、Pygameプロジェクトのソースコードからexeファイルを作成します。

今回は、私のPygameプロジェクトを例にexe化してみます。

GitHub - bioerrorlog/CellForRest_Pygame: My first project - Clicker game in Python.

# Pygameプロジェクトを手元にclone
git clone https://github.com/bioerrorlog/CellForRest_Pygame.git
cd CellForRest_Pygame/game/

このPygameプロジェクトは、以下のようなフォルダ構成になっています。

.
├── CellForRest.py
└── data

CellForRest.pyがメインの実行ファイルで、dataフォルダの中に、画像などのゲームアセットファイルが格納されています。

これらを、以下のPyInstallerコマンドでexe化します。

pyinstaller CellForRest.py --onefile --noconsole

--onefileオプションと--noconsoleオプションは、必要に応じて付与してください。 それぞれ以下の処理が行われます。

  • --onefile
    関連ファイルを1つにまとめてexe化する

  • --noconsole
    exeファイル実行時にコンソールを非表示にする

上記コマンドを実行すると、もともとのディレクトリに以下のようなフォルダが生成されます。

.
├── build
├── CellForRest.py
├── CellForRest.spec
├── data
├── dist
└── __pycache__

生成されたフォルダの中のdistフォルダの中に、exeファイルが作成されています。

└── dist
    └── CellForRest.exe


[関連記事]
www.bioerrorlog.work

3. ゲームアセット依存関係の解決

最後に、exeファイルとゲームアセットフォルダの依存関係を解決します。

もともと、メインスクリプトCellForRest.pyと同階層にゲームアセットフォルダdataを配置していたので、生成したexeファイルでも同様の依存関係を保持する必要があります。

つまり今回の例だと、dataフォルダをdistフォルダ内にコピーします。

└── dist
    ├── data # コピーしてくる
    └── CellForRest.exe

これで、exeファイルCellForRest.exeを実行すれば、ゲームが起動するようになりました。

ゲームを配布するときは、このdistフォルダの中身(dataフォルダとCellForRest.exe)をzipする形になります。

おわりに

今回は、Pyinstallerを用いてPygameのexeファイルを作成する方法を書きました。

私の場合、exeファイルとゲームアセットフォルダの依存関係を保持する必要があることを知らなかったため、しばらくexeファイルが上手く実行できずに苦労してしまいました。

同じ境遇の誰かの参考になれば幸いです。

[関連記事]
www.bioerrorlog.work

www.bioerrorlog.work

参考

Making an Executable from a Pygame Game (PyInstaller) - YouTube

PyInstaller Manual — PyInstaller 4.2 documentation

Using PyInstaller — PyInstaller 4.2 documentation

pyinstaller · PyPI

S3 ListBucketsの実行に必要なIAM権限 | AWS

S3 ListBucketsの実行に必要なIAM PolicyのAction権限について整理します。

はじめに

おはよう。@bioerrorlogです。

S3のAPIを利用する際のIAM Policy設定にはしばしば悩まされます。 S3から提供されているAPIをそのままIAM Policyの"Action"として指定できないことが多いからです。

例えばS3のListBucketsを実行する際、IAM Policyの"Action"に"s3:ListBuckets"としてそのまま権限を指定することはできません。 IAM PolicyのActionテーブルには"s3:ListBuckets"は定義されていないからです。


[関連記事] IAM Policyの"Action"に指定できる権限の一覧はどこにあるのか


そこで今回は、S3のListBuckets実行に必要なIAM PolicyのAction権限について整理します。

検証方法

Lambdaからboto3のlist_buckets()を実行し、必要なIAM Policy権限を調べます。

f:id:BioErrorLog:20210123124952p:plain
LambdaからListBucketsを発行し、必要なIAM権限を調べる

Lambdaでは、以下のシンプルなコードを実行します:

import boto3

s3_client = boto3.client('s3')

def lambda_handler(event, context):
    
    response = s3_client.list_buckets()
    print(response)
  • Lambda runtime:
    Python 3.8

  • boto3 version:
    boto3-1.15.16 botocore-1.18.16

S3 ListBucketsの実行に必要なIAM権限

結論

まず結論から言うと、必要なAction権限は以下の2つです:

  • "s3:ListBucket"
  • "s3:ListAllMyBuckets"

IAM Policyドキュメントは以下のようになります。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:ListAllMyBuckets"
            ],
            "Resource": "*"
        }
    ]
}


以降、ListBucketsの実行に必要なIAM権限を検証していきます。

検証

まず、S3への権限を何も付与せずにLambdaを実行すると、以下のエラーが出力されます。

[ERROR] ClientError: An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied

当然ですが、AccessDeniedエラーが出ました。


次に、試しに"s3:ListBuckets"の権限を指定してみます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBuckets"
            ],
            "Resource": "*"
        }
    ]
}

こちらも当然、同様のAccessDeniedエラーが吐かれました。 "s3:ListBuckets"というActionは定義されていないからです。


次は、"s3:ListBucket"または"s3:ListAllMyBuckets"の権限をそれぞれ単体で付与して実行してみます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": "*"
        }
    ]
}

または

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListAllMyBuckets"
            ],
            "Resource": "*"
        }
    ]
}

しかし、どちらのケースも同様のAccessDeniedエラーが吐かれました。


そして、次のように"s3:ListBucket"と"s3:ListAllMyBuckets"の両方を許可してみます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:ListAllMyBuckets"
            ],
            "Resource": "*"
        }
    ]
}

これでようやく、エラーなくlist_buckets()を実行することが出来ました。

おわりに

今回は、S3 ListBucketsの実行に必要なIAM権限をメモしました。

S3から提供されているAPIはそのままIAM Policyの"Action"として指定できないことが多いので、よくIAM Policyの設定には苦労します。 今回はListBuckets APIについて書きましたが、他にも紛らわしい権限設定のAPIがあります。

必要なAction権限をすぐに調べられる仕組みがあればよいのですが、、
もしあれば是非教えて貰いたいです。

[関連記事]
www.bioerrorlog.work

www.bioerrorlog.work

参考

ListBuckets - Amazon Simple Storage Service

S3 — Boto3 Docs 1.17.93 documentation

Actions, resources, and condition keys for Amazon S3 - Service Authorization Reference