boto3: the Python SDK

Amazon Web Services · Practical · 14 min

Most automation, Lambda code and glue scripts on AWS are written against an SDK, and boto3 is the most common one.

boto3 is the AWS SDK for Python, built on botocore. Create a client with boto3.client("s3"): it exposes every API operation as a snake_case method taking keyword arguments and returning a plain dict. The older resource interface (boto3.resource) offers objects like Bucket, but AWS no longer adds features to it, so prefer clients in new code.

Credentials come from the default credential chain, the same one the CLI uses: explicit arguments, environment variables, the shared config and credentials files (including SSO and assume-role profiles), container credentials (ECS, EKS Pod Identity) and finally the EC2 instance metadata service. Your code should not mention keys at all; the environment decides.

Patterns to know:

  • Paginators: list calls return one page (S3 returns up to 1,000 keys). client.get_paginator("list_objects_v2") iterates all pages for you.
  • Waiters: client.get_waiter("instance_running").wait(...) polls until a state is reached.
  • Errors: service errors raise botocore.exceptions.ClientError; branch on e.response["Error"]["Code"], for example NoSuchKey, ThrottlingException, ConditionalCheckFailedException.
  • Retries: configure with botocore.config.Config(retries={"mode": "standard"}) or "adaptive".

Clients are thread-safe and somewhat expensive to create: build them once, at module level in Lambda, and reuse. Sessions (boto3.Session(profile_name="dev")) are not thread-safe; use one per thread when you need different credentials.

Example

import boto3
from botocore.config import Config
from botocore.exceptions import ClientError

s3 = boto3.client("s3", region_name="eu-west-1",
                  config=Config(retries={"mode": "standard", "max_attempts": 5}))
s3.create_bucket(Bucket="acme-demo-123",
                 CreateBucketConfiguration={"LocationConstraint": "eu-west-1"})
for i in range(3):
    s3.put_object(Bucket="acme-demo-123", Key=f"logs/{i}.txt", Body=b"hi")

pages = s3.get_paginator("list_objects_v2").paginate(Bucket="acme-demo-123", Prefix="logs/")
print([o["Key"] for page in pages for o in page.get("Contents", [])])
# ['logs/0.txt', 'logs/1.txt', 'logs/2.txt']

try:
    s3.head_object(Bucket="acme-demo-123", Key="missing.txt")
except ClientError as e:
    print(e.response["Error"]["Code"])   # 404

Remember

  • Prefer clients; responses are plain dicts
  • Never put keys in code; rely on the credential chain
  • Use paginators for any list call and waiters for state changes
  • Catch ClientError and branch on the error code

Watch out for

  • Calling list_objects_v2 once and assuming you got every key.

Check yourself

  1. A script lists a bucket with 5,000 objects using a single list_objects_v2 call. What happens?
  2. Where should a Lambda function create its boto3 client?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html