Skip to content

Commit e8e0fcf

Browse files
committed
Merge branch 'release-0.0.8'
2 parents d7cddec + e593362 commit e8e0fcf

16 files changed

+239
-33
lines changed

CHANGELOG.rst

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,34 @@
11
Changelog
22
=========
33

4+
0.0.8 - 2015-02-10
5+
------------------
6+
7+
* bugfix:Resources: Fix Amazon S3 resource identifier order.
8+
(`issue 62 <https://github.com/boto/boto3/pull/62>`__)
9+
* bugfix:Resources: Fix collection resource hydration path.
10+
(`issue 61 <https://github.com/boto/boto3/pull/61>`__)
11+
* bugfix:Resources: Re-enable service-level access to all resources,
12+
allowing e.g. ``obj = s3.Object('bucket', 'key')``.
13+
(`issue 60 <https://github.com/boto/boto3/pull/60>`__)
14+
* feature:Botocore: Update to Botocore 0.87.0
15+
16+
* Add support for Amazon DynamoDB secondary index scanning.
17+
* Upgrade to ``requests`` 2.5.1.
18+
* Add support for anonymous (unsigned) clients.
19+
(`botocore issue 448 <https://github.com/boto/botocore/pull/448>`__)
20+
421
0.0.7 - 2015-02-05
522
------------------
623

724
* feature:Resources: Enable support for Amazon Glacier.
825
* feature:Resources: Support plural references and nested JMESPath
926
queries for data members when building parameters and identifiers.
1027
(`issue 52 <https://github.com/boto/boto3/pull/52>`__)
11-
* feature:Resources: Update to the latest resource JSON format.
28+
* feature:Resources: Update to the latest resource JSON format. This is
29+
a **backward-incompatible** change as not all resources are exposed
30+
at the service level anymore. For example, ``s3.Object('bucket', 'key')``
31+
is now ``s3.Bucket('bucket').Object('key')``.
1232
(`issue 51 <https://github.com/boto/boto3/pull/51>`__)
1333
* feature:Resources: Make ``resource.meta`` a proper object. This allows
1434
you to do things like ``resource.meta.client``. This is a **backward-

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright 2013-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
1+
Copyright 2013-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
22

33
Licensed under the Apache License, Version 2.0 (the "License"). You
44
may not use this file except in compliance with the License. A copy of

README.rst

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,30 @@ pull requests on this repository. Thanks!
4040

4141
Quick Start
4242
-----------
43-
First, install the library and set a default region::
43+
First, install the library and set a default region:
44+
45+
.. code-block:: sh
4446
4547
$ pip install boto3
4648
47-
Next, set up credentials (in e.g. ``~/.aws/credentials``)::
49+
Next, set up credentials (in e.g. ``~/.aws/credentials``):
50+
51+
.. code-block:: ini
4852
4953
[default]
5054
aws_access_key_id = YOUR_KEY
5155
aws_secret_access_key = YOUR_SECRET
5256
53-
Then, set up a default region (in e.g. ``~/.aws/config``)::
57+
Then, set up a default region (in e.g. ``~/.aws/config``):
58+
59+
.. code-block:: ini
5460
5561
[default]
5662
region=us-east-1
5763
58-
Then, from a Python interpreter::
64+
Then, from a Python interpreter:
65+
66+
.. code-block:: python
5967
6068
>>> import boto3
6169
>>> s3 = boto3.resource('s3')
@@ -69,7 +77,9 @@ Getting Started
6977
~~~~~~~~~~~~~~~
7078
Assuming that you have Python and ``virtualenv`` installed, set up your
7179
environment and install the required dependencies like this instead of
72-
the ``pip install boto3`` defined above::
80+
the ``pip install boto3`` defined above:
81+
82+
.. code-block:: sh
7383
7484
$ git clone https://github.com/boto/boto3.git
7585
$ cd boto3
@@ -85,20 +95,26 @@ You can run tests in all supported Python versions using ``tox``. Be default,
8595
it will run all of the unit tests, but you can also specify your own
8696
``nosetests`` options. Note that this requires that you have all supported
8797
versions of Python installed, otherwise you must pass ``-e`` or run the
88-
``nosetests`` command directly::
98+
``nosetests`` command directly:
99+
100+
.. code-block:: sh
89101
90102
$ tox
91103
$ tox tests/unit/test_session.py
92104
$ tox -e py26,py33 tests/integration
93105
94-
You can also run individual tests with your default Python version::
106+
You can also run individual tests with your default Python version:
107+
108+
.. code-block:: sh
95109
96110
$ nosetests tests/unit
97111
98112
Generating Documentation
99113
~~~~~~~~~~~~~~~~~~~~~~~~
100114
Sphinx is used for documentation. You can generate HTML locally with the
101-
following::
115+
following:
116+
117+
.. code-block:: sh
102118
103119
$ pip install sphinx sphinx_rtd_theme
104120
$ cd docs

