diff --git a/docs/source/guide/s3-client-context-manager.rst b/docs/source/guide/s3-client-context-manager.rst new file mode 100644 index 0000000000..6cb31bd5d6 --- /dev/null +++ b/docs/source/guide/s3-client-context-manager.rst @@ -0,0 +1,64 @@ +.. Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 + International License (the "License"). You may not use this file except in compliance with the + License. A copy of the License is located at http://creativecommons.org/licenses/by-nc-sa/4.0/. + + This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + either express or implied. See the License for the specific language governing permissions and + limitations under the License. + + +############### +S3 Client Context Manager +############### + +Following the pythonic-way, you may want to flexibly and easily manage resource release using a context manager for +the `boto3.client` instances you create. + +The following is an example of creating a simple context manager for an S3 client: + +.. code-block:: python + + from contextlib import contextmanager + from typing import Optional + + import boto3 + + + @contextmanager + def s3_client(**kwargs): + client = None + try: + client = boto3.client("s3", **kwargs) + yield client + finally: + if client: + client.close() + + +Using the `s3_client` context manager you can easily create service functions that implement the necessary +actions with the storage. + +.. code-block:: python + + def s3_get_object( + key: str, + # Your global constants from project settings here. + bucket_name: str = AWS_BUCKET_NAME, + aws_access_key_id=AWS_ACCESS_KEY_ID, + aws_secret_access_key=AWS_SECRET_ACCESS_KEY, + endpoint_url=AWS_ENDPOINT_URL, + ): + with s3_client( + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + endpoint_url=endpoint_url, + ) as s3_cli: + return s3_cli.get_object(Bucket=bucket_name, Key=key) + + + # Usage example: + key = "path/to/somefile" + s3_obj = s3_get_object(key) + diff --git a/docs/source/guide/s3-examples.rst b/docs/source/guide/s3-examples.rst index acebe6c2c8..70e302a71f 100644 --- a/docs/source/guide/s3-examples.rst +++ b/docs/source/guide/s3-examples.rst @@ -39,3 +39,4 @@ services. s3-example-static-web-host s3-example-configuring-buckets s3-example-privatelink + s3-client-context-manager diff --git a/docs/source/guide/s3-presigned-urls.rst b/docs/source/guide/s3-presigned-urls.rst index 2ca25a54e5..8df01bf267 100644 --- a/docs/source/guide/s3-presigned-urls.rst +++ b/docs/source/guide/s3-presigned-urls.rst @@ -70,6 +70,59 @@ perform a GET request. response = requests.get(url) +Pass the original file names and extensions to presigned URLs +=================================================== + +If you are using S3 as an object store for your web service users' files, a common solution is to store +anonymized "raw" files like `file.bin` in S3, and the original file names and extensions in a relational database. + +In this scenario, unless you explicitly specify a `Content-Disposition` HTTP-header, your users will receive +“raw” files using generated presigned urls. + +.. code-block:: python + + import logging + import boto3 + from botocore.exceptions import ClientError + + + def create_presigned_url(bucket_name, object_name, expiration=3600, + request_content_disposition: str | None = None): + """Generate a presigned URL to share an S3 object + + :param bucket_name: string + :param object_name: string + :param expiration: Time in seconds for the presigned URL to remain valid + :param request_content_disposition: HTTP header that tells the browser the name and extension of the original file. + :return: Presigned URL as string. If error, returns None. + """ + + # Generate a presigned URL for the S3 object + s3_client = boto3.client('s3') + params = {"Bucket": bucket_name, "Key": object_name} + if request_content_disposition: + params["ResponseContentDisposition"] = request_content_disposition + try: + response = s3_client.generate_presigned_url('get_object', + Params=params, + ExpiresIn=expiration) + except ClientError as e: + logging.error(e) + return None + + # The response contains the presigned URL + return response + +By applying these changes, you can generate links with the original file names: +.. code-block:: python + + from urllib.parse import quote + + original_filename = 'demo_file.txt' + url = create_presigned_url('amzn-s3-demo-bucket', 'OBJECT_NAME', + request_content_disposition=f'attachment; filename="{quote(original_filename)}"') + + Using presigned URLs to perform other S3 operations ===================================================