【Python】doctestでdictを扱う
課題
テストの自動化にdoctestは良いな、と思っていたらjsonのテストを行うとテストが通らない問題が発生した。
from datetime import datetime,timedelta
import json
def testFunc(test):
"""
>>> testFunc("Hello!")
{"test":"Hello!"}
"""
return {"test":test}
Failed example:
testFunc("Hello!")
Expected:
{"test":"Hello!"}
Got:
{'test': 'Hello!'}
1 items had no tests:
test
**********************************************************************
1 items had failures:
1 of 1 in test.testFunc
1 tests in 2 items.
0 passed and 1 failed.
***Test Failed*** 1 failures.
解決方法
以下のようにすると良いらしい。
#method1
>>> sorted(testFunc("Hello!").items())
[("test","Hello!")]
又は
#method2
>>> testFunc("Hello!")== {"test":"Hello!"}
True
なお、method2は期待する出力と関数内での出力の順番が異なっていても問題なくテストは通る
from datetime import datetime,timedelta
import json
def func(test):
"""
>>> testFunc("Hello!")=={"hello":"World","test":"Hello!"}
True
"""
return {
"test":test",
"hello":"World"
}
流石に改行を入れるとテストコードが通らないが、きちんとバックスラッシュ()を入れれば改行やインデントも入れられるため、可読性を上げられる。
#NG
>>> testFunc("Hello!")=={
"test":"Hello!",
"hello":"World"
}
True
#OK
>>> testFunc("Hello!")=={\
"test":"Hello!",\
"hello":"World"\
}
True
#NG
Failed example:
testFunc("Hello!")=={
Exception raised:
Traceback (most recent call last):
File "C:\ProgramData\Anaconda3\lib\doctest.py", line 1329, in __run
compileflags, 1), test.globs)
File "<doctest test.testFunc[0]>", line 1
testFunc("Hello!")=={
^
SyntaxError: unexpected EOF while parsing
1 items had no tests:
test
**********************************************************************
1 items had failures:
1 of 1 in test.testFunc
1 tests in 2 items.
0 passed and 1 failed.
***Test Failed*** 1 failures.
#OK
Trying:
testFunc("Hello!")=={ "test":"Hello!", "hello":"World" }
Expecting:
True
ok
1 items had no tests:
test
1 items passed all tests:
1 tests in test.testFunc
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
原因
doctestはあくまで、出力が完全一致しているか確認するだけで、等価性のチェックは行わないらしい。 例えば、以下の値は並び順が異なるが本来は同じ値である。
#Example1
{
"key":"Hello ",
"value":"World"
}
#Example2
{
"value":"World!",
"key":"Hello "
}
しかし、pytdocでのテストでは並び順が異るためエラーと認識される
どうやら、課題で挙げた問題はこの延長線上にあるようだ。
所感
doctestはドキュメントをコード内に書きながらテストコードを書けるため便利だが、大きなdictやlistが出力されるコードではunittestのほうが使い勝手が良いかもしれない。 ただ、現状はそこまで大きな出力を扱うコードを書く予定がないので、まずはdoctestでドキュメントを書きながらテストコードを充実させていくことを主眼に置きたい。
参考
【CloudFormation】S3のバケットの中身をCloudFormationで作成する
はじめに
CloudFormationでシステムのようなものを組んでいるうちに、本当で適当で良いから入力フォームみたいなものが必要になってきた。 こうしたときに便利なのがS3の静的ホスティング機能だろう。
ただ、CloudFormationは、S3の中身までは作成できないという欠点があった。
強力なS3Objectsマクロ
しかし、このS3Objectsを利用すれば、CloudFormationでS3内のファイルを作成することができる github.com
デプロイ
使い方は非常に簡単で、上記のサイトの手順に従ってマクロをデプロイする。適当なアーティファクトバケットを作成する。 次に、git cloneかファイルのダウンロードし*1、以下のコマンドを実行する。
aws cloudformation package \
--template-file macro.template \
--s3-bucket <your bucket name> \
--output-template-file packaged.template
aws cloudformation deploy \
--stack-name s3objects-macro \
--template-file packaged.template \
--capabilities CAPABILITY_IAM
これで、デプロイしたアカウント内ならばこのマクロを利用することができるようになった。 なお、私はコマンドをうつのが面倒だったため、CodeStarを利用した。
テスト
テスト用のコードも用意されているため、実行して有効性を確認する。
aws cloudformation deploy \
--stack-name s3objects-macro-example \
--template-file example.template \
--capabilities CAPABILITY_IAM
使い方
CloudFormationが利用するIAMロールにS3Fullをアタッチし*2、CloudFormationテンプレートの頭に以下のブロックを置くことで、マクロが有効化される。
2019/12/21追記:マクロを動作させるためには、CloudFormationが利用するRoleにAWSLambdaExecuteポリシーをアタッチする必要がある。
Transform: S3Objects
サーバーレス変換など、他のマクロを合わせて利用する場合は、以下のようにリスト表記する。
Transform: - S3Objects - AWS::Serverless-2016-10-31
公式ドキュメントによると、以下の使い方があるようだ。 なお、いずれの項目でも
Target: Bucket: Key:
は必須項目で、アップロード先のバケットとキー名を指定している。
テキストの内容をベタ書き
Transform: S3Objects
Resources:
Bucket:
Type: AWS::S3::Bucket
Object:
Type: AWS::S3::Object
Properties:
Target:
Bucket: !Ref Bucket
Key: README.md
ContentType: text/plain
Body: Hello, world!
Body以下に書かれた内容がそのままファイルの内部に反映される。 非常に短いコードならば問題にならないが、長文になると修正も非常に面倒になるほか、コードを変更した際にテストが行えない点に注意すること。
エンコードされた文字列を記載する
SinglePixel:
Type: AWS::S3::Object
Properties:
Target:
Bucket: !Ref TargetBucket
Key: 1pixel.gif
Base64Body: R0lGODdhAQABAIABAP///0qIbCwAAAAAAQABAAACAkQBADs=
ここでは、Base64の文字列をテンプレート中に記載する。
S3にアップロードされているファイルを指定する。
CopiedObject:
Type: AWS::S3::Object
Properties:
Source:
Bucket: !Ref SourceBucket
Key: index.html
Target:
Bucket: !Ref TargetBucket
Key: index.html
ACL: public-read
予め他のS3にアップロードしておいたファイルを指定してコピーする。
aws-cliのaws s3 cp ~~~をcloudFormationで実行していると考えて良いだろう。
結論
S3Objects変換を利用することで、S3バケットの中身をCloudFormationで作成することができるようになった。 工夫すれば、静的サイトをCloudFormationで作成することができるようになると考えられる。 ただし、アップロードする方法がテキストのベタ書き、または他のs3オブジェクトへのリンクのみという弱点もある。*3
【AWS Athena】Lambdaからクエリを実行した際のIAM由来のエラーと対策
Athenaをテストした際に遭遇したエラーを以下にまとめる。別のエラーに遭遇した場合は後日追加していく。
Unable to Verify/Create Output Bucket
Unable to Verify/Create Output Bucket
S3へのアクセス権の不足が原因。以下のポリシーがアタッチされているか確認する。
- s3:ListBucket
- s3:ListBucketMultipartUploads
- s3:GetObject
- s3:GetBucketLocation
- s3:ListMultipartUploadParts
- s3:AbortMultipartUpload
- s3:PutObject
Access Denied
Access Denied (Service: Amazon S3; Status Code: 403; Error Code: AccessDenied;
S3へのアクセス権の不足。 以下のポリシーがアタッチされているか確認する。
- s3:PutObject
- s3:ListObject
Insufficient permissions to execute the query.
Insufficient permissions to execute the query. User: ROLE_NAME is not authorized to perform: glue:GetTable on resource: arn:aws:glue:ap-northeast-1:UserId:catalog
エラーコードの通り、glue:GetTableのアクセス権が不足している。
HIVE_CANNOT_OPEN_SPLIT
Your query has the following errors:HIVE_CANNOT_OPEN_SPLIT: Error opening Hive split s3://BUCKET_NAME/part-00000-feda4fa5-d862-48ba-88ad-3a468e234a27-c000.snappy.parquet (offset=0, length=7791): com.amazonaws.services.s3.model.AmazonS3Exception: Forbidden (Service: Amazon S3; Status Code:403; Error Code: 403 Forbidden; Request ID: 028A0DA47697C2D7;(以下略)
この記事の通り、glue:GetTablesのアクセス権が不足している。
特に、自分でポリシーを作成した際に忘れやすい。
【AWS Athena】str型Timestampをクエリする
課題
以下のようなデータがあったとする。
{
"timestamp": "2019-02-01 00:00:00",
"data": 100
}
{
"timestamp": "2019-03-01 00:00:00",
"data": 100
}
{
"timestamp": "2019-04-01 00:00:00",
"data": 100
}
このデータを年月ごとに集計するクエリを作成したい。。 正直に以下のSQLでクエリすると次のようになる
SELECT year(timestamp) as year,
month(timestamp) as month,
sum(data)
FROM <your_year(timestamp),month(timestamp)database>.<your_table>
GROUP BY year(timestamp),month(timestamp);
SYNTAX_ERROR: line 1:8: Unexpected parameters (varchar) for function year. Expected: year(timestamp) , year(timestamp with time zone) , year(date) , year(interval year to month)
原因と対策
これは、str型のタイムスタンプがタイムスタンプとして認識されていない事が原因である。 したがって以下のように記述する必要がある。
SELECT year(date_parse(timestamp, \'%Y/%m/%d %H:%i:%s\')),
month(date_parse(timestamp, \'%Y/%m/%d %H:%i:%s\')),
sum(data)
FROM <your_year(timestamp),month(timestamp)database>.<your_table>
GROUP BY year(date_parse(timestamp, \'%Y/%m/%d %H:%i:%s\')),month(date_parse(timestamp, \'%Y/%m/%d %H:%i:%s\'));
参考
AWS AthenaはPrestoベースであることがわかったため、以下のサイトを参考にした * Prestoでの日付の扱い方 - ★データ解析備忘録★ * prestoの気持ち 時間関係(Date and Time Functions and Operators) - Qiita
【AWS Athena】HIVE_CANNOT_OPEN_SPLITが発生する
課題
Glueでparquet.snappyにしたファイルを、Athenaで検索している。 これを定期実行するために、Lambdaを使いたい。 しかし、LambdaからAthenaにクエリを投げると、以下のエラーが発生する。
Your query has the following errors:HIVE_CANNOT_OPEN_SPLIT: Error opening Hive split s3://BUCKET_NAME/part-00000-feda4fa5-d862-48ba-88ad-3a468e234a27-c000.snappy.parquet (offset=0, length=7791): com.amazonaws.services.s3.model.AmazonS3Exception: Forbidden (Service: Amazon S3; Status Code:403; Error Code: 403 Forbidden; Request ID: 028A0DA47697C2D7;(以下略)
これがエラーコードで検索をかけても引っかからない。
気がかりな点は、rootで実行するとこのエラーは発生しない点だ。つまり、IAMポリシーが原因である可能性が高い。 なお、このエラーが最初に発生した際のIAMポリシーは以下のように設定していた。
Policies:
- Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:PutObject
- s3:GetBucketLocation
- s3:ListBucket
- s3:ListBucketMultipartUploads
- s3:ListMultipartUploadParts
- s3:AbortMultipartUpload
Resource: "*"
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
- Effect: Allow
Action:
- "athena:GetWorkGroup"
- "athena:StartQueryExecution"
- "athena:GetQueryResultsStream"
- "athena:CancelQueryExecution"
- "athena:StopQueryExecution"
- "athena:GetQueryExecution"
- "athena:GetQueryResults"
- "athena:BatchGetNamedQuery"
- "athena:GetNamedQuery"
- "athena:ListTagsForResource"
- "athena:BatchGetQueryExecution"
Resource: "*"
- Effect: Allow
Action:
- glue:GetTable
- glue:GetDatabase
- glue:GetPartition
- glue:GetPartitions
- glue:BatchGetPartition
Resource: "*"
結論
試行錯誤した結果、glue:GetTablesのポリシーが付与されていないと、このエラーが発生するようだ。
【CloudFormation】Error: The policy failed legacy parsing
やりたい事
以下のページを参考に、別アカウントのCodePipelineに、CodeCommitの変更を配信したい。また、これをCloudFormationを使ってできる限り省力化したい。
発生したエラー
開発アカウント側で、IAM Policyを作成する際にエラーが発生した。以下がテンプレートである。
AllowTargetAccessPolicy:
Type: AWS::IAM::Policy
Properties:
Roles:
- !Ref AllowTargetAccessRole
PolicyName:
!Join
- ""
- - !Ref AWS::Region
- !Ref AWS::AccountId
- To
- !Ref TargetAccountID
- CodeCommitAccessPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
-
Effect: Allow
Action:
- s3:PutObject
- s3:PutObjectAcl
Resource: !Sub arn:aws:s3:::artifact-bucket-for-${TargetAccountID}-from-${AWS::AccountId}
-
Effect: Allow
Action:
- kms:DescribeKey
- "kms:GenerateDataKey*"
- kms:Encrypt
- "kms:ReEncrypt*"
- kms:Decrypt
Resource: !Sub "arn:aws:kms:${AWS::Region}:${TargetAccountID}:key/${KMS_ID}
-
Effect: Allow
Action:
- codecommit:GetBranch
- codecommit:GetCommit
- codecommit:UploadArchive
- codecommit:GetUploadArchiveStatus
- codecommit:CancelUploadArchive
Resource:
!Sub arn:aws:codecommit:${AWS::Region}:${AWS::AccountId}:${RepositoryName}
すると、以下のエラーが発生した。
The policy failed legacy parsing (Service: AmazonIdentityManagement; Status Code: 400; Error Code: MalformedPolicyDocument;)
リソースをコメントアウトして、問題がKMSに関するポリシードキュメントブロックにあることがわかった。
解決方法
なぜか、!Sub の代わりに!Joinを使用することでエラーが回避できた。 (参考) tycoh.hatenablog.com
AllowTargetAccessPolicy:
Type: AWS::IAM::Policy
Properties:
Roles:
- !Ref AllowTargetAccessRole
PolicyName:
!Join
- ""
- - !Ref AWS::Region
- !Ref AWS::AccountId
- To
- !Ref TargetAccountID
- CodeCommitAccessPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
-
Effect: Allow
Action:
- s3:PutObject
- s3:PutObjectAcl
Resource: !Sub arn:aws:s3:::artifact-bucket-for-${TargetAccountID}-from-${AWS::AccountId}
-
Effect: Allow
Action:
- kms:DescribeKey
- "kms:GenerateDataKey*"
- kms:Encrypt
- "kms:ReEncrypt*"
- kms:Decrypt
Resource: |
!Join
- ""
- - arn:aws:kms
- !Ref AWS::Region
- ":"
- !Ref TargetAccountID
- "key/"
- !Ref KMSArn
-
Effect: Allow
Action:
- codecommit:GetBranch
- codecommit:GetCommit
- codecommit:UploadArchive
- codecommit:GetUploadArchiveStatus
- codecommit:CancelUploadArchive
Resource:
!Sub arn:aws:codecommit:${AWS::Region}:${AWS::AccountId}:${RepositoryName}
原因は後日AWS Forumに質問してみることにする。
【Error】CloudFormation:: Parameter is non alphaNumeric
現象
以下のCloudFormationテンプレートのパラメーターブロックでParameter is non alphaNumericというエラーが発生した。
--- Parameter: MY_MESSAGE: This is test ---
原因
パラメーターブロックでは、記号を使うことができない。そのため、アンダーバーを使うことができない。 そのため、キャメルケースでパラメーターを設定する。
Parameter: myMessage: This is test # または、MyMessage
参考
CloudFormation Parameter Template Error : Parameter is non alphanumeric