Generated banner for AWS SDK Python Usage
Generated logo for AWS SDK Python Usage

AWS SDK Python Usage

AWSOfficialPublished by AWS

  • Skill
  • Tool integration
  • Cloud and DevOps
  • Apache-2.0
  • Medium risk

From the author

AWS SDK for Python (boto3/botocore) development patterns. You MUST use this skill when writing Python code that uses AWS services via boto3 or botocore. This includes creating service clients or resources, configuring sessions and credentials, handling errors with ClientError, using paginators and waiters, S3 file transfers and presigned URLs, DynamoDB table operations, and any boto3/botocore client configuration. Use this skill whenever Python code imports boto3 or botocore, or when the user asks about AWS operations in Python.
— the description by AWS

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides standardized patterns for interacting with AWS services using the Python SDKs boto3 and botocore. It covers essential development tasks including client and resource instantiation, session management, credential handling, error management, and service-specific configurations. The skill emphasizes idiomatic usage, such as reusing client instances, leveraging paginators and waiters, and implementing robust error handling using typed exceptions.

What it does

  • Provides patterns for creating and configuring boto3 clients and resources.
  • Outlines best practices for session management and credential resolution.
  • Demonstrates idiomatic error handling using botocore exceptions.
  • Explains the use of paginators and waiters for efficient API interaction.
  • Details client configuration for retries, timeouts, and connection pooling.
  • Offers guidance on logging and debugging SDK requests.

When to use it

  • When writing Python code that imports or utilizes boto3 or botocore.
  • When implementing AWS service interactions in Python applications.
  • When configuring custom retry logic, timeouts, or connection settings for AWS clients.
  • When troubleshooting credential resolution or API error handling in Python.

Advantages

  • Promotes consistent and maintainable AWS SDK usage patterns.
  • Reduces common errors related to credential management and client lifecycle.
  • Encourages the use of built-in SDK features like paginators and waiters.
  • Provides clear guidance on distinguishing between low-level clients and high-level resources.

Usage examples

“How do I list all S3 buckets using boto3?”
The agent provides a code snippet using the S3 resource interface to iterate through buckets, demonstrating the high-level object-oriented approach.
“Show me how to handle a ResourceNotFoundException when calling Lambda.”
The agent provides a function example that catches the specific exception via client.exceptions and returns a fallback value, rather than using a generic catch-all.
“How can I configure a custom timeout for my S3 client?”
The agent demonstrates using botocore.config.Config to set connect_timeout and read_timeout, then passing this configuration to the boto3 client constructor.
“What is the best way to assume an IAM role in a script?”
The agent explains the use of the STS client to assume a role and provides an example of how to use the resulting temporary credentials to instantiate a new client.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/aws/aws-sdk-python-usage/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip aws-sdk-python-usage.zip -d .agents/skills

Documentation by AWS

Do not use emojis in any code, comments, or output when this skill is active.

AWS SDK for Python (boto3)

boto3 is the high-level Python SDK for AWS. It wraps botocore (the low-level SDK) and provides two distinct interfaces: clients (low-level, 1:1 API mapping) and resources (high-level, object-oriented). Understanding which to use and when is essential.

Client vs Resource

Clients map directly to AWS service APIs. Every service has a client. Responses are plain dicts.

Resources provide an object-oriented interface with attributes and actions. Only some services have resources (S3, DynamoDB, EC2, IAM, SQS, SNS, CloudFormation, CloudWatch, Glacier). Resources auto-marshal types (especially useful for DynamoDB).

import boto3

# Client - low-level, all services
s3_client = boto3.client("s3")
response = s3_client.list_buckets()
buckets = response["Buckets"]  # plain dicts

# Resource - high-level, select services
s3_resource = boto3.resource("s3")
for bucket in s3_resource.buckets.all():
    print(bucket.name)  # attribute access, not dict keys

Use clients when you need full API coverage or the service has no resource interface. Use resources when they exist and simplify your code (especially DynamoDB and S3).

Session and Client Creation

import boto3

# Default session implicitly created
client = boto3.client("s3")
resource = boto3.resource("dynamodb")

# Explicit session use when you need to customize how
# clients are created, use an explicit profile, etc.
session = boto3.Session(
    profile_name="my-profile",
    region_name="us-west-2",
)
client = session.client("s3")

Do not create clients inside loops - reuse a single client instance. Clients are thread safe and can be shared across threads once they're instantiated.

Making API Calls

# Client - pass parameters as keyword arguments, get dicts back
response = client.get_object(Bucket="my-bucket", Key="my-key")
data = response["Body"].read()

