AWS Cloud9でPython基礎～Amazon S3を利用する

2022/4/6(水)に行われたAWSセミナー「Pythonの基礎から学ぶ!サーバーレス開発はじめの一歩」で学習した、AWS Cloud9 から Amazon S3 に Pythonでファイルを読み込み・アップロードする部分の復習。

# 構成図
AWS Cloud9から、 Amazon S3バケット上のテキストを読み込む操作と、テキストをアップロードする操作をPythonで実現する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/a6440a86-5807-63b9-acf4-10269055849e.png)

# 1.S3バケットにあるファイルを読み込む

## 1-1.S3バケットの作成
マネジメントコンソールで S3 画面にいき、「バケットを作成」をクリックする。
バケット名は使用用途がわかる一意の名前を付ける。このバケット名はこの後のPythonコードで使用するため、コピーしてメモしておく。
バケット名以外はデフォルトのまま、「バケットを作成」をクリックする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/4b519eb7-c84c-62b4-81a1-ec9c35946b47.png)

## 1-2.ファイルのアップロード
AWS Cloud9 でサンプルファイルを作成し、S3 バケットにアップロードする。
東京リージョンでAWS Cloud9 の画面を開き、Cloud9には適当に名前を付け、環境はデフォルト値でCloud9を起動する。
Cloud9 の画面上部で +ボタンを押し、New Fileで新しいテキストファイルを作成する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/f1361104-2246-6692-a8a3-dff20e0b3099.png)

テキストファイルには以下を入力する。

```
I want to be able to use Python!
```

ファイルに名前を付けて保存する。[File]-[Save]から名前を付けて保存する。
今回は `sample_file.txt` と名前を付けて保存する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/27057ac6-796c-efd7-05db-03c7cabdf417.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/6d4b6829-1070-0132-1d41-3ace6c86648b.png)

このファイルをS3 バケットにアップロードする。
今回は Cloud9 で利用できる[AWS ツールキット](https://docs.aws.amazon.com/ja_jp/cloud9/latest/user-guide/toolkit-welcome.html)を利用してS3 バケットにファイルをアップロードする。
Cloud9 画面左のAWSロゴマークをクリックし、[Asia Pacific(Tokyo)]-[S3]-[<1-1.S3バケットの作成で作成したバケット名>]の順に選択する。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/79969681-70d7-8a96-0ee8-0b99e44d3bcb.png)
バケット名を右クリックし[Upload Files…]をクリックする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/67562bea-6a15-29a5-efc5-ce59442a70fb.png)
先ほど作成した`sample_file.txt`を選択し「Upload」する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/a09ed65e-d48f-4f4d-33b8-7075ae497d36.png)
これで作成した S3バケットに sample_file.txt をアップロードできた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/2ea9fb83-6887-147d-e99a-080e73a9fef0.png)

## 1-3.Boto3を使ってS3のファイルを読み込む
Cloud9 で `read_file.py` というファイルを作成していく。
[Boto3のS3に関するドキュメント](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html)を見ると、[S3.Client](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#client)を使って`client`を定義した後、[Client.get_object()](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.get_object)を使ってファイルをダウンロードできることがわかる。

Boto3のS3に関するドキュメント↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/08bf0b16-d8c8-71e6-e6b6-eea952ef1c66.png)

S3.Client↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/520bda11-3664-dfbf-1289-faebd485eacf.png)

```py
import boto3

client = boto3.client('s3')
```
Client.get_object()↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/a3e4247c-fbb4-3d3c-2e73-0631beefe9fc.png)

```py
response = client.get_object(
    Bucket='string', # [REQUIRED]
    Key='string', # [REQUIRED]
)
```

まず、以下のように`read_file.py`を記述して保存する。

```py:read_file.py①
import boto3

def read_s3_file():
    s3_client = boto3.client('s3')
    input_object = s3_client.get_object(
    Bucket='<1-1.S3バケットの作成で作成したバケット名>',
    Key='sample_file.txt',
    )
    
    print('Responce:', input_object)
    
read_s3_file()
```

Cloud9 画面下部のTerminalで以下コマンドを実行し、`read_file.py`を実行する。

```
python read_file.py
```

実行結果

```
Responce: {'ResponseMetadata': {'RequestId': 'X7R9QCxxxxxxxxxx', 'HostId': 'm/Vl1mnBeyWFDF7xFlk5BPAVkvZA9FqAu0Qh+8sPaQhE9U12pMJqX6i46Ws1fhZ6Iqxxxxxxxxxx', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amz-id-2': 'm/Vl1mnBeyWFDF7xFlk5BPAVkvZA9FqAu0Qh+8sPaQhE9U12pMJqX6i46Ws1fhZ6Iqxxxxxxxxxx', 'x-amz-request-id': 'X7R9QCxxxxxxxxxx', 'date': 'Sat, 16 Apr 2022 13:28:31 GMT', 'last-modified': 'Sat, 16 Apr 2022 11:28:58 GMT', 'etag': '"3f7b97331fd7e588ee2e60xxxxxxxxxx"', 'accept-ranges': 'bytes', 'content-type': 'text/plain', 'server': 'AmazonS3', 'content-length': '32'}, 'RetryAttempts': 0}, 'AcceptRanges': 'bytes', 'LastModified': datetime.datetime(2022, 4, 16, 11, 28, 58, tzinfo=tzutc()), 'ContentLength': 32, 'ETag': '"3f7b97331fd7e588ee2e60xxxxxxxxxx"', 'ContentType': 'text/plain', 'Metadata': {}, 'Body': <botocore.response.StreamingBody object at 0x7fxxxxxxxxxx>}
```