boto3/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818

1919
__author__ = 'Amazon Web Services'
20-
__version__ = '0.0.7'
20+
__version__ = '0.0.8'
2121

2222

2323
# The default Boto3 session; autoloaded when needed.

boto3/data/resources/cloudformation-2010-05-15.resources.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
"type": "StackResource",
9595
"identifiers": [
9696
{ "target": "StackName", "source": "identifier", "name": "Name" },
97-
{ "target": "StackName", "source": "input" }
97+
{ "target": "LogicalId", "source": "input" }
9898
]
9999
}
100100
}
@@ -135,7 +135,7 @@
135135
},
136136
"StackResource": {
137137
"identifiers": [
138-
{ "name": "LogicalId" },
138+
{ "name": "LogicalId", "memberName": "LogicalResourceId" },
139139
{ "name": "StackName" }
140140
],
141141
"shape": "StackResourceDetail",
@@ -162,7 +162,7 @@
162162
},
163163
"StackResourceSummary": {
164164
"identifiers": [
165-
{ "name": "LogicalId" },
165+
{ "name": "LogicalId", "memberName": "LogicalResourceId" },
166166
{ "name": "StackName" }
167167
],
168168
"shape": "StackResourceSummary",

boto3/data/resources/glacier-2012-06-01.resources.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,11 @@
110110
]
111111
}
112112
},
113-
"InitiateArchiveRetreival": {
113+
"InitiateArchiveRetrieval": {
114114
"request": {
115115
"operation": "InitiateJob",
116116
"params": [
117-
{ "target": "vaultName", "source": "identifier", "name": "Name" },
117+
{ "target": "vaultName", "source": "identifier", "name": "VaultName" },
118118
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
119119
{ "target": "jobParameters.Type", "source": "string", "value": "archive-retrieval" },
120120
{ "target": "jobParameters.ArchiveId", "source": "identifier", "name": "Id" }
@@ -125,7 +125,7 @@
125125
"identifiers": [
126126
{ "target": "Id", "source": "response", "path": "jobId" },
127127
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
128-
{ "target": "VaultName", "source": "identifier", "name": "Name" }
128+
{ "target": "VaultName", "source": "identifier", "name": "VaultName" }
129129
]
130130
}
131131
}

boto3/data/resources/iam-2010-05-08.resources.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@
376376
"request": {
377377
"operation": "UpdateAssumeRolePolicy",
378378
"params": [
379-
{ "target": "RoleName", "source": "identifier", "name": "Name" }
379+
{ "target": "RoleName", "source": "identifier", "name": "RoleName" }
380380
]
381381
}
382382
}

boto3/data/resources/s3-2006-03-01.resources.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -698,8 +698,8 @@
698698
"type": "MultipartUploadPart",
699699
"identifiers": [
700700
{ "target": "BucketName", "source": "identifier", "name": "BucketName" },
701-
{ "target": "MultipartUploadId", "source": "identifier", "name": "Id" },
702701
{ "target": "ObjectKey", "source": "identifier", "name": "ObjectKey" },
702+
{ "target": "MultipartUploadId", "source": "identifier", "name": "Id" },
703703
{ "target": "PartNumber", "source": "input" }
704704
]
705705
}
@@ -770,8 +770,8 @@
770770
"type": "MultipartUpload",
771771
"identifiers": [
772772
{ "target": "BucketName", "source": "identifier", "name": "BucketName" },
773-
{ "target": "Id", "source": "identifier", "name": "MultipartUploadId" },
774-
{ "target": "ObjectKey", "source": "identifier", "name": "ObjectKey" }
773+
{ "target": "ObjectKey", "source": "identifier", "name": "ObjectKey" },
774+
{ "target": "Id", "source": "identifier", "name": "MultipartUploadId" }
775775
]
776776
}
777777
}

boto3/data/resources/sns-2010-03-31.resources.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@
110110
"resource": {
111111
"type": "PlatformEndpoint",
112112
"identifiers": [
113-
{ "target": "PlatformApplicationArn", "source": "identifier", "name": "Arn" },
114113
{ "target": "Arn", "source": "response", "path": "EndpointArn" }
115114
]
116115
}