# Resource - use object methods and attributes
obj = s3_resource.Object("my-bucket", "my-key")
response = obj.get()
data = response["Body"].read()

Parameter names match the exact casing of the AWS API, which is typically PascalCase, not snake_case.

Error Handling

Only catch exceptions when you have something actionable to do - return a fallback value, retry, take a different code path. Catching an exception just to print it and swallow it is wrong: it hides the real error and prevents callers from reacting. Let exceptions propagate by default.

When you do catch, prefer typed exceptions on the client over generic ClientError with string code matching through the client.exceptions attribute:

lambda_client = boto3.client("lambda")

def get_function_config(name: str) -> dict | None:
    """Return function configuration, or None if it doesn't exist."""
    try:
        return lambda_client.get_function_configuration(FunctionName=name)
    except lambda_client.exceptions.ResourceNotFoundException:
        return None  # actionable: convert missing function to None
    # Everything else propagates - caller or main() handles it

Use generic ClientError only as a catch-all in a top-level error handler, not in business logic functions. It lives in botocore, not boto3:

from botocore.exceptions import ClientError

def main() -> int:
    try:
        result = do_the_work()
        print(result)
        return 0
    except ClientError as e:
        print(f"Error: {e}", file=sys.stderr)
        return 1

For the full error hierarchy and botocore exceptions, see references/error-handling.md.

Script Structure

When asked to write a script that uses boto3 or botocore, keep if __name__ == "__main__" to a single function call. Argument parsing, error presentation, and exit codes belong in main(), not scattered across business logic functions:

def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("bucket")
    args = parser.parse_args()

    try:
        do_the_work(args.bucket)
        return 0
    except ClientError as e:
        print(f"Error: {e}", file=sys.stderr)
        return 1

if __name__ == "__main__":
    sys.exit(main())

Never call sys.exit() from a business logic function -- it makes the function untestable and unusable as a library. Raise an exception or return an error value instead, and let main() decide how to present it.

Pagination

Never manually loop with NextToken -- use paginators. When you only need specific fields, use .search() with a JMESPath expression to extract and flatten across pages:

paginator = iam.get_paginator("list_users")
for name in paginator.paginate().search("Users[].UserName"):
    print(name)

# Filter and project
for arn in paginator.paginate().search("Users[?Path == '/admin/'][].Arn"):
    print(arn)

When you need the full response object per item, or need per-page control (e.g. counting pages, batching by page), iterate pages directly:

for page in paginator.paginate():
    for user in page.get("Users", []):
        process(user)

For more details on pagination, see: references/pagination.md.

Waiters

Wait for a resource to reach a desired state:

waiter = client.get_waiter("bucket_exists")
waiter.wait(
    Bucket="my-bucket",
    WaiterConfig={"Delay": 5, "MaxAttempts": 20},
)

For more details on waiters, see references/waiters.md.

Client Configuration

Use botocore.config.Config for retries, timeouts, and connection pool settings, etc.:

from botocore.config import Config

config = Config(
    retries={"total_max_attempts": 2, "mode": "adaptive"},
    connect_timeout=5,
    read_timeout=10,
    max_pool_connections=50,
)
client = boto3.client("s3", config=config)

When creating custom configuration for a client, see references/configuration.md.

Logging

Both boto3 and botocore use the standard library logging module. You can configure logging through the standard logging APIs, or you can use helpers provided by boto3 and botocore for convenience:

# Quick: log all botocore wire-level details to stderr
boto3.set_stream_logger("")  # root logger -- everything
boto3.set_stream_logger("botocore")  # just botocore

# Botocore, log all botocore details
import logging

from botocore.session import Session

session = Session()

session.set_stream_logger('botocore', logging.DEBUG)
# OR: Configure logging to a file.
session.set_file_logger(logging.DEBUG, '/tmp/botocore.log')

set_stream_logger(name, level=logging.DEBUG) adds a StreamHandler to the named logger. This is the idiomatic way to get request/response debug output from the SDK.

Common Issues

Issue: ClientError import location

Wrong: from boto3.exceptions import ClientError Right: from botocore.exceptions import ClientError

Service specific customizations

When writing any Python code that uses the following services, you MUST load these additional reference files for best practices and custom high level APIs:

  • S3 - you MUST load references/s3.md.

  • Dynamodb - you MUST load references/dynamodb.md.

References

  • Client configuration (retries, timeouts, endpoints): references/configuration.md

  • Credentials and sessions: references/credentials.md

  • Error handling patterns: references/error-handling.md

  • Pagination: references/pagination.md

  • Waiters: references/waiters.md

  • S3 transfers and presigned URLs: references/s3.md

  • DynamoDB operations: references/dynamodb.md

