Vue.jsやNuxt.jsの開発用サーバのポート番号を変更する
Vue.jsやNuxt.jsの開発用サーバのポート番号を変更するには package.json
に記述すればよいようです。
Vue.js
package.json
変更前(デフォルト)
"scripts": { "serve": "vue-cli-service serve", // ... },
↓ 変更後
"scripts": { "serve": "vue-cli-service serve --port 8080", // ... },
開発用サーバの起動
$ npm run serve
Nuxt.js
package.json
変更前(デフォルト)
"scripts": { "dev": "nuxt", // ... },
↓ 変更後
"scripts": { "dev": "nuxt --port 8080", // ... },
開発用サーバの起動
$ npm run dev
2023/04/11追記
設定ファイルを書き換えずに一時的に変更したいならば、以下のように環境変数でも指定できます。
$ PORT=8080 npm run dev
Pythonの自作パッケージにリソースファイルを含めるには
Pythonソースコード以外にテキストや画像など任意のファイルをパッケージに含めて、Pythonコードから参照する方法のメモです。
手順の概要
setup.py
にinclude_package_data = True
と記載- パッケージに含めたいファイル名を
MANIFEST.in
に記載 - Pythonコードからは
pkgutil.get_data("sample", "data.txt")
で参照
ソースコード
$ find . -type f ./requirements.txt ./sample/data.txt ./sample/__init__.py ./sample/main.py ./setup.py ./MANIFEST.in
requirements.txt
空ファイル
sample/data.txt
適当なリソースファイル
Hello
sample/__init__.py
空ファイル
sample/main.py
リソースファイルを参照するPythonコードのサンプル
import pkgutil def main(): print(pkgutil.get_data("sample", "data.txt")) # bytes型でファイルの中身を取得できる
setup.py
include_package_data = True
が必要です。
from setuptools import setup, find_packages with open('requirements.txt') as requirements_file: install_requirements = requirements_file.read().splitlines() setup( name = "sample", version = "0.0.0", description = "A sample tool", author = "suzuki-navi", include_package_data = True, # これが必要! packages = find_packages(), install_requires = install_requirements, entry_points = { "console_scripts": [ "sample = sample.main:main", ] }, )
MANIFEST.in
リソースファイルとしてパッケージに含めたいファイルを書きます。
include requirements.txt include sample/*.txt
recursive-include
でディレクトリの中を再帰的に探すこともできるようです。
Including files in source distributions with MANIFEST.in — Python Packaging User Guide
動かしてみる
その場で実行
$ pip install -e . $ sample b'Hello\n'
パッケージ作成
$ python setup.py sdist
tar.gzファイルに sample-0.0.0/sample/data.txt
が含まれます。
$ tar tf dist/sample-0.0.0.tar.gz sample-0.0.0/ sample-0.0.0/MANIFEST.in sample-0.0.0/PKG-INFO sample-0.0.0/requirements.txt sample-0.0.0/sample/ sample-0.0.0/sample/__init__.py sample-0.0.0/sample/data.txt sample-0.0.0/sample/main.py sample-0.0.0/sample.egg-info/ sample-0.0.0/sample.egg-info/PKG-INFO sample-0.0.0/sample.egg-info/SOURCES.txt sample-0.0.0/sample.egg-info/dependency_links.txt sample-0.0.0/sample.egg-info/entry_points.txt sample-0.0.0/sample.egg-info/top_level.txt sample-0.0.0/setup.cfg sample-0.0.0/setup.py
インストールして実行
$ pip install dist/sample-0.0.0.tar.gz
$ sample b'Hello\n'
インストール先に sample/data.txt
が含まれていることがわかります。
$ ls ~/.local/lib/python3.8/site-packages/sample/ data.txt __init__.py main.py __pycache__
S3バケットのオブジェクト数をawscliで確認
S3バケットにあるオブジェクトの数をawscliで確認する方法です。
2つ方法を書きます。1つ目は単純にlsコマンドで数えています。2つ目はCloudWatchのMetricsで、バージョニングが有効化されているバケットの場合は過去バージョンのオブジェクトも含まれます。
aws s3 lsコマンド
aws s3 ls
コマンドに --recursive --sum
を付けると最後にオブジェクト数とデータサイズが表示されます。
$ aws s3 ls --recursive --sum BUCKET_NAME ... Total Objects: 16849 Total Size: 41267890
ずらっと表示されるのが嫌なら tail
コマンドを後ろに付けます。
$ aws s3 ls --recursive --sum BUCKET_NAME | tail -n2 Total Objects: 16849 Total Size: 41267890
データサイズがいらないのであれば、wc
でもよいです。
$ aws s3 ls --recursive BUCKET_NAME | wc -l 16849
いずれもオブジェクトの一覧を出力して数を数えているだけです。オブジェクト数が膨大な場合は、数えるのに時間がかかりますし、API呼び出し回数も膨大になるので、お金もかかります。
昔似たことを書いてました。
aws cloudwatch get-metric-dataコマンド
CloudWatchのMetricsであれば、オブジェクト数が膨大でも問題ありませんが、現在ではなく、最新でも前日のたぶん朝9時ごろ時点での数しかわからないです。それに、コマンドがとても長いです。実行する際には以下のコマンド例から BUCKET_NAME
の部分を探して置き換えてください。
$ aws cloudwatch get-metric-data --metric-data-queries '[{"Id":"objectCount","MetricStat":{"Metric":{"Namespace":"AWS/S3","MetricName":"NumberOfObjects","Dimensions":[{"Name":"StorageType","Value":"AllStorageTypes"},{"Name":"BucketName","Value":"BUCKET_NAME"}]},"Period":86400,"Stat":"Maximum"}}]' --start-time $(date -u +%Y-%m-%dT00:00:00Z -d "1 day ago") --end-time $(date -u +%Y-%m-%dT00:00:00Z) | jq '.MetricDataResults[0] | {Timestamp: .Timestamps[0], Value: .Values[0]}'
以下のような出力を得られます。
{ "MetricDataResults": [ { "Id": "objectCount", "Label": "NumberOfObjects", "Timestamps": [ "2021-02-13T00:00:00Z" ], "Values": [ 16849.0 ], "StatusCode": "Complete" } ], "Messages": [] }
スクリプト化したほうがいいかも。
profile=default bucket_name=BUCKET_NAME aws --profile $profile cloudwatch get-metric-data --metric-data-queries '[{ "Id":"objectCount", "MetricStat":{ "Metric":{ "Namespace":"AWS/S3", "MetricName":"NumberOfObjects", "Dimensions":[ {"Name":"StorageType","Value":"AllStorageTypes"}, {"Name":"BucketName","Value":"'$bucket_name'"} ] }, "Period":86400, "Stat":"Maximum" } }]' \ --start-time $(date -u +%Y-%m-%dT00:00:00Z -d "1 day ago") \ --end-time $(date -u +%Y-%m-%dT00:00:00Z) | jq '.MetricDataResults[0] | {Timestamp: .Timestamps[0], Value: .Values[0]}'
最後にjqを付けることで、出力を少し簡潔にしています。
{ "Timestamp": "2021-02-13T00:00:00Z", "Value": 16849 }
S3バケットのバージョニングが有効化されている場合、CloudWatchで得られるオブジェクト数は過去バージョンも含めた数になります。
参考
バージョン情報
$ aws --version aws-cli/1.18.209 Python/3.8.5 Linux/5.4.0-1036-gcp botocore/1.19.49
大きなファイルをS3にawscliでMultipart Upload
S3にPUTするときの最大サイズは5GBだそうです。これを超えるサイズをアップロードする場合にはMultipart Uploadが必要です。
aws s3 cp
コマンドでは大きいファイルをアップロードする際には自動でMultipart Uploadになりますが、Multipart Uploadの処理の中身を理解するために、aws s3api
コマンドで手動で動かしてみました。
手順概要
aws s3api create-multipart-upload
コマンドでMultipart Upload開始を宣言し、UploadIdを取得aws s3api upload-part
コマンドで分割したファイルをアップロード。分割した数だけこのコマンドを実行- UploadIdは全部同じものを指定
- 1から始まる整数をパーツの番号として指定
- パートごとにETagが返却されるので、それを記録しておく
aws s3api complete-multipart-upload
コマンドでUploadIdとETagのリストを渡すことで完了
手順詳細
set -Ceu set -o pipefail # アップロード先を指定するパラメータ profile=default bucket=SAMPLE_BUCKET key=movies/sample.mov # アップロードするローカルにあるサンプルファイル localfile=sample.mov # 分割したファイルを保存する一時ディレクトリ mkdir -p parts # part 1つあたりのサイズ partsize=6000000 # AWSの仕様によりpart 1つあたりのサイズは5MB以上が必要です # ファイルサイズから分割数を計算 part_count=$(( ($(ls -l sample.mov | awk '{print $5}') + $partsize - 1) / $partsize)) ################################ # 手順1 ################################ # UploadId を取得 upload_id=$(aws --profile $profile s3api create-multipart-upload --bucket $bucket --key $key --query UploadId --output text) ################################ # 手順2 ################################ # 分割したファイルを順番に aws s3api upload-part コマンドによりアップロード。 # 手順3で必要なJSONファイルも同時に作成します。 echo '{"Parts":[' >| parts.json for i in $(seq $part_count); do # 1から分割数までをループ echo $i # アップロードするファイルの$i番目のpartを抜き出す ((cat $localfile | tail -c+$(( ( $i - 1) * $partsize + 1 ))) || true) | head -c $partsize >| parts/$i # headがあるとその前のcatやtailは異常終了してしまいますが、 # 冒頭で set -e しているので、スクリプトが中断しないように true を書いています。 # シンプルに書くと$iが2ならば次のようなコマンドです。 # cat $localfile | tail -c+6000001 | head -c 12000000 > parts/2 echo -n '{"ETag":' >> parts.json # part 1つをアップロード # ETagが出力されるので、そのままJSONに書き出す aws --profile $profile s3api upload-part --bucket $bucket --key $key --part-number $i --body parts/$i --upload-id $upload_id --query ETag --output text >> parts.json # JSONに書き出すPartNumberは1から始まる連番 echo -n ', "PartNumber":'$i'}' >> parts.json if [[ $i < $part_count ]]; then echo ',' >> parts.json else echo >> parts.json fi done echo ']}' >> parts.json # parts.json には以下のようなJSONが書き出されます # {"Parts":[ # {"ETag":"03c58c6387cd642d23657231feb1044f", "PartNumber":1}, # {"ETag":"d18f4e61324478f2b47f907e2b1367b3", "PartNumber":2}, # {"ETag":"52e280adbaa9afcfd30d071255a5b452", "PartNumber":3}, # {"ETag":"144e843c6ad29b05f5faedaf464a3a9a", "PartNumber":4}, # {"ETag":"2251deec7a0d3bad59df68114b18d27d", "PartNumber":5} # ]} ################################ # 手順3 ################################ # ETagのリストをJSONで渡して完了 # これをするまでは aws s3 ls コマンドで見てもアップロード中のファイルは見えない aws --profile $profile s3api complete-multipart-upload --bucket $bucket --key $key --upload-id $upload_id --multipart-upload file://parts.json # aws s3api complete-multipart-uploa コマンドは以下のようなレスポンスをします # { # "VersionId": "Z4epmUvPw5tDhUd5ctQ6hqtbJRABcND8", # "Location": "https://SAMPLE_BUCKET.s3.ap-northeast-1.amazonaws.com/movies%2Fsample.mov", # "Bucket": "SAMPLE_BUCKET", # "Key": "movies/sample.mov", # "ETag": "\"af82619f75aff5484a77ab040f516057-2\"" # }
メモ
分割サイズ
上記スクリプトでは6000000バイト(6MB弱)ずつに分割しています。分割サイズが5MBを下回ると次のようなエラーになってしまいます。
An error occurred (EntityTooSmall) when calling the CompleteMultipartUpload operation: Your proposed upload is smaller than the minimum allowed size
最後のパートだけはサイズが小さくても大丈夫です。どうしようもないですからね。
参考 UploadPart - Amazon Simple Storage Service
list-parts コマンド
手順2のあと以下のようなコマンドを実行すると
aws --profile $profile s3api list-parts --bucket $bucket --key $key --upload-id $upload_id
このようなJSONが出力されます。
{ "Parts": [ { "PartNumber": 1, "LastModified": "2021-02-02T06:05:03.000Z", "ETag": "\"5ec31e0a715293b7512d178890908310\"", "Size": 6000000 }, { "PartNumber": 2, "LastModified": "2021-02-02T06:05:04.000Z", "ETag": "\"9b41c50e2d76cc7959f07cffbafaedd6\"", "Size": 6000000 }, ... ], "Initiator": { ... }, "Owner": { ... }, "StorageClass": "STANDARD" }
手抜きして、これをそのまま手順3のaws s3api complete-multipart-upload
コマンドに渡してしまおうかと思ったのですが、aws s3api complete-multipart-upload
コマンドはETag
とPartNumber
のみが必要で、それ以外の要素がJSONに含まれるとエラーになってしまいました。
それにaws s3api list-parts
コマンドのレスポンスにあるETagはなぜか値自体にダブルクオーテーションが含まれていました。
Pythonのboto3で試すと
同じことをPythonのboto3で試したら、aws s3api list-parts
コマンドと同じく、ETagの値自体にダブルクオーテーションが含まれており、手順3に相当する箇所でもダブルクオーテーションを含めたままで動作しました。違和感・・・
PillowをAWS Lambda + Python + Serverless Frameworkで動かす
画像を処理できるPillowをAWS Lambda + Python + Serverless Frameworkの環境で動かしてみました。
AWS Lambda + Python + Serverless FrameworkにPythonのパッケージをインストールする方法は前回の記事に書きました。
AWS Lambda + Python + Serverless FrameworkのLayerにpipインストール
これに従うだけです。
requirements.txt
はこの1行。
Pillow
serverless.yml
は次のとおり。
service: sample frameworkVersion: '2' provider: name: aws runtime: python3.8 lambdaHashingVersion: 20201221 region: ap-northeast-1 functions: hello: handler: handler.hello events: - httpApi: "*" layers: - { Ref: PythonRequirementsLambdaLayer } plugins: - serverless-python-requirements custom: pythonRequirements: layer: true
handler.py
は次のとおり。画像ファイルをPillowで生成してそれをHTTPレスポンスします。Lambdaからのレスポンス時はBASE64エンコードが必要です。API Gatewayがそれをデコードしたうえでブラウザにレスポンスしてくれます。
import base64 import io import PIL import PIL.Image import PIL.ImageDraw def hello(event, context): print("Hello, Pillow!") print(PIL.__version__) # これはCloudWatch Logsに書き出される image = PIL.Image.new('RGB', (100, 100)) draw = PIL.ImageDraw.Draw(image) draw.rectangle((20, 20, 80, 80)) # 質素な四角形を作成 # バイナリイメージを作成 output = io.BytesIO() image.save(output, format = "JPEG") responseBody = output.getvalue() # BASE64エンコード responseBody = base64.b64encode(responseBody).decode("utf-8") return { "statusCode": 200, "headers": { "Content-Type": "image/jpeg", }, "body": responseBody, "isBase64Encoded": True, }
デプロイ。
$ serverless deploy -v
ローカルに手動でpipインストールしたりせず、全部Serverless Frameworkがいろいろやってくれます。
作成されたAPI Gatewayにブラウザでアクセスすると、次のような画像が表示されます。上記Pythonコードにある draw.rectangle((20, 20, 80, 80))
の絵です。
CloudWatch Logsには以下が書き出されていました。
Hello, Pillow! 8.1.0
AWS Lambda + Python + Serverless FrameworkのLayerにpipインストール
AWS LambdaのLayerにPythonのライブラリをインストールする方法です。Serverless Frameworkを使用している場合です。
少し前にも似た記事を書いたのですが、そのときはLayerに乗せられずLambda本体にライブラリのイメージが含まれてしまっていました。
前回:Serverless FrameworkとAWS Lambda with Pythonの環境にpipインストール
serverless.yml
に以下のように記述すればLayerにできましたので、メモしておきます。
コメントを書いた2か所が必要です。
service: sample frameworkVersion: '2' provider: name: aws runtime: python3.8 lambdaHashingVersion: 20201221 region: ap-northeast-1 functions: hello: handler: handler.hello layers: # LayerとFunctionの紐づけの設定。 # この書き方で serverless-python-requirements が # 作成してくれるLayerと紐づけてくれる。 - { Ref: PythonRequirementsLambdaLayer } plugins: - serverless-python-requirements custom: pythonRequirements: # この設定でpipライブラリをLayerに保存してくれる。 # これだけではFunctionとLayerの紐づけがなされないので、 # 上記functionsの節にもlayersの記述が必要 layer: true
バージョン情報
serverless@2.18.0
serverless-python-requirements@5.1.0
OpenCVからPillowに画像データを変換
PythonのOpenCVから画像を扱うPillowというライブラリに渡してみます。
OpenCVからPillowにはNumpy配列を介して渡せるのですが、3色の順番がOpenCVはBGRで、PillowがRGBという違いがあるようで、変換が必要でした。
import cv2 import PIL.Image def buildVideoCaptures(videoPath, outputPath): cap = cv2.VideoCapture(videoPath) if not cap.isOpened(): return _, img = cap.read() # imgは読み込んだフレームのNumpy配列でのピクセル情報(BGR) # imgのshapeは (高さ, 横幅, 3) # BGRをRGBに変換 # これをしないと色が変になる img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # Numpy配列からPillowのImageオブジェクトに変換 img = PIL.Image.fromarray(img) # 画像ファイルで書き出す img.save(outputPath, format = "JPEG") buildVideoCaptures("./sample.mp4", "./thumbnail.jpg")
バージョン情報
$ pipenv install opencv-python $ pipenv install pillow $ pipenv run pip list Package Version ------------- -------- numpy 1.19.5 opencv-python 4.5.1.48 Pillow 8.1.0 pip 20.3.1 setuptools 51.0.0 wheel 0.36.1
関連
PillowとOpenCVについて最近の記事