boto3/resources/collection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ def __init__(self, model, parent, factory, resource_defs,
312312
operation_name = self._model.request.operation
313313
self._parent = parent
314314

315-
search_path = model.path
315+
search_path = model.resource.path
316316
self._handler = ResourceHandler(search_path, factory, resource_defs,
317317
service_model, model.resource, operation_name)
318318

boto3/resources/factory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def _load_has_relations(self, attrs, service_name, resource_name,
188188
# This is a sub-resource class you can create
189189
# by passing in an identifier, e.g. s3.Bucket(name).
190190
name = subresource.resource.type
191-
attrs[name] = self._create_class_partial(
191+
attrs[subresource.name] = self._create_class_partial(
192192
name, subresource, service_name, resource_name, model,
193193
resource_defs, service_model)
194194

boto3/resources/model.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,69 @@ def batch_actions(self):
312312

313313
return actions
314314

315+
def _get_has_definition(self):
316+
"""
317+
Get a ``has`` relationship definition from a model, where the
318+
service resource model is treated special in that it contains
319+
a relationship to every resource defined for the service. This
320+
allows things like ``s3.Object('bucket-name', 'key')`` to
321+
work even though the JSON doesn't define it explicitly.
322+
323+
:rtype: dict
324+
:return: Mapping of names to subresource and reference
325+
definitions.
326+
"""
327+
if self.name not in self._resource_defs:
328+
# This is the service resource, so let us expose all of
329+
# the defined resources as subresources.
330+
definition = {}
331+
332+
for name, resource_def in self._resource_defs.items():
333+
# It's possible for the service to have renamed a
334+
# resource or to have defined multiple names that
335+
# point to the same resource type, so we need to
336+
# take that into account.
337+
found = False
338+
has_items = self._definition.get('has', {}).items()
339+
for has_name, has_def in has_items:
340+
if has_def.get('resource', {}).get('type') == name:
341+
definition[has_name] = has_def
342+
found = True
343+
344+
if not found:
345+
# Create a relationship definition and attach it
346+
# to the model, such that all identifiers must be
347+
# supplied by the user. It will look something like:
348+
#
349+
# {
350+
# 'resource': {
351+
# 'type': 'ResourceName',
352+
# 'identifiers': [
353+
# {'target': 'Name1', 'source': 'input'},
354+
# {'target': 'Name2', 'source': 'input'},
355+
# ...
356+
# ]
357+
# }
358+
# }
359+
#
360+
fake_has = {
361+
'resource': {
362+
'type': name,
363+
'identifiers': []
364+
}
365+
}
366+
367+
for identifier in resource_def.get('identifiers', []):
368+
fake_has['resource']['identifiers'].append({
369+
'target': identifier['name'], 'source': 'input'
370+
})
371+
372+
definition[name] = fake_has
373+
else:
374+
definition = self._definition.get('has', {})
375+
376+
return definition
377+
315378
def _get_related_resources(self, subresources):
316379
"""
317380
Get a list of sub-resources or references.
@@ -323,7 +386,7 @@ def _get_related_resources(self, subresources):
323386
"""
324387
resources = []
325388

326-
for name, definition in self._definition.get('has', {}).items():
389+
for name, definition in self._get_has_definition().items():
327390
action = Action(name, definition, self._resource_defs)
328391

329392
data_required = False

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def get_version():
2626
]
2727

2828
requires = [
29-
'botocore==0.86.0',
29+
'botocore==0.87.0',
3030
'bcdoc==0.12.2',
3131
'jmespath==0.6.1',
3232
]

tests/integration/test_s3.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ def test_s3(self):
5757
# Lazy-loaded attribute
5858
self.assertEqual(12, obj.content_length)
5959

60+
# Load a similar attribute from the collection response
61+
self.assertEqual(12, list(bucket.objects.all())[0].size)
62+
6063
# Perform a resource action with a low-level response
6164
self.assertEqual(b'hello, world',
6265
obj.get()['Body'].read())
@@ -82,4 +85,39 @@ def test_s3_resource_waiter(self):
8285
obj.wait_until_exists()
8386

8487
# List objects and make sure ours is present
85-
self.assertIn('test.txt', [o.key for o in bucket.objects.all()])
88+
self.assertIn('test.txt', [o.key for o in bucket.objects.all()])
89+
90+
def test_can_create_object_directly(self):
91+
obj = self.s3.Object(self.bucket_name, 'test.txt')
92+
93+
self.assertEqual(obj.bucket_name, self.bucket_name)
94+
self.assertEqual(obj.key, 'test.txt')
95+
96+
def test_s3_multipart(self):
97+
# Create the bucket
98+
bucket = self.create_bucket_resource(self.bucket_name)
99+
bucket.wait_until_exists()
100+
101+
# Create the multipart upload
102+
mpu = bucket.Object('mp-test.txt').initiate_multipart_upload()
103+
self.addCleanup(mpu.abort)
104+
105+
# Create and upload a part
106+
part = mpu.Part(1)
107+
response = part.upload(Body='hello, world!')
108+
109+
# Complete the upload, which requires info on all of the parts
110+
part_info = {
111+
'Parts': [
112+
{
113+
'PartNumber': 1,
114+
'ETag': response['ETag']
115+
}
116+
]
117+
}
118+
119+
mpu.complete(MultipartUpload=part_info)
120+
self.addCleanup(bucket.Object('mp-test.txt').delete)
121+
122+
contents = bucket.Object('mp-test.txt').get()['Body'].read()
123+
self.assertEqual(contents, b'hello, world!')

0 commit comments

Comments
 (0)