Version 0.66.0 of botocore added support for clients (10/16/2014) added initial support for clients. This provided an alternate interface to making AWS calls that provided a number of benefits over the existing interface. At the time, both interfaces were added so that users could opt in to trying the new clients.
Below is an example of the old interface:
import botocore.session
session = botocore.session.get_session()
s3 = session.get_service('s3')
endpoint = s3.get_endpoint('us-west-2')
list_objects = s3.get_operation('ListObjects')
http, response = list_objects.call(endpoint, Bucket='mybucket')
if http.status_code == 200:
print("Contents: %s" % response['Contents])
else:
print("API call failed, status code: %s, error: %s" % (
http.status_code, http.content))
Here's an example of the newer (preferred) client interface:
import botocore.session
session = botocore.session.get_session()
s3 = session.create_client('s3', 'us-west-2')
response = s3.list_objects(Bucket='mybucket')
print("Contents: %s" % response['Contents'])
While there are many improvements with the new interface, here's a few notable improvements:
Botocore has not had a GA (1.0) release yet. In version 1.0, the Service/Operation object will be removed. Leading up to 1.0, we will be deprecating the old interface according to this schedule:
Migrating to clients is straightforward. This section outlines how to upgrade and some common scenarios and how to upgrade.
In botocore's old interface, kwargs could either be snake_cased or CamelCased and botocore would map them to the correct version needed by the service which is CamelCase.
Botocore's client interface accepts only CamelCased args. More specifically, botocore uses the same casing used by the respective AWS service. Most AWS services uses CamelCasing. Some AWS services use lowerCamelCase.
Old
list_objects.call(endpoint, bucket='foo', key='bar')
New
client.list_objects(Bucket='foo', Key='bar')
In the old interface, the connection pooling was tied to an endpoint object. To reuse existing HTTP connection, you needed to keep a reference to the endpoint objects. With clients, connection pooling is tied to a client. Use a single client to make multiple API calls.
Old
service = session.get_service('s3')
endpoint = service.get_endpoint('us-west-2')
operation = service.get_operation('ListObjects')
head_object = service.get_operation('HeadObject')
parsed = operation.call(endpoint, Bucket='mybucket')[1]
for obj in parsed['Contents']:
name = obj['Key']
# Use existing connection be passing in the same endpoint.
print(head_object.call(endpoint, Bucket='mybucket', Key=name))
New
s3 = session.get_client('s3', 'us-west-2')
for obj in s3.list_objects(Bucket='mybucket')['Contents']:
name = obj['Key']
# Using the same client will reuse any existing HTTP
# connections the client was using.
print(s3.head_object(Bucket='mybucket', Key=name))
In the old interface, you would retrieve an API operation using the casing defined by the service, which is typically CamelCase. For example, you'd use service.get_operation('ListObjects'), not service.get_operation('list_objects'). With clients, method names, which map 1 - 1 to operation names are snake_cased, as is common in python code.
Old
service = session.get_service('s3')
list_objects = service.get_operation('ListObjects')
head_object = service.get_operation('HeadObject')
get_object = service.get_operation('GetObject')
New
s3 = session.get_client('s3', 'us-west-2')
list_objects = s3.list_objects
head_object = s3.head_object
get_object = s3.get_object
In the old interface, the return value for an operation.call invocation is a tuple of the HTTP response object, and the parsed dict that results from parsing the HTTP object. In the client interface, only the parsed response is returned. The HTTP response object is not returned.
Old
ec2 = session.get_service('ec2')
endpoint = ec2.get_endpoint('ec2')
describe_instances = ec2.get_operation('DescribeInstances')
http, parsed = describe_instances.call(endpoint)
New
ec2 = session.get_client('ec2', 'us-west-2')
parsed = ec2.describe_instances()
The main reason for returning the HTTP response was to check if an error occurred via the HTTP response status code. This is now no longer required (discussed below). Exception are automatically raised.
Note that if for some reason you do need to see the response status code, it is available via the ResponseMetadata in the parsed dict that's returned.
ec2 = session.get_client('ec2', 'us-west-2')
parsed = ec2.describe_instances()
print("The status code is:",
parsed['ResponseMetadata']['HTTPStatusCode']))
With clients, exceptions are now raised on any non 2xx response. A ClientError exception has both a .msg attribute as well as the parsed error response (which is a dictionary). There isn't really equivalent functionality in the old interface, but below is an example of how you can handle an error:
New
from botocore.exceptions import ClientError
ec2 = session.get_client('ec2', 'us-west-2')
try:
parsed = ec2.describe_instances(InstanceIds=['i-badid'])
except ClientError as e:
logger.error("Received error: %s", e, exc_info=True)
# Only worry about a specific service error code
if e.response['Error']['Code'] == 'InvalidInstanceID.NotFound':
raise
If you run into any issues migrating from the old interface to the newer client interface, please file an issue on github and let us know. We'd be happy to help.