Boto3 Client vs Resource: Key Differences
In the AWS SDK for Python (Boto3), interacting with cloud services is handled through two distinct interfaces: low-level Clients and high-level Resources. While both abstractions communicate with the same underlying AWS service APIs, they differ fundamentally in their internal architecture, data representations, and service coverage. Clients provide a direct, one-to-one mapping with raw AWS HTTP endpoints returning standard Python dictionaries, whereas Resources present an object-oriented, stateful layer that abstracts away network calls into Python classes and collections.
Core Architectural Distinction
The fundamental difference between a Client and a Resource lies in
how they are constructed from the underlying botocore
engine:
- Clients (Low-Level): Clients are generated
dynamically from JSON service definition models found in
botocore. Every operation exposed by a client corresponds strictly to a single API call defined in the official AWS service documentation. A Client behaves as an access layer that takes Python keyword arguments, serializes them into HTTP requests, sends them to AWS, and deserializes the HTTP response into native Python dictionaries. - Resources (High-Level): Resources are built on top of Clients using a secondary resource-definition model. Instead of exposing raw network requests, a Resource organizes an AWS service into identifiers, attributes, actions, and sub-resources. A Resource wraps the low-level client calls, parsing the returned dictionary responses to populate object attributes dynamically.
Data Representation and Return Types
The structural difference becomes most apparent in how both interfaces handle input and return data:
import boto3
# Low-Level Client: Returns a raw dictionary
s3_client = boto3.client('s3')
client_response = s3_client.list_objects_v2(Bucket='my-bucket')
# Access requires dict keys: client_response['Contents'][0]['Key']
# High-Level Resource: Returns Python objects
s3_resource = boto3.resource('s3')
bucket = s3_resource.Bucket('my-bucket')
# Access uses object attributes: [obj.key for obj in bucket.objects.all()]- Data Models: Clients are stateless; they do not
retain data between calls. Resources manage state by storing data inside
object instances after network calls, allowing you to access metadata
through standard Python dot-notation (e.g.,
instance.state['Name']orbucket.creation_date). - Sub-Resources and References: Resources link
related AWS services structurally. For example, calling
bucket.objectsfrom an S3Bucketresource automatically resolves to anObjectSummaryresource without requiring you to manually pass parameters likeBucketNamein subsequent requests.
Pagination vs. Collections
Handling large sets of data requires different mechanisms depending on the interface:
- Paginators in Clients: Clients require you to
either manually inspect truncation tokens (such as
NextContinuationToken) or instantiate a distinctPaginatorobject viaclient.get_paginator('operation_name')to iterate through pages of raw dictionaries. - Collections in Resources: Resources wrap paginators
into native Python iterables called
Collections. Collections automatically handle pagination under the hood, allowing developers to write idiomatic Pythonforloops directly over the resource instances without tracking tokens.
Service Coverage and Maintenance
AWS does not maintain parity between Clients and Resources:
- Clients: Provide 100% service coverage. Whenever AWS releases a new service or updates an existing API operation, the low-level client supports it immediately via automated model updates.
- Resources: Only exist for a select subset of mature AWS services (such as S3, EC2, DynamoDB, IAM, and SQS). AWS has effectively ceased active development of new Service Resources in Boto3. As a result, newer services and newer APIs for legacy services are exclusively accessible through the low-level Client interface.