Files

references/error-handling.md

Error Handling Reference

Core Principle

Only catch an exception when you have an actionable response: return a fallback, retry, take a different code path. If the only thing you'd do is print the error, don't catch it -- let it propagate. The caller (or a top-level handler) is in a better position to decide what to do.

ClientError Anatomy

botocore.exceptions.ClientError is the base exception for all AWS API errors:

from botocore.exceptions import ClientError

try:
    client.describe_instances(InstanceIds=["i-nonexistent"])
except ClientError as e:
    error = e.response["Error"]
    metadata = e.response["ResponseMetadata"]

    error["Code"]               # "InvalidInstanceID.NotFound"
    error["Message"]            # "The instance ID 'i-nonexistent' does not exist"
    metadata["HTTPStatusCode"]  # 400
    metadata["RequestId"]       # AWS request ID for support cases

Service-Specific Exceptions

Each client exposes typed exceptions generated from the service model. These are subclasses of ClientError, so a ClientError catch still works as a fallback:

s3 = boto3.client("s3")
try:
    s3.get_object(Bucket="bucket", Key="key")
except s3.exceptions.NoSuchKey:
    return None  # actionable: missing key is a valid case

List available exceptions for a client:

print([e for e in dir(s3.exceptions) if not e.startswith("_")])

Common botocore Exceptions

from botocore.exceptions import (
    ClientError,              # AWS API returned an error response
    NoCredentialsError,       # no credentials found in the chain
    PartialCredentialsError,  # incomplete credentials (e.g. key without secret)
    NoRegionError,            # no region configured
    ParamValidationError,     # invalid parameters before request is sent
    EndpointConnectionError,  # could not connect to the endpoint
    ConnectTimeoutError,      # connection timed out
    ReadTimeoutError,         # read timed out waiting for response
    WaiterError,              # waiter reached max attempts without success
)

ParamValidationError is raised locally before any network request -- it means the parameters failed botocore's client-side validation.

Error Handling Patterns

Actionable catch: convert to return value
def get_item(table, key: dict) -> dict | None:
    response = table.get_item(Key=key)
    return response.get("Item")  # None if missing, no exception needed

def head_object(client, bucket: str, key: str) -> dict | None:
    try:
        return client.head_object(Bucket=bucket, Key=key)
    except client.exceptions.ClientError as e:
        if e.response["ResponseMetadata"]["HTTPStatusCode"] == 404:
            return None
        raise
Actionable catch: conditional put race
try:
    table.put_item(
        Item=new_item,
        ConditionExpression=Attr("pk").not_exists(),
    )
except table.meta.client.exceptions.ConditionalCheckFailedException:
    # Another writer got there first -- fetch what they wrote
    return table.get_item(Key={"pk": new_item["pk"]})["Item"]
Actionable catch: create-if-not-exists
try:
    client.create_bucket(Bucket="my-bucket")
except client.exceptions.BucketAlreadyOwnedByYou:
    pass  # already exists, that's fine
Top-level catch-all in main()

Business logic functions should let exceptions propagate. The main() function is the right place for a generic catch-all that presents errors cleanly to the user. Keep the catch-all simple -- just ClientError. Other exceptions like NoCredentialsError already have clear messages and can propagate naturally:

from botocore.exceptions import ClientError

def main() -> int:
    try:
        do_the_work()
        return 0
    except ClientError as e:
        print(f"Error: {e}", file=sys.stderr)
        return 1

if __name__ == "__main__":
    sys.exit(main())
What NOT to do
# Wrong: catching just to print and swallow
try:
    client.describe_table(TableName=name)
except client.exceptions.ResourceNotFoundException:
    print("Table not found")     # swallowed -- caller has no idea it failed
except NoCredentialsError:
    print("No credentials")      # swallowed
except EndpointConnectionError:
    print("Can't connect")       # swallowed

# Wrong: sys.exit() from a business logic function
def process_queue(queue_url):
    if not queue_url:
        print("No queue URL provided")
        sys.exit(1)              # untestable, unusable as library code

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Python

Security

What our scanner noticed· Medium risk

  • References to credential files

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
AWS
Publisher
AWS (Official)
Source
aws/agent-toolkit-for-aws · skills/core-skills/aws-sdk-python-usage · commit 0a218f8
Licence
Apache License 2.0 (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • NOTICE (added by AutoGineer from NOTICE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced
Also distributed in
aws/agent-toolkit-for-aws

AutoGineer is not affiliated with or endorsed by AWS or AWS.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download aws-sdk-python-usage.zip