Skip to content

Commit fb82bde

Browse files
Merge branch 'release-1.18.48'
* release-1.18.48: Bumping version to 1.18.48 Add changelog entries from botocore Declare support for Python 3.9 (#2942) Convert functional tests to pytest Convert unit tests to pytest Update tests for Pytest support use pytest instead of nose
2 parents 1f05314 + 7f75005 commit fb82bde

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+840
-919
lines changed

.changes/1.18.48.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"category": "``license-manager``",
4+
"description": "[``botocore``] AWS License Manager now allows customers to get the LicenseArn in the Checkout API Response.",
5+
"type": "api-change"
6+
},
7+
{
8+
"category": "``ec2``",
9+
"description": "[``botocore``] DescribeInstances now returns Platform Details, Usage Operation, and Usage Operation Update Time.",
10+
"type": "api-change"
11+
}
12+
]

.github/workflows/run-tests.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
strategy:
1414
fail-fast: false
1515
matrix:
16-
python-version: [3.6, 3.7, 3.8]
16+
python-version: [3.6, 3.7, 3.8, 3.9]
1717
os: [ubuntu-latest, macOS-latest, windows-latest ]
1818

1919
steps:

.gitignore

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ tests/.coverage
1313
.tox
1414
.coverage
1515
coverage.xml
16-
nosetests.xml
1716

1817
# Common virtualenv names
1918
venv

CHANGELOG.rst

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
CHANGELOG
33
=========
44

5+
1.18.48
6+
=======
7+
8+
* api-change:``license-manager``: [``botocore``] AWS License Manager now allows customers to get the LicenseArn in the Checkout API Response.
9+
* api-change:``ec2``: [``botocore``] DescribeInstances now returns Platform Details, Usage Operation, and Usage Operation Update Time.
10+
11+
512
1.18.47
613
=======
714

README.rst

+3-3
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ Running Tests
8484
~~~~~~~~~~~~~
8585
You can run tests in all supported Python versions using ``tox``. By default,
8686
it will run all of the unit and functional tests, but you can also specify your own
87-
``nosetests`` options. Note that this requires that you have all supported
87+
``pytest`` options. Note that this requires that you have all supported
8888
versions of Python installed, otherwise you must pass ``-e`` or run the
89-
``nosetests`` command directly:
89+
``pytest`` command directly:
9090

9191
.. code-block:: sh
9292
@@ -98,7 +98,7 @@ You can also run individual tests with your default Python version:
9898

9999
.. code-block:: sh
100100
101-
$ nosetests tests/unit
101+
$ pytest tests/unit
102102
103103
104104
Getting Help

boto3/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919

2020
__author__ = 'Amazon Web Services'
21-
__version__ = '1.18.47'
21+
__version__ = '1.18.48'
2222

2323

2424
# The default Boto3 session; autoloaded when needed.

scripts/ci/run-tests

+3-4
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ def process_args(args):
3434
test_args = ""
3535
if args.with_cov:
3636
test_args += (
37-
f"--with-xunit --cover-erase --with-coverage "
38-
f"--cover-package {PACKAGE} --cover-xml -v "
37+
f"--cov={PACKAGE} --cov-report xml -v "
3938
)
4039
dirs = " ".join(args.test_dirs)
4140