次に、ファイルの中身を取り出す。
以下のように`read_file.py`を変更して保存する。

```py:read_file.py②
import boto3

def read_s3_file():
    s3_client = boto3.client('s3')
    input_object = s3_client.get_object(
    Bucket='<1-1.S3バケットの作成で作成したバケット名>',
    Key='sample_file.txt',
    )
    
    print('Responce:', input_object)
    input_object_byte = input_object['Body'].read()
    print('Byte:', input_object_byte)
    
read_s3_file()
```

実行結果

```
Responce: {'ResponseMetadata': {'RequestId': '1NHTFSxxxxxxxxxx', 'HostId': 'Sdc7A9A7RQzY+Go9UycTCAB6NgP2KpGnnCendmqOnY2H8QlYZmjGRJr5Wa+t2FvA+Txxxxxxxxxx', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amz-id-2': 'Sdc7A9A7RQzY+Go9UycTCAB6NgP2KpGnnCendmqOnY2H8QlYZmjGRJr5Wa+t2FvA+Txxxxxxxxxx', 'x-amz-request-id': '1NHTFSxxxxxxxxxx', 'date': 'Sat, 16 Apr 2022 13:34:38 GMT', 'last-modified': 'Sat, 16 Apr 2022 11:28:58 GMT', 'etag': '"3f7b97331fd7e588ee2e60xxxxxxxxxx"', 'accept-ranges': 'bytes', 'content-type': 'text/plain', 'server': 'AmazonS3', 'content-length': '32'}, 'RetryAttempts': 0}, 'AcceptRanges': 'bytes', 'LastModified': datetime.datetime(2022, 4, 16, 11, 28, 58, tzinfo=tzutc()), 'ContentLength': 32, 'ETag': '"3f7b97331fd7e588ee2e60xxxxxxxxxx"', 'ContentType': 'text/plain', 'Metadata': {}, 'Body': <botocore.response.StreamingBody object at 0x7fxxxxxxxxxx>}
Byte: b'I want to be able to use Python!'
```

