boto3 Fundamentals Best Practices
These are the habits that separate a quick boto3 script from code you can trust in production.
Search across all documentation pages
These are the habits that separate a quick boto3 script from code you can trust in production.
Because this section is about the boto3 library itself, every example is plain Python. Walk the list once now, then treat it as a review checklist before shipping.
Build the session once, then reuse the client everywhere.
import boto3
# Module scope: one session, one client, reused across calls and (safely) threads.
session = boto3.Session(region_name="us-east-1")
s3 = session.client("s3")
def handler(event, context):
return s3.list_buckets()["Buckets"]profile_name rather than flipping AWS_PROFILE mid-process.Authorization header when debugging.Let the default chain do the work; do not pass keys by hand.
import boto3
# No keys in code: the default chain finds env vars, a profile, or an IAM role.
s3 = boto3.client("s3", region_name="us-east-1")ClientError, not bare Exception. Handle botocore.exceptions.ClientError for service errors and branch on the error code.ClientError is an AWS response; BotoCoreError subclasses (like EndpointConnectionError) are local problems.retries in a Config instead of hand-rolling loops.err.response["ResponseMetadata"]["RequestId"] so AWS support can trace the call.Match the specific error and read its code and request id.
from botocore.exceptions import ClientError
try:
s3.get_object(Bucket="my-bucket", Key="missing.txt")
except ClientError as err:
code = err.response["Error"]["Code"] # e.g. "NoSuchKey"
rid = err.response["ResponseMetadata"]["RequestId"]
print(code, rid)client.get_paginator(operation) instead of managing NextToken or ContinuationToken by hand.Prefix, KeyConditionExpression, or FilterExpression so you fetch only what you need.BatchGetItem / BatchWriteItem over per-item calls, and stream large object bodies rather than buffering them.region_name on the session or client so behavior does not depend on ambient defaults.botocore.config.Config. Set retries, timeouts, and pool size there rather than editing internals.boto3-stubs[<service>] so mypy, pyright, and your editor understand clients and responses.Type your clients so mistakes surface before runtime.
# pip install 'boto3-stubs[s3]'
import boto3
from mypy_boto3_s3 import S3Client
s3: S3Client = boto3.client("s3", region_name="us-east-1")
s3.get_object(Bucket="my-bucket", Key="k.txt") # params now checkedRebuilding a client per call is a common, silent waste. Construction sets up connection pools and config, so reusing one client is cheaper and faster.
No. Sessions and resources are not thread-safe; create one session per thread. A single client, however, is safe to share across threads.
Hardcoded keys leak into source control and logs, are hard to rotate, and grant standing access. The default chain uses environment, profiles, and rotating IAM role credentials instead.
Usually no. botocore retries transient and throttling errors with backoff by default. Tune the retries in a Config rather than reimplementing loops.
Clients. The Resources API is in maintenance mode with partial coverage, so low-level clients are the complete, actively developed surface.
Catch ClientError from botocore.exceptions and read err.response["Error"]["Code"]. Handle BotoCoreError subclasses separately for client-side failures.
It uniquely identifies the call on the AWS side. When you open a support case or debug an intermittent failure, the request id lets AWS trace exactly what happened.
Only when an operation returns a single, bounded result with no continuation token. Any list operation that can return a token should use a paginator.
For shared or CI-checked code, yes. boto3-stubs[<service>] gives autocomplete and catches wrong parameter names before runtime, at zero runtime cost.
Unbounded loops and tight polling. They turn one logical action into thousands of billed requests, so use waiters, batching, caching, and events instead.
In a botocore.config.Config passed to the client, for example retries={"max_attempts": 5, "mode": "adaptive"} plus connect_timeout and read_timeout.
Share one client across threads for I/O-bound work, or adopt aioboto3 if you are already on asyncio. Both let requests overlap instead of running one at a time.
Stack versions: This page was written for boto3 1.43.x on Python 3.10+ (and the AWS SDK for JavaScript v3, Node.js 18+, where contrasted).
Reviewed by Chris St. John·Last updated Jul 23, 2026