@@ -53,8 +52,8 @@ if __name__ == "__main__":
5352
parser.add_argument(
5453
"-r",
5554
"--test-runner",
56-
default="nosetests",
57-
help="Test runner to execute tests. Defaults to nose.",
55+
default="pytest",
56+
help="Test runner to execute tests. Defaults to pytest.",
5857
)
5958
parser.add_argument(
6059
"-c",

setup.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ universal = 0
33

44
[metadata]
55
requires_dist =
6-
botocore>=1.21.47,<1.22.0
6+
botocore>=1.21.48,<1.22.0
77
jmespath>=0.7.1,<1.0.0
88
s3transfer>=0.5.0,<0.6.0
99

setup.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@
66
import os
77
import re
88

9-
from setuptools import setup, find_packages
10-
9+
from setuptools import find_packages, setup
1110

1211
ROOT = os.path.dirname(__file__)
1312
VERSION_RE = re.compile(r'''__version__ = ['"]([0-9.]+)['"]''')
1413

1514

1615
requires = [
17-
'botocore>=1.21.47,<1.22.0',
16+
'botocore>=1.21.48,<1.22.0',
1817
'jmespath>=0.7.1,<1.0.0',
1918
's3transfer>=0.5.0,<0.6.0'
2019
]
@@ -54,6 +53,7 @@ def get_version():
5453
'Programming Language :: Python :: 3.6',
5554
'Programming Language :: Python :: 3.7',
5655
'Programming Language :: Python :: 3.8',
56+
'Programming Language :: Python :: 3.9',
5757
],
5858
project_urls={
5959
'Documentation': 'https://boto3.amazonaws.com/v1/documentation/api/latest/index.html',

tests/functional/docs/__init__.py

+8-10
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616
class BaseDocsFunctionalTests(unittest.TestCase):
1717
def assert_contains_lines_in_order(self, lines, contents):
1818
for line in lines:
19-
self.assertIn(line, contents)
19+
assert line in contents
2020
beginning = contents.find(line)
2121
contents = contents[(beginning + len(line)):]
2222

2323
def get_class_document_block(self, class_name, contents):
2424
start_class_document = '.. py:class:: %s' % class_name
2525
start_index = contents.find(start_class_document)
26-
self.assertNotEqual(start_index, -1, 'Class is not found in contents')
26+
assert start_index != -1, 'Class is not found in contents'
2727
contents = contents[start_index:]
2828
end_index = contents.find(
2929
' .. py:class::', len(start_class_document))
@@ -32,7 +32,7 @@ def get_class_document_block(self, class_name, contents):
3232
def get_method_document_block(self, method_name, contents):
3333
start_method_document = ' .. py:method:: %s(' % method_name
3434
start_index = contents.find(start_method_document)
35-
self.assertNotEqual(start_index, -1, 'Method is not found in contents')
35+
assert start_index != -1, 'Method is not found in contents'
3636
contents = contents[start_index:]
3737
end_index = contents.find(
3838
' .. py:method::', len(start_method_document))
@@ -41,8 +41,7 @@ def get_method_document_block(self, method_name, contents):
4141
def get_request_syntax_document_block(self, contents):
4242
start_marker = '**Request Syntax**'
4343
start_index = contents.find(start_marker)
44-
self.assertNotEqual(
45-
start_index, -1, 'There is no request syntax section')
44+
assert start_index != -1, 'There is no request syntax section'
4645
contents = contents[start_index:]
4746
end_index = contents.find(
4847
':type', len(start_marker))
@@ -51,8 +50,7 @@ def get_request_syntax_document_block(self, contents):
5150
def get_response_syntax_document_block(self, contents):
5251
start_marker = '**Response Syntax**'
5352
start_index = contents.find(start_marker)
54-
self.assertNotEqual(
55-
start_index, -1, 'There is no response syntax section')
53+
assert start_index != -1, 'There is no response syntax section'
5654
contents = contents[start_index:]
5755
end_index = contents.find(
5856
'**Response Structure**', len(start_marker))
@@ -61,19 +59,19 @@ def get_response_syntax_document_block(self, contents):
6159
def get_request_parameter_document_block(self, param_name, contents):
6260
start_param_document = ':type %s:' % param_name
6361
start_index = contents.find(start_param_document)
64-
self.assertNotEqual(start_index, -1, 'Param is not found in contents')
62+
assert start_index != -1, 'Param is not found in contents'
6563
contents = contents[start_index:]
6664
end_index = contents.find(':type', len(start_param_document))
6765
return contents[:end_index]
6866

6967
def get_response_parameter_document_block(self, param_name, contents):
7068
start_param_document = '**Response Structure**'
7169
start_index = contents.find(start_param_document)
72-
self.assertNotEqual(start_index, -1, 'There is no response structure')
70+
assert start_index != -1, 'There is no response structure'
7371

7472
start_param_document = '- **%s**' % param_name
7573
start_index = contents.find(start_param_document)
76-
self.assertNotEqual(start_index, -1, 'Param is not found in contents')
74+
assert start_index != -1, 'Param is not found in contents'
7775
contents = contents[start_index:]
7876
end_index = contents.find('- **', len(start_param_document))
7977
return contents[:end_index]

tests/functional/docs/test_smoke.py

+60-34
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
13-
from nose.tools import assert_true
13+
import pytest
1414
import botocore.session
1515
from botocore import xform_name
1616
from botocore.exceptions import DataNotFoundError
@@ -19,50 +19,76 @@
1919
from boto3.docs.service import ServiceDocumenter
2020

2121

22-
def test_docs_generated():
23-
"""Verify we can generate the appropriate docs for all services"""
22+
@pytest.fixture
23+
def botocore_session():
24+
return botocore.session.get_session()
25+
26+
@pytest.fixture
27+
def boto3_session():
28+
return boto3.Session(region_name='us-east-1')
29+
30+
def all_services():
2431
botocore_session = botocore.session.get_session()
2532
session = boto3.Session(region_name='us-east-1')
2633
for service_name in session.get_available_services():
27-
generated_docs = ServiceDocumenter(
28-
service_name, session=session).document_service()
29-
generated_docs = generated_docs.decode('utf-8')
30-
client = boto3.client(service_name, 'us-east-1')
34+
yield service_name
35+
36+
37+
@pytest.fixture
38+
def available_resources():
39+
session = boto3.Session(region_name='us-east-1')
40+
return session.get_available_resources()
41+
3142

32-
# Check that all of the services have the appropriate title
33-
yield (_assert_has_title, generated_docs, client)
43+
@pytest.mark.parametrize('service_name', all_services())
44+
def test_documentation(
45+
boto3_session, botocore_session, available_resources, service_name
46+
):
47+
generated_docs = ServiceDocumenter(
48+
service_name, session=boto3_session).document_service()
49+
generated_docs = generated_docs.decode('utf-8')
50+
client = boto3.client(service_name, 'us-east-1')
3451

35-
# Check that all services have the client documented.
36-
yield (_assert_has_client_documentation, generated_docs, service_name,
37-
client)
52+
# Check that all of the services have the appropriate title
53+
_assert_has_title(generated_docs, client)
3854

39-
# If the client can paginate, make sure the paginators are documented.
40-
try:
41-
paginator_model = botocore_session.get_paginator_model(
55+
56+
# Check that all services have the client documented.
57+
_assert_has_client_documentation(generated_docs, service_name, client)
58+
59+
60+
#If the service has resources, make sure the service resource
61+
#is at least documented.
62+
if service_name in available_resources:
63+
64+
resource = boto3.resource(service_name, 'us-east-1')
65+
_assert_has_resource_documentation(
66+
generated_docs, service_name, resource
67+
)
68+
69+
# If the client can paginate, make sure the paginators are documented.
70+
try:
71+
paginator_model = botocore_session.get_paginator_model(
4272
service_name)
43-
yield (_assert_has_paginator_documentation, generated_docs,
44-
service_name, client,
45-
sorted(paginator_model._paginator_config))
46-
except DataNotFoundError:
47-
pass
48-
49-
# If the client has waiters, make sure the waiters are documented
50-
if client.waiter_names:
51-
waiter_model = botocore_session.get_waiter_model(service_name)
52-
yield (_assert_has_waiter_documentation, generated_docs,
53-
service_name, client, waiter_model)
54-
55-
# If the service has resources, make sure the service resource
56-
# is at least documented.
57-
if service_name in session.get_available_resources():
58-
resource = boto3.resource(service_name, 'us-east-1')
59-
yield (_assert_has_resource_documentation, generated_docs,
60-
service_name, resource)
73+
_assert_has_paginator_documentation(
74+
generated_docs, service_name, client,
75+
sorted(paginator_model._paginator_config)
76+
)
77+
except DataNotFoundError:
78+
pass
79+
80+
81+
# If the client has waiters, make sure the waiters are documented.
82+
if client.waiter_names:
83+
waiter_model = botocore_session.get_waiter_model(service_name)
84+
_assert_has_waiter_documentation(
85+
generated_docs, service_name, client, waiter_model
86+
)
6187

6288

6389
def _assert_contains_lines_in_order(lines, contents):
6490
for line in lines:
65-
assert_true(line in contents)
91+
assert line in contents
6692
beginning = contents.find(line)
6793
contents = contents[(beginning + len(line)):]
6894

tests/functional/dynamodb/test_stubber_conditions.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def test_table_query_can_be_stubbed_with_expressions(self):
4141
response = table.query(KeyConditionExpression=key_expr,
4242
FilterExpression=filter_expr)
4343

44-
self.assertEqual(list(), response['Items'])
44+
assert response['Items'] == []
4545
stubber.assert_no_pending_responses()
4646

4747
def test_table_scan_can_be_stubbed_with_expressions(self):
@@ -59,5 +59,5 @@ def test_table_scan_can_be_stubbed_with_expressions(self):
5959
with stubber:
6060
response = table.scan(FilterExpression=filter_expr)
6161

62-
self.assertEqual(list(), response['Items'])
62+
assert response['Items'] == []
6363
stubber.assert_no_pending_responses()

tests/functional/dynamodb/test_table.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def setUp(self):
2525

2626
def test_resource_has_batch_writer_added(self):
2727
table = self.resource.Table('mytable')
28-
self.assertTrue(hasattr(table, 'batch_writer'))
28+
assert hasattr(table, 'batch_writer')
2929

3030
def test_operation_without_output(self):
3131
table = self.resource.Table('mytable')

tests/functional/test_collection.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ def setUp(self):
2525
self.ec2_resource = self.session.resource('ec2')
2626

2727
def test_can_use_collection_methods(self):
28-
self.assertIsInstance(
29-
self.ec2_resource.instances.all(), ResourceCollection)
28+
assert isinstance(self.ec2_resource.instances.all(), ResourceCollection)
3029

3130
def test_can_chain_methods(self):
32-
self.assertIsInstance(
31+
assert isinstance(
3332
self.ec2_resource.instances.all().page_size(5), ResourceCollection)

tests/functional/test_dynamodb.py

+12-14
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,12 @@ def test_resource(self):
4343
table.scan(FilterExpression=Attr('mykey').eq('myvalue'))
4444
request = self.make_request_mock.call_args_list[0][0][1]
4545
request_params = json.loads(request['body'].decode('utf-8'))
46-
self.assertEqual(
47-
request_params,
48-
{'TableName': 'MyTable',
49-
'FilterExpression': '#n0 = :v0',
50-
'ExpressionAttributeNames': {'#n0': 'mykey'},
51-
'ExpressionAttributeValues': {':v0': {'S': 'myvalue'}}}
52-
)
46+
assert request_params == {
47+
'TableName': 'MyTable',
48+
'FilterExpression': '#n0 = :v0',
49+
'ExpressionAttributeNames': {'#n0': 'mykey'},
50+
'ExpressionAttributeValues': {':v0': {'S': 'myvalue'}}
51+
}
5352

5453
def test_client(self):
5554
dynamodb = self.session.client('dynamodb')
@@ -62,10 +61,9 @@ def test_client(self):
6261
)
6362
request = self.make_request_mock.call_args_list[0][0][1]
6463
request_params = json.loads(request['body'].decode('utf-8'))
65-
self.assertEqual(
66-
request_params,
67-
{'TableName': 'MyTable',
68-
'FilterExpression': '#n0 = :v0',
69-
'ExpressionAttributeNames': {'#n0': 'mykey'},
70-
'ExpressionAttributeValues': {':v0': {'S': 'myvalue'}}}
71-
)
64+
assert request_params == {
65+
'TableName': 'MyTable',
66+
'FilterExpression': '#n0 = :v0',
67+
'ExpressionAttributeNames': {'#n0': 'mykey'},
68+
'ExpressionAttributeValues': {':v0': {'S': 'myvalue'}}
69+
}

0 commit comments

Comments
 (0)