`input_object_byte = input_object['Body']` で、`Body`を取り出して、`botocore.response.StreamingBody.read()`を使い、ファイルの中身を取り出した。
`botocore.response.StreamingBody`の仕様はドキュメントに記載されている⇒[Response Reference](https://botocore.amazonaws.com/v1/documentation/api/latest/reference/response.html)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/f0c2039a-9b01-1af7-d503-6dc34c4485ef.png)

`botocore.response.StreamingBody.read()`の値は、先ほどの出力の`Byte:`～の後の文字列。

```
Byte: b'I am learning to code.'
```

ここで、`'I am learning to code.'`の前の`b`は、ここでの出力が[バイトオブジェクト](https://docs.python.org/ja/3/library/stdtypes.html#bytes)であるという意味。
[bytes.decode()](https://docs.python.org/ja/3/library/stdtypes.html#bytes.decode) を使って`str`型に変換できる。

以下のように`read_file.py`を変更して保存する。

```py:read_file.py③
import boto3

def read_s3_file():
    s3_client = boto3.client('s3')
    input_object = s3_client.get_object(
    Bucket='<1-1.S3バケットの作成で作成したバケット名>',
    Key='sample_file.txt',
    )
    
    print('Responce:', input_object)
    input_object_byte = input_object['Body'].read()
    print('Byte:', input_object_byte)
    input_object_text = input_object_byte.decode()
    print('str:', input_object_text)
    
read_s3_file()
```

実行結果

```
Responce: {'ResponseMetadata': {'RequestId': 'HPTKQHxxxxxxxxxx', 'HostId': 'DznSU7HNoWEFokVXHdOGbGKthpmLWLp1kNTV0VUty61N7jrqBBsYt24QtOOGkezUFjxxxxxxxxxx', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amz-id-2': 'DznSU7HNoWEFokVXHdOGbGKthpmLWLp1kNTV0VUty61N7jrqBBsYt24QtOOGkezUFjxxxxxxxxxx', 'x-amz-request-id': 'HPTKQHxxxxxxxxxx', 'date': 'Sat, 16 Apr 2022 13:49:07 GMT', 'last-modified': 'Sat, 16 Apr 2022 11:28:58 GMT', 'etag': '"3f7b97331fd7e588ee2e60xxxxxxxxxx"', 'accept-ranges': 'bytes', 'content-type': 'text/plain', 'server': 'AmazonS3', 'content-length': '32'}, 'RetryAttempts': 0}, 'AcceptRanges': 'bytes', 'LastModified': datetime.datetime(2022, 4, 16, 11, 28, 58, tzinfo=tzutc()), 'ContentLength': 32, 'ETag': '"3f7b97331fd7e588ee2e60xxxxxxxxxx"', 'ContentType': 'text/plain', 'Metadata': {}, 'Body': <botocore.response.StreamingBody object at 0x7fxxxxxxxxxx>}
Byte: b'I want to be able to use Python!'
str: I want to be able to use Python!
```

出力の`Str:`以降が、S3 バケット内のオブジェクトから読み取ったテキストファイルに記載された文字列であることが確認できる。

# 2.S3バケットへファイルをアップロードする

## 2-1.Boto3でS3にファイルを作成する

Cloud9 で `write_file.py` というファイルを作成していく。
[Boto3のS3に関するドキュメント](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html)を見ると、[S3.Client](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#client)を使ってclientを定義した後、[Client.put_object()](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.put_object) を使ってファイルをダウンロードできることがわかる。

S3.Client↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/cc3d45f8-10d4-104d-b037-d1bedb4680ba.png)

```py
import boto3

client = boto3.client('s3')
```
Client.put_object()↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/28a9e1e5-9f5a-c420-b5f0-e9af0940bda2.png)

```py
response = client.put_object(
    Bucket='string', # [REQUIRED]
    Key='string', # [REQUIRED]
    Body=b'bytes'|file,
)
```

以下のように`write_file.py`を記述して保存する。

```py:write_file.py
import boto3

def write_s3_file():
    s3_client = boto3.client('s3')
    
    output_text = 'This is uploaded.'.encode()
    
    s3_client.put_object(
    Bucket='<1-1.S3バケットの作成で作成したバケット名>', 
    Key='upload.txt', 
    Body=output_text,
    )
    
write_s3_file()
```

`output_text = 'This is uploaded.'.encode()` 部について補足する。
[Client.put_object()](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.put_object) のRequest Syntaxの`Body`は以下のようになっており、ファイルもしくはバイトオブジェクトを要求している。

```py
Body=b'bytes'|file,
```

通常の文字列を渡すとエラーになるので、Pythonの`str`型が持っている関数[encode()](https://docs.python.org/ja/3/library/stdtypes.html#str.encode) を使ってバイトオブジェクトに変換して渡している。

Cloud9 画面下部のTerminalで以下コマンドを実行し、`write_file.py`を実行する。

```
python write_file.py
```

Terminal上には実行結果は何も表示されない。エラーなく実行できればOK。
Cloud9 で利用できるAWS ツールキットを利用してS3 バケットにファイルがアップロードされたか確認する。

Cloud9 画面左のAWSロゴマークをクリックし、[Asia Pacific(Tokyo)]-[S3]-[<1-1.S3バケットの作成で作成したバケット名>]の順に選択する。`upload.txt`がアップロードされているのが確認できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/05cb7af9-d030-daa8-ffff-fd2f7527b006.png)

アップロードされているのが確認できなければ、AWS ツールキットメニュー右上のファイルのようなマークを右クリックし、`Refresh Explorer`をクリックして更新する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/9404faa3-1c3a-3e0d-89cc-76de5e0d47e5.png)

# 参考

- [AWSツールキット](https://docs.aws.amazon.com/ja_jp/cloud9/latest/user-guide/toolkit-welcome.html)
- [S3.Client.get_objec](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.get_object)
- [botocore.response.StreamingBodyの仕様](https://botocore.amazonaws.com/v1/documentation/api/latest/reference/response.html)
- [バイトオブジェクト](https://docs.python.org/ja/3/library/stdtypes.html#bytes)
- [bytes.decode](https://docs.python.org/ja/3/library/stdtypes.html#bytes.decode)
- [Client.put_object()](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.put_object)

[AWS Cloud9でPython基礎](https://qiita.com/emiki/items/65eb26492be5315d929d)
[AWS Cloud9でPython基礎～ファイルの読み書き](https://qiita.com/emiki/items/f93bddb4a1fd76b102b5)
[AWS LambdaでPython基礎](https://qiita.com/emiki/items/5daa0e31b8a8fa7c680a)
[AWS Cloud9でPython基礎～Amazon S3を利用する](https://qiita.com/emiki/items/7433971c9f1ee03dd9ea)
[AWS Cloud9でPython基礎～Amazon S3とAmazon Translateを利用する](https://qiita.com/emiki/items/b95fad815d9f8e360dd0)
[AWS LambdaでPython基礎～Amazon S3とAmazon Translateを利用する](https://qiita.com/emiki/items/0f9bbddd6f4cd475f12b)