From 7693d7733345c29da0d007392d70b1acdfac2b62 Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Mon, 26 Jul 2021 07:40:39 +0100 Subject: [PATCH] Remove Py2 support (#3915) --- .github/workflows/build.yml | 11 +-- codecov.yml | 2 +- moto/apigateway/utils.py | 3 +- moto/applicationautoscaling/utils.py | 2 +- moto/awslambda/policy.py | 4 +- moto/batch/models.py | 3 +- moto/batch/responses.py | 2 +- moto/cloudformation/responses.py | 2 +- moto/cloudformation/utils.py | 3 +- moto/cognitoidp/utils.py | 3 +- moto/core/models.py | 70 ++++---------- moto/core/responses.py | 15 ++- moto/core/utils.py | 26 ++---- moto/datapipeline/utils.py | 5 +- moto/dynamodb/responses.py | 3 +- moto/dynamodb2/models/dynamo_type.py | 8 +- moto/dynamodb2/parsing/ast_nodes.py | 11 +-- moto/dynamodb2/parsing/expressions.py | 7 +- moto/dynamodb2/responses.py | 6 +- moto/dynamodbstreams/responses.py | 3 +- moto/ec2/models.py | 37 +++----- moto/ec2/responses/instances.py | 3 +- moto/ec2/responses/key_pairs.py | 3 +- moto/ec2/responses/launch_templates.py | 13 ++- moto/ec2/utils.py | 7 +- moto/elb/urls.py | 2 +- moto/emr/responses.py | 2 +- moto/emr/utils.py | 4 +- moto/events/models.py | 3 +- moto/forecast/models.py | 3 +- moto/glacier/responses.py | 2 +- moto/glacier/utils.py | 2 +- moto/iam/access_control.py | 7 +- moto/iam/models.py | 2 +- moto/iam/policy_validation.py | 22 ++--- moto/iam/utils.py | 8 +- moto/instance_metadata/responses.py | 2 +- moto/iot/responses.py | 2 +- moto/iotdata/responses.py | 2 +- moto/kinesis/models.py | 7 +- moto/kms/responses.py | 4 +- moto/managedblockchain/responses.py | 2 +- moto/managedblockchain/utils.py | 2 +- moto/polly/responses.py | 2 +- moto/redshift/responses.py | 3 +- moto/resourcegroupstaggingapi/models.py | 7 +- moto/route53/responses.py | 2 +- moto/s3/models.py | 33 +++---- moto/s3/responses.py | 17 ++-- moto/s3/utils.py | 9 +- moto/s3bucket_path/utils.py | 2 +- moto/secretsmanager/utils.py | 5 +- moto/server.py | 7 +- moto/ses/responses.py | 11 +-- moto/sns/models.py | 21 ++--- moto/sqs/models.py | 7 +- moto/sqs/responses.py | 2 +- moto/stepfunctions/utils.py | 2 +- moto/sts/utils.py | 4 +- moto/swf/responses.py | 5 +- moto/utilities/utils.py | 4 - moto/xray/responses.py | 2 +- requirements-dev.txt | 5 +- requirements-tests.txt | 5 +- setup.py | 60 ++---------- tests/Dockerfile | 7 ++ tests/helpers.py | 8 -- tests/test_cloudformation/test_server.py | 2 +- tests/test_core/test_socket.py | 29 +++--- tests/test_dynamodb/test_dynamodb.py | 1 - tests/test_ec2/helpers.py | 4 +- tests/test_ec2/test_elastic_ip_addresses.py | 3 +- tests/test_ec2/test_instances.py | 8 +- tests/test_emr/test_emr.py | 93 +++++++++---------- tests/test_emr/test_emr_boto3.py | 25 +++-- tests/test_iam/test_iam.py | 2 +- tests/test_iot/test_server.py | 2 +- tests/test_iotdata/test_server.py | 2 +- tests/test_kms/test_kms.py | 8 +- tests/test_kms/test_kms_boto3.py | 3 +- tests/test_logs/test_logs.py | 3 +- .../organizations_test_utils.py | 15 ++- .../test_organizations_boto3.py | 7 +- tests/test_rds/test_rds.py | 14 --- tests/test_s3/test_s3.py | 23 +++-- tests/test_s3/test_server.py | 2 +- .../test_s3bucket_path/test_s3bucket_path.py | 4 +- .../cloudformation_test_configs.py | 5 +- .../test_secretsmanager.py | 21 ++--- tests/test_ses/test_ses_boto3.py | 4 +- tests/test_ses/test_ses_sns_boto3.py | 2 - tests/test_sns/test_topics.py | 15 +-- tests/test_sns/test_topics_boto3.py | 16 +--- tests/test_sqs/test_sqs.py | 5 +- 94 files changed, 313 insertions(+), 565 deletions(-) create mode 100644 tests/Dockerfile diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 95e72349b..bce899cf8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ 2.7, 3.6, 3.7, 3.8 ] + python-version: [ 3.6, 3.7, 3.8 ] steps: - uses: actions/checkout@v2 @@ -59,7 +59,7 @@ jobs: path: ${{ steps.pip-cache-dir.outputs.dir }} key: pip-${{ matrix.python-version }}-${{ hashFiles('**/setup.py') }}-4 - name: Update pip - if: ${{ steps.pip-cache.outputs.cache-hit != 'true' && matrix.python-version != '2.7' }} + if: ${{ steps.pip-cache.outputs.cache-hit != 'true' }} run: | python -m pip install pip==21.1.3 - name: Install project dependencies @@ -92,7 +92,6 @@ jobs: key: pip-${{ matrix.python-version }}-${{ hashFiles('**/setup.py') }}-4 # Update PIP - recent version does not support PY2 though - name: Update pip - if: ${{ matrix.python-version != '2.7' }} run: | # https://github.com/pypa/pip/issues/10201 python -m pip install pip==21.1.3 @@ -111,7 +110,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [2.7, 3.6, 3.7, 3.8] + python-version: [3.6, 3.7, 3.8] steps: - uses: actions/checkout@v2 @@ -131,7 +130,6 @@ jobs: path: ${{ steps.pip-cache.outputs.dir }} key: pip-${{ matrix.python-version }}-${{ hashFiles('**/setup.py') }}-4 - name: Update pip - if: ${{ matrix.python-version != '2.7' }} run: | python -m pip install pip==21.1.3 - name: Install project dependencies @@ -154,7 +152,7 @@ jobs: needs: lint strategy: matrix: - python-version: [2.7, 3.6, 3.7, 3.8] + python-version: [3.6, 3.7, 3.8] steps: - uses: actions/checkout@v2 @@ -179,7 +177,6 @@ jobs: path: ${{ steps.pip-cache.outputs.dir }} key: pip-${{ matrix.python-version }}-${{ hashFiles('**/setup.py') }}-4 - name: Update pip - if: ${{ matrix.python-version != '2.7' }} run: | python -m pip install pip==21.1.3 - name: Install project dependencies diff --git a/codecov.yml b/codecov.yml index 4fe85e33f..3f64ecf7c 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,7 +1,7 @@ codecov: notify: # Leave a GitHub comment after all builds have passed - after_n_builds: 8 + after_n_builds: 6 coverage: status: project: diff --git a/moto/apigateway/utils.py b/moto/apigateway/utils.py index 807848f66..d583f64a1 100644 --- a/moto/apigateway/utils.py +++ b/moto/apigateway/utils.py @@ -1,5 +1,4 @@ from __future__ import unicode_literals -import six import random import string @@ -7,4 +6,4 @@ import string def create_id(): size = 10 chars = list(range(10)) + list(string.ascii_lowercase) - return "".join(six.text_type(random.choice(chars)) for x in range(size)) + return "".join(str(random.choice(chars)) for x in range(size)) diff --git a/moto/applicationautoscaling/utils.py b/moto/applicationautoscaling/utils.py index 72330c508..7a6a130e1 100644 --- a/moto/applicationautoscaling/utils.py +++ b/moto/applicationautoscaling/utils.py @@ -1,4 +1,4 @@ -from six.moves.urllib.parse import urlparse +from urllib.parse import urlparse def region_from_applicationautoscaling_url(url): diff --git a/moto/awslambda/policy.py b/moto/awslambda/policy.py index 495e2cee6..35c084f80 100644 --- a/moto/awslambda/policy.py +++ b/moto/awslambda/policy.py @@ -3,8 +3,6 @@ from __future__ import unicode_literals import json import uuid -from six import string_types - from moto.awslambda.exceptions import PreconditionFailedException @@ -96,7 +94,7 @@ class Policy: obj[key] = value def principal_formatter(self, obj): - if isinstance(obj, string_types): + if isinstance(obj, str): if obj.endswith(".amazonaws.com"): return {"Service": obj} if obj.endswith(":root"): diff --git a/moto/batch/models.py b/moto/batch/models.py index 2f00b7f83..5c4a16775 100644 --- a/moto/batch/models.py +++ b/moto/batch/models.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals import re from itertools import cycle -import six import datetime import time import uuid @@ -867,7 +866,7 @@ class BatchBackend(BaseBackend): security_group_names=[], instance_type=instance_type, region_name=self.region_name, - subnet_id=six.next(subnet_cycle), + subnet_id=next(subnet_cycle), key_name=compute_resources.get("ec2KeyPair", "AWS_OWNED"), security_group_ids=compute_resources["securityGroupIds"], ) diff --git a/moto/batch/responses.py b/moto/batch/responses.py index 61b00e9c9..200ace8b8 100644 --- a/moto/batch/responses.py +++ b/moto/batch/responses.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse from .models import batch_backends -from six.moves.urllib.parse import urlsplit +from urllib.parse import urlsplit from .exceptions import AWSError diff --git a/moto/cloudformation/responses.py b/moto/cloudformation/responses.py index 1667d59db..7213b99e1 100644 --- a/moto/cloudformation/responses.py +++ b/moto/cloudformation/responses.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import json import yaml -from six.moves.urllib.parse import urlparse +from urllib.parse import urlparse from moto.core.responses import BaseResponse from moto.core.utils import amzn_request_id diff --git a/moto/cloudformation/utils.py b/moto/cloudformation/utils.py index c9e522efb..2bef25cc7 100644 --- a/moto/cloudformation/utils.py +++ b/moto/cloudformation/utils.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals import uuid -import six import random import yaml import os @@ -37,7 +36,7 @@ def generate_stackset_arn(stackset_id, region_name): def random_suffix(): size = 12 chars = list(range(10)) + list(string.ascii_uppercase) - return "".join(six.text_type(random.choice(chars)) for x in range(size)) + return "".join(str(random.choice(chars)) for x in range(size)) def yaml_tag_constructor(loader, tag, node): diff --git a/moto/cognitoidp/utils.py b/moto/cognitoidp/utils.py index 11f34bcae..c0976f46f 100644 --- a/moto/cognitoidp/utils.py +++ b/moto/cognitoidp/utils.py @@ -1,5 +1,4 @@ from __future__ import unicode_literals -import six import random import string import hashlib @@ -10,7 +9,7 @@ import base64 def create_id(): size = 26 chars = list(range(10)) + list(string.ascii_lowercase) - return "".join(six.text_type(random.choice(chars)) for x in range(size)) + return "".join(str(random.choice(chars)) for x in range(size)) def check_secret_hash(app_client_secret, app_client_id, username, secret_hash): diff --git a/moto/core/models.py b/moto/core/models.py index 05f0c37d2..36cc5056e 100644 --- a/moto/core/models.py +++ b/moto/core/models.py @@ -7,7 +7,6 @@ import inspect import os import pkg_resources import re -import six import types from abc import abstractmethod from io import BytesIO @@ -16,7 +15,8 @@ from botocore.config import Config from botocore.handlers import BUILTIN_HANDLERS from botocore.awsrequest import AWSResponse from distutils.version import LooseVersion -from six.moves.urllib.parse import urlparse +from http.client import responses as http_responses +from urllib.parse import urlparse from werkzeug.wrappers import Request from moto import settings @@ -33,7 +33,7 @@ ACCOUNT_ID = os.environ.get("MOTO_ACCOUNT_ID", "123456789012") RESPONSES_VERSION = pkg_resources.get_distribution("responses").version -class BaseMockAWS(object): +class BaseMockAWS: nested_count = 0 mocks_active = False @@ -213,12 +213,12 @@ class CallbackResponse(responses.CallbackResponse): url = urlparse(request.url) if request.body is None: body = None - elif isinstance(request.body, six.text_type): - body = six.BytesIO(six.b(request.body)) + elif isinstance(request.body, str): + body = BytesIO(request.body.encode("UTF-8")) elif hasattr(request.body, "read"): - body = six.BytesIO(request.body.read()) + body = BytesIO(request.body.read()) else: - body = six.BytesIO(request.body) + body = BytesIO(request.body) req = Request.from_values( path="?".join([url.path, url.query]), input_stream=body, @@ -228,7 +228,7 @@ class CallbackResponse(responses.CallbackResponse): base_url="{scheme}://{netloc}".format( scheme=url.scheme, netloc=url.netloc ), - headers=[(k, v) for k, v in six.iteritems(request.headers)], + headers=[(k, v) for k, v in request.headers.items()], ) request = req headers = self.get_headers() @@ -243,7 +243,7 @@ class CallbackResponse(responses.CallbackResponse): return responses.HTTPResponse( status=status, - reason=six.moves.http_client.responses.get(status), + reason=http_responses.get(status), body=body, headers=headers, preload_content=False, @@ -261,7 +261,7 @@ class CallbackResponse(responses.CallbackResponse): if responses._is_string(url): if responses._has_unicode(url): url = responses._clean_unicode(url) - if not isinstance(other, six.text_type): + if not isinstance(other, str): other = other.encode("ascii").decode("utf8") return self._url_matches_strict(url, other) elif isinstance(url, responses.Pattern) and url.match(other): @@ -319,7 +319,7 @@ BOTOCORE_HTTP_METHODS = ["GET", "DELETE", "HEAD", "OPTIONS", "PATCH", "POST", "P class MockRawResponse(BytesIO): def __init__(self, input): - if isinstance(input, six.text_type): + if isinstance(input, str): input = input.encode("utf-8") super(MockRawResponse, self).__init__(input) @@ -330,7 +330,7 @@ class MockRawResponse(BytesIO): contents = self.read() -class BotocoreStubber(object): +class BotocoreStubber: def __init__(self): self.enabled = False self.methods = defaultdict(list) @@ -363,7 +363,7 @@ class BotocoreStubber(object): if response_callback is not None: for header, value in request.headers.items(): - if isinstance(value, six.binary_type): + if isinstance(value, bytes): request.headers[header] = value.decode("utf-8") status, headers, body = response_callback( request, request.url, request.headers @@ -479,43 +479,10 @@ class ServerModeMockAWS(BaseMockAWS): kwargs["endpoint_url"] = "http://localhost:5000" return real_boto3_resource(*args, **kwargs) - def fake_httplib_send_output(self, message_body=None, *args, **kwargs): - def _convert_to_bytes(mixed_buffer): - bytes_buffer = [] - for chunk in mixed_buffer: - if isinstance(chunk, six.text_type): - bytes_buffer.append(chunk.encode("utf-8")) - else: - bytes_buffer.append(chunk) - msg = b"\r\n".join(bytes_buffer) - return msg - - self._buffer.extend((b"", b"")) - msg = _convert_to_bytes(self._buffer) - del self._buffer[:] - if isinstance(message_body, bytes): - msg += message_body - message_body = None - self.send(msg) - # if self._expect_header_set: - # read, write, exc = select.select([self.sock], [], [self.sock], 1) - # if read: - # self._handle_expect_response(message_body) - # return - if message_body is not None: - self.send(message_body) - self._client_patcher = patch("boto3.client", fake_boto3_client) self._resource_patcher = patch("boto3.resource", fake_boto3_resource) - if six.PY2: - self._httplib_patcher = patch( - "httplib.HTTPConnection._send_output", fake_httplib_send_output - ) - self._client_patcher.start() self._resource_patcher.start() - if six.PY2: - self._httplib_patcher.start() def _get_region(self, *args, **kwargs): if "region_name" in kwargs: @@ -529,8 +496,6 @@ class ServerModeMockAWS(BaseMockAWS): if self._client_patcher: self._client_patcher.stop() self._resource_patcher.stop() - if six.PY2: - self._httplib_patcher.stop() class Model(type): @@ -572,8 +537,7 @@ class InstanceTrackerMeta(type): return cls -@six.add_metaclass(InstanceTrackerMeta) -class BaseModel(object): +class BaseModel(metaclass=InstanceTrackerMeta): def __new__(cls, *args, **kwargs): instance = super(BaseModel, cls).__new__(cls) cls.instances.append(instance) @@ -630,7 +594,7 @@ class CloudFormationModel(BaseModel): pass -class BaseBackend(object): +class BaseBackend: def _reset_model_refs(self): # Remove all references to the models stored for service, models in model_data.items(): @@ -724,7 +688,7 @@ class BaseBackend(object): # raise NotImplementedError() -class ConfigQueryModel(object): +class ConfigQueryModel: def __init__(self, backends): """Inits based on the resource type's backends (1 for each region if applicable)""" self.backends = backends @@ -817,7 +781,7 @@ class ConfigQueryModel(object): raise NotImplementedError() -class base_decorator(object): +class base_decorator: mock_backend = MockAWS def __init__(self, backends): diff --git a/moto/core/responses.py b/moto/core/responses.py index 8f6a6c946..065341ec6 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -15,8 +15,7 @@ from moto.core.exceptions import DryRunClientError from jinja2 import Environment, DictLoader, TemplateNotFound -import six -from six.moves.urllib.parse import parse_qs, parse_qsl, urlparse +from urllib.parse import parse_qs, parse_qsl, urlparse import xmltodict from werkzeug.exceptions import HTTPException @@ -32,24 +31,24 @@ log = logging.getLogger(__name__) def _decode_dict(d): decoded = OrderedDict() for key, value in d.items(): - if isinstance(key, six.binary_type): + if isinstance(key, bytes): newkey = key.decode("utf-8") elif isinstance(key, (list, tuple)): newkey = [] for k in key: - if isinstance(k, six.binary_type): + if isinstance(k, bytes): newkey.append(k.decode("utf-8")) else: newkey.append(k) else: newkey = key - if isinstance(value, six.binary_type): + if isinstance(value, bytes): newvalue = value.decode("utf-8") elif isinstance(value, (list, tuple)): newvalue = [] for v in value: - if isinstance(v, six.binary_type): + if isinstance(v, bytes): newvalue.append(v.decode("utf-8")) else: newvalue.append(v) @@ -221,7 +220,7 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): querystring[key] = [value] raw_body = self.body - if isinstance(self.body, six.binary_type): + if isinstance(self.body, bytes): self.body = self.body.decode("utf-8") if not querystring: @@ -405,7 +404,7 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): except HTTPException as http_error: response = http_error.description, dict(status=http_error.code) - if isinstance(response, six.string_types): + if isinstance(response, str): return 200, headers, response else: return self._send_response(headers, response) diff --git a/moto/core/utils.py b/moto/core/utils.py index 814e34234..6ee231ea9 100644 --- a/moto/core/utils.py +++ b/moto/core/utils.py @@ -6,10 +6,9 @@ import datetime import inspect import random import re -import six import string from botocore.exceptions import ClientError -from six.moves.urllib.parse import urlparse +from urllib.parse import urlparse REQUEST_ID_LONG = string.digits + string.ascii_uppercase @@ -68,20 +67,13 @@ def camelcase_to_pascal(argument): def method_names_from_class(clazz): - # On Python 2, methods are different from functions, and the `inspect` - # predicates distinguish between them. On Python 3, methods are just - # regular functions, and `inspect.ismethod` doesn't work, so we have to - # use `inspect.isfunction` instead - if six.PY2: - predicate = inspect.ismethod - else: - predicate = inspect.isfunction + predicate = inspect.isfunction return [x[0] for x in inspect.getmembers(clazz, predicate=predicate)] def get_random_hex(length=8): chars = list(range(10)) + ["a", "b", "c", "d", "e", "f"] - return "".join(six.text_type(random.choice(chars)) for x in range(length)) + return "".join(str(random.choice(chars)) for x in range(length)) def get_random_message_id(): @@ -184,7 +176,7 @@ class convert_flask_to_responses_response(object): def __call__(self, request, *args, **kwargs): for key, val in request.headers.items(): - if isinstance(val, six.binary_type): + if isinstance(val, bytes): request.headers[key] = val.decode("utf-8") result = self.callback(request, request.url, request.headers) @@ -233,10 +225,6 @@ def gen_amz_crc32(response, headerdict=None): response = response.encode("utf-8") crc = binascii.crc32(response) - if six.PY2: - # https://python.readthedocs.io/en/v2.7.2/library/binascii.html - # TLDR: Use bitshift to match Py3 behaviour - crc = crc & 0xFFFFFFFF if headerdict is not None and isinstance(headerdict, dict): headerdict.update({"x-amz-crc32": str(crc)}) @@ -261,7 +249,7 @@ def amz_crc32(f): headers = {} status = 200 - if isinstance(response, six.string_types): + if isinstance(response, str): body = response else: if len(response) == 2: @@ -293,7 +281,7 @@ def amzn_request_id(f): headers = {} status = 200 - if isinstance(response, six.string_types): + if isinstance(response, str): body = response else: if len(response) == 2: @@ -405,7 +393,7 @@ def remap_nested_keys(root, key_transform): if isinstance(root, dict): return { key_transform(k): remap_nested_keys(v, key_transform) - for k, v in six.iteritems(root) + for k, v in root.items() } return root diff --git a/moto/datapipeline/utils.py b/moto/datapipeline/utils.py index b14fe6f1a..c3c6820a2 100644 --- a/moto/datapipeline/utils.py +++ b/moto/datapipeline/utils.py @@ -1,4 +1,3 @@ -import six from moto.compat import collections_abc from moto.core.utils import get_random_hex @@ -14,9 +13,7 @@ def remove_capitalization_of_dict_keys(obj): normalized_key = key[:1].lower() + key[1:] result[normalized_key] = remove_capitalization_of_dict_keys(value) return result - elif isinstance(obj, collections_abc.Iterable) and not isinstance( - obj, six.string_types - ): + elif isinstance(obj, collections_abc.Iterable) and not isinstance(obj, str): result = obj.__class__() for item in obj: result += (remove_capitalization_of_dict_keys(item),) diff --git a/moto/dynamodb/responses.py b/moto/dynamodb/responses.py index 85ae58fc5..34ab8f187 100644 --- a/moto/dynamodb/responses.py +++ b/moto/dynamodb/responses.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals import json -import six from moto.core.responses import BaseResponse from moto.core.utils import camelcase_to_underscores @@ -28,7 +27,7 @@ class DynamoHandler(BaseResponse): if endpoint: endpoint = camelcase_to_underscores(endpoint) response = getattr(self, endpoint)() - if isinstance(response, six.string_types): + if isinstance(response, str): return 200, self.response_headers, response else: diff --git a/moto/dynamodb2/models/dynamo_type.py b/moto/dynamodb2/models/dynamo_type.py index 1fc1bcef3..5a8fa93eb 100644 --- a/moto/dynamodb2/models/dynamo_type.py +++ b/moto/dynamodb2/models/dynamo_type.py @@ -1,5 +1,3 @@ -import six - from moto.dynamodb2.comparisons import get_comparison_func from moto.dynamodb2.exceptions import InvalidUpdateExpression, IncorrectDataType from moto.dynamodb2.models.utilities import attribute_is_list, bytesize @@ -200,7 +198,7 @@ class DynamoType(object): raise TypeError("Sum only supported for Numbers.") def __getitem__(self, item): - if isinstance(item, six.string_types): + if isinstance(item, str): # If our DynamoType is a map it should be subscriptable with a key if self.type == DDBType.MAP: return self.value[item] @@ -222,7 +220,7 @@ class DynamoType(object): self.value.append(value) else: self.value[key] = value - elif isinstance(key, six.string_types): + elif isinstance(key, str): if self.is_map(): self.value[key] = value else: @@ -251,7 +249,7 @@ class DynamoType(object): Returns DynamoType or None. """ - if isinstance(key, six.string_types) and self.is_map(): + if isinstance(key, str) and self.is_map(): if "." in key and key.split(".")[0] in self.value: return self.value[key.split(".")[0]].child_attr( ".".join(key.split(".")[1:]) diff --git a/moto/dynamodb2/parsing/ast_nodes.py b/moto/dynamodb2/parsing/ast_nodes.py index 81735a8c9..4b1907dad 100644 --- a/moto/dynamodb2/parsing/ast_nodes.py +++ b/moto/dynamodb2/parsing/ast_nodes.py @@ -2,13 +2,10 @@ import abc from abc import abstractmethod from collections import deque -import six - from moto.dynamodb2.models import DynamoType -@six.add_metaclass(abc.ABCMeta) -class Node: +class Node(metaclass=abc.ABCMeta): def __init__(self, children=None): self.type = self.__class__.__name__ assert children is None or isinstance(children, list) @@ -31,8 +28,7 @@ class LeafNode(Node): super(LeafNode, self).__init__(children) -@six.add_metaclass(abc.ABCMeta) -class Expression(Node): +class Expression(Node, metaclass=abc.ABCMeta): """ Abstract Syntax Tree representing the expression @@ -51,8 +47,7 @@ class UpdateExpression(Expression): """ -@six.add_metaclass(abc.ABCMeta) -class UpdateExpressionClause(UpdateExpression): +class UpdateExpressionClause(UpdateExpression, metaclass=abc.ABCMeta): """ UpdateExpressionClause* => UpdateExpressionSetClause UpdateExpressionClause* => UpdateExpressionRemoveClause diff --git a/moto/dynamodb2/parsing/expressions.py b/moto/dynamodb2/parsing/expressions.py index 4c1d42a55..b8ad67f93 100644 --- a/moto/dynamodb2/parsing/expressions.py +++ b/moto/dynamodb2/parsing/expressions.py @@ -1,7 +1,6 @@ import logging from abc import abstractmethod import abc -import six from collections import deque from moto.dynamodb2.parsing.ast_nodes import ( @@ -127,8 +126,7 @@ class NestableExpressionParserMixin(object): return target_node -@six.add_metaclass(abc.ABCMeta) -class ExpressionParser: +class ExpressionParser(metaclass=abc.ABCMeta): """Abstract class""" def __init__(self, expression_token_list, token_pos=0): @@ -968,8 +966,7 @@ class UpdateExpressionAddActionsParser(UpdateExpressionActionsParser): return UpdateExpressionAddActions -@six.add_metaclass(abc.ABCMeta) -class UpdateExpressionPathValueParser(ExpressionParser): +class UpdateExpressionPathValueParser(ExpressionParser, metaclass=abc.ABCMeta): def _parse_path_and_value(self): """ UpdateExpressionAddActionParser only gets called when expecting an AddAction. So we should be aggressive on diff --git a/moto/dynamodb2/responses.py b/moto/dynamodb2/responses.py index e147141eb..07c51da3c 100644 --- a/moto/dynamodb2/responses.py +++ b/moto/dynamodb2/responses.py @@ -5,7 +5,6 @@ import json import re import itertools -import six from moto.core.responses import BaseResponse from moto.core.utils import camelcase_to_underscores, amz_crc32, amzn_request_id @@ -87,7 +86,7 @@ class DynamoHandler(BaseResponse): if endpoint: endpoint = camelcase_to_underscores(endpoint) response = getattr(self, endpoint)() - if isinstance(response, six.string_types): + if isinstance(response, str): return 200, self.response_headers, response else: @@ -482,8 +481,7 @@ class DynamoHandler(BaseResponse): index = table.schema reverse_attribute_lookup = dict( - (v, k) - for k, v in six.iteritems(self.body.get("ExpressionAttributeNames", {})) + (v, k) for k, v in self.body.get("ExpressionAttributeNames", {}).items() ) if " and " in key_condition_expression.lower(): diff --git a/moto/dynamodbstreams/responses.py b/moto/dynamodbstreams/responses.py index d4f5c78a6..001d989a6 100644 --- a/moto/dynamodbstreams/responses.py +++ b/moto/dynamodbstreams/responses.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse from .models import dynamodbstreams_backends -from six import string_types class DynamoDBStreamsHandler(BaseResponse): @@ -25,7 +24,7 @@ class DynamoDBStreamsHandler(BaseResponse): shard_iterator_type = self._get_param("ShardIteratorType") sequence_number = self._get_param("SequenceNumber") # according to documentation sequence_number param should be string - if isinstance(sequence_number, string_types): + if isinstance(sequence_number, str): sequence_number = int(sequence_number) return self.backend.get_shard_iterator( diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 9b49c9d22..adf790c0d 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -6,7 +6,6 @@ import ipaddress import json import os import re -import six import warnings from boto3 import Session @@ -569,12 +568,9 @@ class Instance(TaggedEC2Resource, BotoInstance, CloudFormationModel): # handle weird bug around user_data -- something grabs the repr(), so # it must be clean if isinstance(self.user_data, list) and len(self.user_data) > 0: - if six.PY3 and isinstance(self.user_data[0], six.binary_type): + if isinstance(self.user_data[0], bytes): # string will have a "b" prefix -- need to get rid of it self.user_data[0] = self.user_data[0].decode("utf-8") - elif six.PY2 and isinstance(self.user_data[0], six.text_type): - # string will have a "u" prefix -- need to get rid of it - self.user_data[0] = self.user_data[0].encode("utf-8") if self.subnet_id: subnet = ec2_backend.get_subnet(self.subnet_id) @@ -2281,9 +2277,7 @@ class SecurityGroupBackend(object): if group is None: raise InvalidSecurityGroupNotFoundError(group_name_or_id) if ip_ranges: - if isinstance(ip_ranges, str) or ( - six.PY2 and isinstance(ip_ranges, unicode) # noqa - ): + if isinstance(ip_ranges, str): ip_ranges = [{"CidrIp": str(ip_ranges)}] elif not isinstance(ip_ranges, list): ip_ranges = [json.loads(ip_ranges)] @@ -3096,9 +3090,7 @@ class VPCBackend(object): ): vpc_id = random_vpc_id() try: - vpc_cidr_block = ipaddress.IPv4Network( - six.text_type(cidr_block), strict=False - ) + vpc_cidr_block = ipaddress.IPv4Network(str(cidr_block), strict=False) except ValueError: raise InvalidCIDRBlockParameterError(cidr_block) if vpc_cidr_block.prefixlen < 16 or vpc_cidr_block.prefixlen > 28: @@ -3465,9 +3457,9 @@ class Subnet(TaggedEC2Resource, CloudFormationModel): self.id = subnet_id self.vpc_id = vpc_id self.cidr_block = cidr_block - self.cidr = ipaddress.IPv4Network(six.text_type(self.cidr_block), strict=False) + self.cidr = ipaddress.IPv4Network(str(self.cidr_block), strict=False) self._available_ip_addresses = ( - ipaddress.IPv4Network(six.text_type(self.cidr_block)).num_addresses - 5 + ipaddress.IPv4Network(str(self.cidr_block)).num_addresses - 5 ) self._availability_zone = availability_zone self.default_for_az = default_for_az @@ -3479,7 +3471,7 @@ class Subnet(TaggedEC2Resource, CloudFormationModel): # Theory is we assign ip's as we go (as 16,777,214 usable IPs in a /8) self._subnet_ip_generator = self.cidr.hosts() self.reserved_ips = [ - six.next(self._subnet_ip_generator) for _ in range(0, 3) + next(self._subnet_ip_generator) for _ in range(0, 3) ] # Reserved by AWS self._unused_ips = set() # if instance is destroyed hold IP here for reuse self._subnet_ips = {} # has IP: instance @@ -3580,11 +3572,11 @@ class Subnet(TaggedEC2Resource, CloudFormationModel): try: new_ip = self._unused_ips.pop() except KeyError: - new_ip = six.next(self._subnet_ip_generator) + new_ip = next(self._subnet_ip_generator) # Skips any IP's if they've been manually specified while str(new_ip) in self._subnet_ips: - new_ip = six.next(self._subnet_ip_generator) + new_ip = next(self._subnet_ip_generator) if new_ip == self.cidr.broadcast_address: raise StopIteration() # Broadcast address cant be used obviously @@ -3646,14 +3638,12 @@ class SubnetBackend(object): ) # Validate VPC exists and the supplied CIDR block is a subnet of the VPC's vpc_cidr_blocks = [ ipaddress.IPv4Network( - six.text_type(cidr_block_association["cidr_block"]), strict=False + str(cidr_block_association["cidr_block"]), strict=False ) for cidr_block_association in vpc.get_cidr_block_association_set() ] try: - subnet_cidr_block = ipaddress.IPv4Network( - six.text_type(cidr_block), strict=False - ) + subnet_cidr_block = ipaddress.IPv4Network(str(cidr_block), strict=False) except ValueError: raise InvalidCIDRBlockParameterError(cidr_block) @@ -4382,9 +4372,7 @@ class RouteBackend(object): try: if destination_cidr_block: - ipaddress.IPv4Network( - six.text_type(destination_cidr_block), strict=False - ) + ipaddress.IPv4Network(str(destination_cidr_block), strict=False) except ValueError: raise InvalidDestinationCIDRBlockParameterError(destination_cidr_block) @@ -4677,8 +4665,7 @@ class SpotInstanceRequest(BotoSpotRequest, TaggedEC2Resource): return instance -@six.add_metaclass(Model) -class SpotRequestBackend(object): +class SpotRequestBackend(object, metaclass=Model): def __init__(self): self.spot_instance_requests = {} super(SpotRequestBackend, self).__init__() diff --git a/moto/ec2/responses/instances.py b/moto/ec2/responses/instances.py index 6965eaa77..d85cef099 100644 --- a/moto/ec2/responses/instances.py +++ b/moto/ec2/responses/instances.py @@ -12,7 +12,6 @@ from moto.elbv2 import elbv2_backends from moto.core import ACCOUNT_ID from copy import deepcopy -import six class InstanceResponse(BaseResponse): @@ -322,7 +321,7 @@ class InstanceResponse(BaseResponse): if isinstance(bool_str, bool): return bool_str - if isinstance(bool_str, six.text_type): + if isinstance(bool_str, str): return str(bool_str).lower() == "true" return False diff --git a/moto/ec2/responses/key_pairs.py b/moto/ec2/responses/key_pairs.py index fa2e60904..1faa3d811 100644 --- a/moto/ec2/responses/key_pairs.py +++ b/moto/ec2/responses/key_pairs.py @@ -1,5 +1,4 @@ from __future__ import unicode_literals -import six from moto.core.responses import BaseResponse from moto.ec2.utils import filters_from_querystring @@ -15,7 +14,7 @@ class KeyPairs(BaseResponse): def delete_key_pair(self): name = self._get_param("KeyName") if self.is_not_dryrun("DeleteKeyPair"): - success = six.text_type(self.ec2_backend.delete_key_pair(name)).lower() + success = str(self.ec2_backend.delete_key_pair(name)).lower() return self.response_template(DELETE_KEY_PAIR_RESPONSE).render( success=success ) diff --git a/moto/ec2/responses/launch_templates.py b/moto/ec2/responses/launch_templates.py index 22faba539..0d803f164 100644 --- a/moto/ec2/responses/launch_templates.py +++ b/moto/ec2/responses/launch_templates.py @@ -1,4 +1,3 @@ -import six import uuid from moto.core.responses import BaseResponse from moto.ec2.models import OWNER_ID @@ -29,10 +28,10 @@ def xml_serialize(tree, key, value): node = ElementTree.SubElement(tree, name) - if isinstance(value, (str, int, float, six.text_type)): + if isinstance(value, (str, int, float, str)): node.text = str(value) elif isinstance(value, dict): - for dictkey, dictvalue in six.iteritems(value): + for dictkey, dictvalue in value.items(): xml_serialize(node, dictkey, dictvalue) elif isinstance(value, list): for item in value: @@ -53,7 +52,7 @@ def pretty_xml(tree): def parse_object(raw_data): out_data = {} - for key, value in six.iteritems(raw_data): + for key, value in raw_data.items(): key_fix_splits = key.split("_") key_len = len(key_fix_splits) @@ -75,7 +74,7 @@ def parse_object(raw_data): def parse_lists(data): - for key, value in six.iteritems(data): + for key, value in data.items(): if isinstance(value, dict): keys = data[key].keys() is_list = all(map(lambda k: k.isnumeric(), keys)) @@ -106,13 +105,13 @@ class LaunchTemplates(BaseResponse): if "TagSpecifications" not in parsed_template_data: parsed_template_data["TagSpecifications"] = [] converted_tag_spec = [] - for resource_type, tags in six.iteritems(tag_spec): + for resource_type, tags in tag_spec.items(): converted_tag_spec.append( { "ResourceType": resource_type, "Tags": [ {"Key": key, "Value": value} - for key, value in six.iteritems(tags) + for key, value in tags.items() ], } ) diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index e47f06b85..467a3b3db 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -5,7 +5,6 @@ import hashlib import fnmatch import random import re -import six from cryptography.hazmat.primitives import serialization from cryptography.hazmat.backends import default_backend @@ -53,7 +52,7 @@ EC2_PREFIX_TO_RESOURCE = dict((v, k) for (k, v) in EC2_RESOURCE_TO_PREFIX.items( def random_resource_id(size=8): chars = list(range(10)) + ["a", "b", "c", "d", "e", "f"] - resource_id = "".join(six.text_type(random.choice(chars)) for _ in range(size)) + resource_id = "".join(str(random.choice(chars)) for _ in range(size)) return resource_id @@ -462,7 +461,7 @@ def is_filter_matching(obj, filter, filter_value): if filter_value is None: return False - if isinstance(value, six.string_types): + if isinstance(value, str): if not isinstance(filter_value, list): filter_value = [filter_value] if any(fnmatch.fnmatch(value, pattern) for pattern in filter_value): @@ -582,7 +581,7 @@ def rsa_public_key_parse(key_material): from sshpubkeys.keys import SSHKey try: - if not isinstance(key_material, six.binary_type): + if not isinstance(key_material, bytes): key_material = key_material.encode("ascii") decoded_key = base64.b64decode(key_material).decode("ascii") diff --git a/moto/elb/urls.py b/moto/elb/urls.py index bb7f1c7bf..2f5567e42 100644 --- a/moto/elb/urls.py +++ b/moto/elb/urls.py @@ -1,5 +1,5 @@ from __future__ import unicode_literals -from six.moves.urllib.parse import parse_qs +from urllib.parse import parse_qs from botocore.awsrequest import AWSPreparedRequest from moto.elb.responses import ELBResponse diff --git a/moto/emr/responses.py b/moto/emr/responses.py index 044ff0cd7..9a3505357 100644 --- a/moto/emr/responses.py +++ b/moto/emr/responses.py @@ -6,7 +6,7 @@ from functools import wraps import pytz -from six.moves.urllib.parse import urlparse +from urllib.parse import urlparse from moto.core.responses import AWSServiceSpec from moto.core.responses import BaseResponse from moto.core.responses import xml_to_json_response diff --git a/moto/emr/utils.py b/moto/emr/utils.py index 99cc2ad12..489f5f89f 100644 --- a/moto/emr/utils.py +++ b/moto/emr/utils.py @@ -9,12 +9,10 @@ from moto.core.utils import ( iso_8601_datetime_with_milliseconds, ) -import six - def random_id(size=13): chars = list(range(10)) + list(string.ascii_uppercase) - return "".join(six.text_type(random.choice(chars)) for x in range(size)) + return "".join(str(random.choice(chars)) for x in range(size)) def random_cluster_id(size=13): diff --git a/moto/events/models.py b/moto/events/models.py index 810f9a9b5..434c2fb6b 100644 --- a/moto/events/models.py +++ b/moto/events/models.py @@ -10,7 +10,6 @@ from enum import Enum, unique from operator import lt, le, eq, ge, gt from boto3 import Session -from six import string_types from moto.core.exceptions import JsonRESTError from moto.core import ACCOUNT_ID, BaseBackend, CloudFormationModel, BaseModel @@ -640,7 +639,7 @@ class EventPattern: return all(nested_filter_matches + filter_list_matches) def _does_item_match_filters(self, item, filters): - allowed_values = [value for value in filters if isinstance(value, string_types)] + allowed_values = [value for value in filters if isinstance(value, str)] allowed_values_match = item in allowed_values if allowed_values else True named_filter_matches = [ self._does_item_match_named_filter(item, filter) diff --git a/moto/forecast/models.py b/moto/forecast/models.py index 67b9b0881..8b4a464dd 100644 --- a/moto/forecast/models.py +++ b/moto/forecast/models.py @@ -2,7 +2,6 @@ import re from datetime import datetime from boto3 import Session -from six import iteritems from moto.core import ACCOUNT_ID, BaseBackend from moto.core.utils import iso_8601_datetime_without_milliseconds @@ -160,7 +159,7 @@ class ForecastBackend(BaseBackend): dsg.update(dataset_arns) def list_dataset_groups(self): - return [v for (_, v) in iteritems(self.dataset_groups)] + return [v for (_, v) in self.dataset_groups.items()] def reset(self): region_name = self.region_name diff --git a/moto/glacier/responses.py b/moto/glacier/responses.py index 5a82be479..dc8e60df0 100644 --- a/moto/glacier/responses.py +++ b/moto/glacier/responses.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import json -from six.moves.urllib.parse import urlparse, parse_qs +from urllib.parse import urlparse, parse_qs from moto.core.responses import _TemplateEnvironmentMixin from .models import glacier_backends diff --git a/moto/glacier/utils.py b/moto/glacier/utils.py index d6dd7c656..be7c38774 100644 --- a/moto/glacier/utils.py +++ b/moto/glacier/utils.py @@ -1,7 +1,7 @@ import random import string -from six.moves.urllib.parse import urlparse +from urllib.parse import urlparse def region_from_glacier_url(url): diff --git a/moto/iam/access_control.py b/moto/iam/access_control.py index bcde25d9e..c73af587a 100644 --- a/moto/iam/access_control.py +++ b/moto/iam/access_control.py @@ -18,11 +18,9 @@ import re from abc import abstractmethod, ABCMeta from enum import Enum -import six from botocore.auth import SigV4Auth, S3SigV4Auth from botocore.awsrequest import AWSRequest from botocore.credentials import Credentials -from six import string_types from moto.core import ACCOUNT_ID from moto.core.exceptions import ( @@ -160,8 +158,7 @@ class CreateAccessKeyFailure(Exception): self.reason = reason -@six.add_metaclass(ABCMeta) -class IAMRequestBase(object): +class IAMRequestBase(object, metaclass=ABCMeta): def __init__(self, method, path, data, headers): log.debug( "Creating {class_name} with method={method}, path={path}, data={data}, headers={headers}".format( @@ -332,7 +329,7 @@ class IAMPolicy(object): if policy_version.is_default ) policy_document = default_version.document - elif isinstance(policy, string_types): + elif isinstance(policy, str): policy_document = policy else: policy_document = policy["policy_document"] diff --git a/moto/iam/models.py b/moto/iam/models.py index f16cf10a5..38779c7b7 100755 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -13,7 +13,7 @@ import time from cryptography import x509 from cryptography.hazmat.backends import default_backend -from six.moves.urllib import parse +from urllib import parse from moto.core.exceptions import RESTError from moto.core import BaseBackend, BaseModel, ACCOUNT_ID, CloudFormationModel from moto.core.utils import ( diff --git a/moto/iam/policy_validation.py b/moto/iam/policy_validation.py index 95610ac4d..3408bede7 100644 --- a/moto/iam/policy_validation.py +++ b/moto/iam/policy_validation.py @@ -1,8 +1,6 @@ import json import re -from six import string_types - from moto.iam.exceptions import MalformedPolicyDocument @@ -190,7 +188,7 @@ class IAMPolicyDocumentValidator: @staticmethod def _validate_effect_syntax(statement): assert "Effect" in statement - assert isinstance(statement["Effect"], string_types) + assert isinstance(statement["Effect"], str) assert statement["Effect"].lower() in [ allowed_effect.lower() for allowed_effect in VALID_EFFECTS ] @@ -222,10 +220,10 @@ class IAMPolicyDocumentValidator: @staticmethod def _validate_string_or_list_of_strings_syntax(statement, key): if key in statement: - assert isinstance(statement[key], (string_types, list)) + assert isinstance(statement[key], (str, list)) if isinstance(statement[key], list): for resource in statement[key]: - assert isinstance(resource, string_types) + assert isinstance(resource, str) @staticmethod def _validate_condition_syntax(statement): @@ -237,7 +235,7 @@ class IAMPolicyDocumentValidator: condition_element_key, condition_element_value, ) in condition_value.items(): - assert isinstance(condition_element_value, (list, string_types)) + assert isinstance(condition_element_value, (list, str)) if ( IAMPolicyDocumentValidator._strip_condition_key(condition_key) @@ -262,11 +260,11 @@ class IAMPolicyDocumentValidator: @staticmethod def _validate_sid_syntax(statement): if "Sid" in statement: - assert isinstance(statement["Sid"], string_types) + assert isinstance(statement["Sid"], str) def _validate_id_syntax(self): if "Id" in self._policy_json: - assert isinstance(self._policy_json["Id"], string_types) + assert isinstance(self._policy_json["Id"], str) def _validate_resource_exist(self): for statement in self._statements: @@ -295,7 +293,7 @@ class IAMPolicyDocumentValidator: def _validate_action_like_for_prefixes(self, key): for statement in self._statements: if key in statement: - if isinstance(statement[key], string_types): + if isinstance(statement[key], str): self._validate_action_prefix(statement[key]) else: for action in statement[key]: @@ -328,7 +326,7 @@ class IAMPolicyDocumentValidator: def _validate_resource_like_for_formats(self, key): for statement in self._statements: if key in statement: - if isinstance(statement[key], string_types): + if isinstance(statement[key], str): self._validate_resource_format(statement[key]) else: for resource in sorted(statement[key], reverse=True): @@ -438,7 +436,7 @@ class IAMPolicyDocumentValidator: @staticmethod def _legacy_parse_resource_like(statement, key): - if isinstance(statement[key], string_types): + if isinstance(statement[key], str): if statement[key] != "*": assert statement[key].count(":") >= 5 or "::" not in statement[key] assert statement[key].split(":")[2] != "" @@ -459,7 +457,7 @@ class IAMPolicyDocumentValidator: condition_element_key, condition_element_value, ) in condition_value.items(): - if isinstance(condition_element_value, string_types): + if isinstance(condition_element_value, str): IAMPolicyDocumentValidator._legacy_parse_date_condition_value( condition_element_value ) diff --git a/moto/iam/utils.py b/moto/iam/utils.py index 391f54dbd..b333a07bc 100644 --- a/moto/iam/utils.py +++ b/moto/iam/utils.py @@ -1,12 +1,11 @@ from __future__ import unicode_literals import random import string -import six def random_alphanumeric(length): return "".join( - six.text_type(random.choice(string.ascii_letters + string.digits + "+" + "/")) + str(random.choice(string.ascii_letters + string.digits + "+" + "/")) for _ in range(length) ) @@ -14,13 +13,12 @@ def random_alphanumeric(length): def random_resource_id(size=20): chars = list(range(10)) + list(string.ascii_lowercase) - return "".join(six.text_type(random.choice(chars)) for x in range(size)) + return "".join(str(random.choice(chars)) for x in range(size)) def random_access_key(): return "".join( - six.text_type(random.choice(string.ascii_uppercase + string.digits)) - for _ in range(16) + str(random.choice(string.ascii_uppercase + string.digits)) for _ in range(16) ) diff --git a/moto/instance_metadata/responses.py b/moto/instance_metadata/responses.py index 81dfd8b59..9bdb66ce3 100644 --- a/moto/instance_metadata/responses.py +++ b/moto/instance_metadata/responses.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import datetime import json -from six.moves.urllib.parse import urlparse +from urllib.parse import urlparse from moto.core.responses import BaseResponse diff --git a/moto/iot/responses.py b/moto/iot/responses.py index c0de60073..2d7f636c0 100644 --- a/moto/iot/responses.py +++ b/moto/iot/responses.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import json -from six.moves.urllib.parse import unquote +from urllib.parse import unquote from moto.core.responses import BaseResponse from .models import iot_backends diff --git a/moto/iotdata/responses.py b/moto/iotdata/responses.py index e9d087518..c7121f379 100644 --- a/moto/iotdata/responses.py +++ b/moto/iotdata/responses.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse from .models import iotdata_backends import json -from six.moves.urllib.parse import unquote +from urllib.parse import unquote class IoTDataPlaneResponse(BaseResponse): diff --git a/moto/kinesis/models.py b/moto/kinesis/models.py index dea436dfe..a385a0312 100644 --- a/moto/kinesis/models.py +++ b/moto/kinesis/models.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals import datetime import time import re -import six import itertools from operator import attrgetter @@ -180,13 +179,13 @@ class Stream(CloudFormationModel): raise ShardNotFoundError(shard_id) def get_shard_for_key(self, partition_key, explicit_hash_key): - if not isinstance(partition_key, six.string_types): + if not isinstance(partition_key, str): raise InvalidArgumentError("partition_key") if len(partition_key) > 256: raise InvalidArgumentError("partition_key") if explicit_hash_key: - if not isinstance(explicit_hash_key, six.string_types): + if not isinstance(explicit_hash_key, str): raise InvalidArgumentError("explicit_hash_key") key = int(explicit_hash_key) @@ -227,7 +226,7 @@ class Stream(CloudFormationModel): "StreamARN": self.arn, "StreamName": self.stream_name, "StreamStatus": self.status, - "StreamCreationTimestamp": six.text_type(self.creation_datetime), + "StreamCreationTimestamp": str(self.creation_datetime), "OpenShardCount": self.shard_count, } } diff --git a/moto/kms/responses.py b/moto/kms/responses.py index 995c097e0..b80f955a2 100644 --- a/moto/kms/responses.py +++ b/moto/kms/responses.py @@ -5,8 +5,6 @@ import json import os import re -import six - from moto.core.responses import BaseResponse from .models import kms_backends from .exceptions import ( @@ -343,7 +341,7 @@ class KmsResponse(BaseResponse): self._validate_key_id(key_id) - if isinstance(plaintext, six.text_type): + if isinstance(plaintext, str): plaintext = plaintext.encode("utf-8") ciphertext_blob, arn = self.kms_backend.encrypt( diff --git a/moto/managedblockchain/responses.py b/moto/managedblockchain/responses.py index 011a69e79..b5f1ead41 100644 --- a/moto/managedblockchain/responses.py +++ b/moto/managedblockchain/responses.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import json -from six.moves.urllib.parse import urlparse, parse_qs +from urllib.parse import urlparse, parse_qs from moto.core.responses import BaseResponse from .exceptions import exception_handler diff --git a/moto/managedblockchain/utils.py b/moto/managedblockchain/utils.py index 84d5137a8..96214031f 100644 --- a/moto/managedblockchain/utils.py +++ b/moto/managedblockchain/utils.py @@ -3,7 +3,7 @@ import random import re import string -from six.moves.urllib.parse import parse_qs, urlparse +from urllib.parse import parse_qs, urlparse def region_from_managedblckchain_url(url): diff --git a/moto/polly/responses.py b/moto/polly/responses.py index e7de01b2b..4837ba968 100644 --- a/moto/polly/responses.py +++ b/moto/polly/responses.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import json import re -from six.moves.urllib.parse import urlsplit +from urllib.parse import urlsplit from moto.core.responses import BaseResponse from .models import polly_backends diff --git a/moto/redshift/responses.py b/moto/redshift/responses.py index cbd6d01a7..45e0932ae 100644 --- a/moto/redshift/responses.py +++ b/moto/redshift/responses.py @@ -5,7 +5,6 @@ import json import xmltodict from jinja2 import Template -from six import iteritems from moto.core.responses import BaseResponse from .models import redshift_backends @@ -250,7 +249,7 @@ class RedshiftResponse(BaseResponse): cluster_kwargs = {} # We only want parameters that were actually passed in, otherwise # we'll stomp all over our cluster metadata with None values. - for (key, value) in iteritems(request_kwargs): + for (key, value) in request_kwargs.items(): if value is not None and value != []: cluster_kwargs[key] = value diff --git a/moto/resourcegroupstaggingapi/models.py b/moto/resourcegroupstaggingapi/models.py index f8d61ea19..ac4ab2704 100644 --- a/moto/resourcegroupstaggingapi/models.py +++ b/moto/resourcegroupstaggingapi/models.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals import uuid -import six from boto3 import Session from moto.core import ACCOUNT_ID @@ -575,7 +574,7 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend): try: while True: # Generator format: [{'ResourceARN': str, 'Tags': [{'Key': str, 'Value': str]}, ...] - next_item = six.next(generator) + next_item = next(generator) resource_tags = len(next_item["Tags"]) if current_resources >= resources_per_page: @@ -625,7 +624,7 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend): try: while True: # Generator format: ['tag', 'tag', 'tag', ...] - next_item = six.next(generator) + next_item = next(generator) if current_tags + 1 >= 128: break @@ -671,7 +670,7 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend): try: while True: # Generator format: ['value', 'value', 'value', ...] - next_item = six.next(generator) + next_item = next(generator) if current_tags + 1 >= 128: break diff --git a/moto/route53/responses.py b/moto/route53/responses.py index 7c3410791..3d7aace40 100644 --- a/moto/route53/responses.py +++ b/moto/route53/responses.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals from jinja2 import Template -from six.moves.urllib.parse import parse_qs, urlparse +from urllib.parse import parse_qs, urlparse from moto.core.responses import BaseResponse from .models import route53_backend diff --git a/moto/s3/models.py b/moto/s3/models.py index 5c976195f..66b6e22f2 100644 --- a/moto/s3/models.py +++ b/moto/s3/models.py @@ -18,7 +18,6 @@ import sys import time import uuid -import six from bisect import insort from moto.core import ACCOUNT_ID, BaseBackend, BaseModel, CloudFormationModel @@ -153,7 +152,7 @@ class FakeKey(BaseModel): # Hack for working around moto's own unit tests; this probably won't # actually get hit in normal use. - if isinstance(new_value, six.text_type): + if isinstance(new_value, str): new_value = new_value.encode(DEFAULT_TEXT_ENCODING) self._value_buffer.write(new_value) self.contentsize = len(new_value) @@ -284,7 +283,7 @@ class FakeKey(BaseModel): return state def __setstate__(self, state): - self.__dict__.update({k: v for k, v in six.iteritems(state) if k != "value"}) + self.__dict__.update({k: v for k, v in state.items() if k != "value"}) self._value_buffer = tempfile.SpooledTemporaryFile( max_size=self._max_buffer_size @@ -628,24 +627,16 @@ class CorsRule(BaseModel): max_age_seconds=None, ): self.allowed_methods = ( - [allowed_methods] - if isinstance(allowed_methods, six.string_types) - else allowed_methods + [allowed_methods] if isinstance(allowed_methods, str) else allowed_methods ) self.allowed_origins = ( - [allowed_origins] - if isinstance(allowed_origins, six.string_types) - else allowed_origins + [allowed_origins] if isinstance(allowed_origins, str) else allowed_origins ) self.allowed_headers = ( - [allowed_headers] - if isinstance(allowed_headers, six.string_types) - else allowed_headers + [allowed_headers] if isinstance(allowed_headers, str) else allowed_headers ) self.exposed_headers = ( - [expose_headers] - if isinstance(expose_headers, six.string_types) - else expose_headers + [expose_headers] if isinstance(expose_headers, str) else expose_headers ) self.max_age_seconds = max_age_seconds @@ -960,20 +951,20 @@ class FakeBucket(CloudFormationModel): for rule in rules: assert isinstance(rule["AllowedMethod"], list) or isinstance( - rule["AllowedMethod"], six.string_types + rule["AllowedMethod"], str ) assert isinstance(rule["AllowedOrigin"], list) or isinstance( - rule["AllowedOrigin"], six.string_types + rule["AllowedOrigin"], str ) assert isinstance(rule.get("AllowedHeader", []), list) or isinstance( - rule.get("AllowedHeader", ""), six.string_types + rule.get("AllowedHeader", ""), str ) assert isinstance(rule.get("ExposedHeader", []), list) or isinstance( - rule.get("ExposedHeader", ""), six.string_types + rule.get("ExposedHeader", ""), str ) - assert isinstance(rule.get("MaxAgeSeconds", "0"), six.string_types) + assert isinstance(rule.get("MaxAgeSeconds", "0"), str) - if isinstance(rule["AllowedMethod"], six.string_types): + if isinstance(rule["AllowedMethod"], str): methods = [rule["AllowedMethod"]] else: methods = rule["AllowedMethod"] diff --git a/moto/s3/responses.py b/moto/s3/responses.py index fb3f2974b..148bea96f 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -4,7 +4,6 @@ import os import re import sys -import six from botocore.awsrequest import AWSPreparedRequest from moto.core.utils import ( @@ -12,7 +11,7 @@ from moto.core.utils import ( py2_strip_unicode_keys, unix_time_millis, ) -from six.moves.urllib.parse import ( +from urllib.parse import ( parse_qs, parse_qsl, urlparse, @@ -270,11 +269,11 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): @staticmethod def _send_response(response): - if isinstance(response, six.string_types): + if isinstance(response, str): return 200, {}, response.encode("utf-8") else: status_code, headers, response_content = response - if not isinstance(response_content, six.binary_type): + if not isinstance(response_content, bytes): response_content = response_content.encode("utf-8") return status_code, headers, response_content @@ -299,7 +298,7 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): body = request.data if body is None: body = b"" - if isinstance(body, six.binary_type): + if isinstance(body, bytes): body = body.decode("utf-8") body = "{0}".format(body).encode("utf-8") @@ -501,7 +500,7 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): bucket = self.backend.get_bucket(bucket_name) prefix = querystring.get("prefix", [None])[0] - if prefix and isinstance(prefix, six.binary_type): + if prefix and isinstance(prefix, bytes): prefix = prefix.decode("utf-8") delimiter = querystring.get("delimiter", [None])[0] max_keys = int(querystring.get("max-keys", [1000])[0]) @@ -553,7 +552,7 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): raise InvalidContinuationToken() prefix = querystring.get("prefix", [None])[0] - if prefix and isinstance(prefix, six.binary_type): + if prefix and isinstance(prefix, bytes): prefix = prefix.decode("utf-8") delimiter = querystring.get("delimiter", [None])[0] result_keys, result_folders = self.backend.prefix_query( @@ -1002,7 +1001,7 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): except S3ClientError as s3error: response = s3error.code, {}, s3error.description - if isinstance(response, six.string_types): + if isinstance(response, str): status_code = 200 response_content = response else: @@ -1326,7 +1325,7 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): # you can have a quoted ?version=abc with a version Id, so work on # we need to parse the unquoted string first src_key = request.headers.get("x-amz-copy-source") - if isinstance(src_key, six.binary_type): + if isinstance(src_key, bytes): src_key = src_key.decode("utf-8") src_key_parsed = urlparse(src_key) src_bucket, src_key = ( diff --git a/moto/s3/utils.py b/moto/s3/utils.py index 36ff56b5e..951e0a8b1 100644 --- a/moto/s3/utils.py +++ b/moto/s3/utils.py @@ -2,8 +2,7 @@ from __future__ import unicode_literals import logging import re -import six -from six.moves.urllib.parse import urlparse, unquote, quote +from urllib.parse import urlparse, unquote, quote from requests.structures import CaseInsensitiveDict import sys from moto.settings import S3_IGNORE_SUBDOMAIN_BUCKETNAME @@ -75,7 +74,7 @@ def metadata_from_headers(headers): metadata = CaseInsensitiveDict() meta_regex = re.compile(r"^x-amz-meta-([a-zA-Z0-9\-_.]+)$", flags=re.IGNORECASE) for header, value in headers.items(): - if isinstance(header, six.string_types): + if isinstance(header, str): result = meta_regex.match(header) meta_key = None if result: @@ -94,14 +93,10 @@ def metadata_from_headers(headers): def clean_key_name(key_name): - if six.PY2: - return unquote(key_name.encode("utf-8")).decode("utf-8") return unquote(key_name) def undo_clean_key_name(key_name): - if six.PY2: - return quote(key_name.encode("utf-8")).decode("utf-8") return quote(key_name) diff --git a/moto/s3bucket_path/utils.py b/moto/s3bucket_path/utils.py index d514a1b35..ada2c5268 100644 --- a/moto/s3bucket_path/utils.py +++ b/moto/s3bucket_path/utils.py @@ -1,5 +1,5 @@ from __future__ import unicode_literals -from six.moves.urllib.parse import urlparse +from urllib.parse import urlparse def bucket_name_from_url(url): diff --git a/moto/secretsmanager/utils.py b/moto/secretsmanager/utils.py index 2ddf50dd5..b79021833 100644 --- a/moto/secretsmanager/utils.py +++ b/moto/secretsmanager/utils.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals import random import string -import six import re from moto.core import ACCOUNT_ID @@ -56,9 +55,7 @@ def random_password( if exclude_characters: password = _exclude_characters(password, exclude_characters) - password = "".join( - six.text_type(random.choice(password)) for x in range(password_length) - ) + password = "".join(str(random.choice(password)) for x in range(password_length)) if require_each_included_type: password = _add_password_require_each_included_type( diff --git a/moto/server.py b/moto/server.py index 11ac0443b..e1580ca6e 100644 --- a/moto/server.py +++ b/moto/server.py @@ -9,12 +9,11 @@ import signal import sys from threading import Lock -import six from flask import Flask from flask_cors import CORS from flask.testing import FlaskClient -from six.moves.urllib.parse import urlencode +from urllib.parse import urlencode from werkzeug.routing import BaseConverter from werkzeug.serving import run_simple @@ -134,9 +133,7 @@ class DomainDispatcherApplication(object): path_info = environ.get("PATH_INFO", "") # The URL path might contain non-ASCII text, for instance unicode S3 bucket names - if six.PY2 and isinstance(path_info, str): - path_info = six.u(path_info) - if six.PY3 and isinstance(path_info, six.binary_type): + if isinstance(path_info, bytes): path_info = path_info.decode("utf-8") if path_info.startswith("/moto-api") or path_info == "/favicon.ico": diff --git a/moto/ses/responses.py b/moto/ses/responses.py index 61a6c5659..8ce124b96 100644 --- a/moto/ses/responses.py +++ b/moto/ses/responses.py @@ -1,8 +1,6 @@ from __future__ import unicode_literals import base64 -import six - from moto.core.responses import BaseResponse from .models import ses_backend from datetime import datetime @@ -59,7 +57,7 @@ class EmailResponse(BaseResponse): destinations = {"ToAddresses": [], "CcAddresses": [], "BccAddresses": []} for dest_type in destinations: # consume up to 51 to allow exception - for i in six.moves.range(1, 52): + for i in range(1, 52): field = "Destination.%s.member.%s" % (dest_type, i) address = self.querystring.get(field) if address is None: @@ -80,7 +78,7 @@ class EmailResponse(BaseResponse): destinations = {"ToAddresses": [], "CcAddresses": [], "BccAddresses": []} for dest_type in destinations: # consume up to 51 to allow exception - for i in six.moves.range(1, 52): + for i in range(1, 52): field = "Destination.%s.member.%s" % (dest_type, i) address = self.querystring.get(field) if address is None: @@ -100,11 +98,10 @@ class EmailResponse(BaseResponse): raw_data = self.querystring.get("RawMessage.Data")[0] raw_data = base64.b64decode(raw_data) - if six.PY3: - raw_data = raw_data.decode("utf-8") + raw_data = raw_data.decode("utf-8") destinations = [] # consume up to 51 to allow exception - for i in six.moves.range(1, 52): + for i in range(1, 52): field = "Destinations.member.%s" % i address = self.querystring.get(field) if address is None: diff --git a/moto/sns/models.py b/moto/sns/models.py index 58f93735a..15631f7e3 100644 --- a/moto/sns/models.py +++ b/moto/sns/models.py @@ -5,7 +5,6 @@ import uuid import json import requests -import six import re from boto3 import Session @@ -59,7 +58,7 @@ class Topic(CloudFormationModel): self._tags = {} def publish(self, message, subject=None, message_attributes=None): - message_id = six.text_type(uuid.uuid4()) + message_id = str(uuid.uuid4()) subscriptions, _ = self.sns_backend.list_subscriptions(self.arn) for subscription in subscriptions: subscription.publish( @@ -228,7 +227,7 @@ class Subscription(BaseModel): def _field_match(field, rules, message_attributes): for rule in rules: # TODO: boolean value matching is not supported, SNS behavior unknown - if isinstance(rule, six.string_types): + if isinstance(rule, str): if field not in message_attributes: return False if message_attributes[field]["Value"] == rule: @@ -239,7 +238,7 @@ class Subscription(BaseModel): return True except (ValueError, TypeError): pass - if isinstance(rule, (six.integer_types, float)): + if isinstance(rule, (int, float)): if field not in message_attributes: return False if message_attributes[field]["Type"] == "Number": @@ -273,7 +272,7 @@ class Subscription(BaseModel): return all( _field_match(field, rules, message_attributes) - for field, rules in six.iteritems(self._filter_policy) + for field, rules in self._filter_policy.items() ) def get_post_data(self, message, message_id, subject, message_attributes=None): @@ -356,7 +355,7 @@ class PlatformEndpoint(BaseModel): raise SnsEndpointDisabled("Endpoint %s disabled" % self.id) # This is where we would actually send a message - message_id = six.text_type(uuid.uuid4()) + message_id = str(uuid.uuid4()) self.messages[message_id] = message return message_id @@ -540,7 +539,7 @@ class SNSBackend(BaseBackend): if len(message) > MAXIMUM_SMS_MESSAGE_BYTES: raise ValueError("SMS message must be less than 1600 bytes") - message_id = six.text_type(uuid.uuid4()) + message_id = str(uuid.uuid4()) self.sms_messages[message_id] = (phone_number, message) return message_id @@ -656,7 +655,7 @@ class SNSBackend(BaseBackend): def _validate_filter_policy(self, value): # TODO: extend validation checks combinations = 1 - for rules in six.itervalues(value): + for rules in value.values(): combinations *= len(rules) # Even the official documentation states the total combination of values must not exceed 100, in reality it is 150 # https://docs.aws.amazon.com/sns/latest/dg/sns-subscription-filter-policies.html#subscription-filter-policy-constraints @@ -665,15 +664,15 @@ class SNSBackend(BaseBackend): "Invalid parameter: FilterPolicy: Filter policy is too complex" ) - for field, rules in six.iteritems(value): + for field, rules in value.items(): for rule in rules: if rule is None: continue - if isinstance(rule, six.string_types): + if isinstance(rule, str): continue if isinstance(rule, bool): continue - if isinstance(rule, (six.integer_types, float)): + if isinstance(rule, (int, float)): if rule <= -1000000000 or rule >= 1000000000: raise InternalError("Unknown") continue diff --git a/moto/sqs/models.py b/moto/sqs/models.py index 84664d53b..57298e5e3 100644 --- a/moto/sqs/models.py +++ b/moto/sqs/models.py @@ -7,7 +7,6 @@ import random import re import string -import six import struct from copy import deepcopy from xml.sax.saxutils import escape @@ -147,7 +146,7 @@ class Message(BaseModel): @staticmethod def utf8(string): - if isinstance(string, six.string_types): + if isinstance(string, str): return string.encode("utf-8") return string @@ -307,7 +306,7 @@ class Queue(CloudFormationModel): ) bool_fields = ("ContentBasedDeduplication", "FifoQueue") - for key, value in six.iteritems(attributes): + for key, value in attributes.items(): if key in integer_fields: value = int(value) if key in bool_fields: @@ -328,7 +327,7 @@ class Queue(CloudFormationModel): def _setup_dlq(self, policy): - if isinstance(policy, six.text_type): + if isinstance(policy, str): try: self.redrive_policy = json.loads(policy) except ValueError: diff --git a/moto/sqs/responses.py b/moto/sqs/responses.py index 8c68df147..b2725d577 100644 --- a/moto/sqs/responses.py +++ b/moto/sqs/responses.py @@ -10,7 +10,7 @@ from moto.core.utils import ( underscores_to_camelcase, camelcase_to_pascal, ) -from six.moves.urllib.parse import urlparse +from urllib.parse import urlparse from .exceptions import ( EmptyBatchRequest, diff --git a/moto/stepfunctions/utils.py b/moto/stepfunctions/utils.py index 130ffe792..79e66d22e 100644 --- a/moto/stepfunctions/utils.py +++ b/moto/stepfunctions/utils.py @@ -1,7 +1,7 @@ from functools import wraps from botocore.paginate import TokenDecoder, TokenEncoder -from six.moves import reduce +from functools import reduce from .exceptions import InvalidToken diff --git a/moto/sts/utils.py b/moto/sts/utils.py index e71dc60a9..668c5d0e1 100644 --- a/moto/sts/utils.py +++ b/moto/sts/utils.py @@ -3,8 +3,6 @@ import os import random import string -import six - ACCOUNT_SPECIFIC_ACCESS_KEY_PREFIX = "8NWMTLYQ" ACCOUNT_SPECIFIC_ASSUMED_ROLE_ID_PREFIX = "3X42LBCD" SESSION_TOKEN_PREFIX = "FQoGZXIvYXdzEBYaD" @@ -34,6 +32,6 @@ def random_assumed_role_id(): def _random_uppercase_or_digit_sequence(length): return "".join( - six.text_type(random.choice(string.ascii_uppercase + string.digits)) + str(random.choice(string.ascii_uppercase + string.digits)) for _ in range(length) ) diff --git a/moto/swf/responses.py b/moto/swf/responses.py index a2d576a11..787471940 100644 --- a/moto/swf/responses.py +++ b/moto/swf/responses.py @@ -1,5 +1,4 @@ import json -import six from moto.core.responses import BaseResponse @@ -31,7 +30,7 @@ class SWFResponse(BaseResponse): self._check_string(parameter) def _check_string(self, parameter): - if not isinstance(parameter, six.string_types): + if not isinstance(parameter, str): raise SWFSerializationException(parameter) def _check_none_or_list_of_strings(self, parameter): @@ -42,7 +41,7 @@ class SWFResponse(BaseResponse): if not isinstance(parameter, list): raise SWFSerializationException(parameter) for i in parameter: - if not isinstance(i, six.string_types): + if not isinstance(i, str): raise SWFSerializationException(parameter) def _check_exclusivity(self, **kwargs): diff --git a/moto/utilities/utils.py b/moto/utilities/utils.py index 14d73e981..dcc909d96 100644 --- a/moto/utilities/utils.py +++ b/moto/utilities/utils.py @@ -1,10 +1,6 @@ import json import random import string -import six - -if six.PY2: - from io import open def random_string(length=None): diff --git a/moto/xray/responses.py b/moto/xray/responses.py index aaf56c80a..1c5cf8ef5 100644 --- a/moto/xray/responses.py +++ b/moto/xray/responses.py @@ -4,7 +4,7 @@ import datetime from moto.core.responses import BaseResponse from moto.core.exceptions import AWSError -from six.moves.urllib.parse import urlsplit +from urllib.parse import urlsplit from .models import xray_backends from .exceptions import BadSegmentException diff --git a/requirements-dev.txt b/requirements-dev.txt index dd3325540..79173ae9a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,12 +1,11 @@ -e .[all,server] -r requirements-tests.txt -black==19.10b0; python_version >= '3.6' -regex==2019.11.1; python_version >= '3.6' # Needed for black +black==19.10b0 +regex==2019.11.1 coverage==4.5.4 flake8==3.7.8 boto>=2.45.0 -prompt-toolkit==2.0.10 # 3.x is not available with python2 click inflection==0.3.1 lxml diff --git a/requirements-tests.txt b/requirements-tests.txt index 02bdfdd2a..847ce539e 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -1,7 +1,4 @@ pytest -pytest-cov; python_version >= '3.6' -# https://github.com/aws/aws-xray-sdk-python/issues/196 -# Downgrade pytest-cov to the last version that uses coverage < 5 -pytest-cov<=2.10.1; python_version < '3.6' +pytest-cov sure==1.4.11 freezegun diff --git a/setup.py b/setup.py index dfb6a5852..b2e52e990 100755 --- a/setup.py +++ b/setup.py @@ -1,12 +1,9 @@ #!/usr/bin/env python from __future__ import unicode_literals -import codecs from io import open import os import re -import setuptools from setuptools import setup, find_packages -import sys # Borrowed from pip at https://github.com/pypa/pip/blob/62c27dee45625e1b63d1e023b0656310f276e050/setup.py#L11-L15 here = os.path.abspath(os.path.dirname(__file__)) @@ -35,43 +32,18 @@ install_requires = [ "cryptography>=3.3.1", "requests>=2.5", "xmltodict", - "six>1.9", "werkzeug", "pytz", "python-dateutil<3.0.0,>=2.1", "responses>=0.9.0", "MarkupSafe!=2.0.0a1", # This is a Jinja2 dependency, 2.0.0a1 currently seems broken -] - -# -# Avoid pins where they are not necessary. These pins were introduced by the -# following commit for Py2 compatibility. They are not required for non-Py2 -# users. -# -# https://github.com/mpenkov/moto/commit/00134d2df37bb4dcd5f447ef951d383bfec0903c -# -install_requires += [ - # - # This is an indirect dependency. Version 5.0.0 claims to be for - # Py2.6+, but it really isn't. - # - # https://github.com/jaraco/configparser/issues/51 - # - "configparser<5.0; python_version < '3'", "Jinja2>=2.10.1", - "Jinja2<3.0.0; python_version < '3'", - "mock<=3.0.5; python_version < '3'", "more-itertools", - "more-itertools==5.0.0; python_version < '3'", - # Indirect - Py2 works with 4.5, breaks with 4.7, but officially only supported by 4.0 - "rsa<=4.0; python_version < '3'", "setuptools", - "setuptools==44.0.0; python_version < '3'", ] _dep_PyYAML = "PyYAML>=5.1" -_dep_python_jose_py2 = "python-jose[cryptography]>=3.1.0,<3.3.0; python_version<'3'" -_dep_python_jose_py3 = "python-jose[cryptography]>=3.1.0,<4.0.0; python_version>'3'" +_dep_python_jose = "python-jose[cryptography]>=3.1.0,<4.0.0" _dep_python_jose_ecdsa_pin = ( "ecdsa<0.15" # https://github.com/spulec/moto/pull/3263#discussion_r477404984 ) @@ -80,23 +52,18 @@ _dep_jsondiff = "jsondiff>=1.1.2" _dep_aws_xray_sdk = "aws-xray-sdk!=0.96,>=0.93" _dep_idna = "idna<3,>=2.5" _dep_cfn_lint = "cfn-lint>=0.4.0" -_dep_decorator = "decorator<=4.4.2; python_version<'3'" # Transitive dependency - last version that supports py2.7 -_dep_sshpubkeys_py2 = "sshpubkeys==3.1.0; python_version<'3'" -_dep_sshpubkeys_py3 = "sshpubkeys>=3.1.0; python_version>'3'" +_dep_sshpubkeys = "sshpubkeys>=3.1.0" all_extra_deps = [ _dep_PyYAML, - _dep_python_jose_py2, - _dep_python_jose_py3, + _dep_python_jose, _dep_python_jose_ecdsa_pin, _dep_docker, _dep_jsondiff, _dep_aws_xray_sdk, _dep_idna, _dep_cfn_lint, - _dep_decorator, - _dep_sshpubkeys_py2, - _dep_sshpubkeys_py3, + _dep_sshpubkeys, ] all_server_deps = all_extra_deps + ["flask", "flask-cors"] @@ -104,20 +71,20 @@ all_server_deps = all_extra_deps + ["flask", "flask-cors"] # i.e. even those without extra dependencies. # Would be good for future-compatibility, I guess. extras_per_service = { - "apigateway": [_dep_python_jose_py2, _dep_python_jose_py3, _dep_python_jose_ecdsa_pin], + "apigateway": [_dep_python_jose, _dep_python_jose_ecdsa_pin], "awslambda": [_dep_docker], "batch": [_dep_docker], - "cloudformation": [_dep_docker, _dep_PyYAML, _dep_cfn_lint, _dep_decorator], - "cognitoidp": [_dep_python_jose_py2, _dep_python_jose_py3, _dep_python_jose_ecdsa_pin], + "cloudformation": [_dep_docker, _dep_PyYAML, _dep_cfn_lint], + "cognitoidp": [_dep_python_jose, _dep_python_jose_ecdsa_pin], "dynamodb2": [_dep_docker], "dynamodbstreams": [_dep_docker], - "ec2": [_dep_docker, _dep_sshpubkeys_py2, _dep_sshpubkeys_py3], + "ec2": [_dep_docker, _dep_sshpubkeys], "iotdata": [_dep_jsondiff], "s3": [_dep_PyYAML], "ses": [_dep_docker], "sns": [_dep_docker], "sqs": [_dep_docker], - "ssm": [_dep_docker, _dep_PyYAML, _dep_decorator], + "ssm": [_dep_docker, _dep_PyYAML], "xray": [_dep_aws_xray_sdk], } extras_require = { @@ -127,13 +94,6 @@ extras_require = { extras_require.update(extras_per_service) -# https://hynek.me/articles/conditional-python-dependencies/ -if int(setuptools.__version__.split(".", 1)[0]) < 18: - if sys.version_info[0:2] < (3, 3): - install_requires.append("backports.tempfile") -else: - extras_require[":python_version<'3.3'"] = ["backports.tempfile"] - setup( name="moto", @@ -157,8 +117,6 @@ setup( license="Apache", test_suite="tests", classifiers=[ - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", diff --git a/tests/Dockerfile b/tests/Dockerfile new file mode 100644 index 000000000..8117b3e08 --- /dev/null +++ b/tests/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.7-buster + +ADD . /moto/ +ENV PYTHONUNBUFFERED 1 +WORKDIR /moto/ +RUN make init +RUN make test \ No newline at end of file diff --git a/tests/helpers.py b/tests/helpers.py index 9293bcad9..e4682ba08 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals import boto from unittest import SkipTest -import six def version_tuple(v): @@ -26,10 +25,3 @@ class requires_boto_gte(object): if boto_version >= required: return test return skip_test - - -class disable_on_py3(object): - def __call__(self, test): - if not six.PY3: - return test - return skip_test diff --git a/tests/test_cloudformation/test_server.py b/tests/test_cloudformation/test_server.py index f3f037c42..04d7e591a 100644 --- a/tests/test_cloudformation/test_server.py +++ b/tests/test_cloudformation/test_server.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import json -from six.moves.urllib.parse import urlencode +from urllib.parse import urlencode import re import sure # noqa diff --git a/tests/test_core/test_socket.py b/tests/test_core/test_socket.py index 5e446ca1a..12d960ceb 100644 --- a/tests/test_core/test_socket.py +++ b/tests/test_core/test_socket.py @@ -2,32 +2,27 @@ import unittest from moto import mock_dynamodb2_deprecated, mock_dynamodb2 import socket -from six import PY3 - class TestSocketPair(unittest.TestCase): @mock_dynamodb2_deprecated def test_asyncio_deprecated(self): - if PY3: - self.assertIn( - "moto.packages.httpretty.core.fakesock.socket", - str(socket.socket), - "Our mock should be present", - ) - import asyncio + self.assertIn( + "httpretty.core.fakesock.socket", + str(socket.socket), + "Our mock should be present", + ) + import asyncio - self.assertIsNotNone(asyncio.get_event_loop()) + self.assertIsNotNone(asyncio.get_event_loop()) @mock_dynamodb2_deprecated def test_socket_pair_deprecated(self): - # In Python2, the fakesocket is not set, for some reason. - if PY3: - self.assertIn( - "moto.packages.httpretty.core.fakesock.socket", - str(socket.socket), - "Our mock should be present", - ) + self.assertIn( + "httpretty.core.fakesock.socket", + str(socket.socket), + "Our mock should be present", + ) a, b = socket.socketpair() self.assertIsNotNone(a) self.assertIsNotNone(b) diff --git a/tests/test_dynamodb/test_dynamodb.py b/tests/test_dynamodb/test_dynamodb.py index 3e1092025..0a8369b1e 100644 --- a/tests/test_dynamodb/test_dynamodb.py +++ b/tests/test_dynamodb/test_dynamodb.py @@ -1,5 +1,4 @@ from __future__ import unicode_literals -import six import boto import boto.dynamodb import sure # noqa diff --git a/tests/test_ec2/helpers.py b/tests/test_ec2/helpers.py index 6dd281874..9d231d19a 100644 --- a/tests/test_ec2/helpers.py +++ b/tests/test_ec2/helpers.py @@ -1,12 +1,10 @@ -import six - from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa def rsa_check_private_key(private_key_material): - assert isinstance(private_key_material, six.string_types) + assert isinstance(private_key_material, str) private_key = serialization.load_pem_private_key( data=private_key_material.encode("ascii"), diff --git a/tests/test_ec2/test_elastic_ip_addresses.py b/tests/test_ec2/test_elastic_ip_addresses.py index 584ec3516..ed05eb224 100644 --- a/tests/test_ec2/test_elastic_ip_addresses.py +++ b/tests/test_ec2/test_elastic_ip_addresses.py @@ -5,7 +5,6 @@ import pytest import boto import boto3 from boto.exception import EC2ResponseError -import six import sure # noqa @@ -30,7 +29,7 @@ def test_eip_allocate_classic(): standard = conn.allocate_address() standard.should.be.a(boto.ec2.address.Address) - standard.public_ip.should.be.a(six.text_type) + standard.public_ip.should.be.a(str) standard.instance_id.should.be.none standard.domain.should.be.equal("standard") diff --git a/tests/test_ec2/test_instances.py b/tests/test_ec2/test_instances.py index 0ccf8f2ff..5303e2da9 100644 --- a/tests/test_ec2/test_instances.py +++ b/tests/test_ec2/test_instances.py @@ -8,7 +8,6 @@ from unittest import SkipTest import base64 import ipaddress -import six import boto import boto3 from boto.ec2.instance import Reservation, InstanceAttribute @@ -21,10 +20,7 @@ from tests import EXAMPLE_AMI_ID from tests.helpers import requires_boto_gte -if six.PY2: - decode_method = base64.decodestring -else: - decode_method = base64.decodebytes +decode_method = base64.decodebytes ################ Test Readme ############### def add_servers(ami_id, count): @@ -1050,7 +1046,7 @@ def test_run_instance_with_subnet_boto3(): instance = resp["Instances"][0] instance["SubnetId"].should.equal(subnet_id) - priv_ipv4 = ipaddress.ip_address(six.text_type(instance["PrivateIpAddress"])) + priv_ipv4 = ipaddress.ip_address(str(instance["PrivateIpAddress"])) subnet_cidr.should.contain(priv_ipv4) diff --git a/tests/test_emr/test_emr.py b/tests/test_emr/test_emr.py index 0dea23066..999298d5c 100644 --- a/tests/test_emr/test_emr.py +++ b/tests/test_emr/test_emr.py @@ -8,7 +8,6 @@ from boto.emr.bootstrap_action import BootstrapAction from boto.emr.instance_group import InstanceGroup from boto.emr.step import StreamingStep -import six import sure # noqa from moto import mock_emr_deprecated @@ -86,7 +85,7 @@ def test_describe_cluster(): cluster.id.should.equal(cluster_id) cluster.loguri.should.equal(args["log_uri"]) - cluster.masterpublicdnsname.should.be.a(six.string_types) + cluster.masterpublicdnsname.should.be.a(str) cluster.name.should.equal(args["name"]) int(cluster.normalizedinstancehours).should.equal(0) # cluster.release_label @@ -96,11 +95,11 @@ def test_describe_cluster(): cluster.servicerole.should.equal(args["service_role"]) cluster.status.state.should.equal("TERMINATED") - cluster.status.statechangereason.message.should.be.a(six.string_types) - cluster.status.statechangereason.code.should.be.a(six.string_types) - cluster.status.timeline.creationdatetime.should.be.a(six.string_types) - # cluster.status.timeline.enddatetime.should.be.a(six.string_types) - # cluster.status.timeline.readydatetime.should.be.a(six.string_types) + cluster.status.statechangereason.message.should.be.a(str) + cluster.status.statechangereason.code.should.be.a(str) + cluster.status.timeline.creationdatetime.should.be.a(str) + # cluster.status.timeline.enddatetime.should.be.a(str) + # cluster.status.timeline.readydatetime.should.be.a(str) dict((item.key, item.value) for item in cluster.tags).should.equal(input_tags) @@ -195,10 +194,10 @@ def test_describe_jobflow(): jf = conn.describe_jobflow(cluster_id) jf.amiversion.should.equal(args["ami_version"]) jf.bootstrapactions.should.equal(None) - jf.creationdatetime.should.be.a(six.string_types) + jf.creationdatetime.should.be.a(str) jf.should.have.property("laststatechangereason") - jf.readydatetime.should.be.a(six.string_types) - jf.startdatetime.should.be.a(six.string_types) + jf.readydatetime.should.be.a(str) + jf.startdatetime.should.be.a(str) jf.state.should.equal("WAITING") jf.ec2keyname.should.equal(args["ec2_keyname"]) @@ -207,24 +206,24 @@ def test_describe_jobflow(): int(jf.instancecount).should.equal(2) for ig in jf.instancegroups: - ig.creationdatetime.should.be.a(six.string_types) - # ig.enddatetime.should.be.a(six.string_types) - ig.should.have.property("instancegroupid").being.a(six.string_types) + ig.creationdatetime.should.be.a(str) + # ig.enddatetime.should.be.a(str) + ig.should.have.property("instancegroupid").being.a(str) int(ig.instancerequestcount).should.equal(1) ig.instancerole.should.be.within(["MASTER", "CORE"]) int(ig.instancerunningcount).should.equal(1) ig.instancetype.should.equal("c1.medium") - ig.laststatechangereason.should.be.a(six.string_types) + ig.laststatechangereason.should.be.a(str) ig.market.should.equal("ON_DEMAND") - ig.name.should.be.a(six.string_types) - ig.readydatetime.should.be.a(six.string_types) - ig.startdatetime.should.be.a(six.string_types) + ig.name.should.be.a(str) + ig.readydatetime.should.be.a(str) + ig.startdatetime.should.be.a(str) ig.state.should.equal("RUNNING") jf.keepjobflowalivewhennosteps.should.equal("true") - jf.masterinstanceid.should.be.a(six.string_types) + jf.masterinstanceid.should.be.a(str) jf.masterinstancetype.should.equal(args["master_instance_type"]) - jf.masterpublicdnsname.should.be.a(six.string_types) + jf.masterpublicdnsname.should.be.a(str) int(jf.normalizedinstancehours).should.equal(0) jf.availabilityzone.should.equal(args["availability_zone"]) jf.slaveinstancetype.should.equal(args["slave_instance_type"]) @@ -288,12 +287,12 @@ def test_list_clusters(): x.name.should.equal(y["name"]) x.normalizedinstancehours.should.equal(y["normalizedinstancehours"]) x.status.state.should.equal(y["state"]) - x.status.timeline.creationdatetime.should.be.a(six.string_types) + x.status.timeline.creationdatetime.should.be.a(str) if y["state"] == "TERMINATED": - x.status.timeline.enddatetime.should.be.a(six.string_types) + x.status.timeline.enddatetime.should.be.a(str) else: x.status.timeline.shouldnt.have.property("enddatetime") - x.status.timeline.readydatetime.should.be.a(six.string_types) + x.status.timeline.readydatetime.should.be.a(str) if not hasattr(resp, "marker"): break args = {"marker": resp.marker} @@ -489,18 +488,18 @@ def test_instance_groups(): y = input_groups[x.name] if hasattr(y, "bidprice"): x.bidprice.should.equal(y.bidprice) - x.creationdatetime.should.be.a(six.string_types) - # x.enddatetime.should.be.a(six.string_types) + x.creationdatetime.should.be.a(str) + # x.enddatetime.should.be.a(str) x.should.have.property("instancegroupid") int(x.instancerequestcount).should.equal(y.num_instances) x.instancerole.should.equal(y.role) int(x.instancerunningcount).should.equal(y.num_instances) x.instancetype.should.equal(y.type) - x.laststatechangereason.should.be.a(six.string_types) + x.laststatechangereason.should.be.a(str) x.market.should.equal(y.market) - x.name.should.be.a(six.string_types) - x.readydatetime.should.be.a(six.string_types) - x.startdatetime.should.be.a(six.string_types) + x.name.should.be.a(str) + x.readydatetime.should.be.a(str) + x.startdatetime.should.be.a(str) x.state.should.equal("RUNNING") for x in conn.list_instance_groups(job_id).instancegroups: @@ -519,11 +518,11 @@ def test_instance_groups(): int(x.runninginstancecount).should.equal(y.num_instances) # ShrinkPolicy x.status.state.should.equal("RUNNING") - x.status.statechangereason.code.should.be.a(six.string_types) - x.status.statechangereason.message.should.be.a(six.string_types) - x.status.timeline.creationdatetime.should.be.a(six.string_types) - # x.status.timeline.enddatetime.should.be.a(six.string_types) - x.status.timeline.readydatetime.should.be.a(six.string_types) + x.status.statechangereason.code.should.be.a(str) + x.status.statechangereason.message.should.be.a(str) + x.status.timeline.creationdatetime.should.be.a(str) + # x.status.timeline.enddatetime.should.be.a(str) + x.status.timeline.readydatetime.should.be.a(str) igs = dict((g.name, g) for g in jf.instancegroups) @@ -571,14 +570,14 @@ def test_steps(): for step in jf.steps: step.actiononfailure.should.equal("TERMINATE_JOB_FLOW") list(arg.value for arg in step.args).should.have.length_of(8) - step.creationdatetime.should.be.a(six.string_types) - # step.enddatetime.should.be.a(six.string_types) + step.creationdatetime.should.be.a(str) + # step.enddatetime.should.be.a(str) step.jar.should.equal("/home/hadoop/contrib/streaming/hadoop-streaming.jar") - step.laststatechangereason.should.be.a(six.string_types) + step.laststatechangereason.should.be.a(str) step.mainclass.should.equal("") - step.name.should.be.a(six.string_types) - # step.readydatetime.should.be.a(six.string_types) - # step.startdatetime.should.be.a(six.string_types) + step.name.should.be.a(str) + # step.readydatetime.should.be.a(str) + # step.startdatetime.should.be.a(str) step.state.should.be.within(["STARTING", "PENDING"]) expected = dict((s.name, s) for s in input_steps) @@ -602,13 +601,13 @@ def test_steps(): x.config.jar.should.equal("/home/hadoop/contrib/streaming/hadoop-streaming.jar") x.config.mainclass.should.equal("") # properties - x.should.have.property("id").should.be.a(six.string_types) + x.should.have.property("id").should.be.a(str) x.name.should.equal(y.name) x.status.state.should.be.within(["STARTING", "PENDING"]) # x.status.statechangereason - x.status.timeline.creationdatetime.should.be.a(six.string_types) - # x.status.timeline.enddatetime.should.be.a(six.string_types) - # x.status.timeline.startdatetime.should.be.a(six.string_types) + x.status.timeline.creationdatetime.should.be.a(str) + # x.status.timeline.enddatetime.should.be.a(str) + # x.status.timeline.startdatetime.should.be.a(str) x = conn.describe_step(cluster_id, x.id) list(arg.value for arg in x.config.args).should.equal( @@ -626,13 +625,13 @@ def test_steps(): x.config.jar.should.equal("/home/hadoop/contrib/streaming/hadoop-streaming.jar") x.config.mainclass.should.equal("") # properties - x.should.have.property("id").should.be.a(six.string_types) + x.should.have.property("id").should.be.a(str) x.name.should.equal(y.name) x.status.state.should.be.within(["STARTING", "PENDING"]) # x.status.statechangereason - x.status.timeline.creationdatetime.should.be.a(six.string_types) - # x.status.timeline.enddatetime.should.be.a(six.string_types) - # x.status.timeline.startdatetime.should.be.a(six.string_types) + x.status.timeline.creationdatetime.should.be.a(str) + # x.status.timeline.enddatetime.should.be.a(str) + # x.status.timeline.startdatetime.should.be.a(str) @requires_boto_gte("2.39") def test_list_steps_with_states(): diff --git a/tests/test_emr/test_emr_boto3.py b/tests/test_emr/test_emr_boto3.py index 2c8a44b47..72c2cfd9c 100644 --- a/tests/test_emr/test_emr_boto3.py +++ b/tests/test_emr/test_emr_boto3.py @@ -7,7 +7,6 @@ from datetime import datetime import boto3 import json import pytz -import six import sure # noqa from botocore.exceptions import ClientError import pytest @@ -157,13 +156,13 @@ def test_describe_cluster(): cl["Id"].should.equal(cluster_id) cl["KerberosAttributes"].should.equal(args["KerberosAttributes"]) cl["LogUri"].should.equal(args["LogUri"]) - cl["MasterPublicDnsName"].should.be.a(six.string_types) + cl["MasterPublicDnsName"].should.be.a(str) cl["Name"].should.equal(args["Name"]) cl["NormalizedInstanceHours"].should.equal(0) # cl['ReleaseLabel'].should.equal('emr-5.0.0') cl.shouldnt.have.key("RequestedAmiVersion") cl["RunningAmiVersion"].should.equal("1.0.0") - cl["SecurityConfiguration"].should.be.a(six.string_types) + cl["SecurityConfiguration"].should.be.a(str) cl["SecurityConfiguration"].should.equal(args["SecurityConfiguration"]) cl["ServiceRole"].should.equal(args["ServiceRole"]) @@ -276,7 +275,7 @@ def test_describe_job_flow(): esd = jf["ExecutionStatusDetail"] esd["CreationDateTime"].should.be.a("datetime.datetime") # esd['EndDateTime'].should.be.a('datetime.datetime') - # esd['LastStateChangeReason'].should.be.a(six.string_types) + # esd['LastStateChangeReason'].should.be.a(str) esd["ReadyDateTime"].should.be.a("datetime.datetime") esd["StartDateTime"].should.be.a("datetime.datetime") esd["State"].should.equal("WAITING") @@ -289,21 +288,21 @@ def test_describe_job_flow(): # ig['BidPrice'] ig["CreationDateTime"].should.be.a("datetime.datetime") # ig['EndDateTime'].should.be.a('datetime.datetime') - ig["InstanceGroupId"].should.be.a(six.string_types) + ig["InstanceGroupId"].should.be.a(str) ig["InstanceRequestCount"].should.be.a(int) ig["InstanceRole"].should.be.within(["MASTER", "CORE"]) ig["InstanceRunningCount"].should.be.a(int) ig["InstanceType"].should.be.within(["c3.medium", "c3.xlarge"]) - # ig['LastStateChangeReason'].should.be.a(six.string_types) + # ig['LastStateChangeReason'].should.be.a(str) ig["Market"].should.equal("ON_DEMAND") - ig["Name"].should.be.a(six.string_types) + ig["Name"].should.be.a(str) ig["ReadyDateTime"].should.be.a("datetime.datetime") ig["StartDateTime"].should.be.a("datetime.datetime") ig["State"].should.equal("RUNNING") attrs["KeepJobFlowAliveWhenNoSteps"].should.equal(True) - # attrs['MasterInstanceId'].should.be.a(six.string_types) + # attrs['MasterInstanceId'].should.be.a(str) attrs["MasterInstanceType"].should.equal(args["Instances"]["MasterInstanceType"]) - attrs["MasterPublicDnsName"].should.be.a(six.string_types) + attrs["MasterPublicDnsName"].should.be.a(str) attrs["NormalizedInstanceHours"].should.equal(0) attrs["Placement"]["AvailabilityZone"].should.equal( args["Instances"]["Placement"]["AvailabilityZone"] @@ -911,8 +910,8 @@ def test_instance_groups(): x["RunningInstanceCount"].should.equal(y["InstanceCount"]) # ShrinkPolicy x["Status"]["State"].should.equal("RUNNING") - x["Status"]["StateChangeReason"]["Code"].should.be.a(six.string_types) - # x['Status']['StateChangeReason']['Message'].should.be.a(six.string_types) + x["Status"]["StateChangeReason"]["Code"].should.be.a(str) + # x['Status']['StateChangeReason']['Message'].should.be.a(str) x["Status"]["Timeline"]["CreationDateTime"].should.be.a("datetime.datetime") # x['Status']['Timeline']['EndDateTime'].should.be.a('datetime.datetime') x["Status"]["Timeline"]["ReadyDateTime"].should.be.a("datetime.datetime") @@ -1022,7 +1021,7 @@ def test_steps(): x["Config"]["Jar"].should.equal(y["HadoopJarStep"]["Jar"]) # x['Config']['MainClass'].should.equal(y['HadoopJarStep']['MainClass']) # Properties - x["Id"].should.be.a(six.string_types) + x["Id"].should.be.a(str) x["Name"].should.equal(y["Name"]) x["Status"]["State"].should.be.within(["STARTING", "PENDING"]) # StateChangeReason @@ -1038,7 +1037,7 @@ def test_steps(): x["Config"]["Jar"].should.equal(y["HadoopJarStep"]["Jar"]) # x['Config']['MainClass'].should.equal(y['HadoopJarStep']['MainClass']) # Properties - x["Id"].should.be.a(six.string_types) + x["Id"].should.be.a(str) x["Name"].should.equal(y["Name"]) x["Status"]["State"].should.be.within(["STARTING", "PENDING"]) # StateChangeReason diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index a30aa83f4..8bb6ef9d8 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -18,7 +18,7 @@ import pytest from datetime import datetime from tests.helpers import requires_boto_gte from uuid import uuid4 -from six.moves.urllib import parse +from urllib import parse MOCK_CERT = """-----BEGIN CERTIFICATE----- diff --git a/tests/test_iot/test_server.py b/tests/test_iot/test_server.py index 727331deb..f788d2350 100644 --- a/tests/test_iot/test_server.py +++ b/tests/test_iot/test_server.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import json -from six.moves.urllib.parse import quote +from urllib.parse import quote import pytest import sure # noqa diff --git a/tests/test_iotdata/test_server.py b/tests/test_iotdata/test_server.py index eb10022b8..b87d888e5 100644 --- a/tests/test_iotdata/test_server.py +++ b/tests/test_iotdata/test_server.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals -from six.moves.urllib.parse import quote +from urllib.parse import quote import pytest import sure # noqa diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index bb1b013e0..de7a93fce 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -5,7 +5,6 @@ import re import boto.kms import boto3 -import six import sure # noqa from boto.exception import JSONResponseError from boto.kms.exceptions import AlreadyExistsException, NotFoundException @@ -23,7 +22,7 @@ PLAINTEXT_VECTORS = [ def _get_encoded_value(plaintext): - if isinstance(plaintext, six.binary_type): + if isinstance(plaintext, bytes): return plaintext return plaintext.encode("utf-8") @@ -682,10 +681,7 @@ def test__assert_default_policy(): ) -if six.PY2: - sort = sorted -else: - sort = lambda l: sorted(l, key=lambda d: d.keys()) +sort = lambda l: sorted(l, key=lambda d: d.keys()) @mock_kms diff --git a/tests/test_kms/test_kms_boto3.py b/tests/test_kms/test_kms_boto3.py index b65b81959..23b7c9bc3 100644 --- a/tests/test_kms/test_kms_boto3.py +++ b/tests/test_kms/test_kms_boto3.py @@ -7,7 +7,6 @@ import os import boto3 import botocore.exceptions -import six import sure # noqa from freezegun import freeze_time import pytest @@ -22,7 +21,7 @@ PLAINTEXT_VECTORS = [ def _get_encoded_value(plaintext): - if isinstance(plaintext, six.binary_type): + if isinstance(plaintext, bytes): return plaintext return plaintext.encode("utf-8") diff --git a/tests/test_logs/test_logs.py b/tests/test_logs/test_logs.py index 4e4b222de..df4b562fe 100644 --- a/tests/test_logs/test_logs.py +++ b/tests/test_logs/test_logs.py @@ -2,7 +2,6 @@ import os import time from unittest import SkipTest import boto3 -import six from botocore.exceptions import ClientError import pytest import sure # noqa @@ -76,7 +75,7 @@ def test_put_logs(): ) events = res["events"] nextSequenceToken = putRes["nextSequenceToken"] - assert isinstance(nextSequenceToken, six.string_types) == True + assert isinstance(nextSequenceToken, str) == True assert len(nextSequenceToken) == 56 events.should.have.length_of(2) diff --git a/tests/test_organizations/organizations_test_utils.py b/tests/test_organizations/organizations_test_utils.py index 4c26d788d..f45197ac7 100644 --- a/tests/test_organizations/organizations_test_utils.py +++ b/tests/test_organizations/organizations_test_utils.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals -import six import datetime from moto.organizations import utils @@ -72,7 +71,7 @@ def validate_roots(org, response): root.should.have.key("Arn").should.equal( utils.ROOT_ARN_FORMAT.format(org["MasterAccountId"], org["Id"], root["Id"]) ) - root.should.have.key("Name").should.be.a(six.string_types) + root.should.have.key("Name").should.be.a(str) root.should.have.key("PolicyTypes").should.be.a(list) root["PolicyTypes"][0].should.have.key("Type").should.equal( "SERVICE_CONTROL_POLICY" @@ -87,7 +86,7 @@ def validate_organizational_unit(org, response): ou.should.have.key("Arn").should.equal( utils.OU_ARN_FORMAT.format(org["MasterAccountId"], org["Id"], ou["Id"]) ) - ou.should.have.key("Name").should.be.a(six.string_types) + ou.should.have.key("Name").should.be.a(str) def validate_account(org, account): @@ -103,7 +102,7 @@ def validate_account(org, account): account["Email"].should.match(utils.EMAIL_REGEX) account["JoinedMethod"].should.be.within(["INVITED", "CREATED"]) account["Status"].should.be.within(["ACTIVE", "SUSPENDED"]) - account["Name"].should.be.a(six.string_types) + account["Name"].should.be.a(str) account["JoinedTimestamp"].should.be.a(datetime.datetime) @@ -120,7 +119,7 @@ def validate_create_account_status(create_status): ) create_status["Id"].should.match(utils.CREATE_ACCOUNT_STATUS_ID_REGEX) create_status["AccountId"].should.match(utils.ACCOUNT_ID_REGEX) - create_status["AccountName"].should.be.a(six.string_types) + create_status["AccountName"].should.be.a(str) create_status["State"].should.equal("SUCCEEDED") create_status["RequestedTimestamp"].should.be.a(datetime.datetime) create_status["CompletedTimestamp"].should.be.a(datetime.datetime) @@ -132,13 +131,13 @@ def validate_policy_summary(org, summary): summary.should.have.key("Arn").should.equal( utils.SCP_ARN_FORMAT.format(org["MasterAccountId"], org["Id"], summary["Id"]) ) - summary.should.have.key("Name").should.be.a(six.string_types) - summary.should.have.key("Description").should.be.a(six.string_types) + summary.should.have.key("Name").should.be.a(str) + summary.should.have.key("Description").should.be.a(str) summary.should.have.key("Type").should.equal("SERVICE_CONTROL_POLICY") summary.should.have.key("AwsManaged").should.be.a(bool) def validate_service_control_policy(org, response): response.should.have.key("PolicySummary").should.be.a(dict) - response.should.have.key("Content").should.be.a(six.string_types) + response.should.have.key("Content").should.be.a(str) validate_policy_summary(org, response["PolicySummary"]) diff --git a/tests/test_organizations/test_organizations_boto3.py b/tests/test_organizations/test_organizations_boto3.py index be4c5ac22..384a86998 100644 --- a/tests/test_organizations/test_organizations_boto3.py +++ b/tests/test_organizations/test_organizations_boto3.py @@ -14,7 +14,6 @@ from moto.organizations.models import ( import boto3 import json -import six import sure # noqa from botocore.exceptions import ClientError import pytest @@ -947,9 +946,9 @@ def test_list_targets_for_policy(): response = client.list_targets_for_policy(PolicyId=policy_id) for target in response["Targets"]: target.should.be.a(dict) - target.should.have.key("Name").should.be.a(six.string_types) - target.should.have.key("Arn").should.be.a(six.string_types) - target.should.have.key("TargetId").should.be.a(six.string_types) + target.should.have.key("Name").should.be.a(str) + target.should.have.key("Arn").should.be.a(str) + target.should.have.key("TargetId").should.be.a(str) target.should.have.key("Type").should.be.within( ["ROOT", "ORGANIZATIONAL_UNIT", "ACCOUNT"] ) diff --git a/tests/test_rds/test_rds.py b/tests/test_rds/test_rds.py index a3e7dc9dd..da07b4e24 100644 --- a/tests/test_rds/test_rds.py +++ b/tests/test_rds/test_rds.py @@ -7,7 +7,6 @@ from boto.exception import BotoServerError import sure # noqa from moto import mock_ec2_deprecated, mock_rds_deprecated, mock_rds -from tests.helpers import disable_on_py3 @mock_rds_deprecated @@ -145,19 +144,6 @@ def test_delete_non_existent_security_group(): ) -@disable_on_py3() -@mock_rds_deprecated -def test_security_group_authorize(): - conn = boto.rds.connect_to_region("us-west-2") - security_group = conn.create_dbsecurity_group("db_sg", "DB Security Group") - list(security_group.ip_ranges).should.equal([]) - - security_group.authorize(cidr_ip="10.3.2.45/32") - security_group = conn.get_all_dbsecurity_groups()[0] - list(security_group.ip_ranges).should.have.length_of(1) - security_group.ip_ranges[0].cidr_ip.should.equal("10.3.2.45/32") - - @mock_rds_deprecated def test_add_security_group_to_database(): conn = boto.rds.connect_to_region("us-west-2") diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index f59520366..4d84f1953 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -5,9 +5,9 @@ import datetime import sys import os from boto3 import Session -from six.moves.urllib.request import urlopen -from six.moves.urllib.error import HTTPError -from six.moves.urllib.parse import urlparse, parse_qs +from urllib.request import urlopen +from urllib.error import HTTPError +from urllib.parse import urlparse, parse_qs from functools import wraps from gzip import GzipFile from io import BytesIO @@ -25,7 +25,6 @@ from botocore.handlers import disable_signing from boto.s3.connection import S3Connection from boto.s3.key import Key from freezegun import freeze_time -import six import requests from moto.s3 import models @@ -3823,7 +3822,7 @@ def test_boto3_list_object_versions(): s3.put_bucket_versioning( Bucket=bucket_name, VersioningConfiguration={"Status": "Enabled"} ) - items = (six.b("v1"), six.b("v2")) + items = (b"v1", b"v2") for body in items: s3.put_object(Bucket=bucket_name, Key=key, Body=body) response = s3.list_object_versions(Bucket=bucket_name) @@ -3846,7 +3845,7 @@ def test_boto3_list_object_versions_with_versioning_disabled(): bucket_name = "mybucket" key = "key-with-versions" s3.create_bucket(Bucket=bucket_name) - items = (six.b("v1"), six.b("v2")) + items = (b"v1", b"v2") for body in items: s3.put_object(Bucket=bucket_name, Key=key, Body=body) response = s3.list_object_versions(Bucket=bucket_name) @@ -3869,12 +3868,12 @@ def test_boto3_list_object_versions_with_versioning_enabled_late(): bucket_name = "mybucket" key = "key-with-versions" s3.create_bucket(Bucket=bucket_name) - items = (six.b("v1"), six.b("v2")) - s3.put_object(Bucket=bucket_name, Key=key, Body=six.b("v1")) + items = (b"v1", b"v2") + s3.put_object(Bucket=bucket_name, Key=key, Body=b"v1") s3.put_bucket_versioning( Bucket=bucket_name, VersioningConfiguration={"Status": "Enabled"} ) - s3.put_object(Bucket=bucket_name, Key=key, Body=six.b("v2")) + s3.put_object(Bucket=bucket_name, Key=key, Body=b"v2") response = s3.list_object_versions(Bucket=bucket_name) # Two object versions should be returned @@ -3901,7 +3900,7 @@ def test_boto3_bad_prefix_list_object_versions(): s3.put_bucket_versioning( Bucket=bucket_name, VersioningConfiguration={"Status": "Enabled"} ) - items = (six.b("v1"), six.b("v2")) + items = (b"v1", b"v2") for body in items: s3.put_object(Bucket=bucket_name, Key=key, Body=body) response = s3.list_object_versions(Bucket=bucket_name, Prefix=bad_prefix) @@ -3919,7 +3918,7 @@ def test_boto3_delete_markers(): s3.put_bucket_versioning( Bucket=bucket_name, VersioningConfiguration={"Status": "Enabled"} ) - items = (six.b("v1"), six.b("v2")) + items = (b"v1", b"v2") for body in items: s3.put_object(Bucket=bucket_name, Key=key, Body=body) @@ -3962,7 +3961,7 @@ def test_boto3_multiple_delete_markers(): s3.put_bucket_versioning( Bucket=bucket_name, VersioningConfiguration={"Status": "Enabled"} ) - items = (six.b("v1"), six.b("v2")) + items = (b"v1", b"v2") for body in items: s3.put_object(Bucket=bucket_name, Key=key, Body=body) diff --git a/tests/test_s3/test_server.py b/tests/test_s3/test_server.py index 19fdd2a2a..dfdfdc2f8 100644 --- a/tests/test_s3/test_server.py +++ b/tests/test_s3/test_server.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import io -from six.moves.urllib.parse import urlparse, parse_qs +from urllib.parse import urlparse, parse_qs import sure # noqa from flask.testing import FlaskClient diff --git a/tests/test_s3bucket_path/test_s3bucket_path.py b/tests/test_s3bucket_path/test_s3bucket_path.py index 06ec9cafd..44007e6a2 100644 --- a/tests/test_s3bucket_path/test_s3bucket_path.py +++ b/tests/test_s3bucket_path/test_s3bucket_path.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals -from six.moves.urllib.request import urlopen -from six.moves.urllib.error import HTTPError +from urllib.request import urlopen +from urllib.error import HTTPError import boto from boto.exception import S3ResponseError diff --git a/tests/test_sagemaker/cloudformation_test_configs.py b/tests/test_sagemaker/cloudformation_test_configs.py index 258b94d6d..414ac3a1a 100644 --- a/tests/test_sagemaker/cloudformation_test_configs.py +++ b/tests/test_sagemaker/cloudformation_test_configs.py @@ -1,13 +1,10 @@ import json from abc import ABCMeta, abstractmethod -import six - from moto.sts.models import ACCOUNT_ID -@six.add_metaclass(ABCMeta) -class TestConfig: +class TestConfig(metaclass=ABCMeta): """Provides the interface to use for creating test configurations. This class will provide the interface for what information will be diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py index ac699b4bf..d4420a8ec 100644 --- a/tests/test_secretsmanager/test_secretsmanager.py +++ b/tests/test_secretsmanager/test_secretsmanager.py @@ -11,7 +11,6 @@ import pytz from datetime import datetime import sure # noqa import pytest -from six import b DEFAULT_SECRET_NAME = "test-secret" @@ -48,10 +47,10 @@ def test_get_secret_value_binary(): conn = boto3.client("secretsmanager", region_name="us-west-2") create_secret = conn.create_secret( - Name="java-util-test-password", SecretBinary=b("foosecret") + Name="java-util-test-password", SecretBinary=b"foosecret" ) result = conn.get_secret_value(SecretId="java-util-test-password") - assert result["SecretBinary"] == b("foosecret") + assert result["SecretBinary"] == b"foosecret" @mock_secretsmanager @@ -776,7 +775,7 @@ def test_put_secret_value_on_non_existing_secret(): @mock_secretsmanager def test_put_secret_value_puts_new_secret(): conn = boto3.client("secretsmanager", region_name="us-west-2") - conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b("foosecret")) + conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b"foosecret") put_secret_value_dict = conn.put_secret_value( SecretId=DEFAULT_SECRET_NAME, SecretString="foosecret", @@ -795,10 +794,10 @@ def test_put_secret_value_puts_new_secret(): @mock_secretsmanager def test_put_secret_binary_value_puts_new_secret(): conn = boto3.client("secretsmanager", region_name="us-west-2") - conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b("foosecret")) + conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b"foosecret") put_secret_value_dict = conn.put_secret_value( SecretId=DEFAULT_SECRET_NAME, - SecretBinary=b("foosecret"), + SecretBinary=b"foosecret", VersionStages=["AWSCURRENT"], ) version_id = put_secret_value_dict["VersionId"] @@ -808,21 +807,21 @@ def test_put_secret_binary_value_puts_new_secret(): ) assert get_secret_value_dict - assert get_secret_value_dict["SecretBinary"] == b("foosecret") + assert get_secret_value_dict["SecretBinary"] == b"foosecret" @mock_secretsmanager def test_create_and_put_secret_binary_value_puts_new_secret(): conn = boto3.client("secretsmanager", region_name="us-west-2") - conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b("foosecret")) + conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b"foosecret") conn.put_secret_value( - SecretId=DEFAULT_SECRET_NAME, SecretBinary=b("foosecret_update") + SecretId=DEFAULT_SECRET_NAME, SecretBinary=b"foosecret_update" ) latest_secret = conn.get_secret_value(SecretId=DEFAULT_SECRET_NAME) assert latest_secret - assert latest_secret["SecretBinary"] == b("foosecret_update") + assert latest_secret["SecretBinary"] == b"foosecret_update" @mock_secretsmanager @@ -840,7 +839,7 @@ def test_put_secret_binary_requires_either_string_or_binary(): @mock_secretsmanager def test_put_secret_value_can_get_first_version_if_put_twice(): conn = boto3.client("secretsmanager", region_name="us-west-2") - conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b("foosecret")) + conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b"foosecret") put_secret_value_dict = conn.put_secret_value( SecretId=DEFAULT_SECRET_NAME, SecretString="first_secret", diff --git a/tests/test_ses/test_ses_boto3.py b/tests/test_ses/test_ses_boto3.py index de91b5522..ae0c5f7c5 100644 --- a/tests/test_ses/test_ses_boto3.py +++ b/tests/test_ses/test_ses_boto3.py @@ -3,8 +3,8 @@ import json import boto3 from botocore.exceptions import ClientError -from six.moves.email_mime_multipart import MIMEMultipart -from six.moves.email_mime_text import MIMEText +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText import pytest diff --git a/tests/test_ses/test_ses_sns_boto3.py b/tests/test_ses/test_ses_sns_boto3.py index 2a165080e..9676db371 100644 --- a/tests/test_ses/test_ses_sns_boto3.py +++ b/tests/test_ses/test_ses_sns_boto3.py @@ -3,8 +3,6 @@ from __future__ import unicode_literals import boto3 import json from botocore.exceptions import ClientError -from six.moves.email_mime_multipart import MIMEMultipart -from six.moves.email_mime_text import MIMEText import sure # noqa from moto import mock_ses, mock_sns, mock_sqs diff --git a/tests/test_sns/test_topics.py b/tests/test_sns/test_topics.py index e46c44cc7..61c2a1a2f 100644 --- a/tests/test_sns/test_topics.py +++ b/tests/test_sns/test_topics.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals import boto import json -import six import sure # noqa @@ -124,17 +123,9 @@ def test_topic_attributes(): DEFAULT_EFFECTIVE_DELIVERY_POLICY ) - # boto can't handle prefix-mandatory strings: - # i.e. unicode on Python 2 -- u"foobar" - # and bytes on Python 3 -- b"foobar" - if six.PY2: - policy = json.dumps({b"foo": b"bar"}) - displayname = b"My display name" - delivery = {b"http": {b"defaultHealthyRetryPolicy": {b"numRetries": 5}}} - else: - policy = json.dumps({"foo": "bar"}) - displayname = "My display name" - delivery = {"http": {"defaultHealthyRetryPolicy": {"numRetries": 5}}} + policy = json.dumps({"foo": "bar"}) + displayname = "My display name" + delivery = {"http": {"defaultHealthyRetryPolicy": {"numRetries": 5}}} conn.set_topic_attributes(topic_arn, "Policy", policy) conn.set_topic_attributes(topic_arn, "DisplayName", displayname) conn.set_topic_attributes(topic_arn, "DeliveryPolicy", delivery) diff --git a/tests/test_sns/test_topics_boto3.py b/tests/test_sns/test_topics_boto3.py index e6475125f..875d4f181 100644 --- a/tests/test_sns/test_topics_boto3.py +++ b/tests/test_sns/test_topics_boto3.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals import boto3 -import six import json # import sure # noqa @@ -202,18 +201,9 @@ def test_topic_attributes(): # boto can't handle prefix-mandatory strings: # i.e. unicode on Python 2 -- u"foobar" # and bytes on Python 3 -- b"foobar" - if six.PY2: - policy = json.dumps({b"foo": b"bar"}) - displayname = b"My display name" - delivery = json.dumps( - {b"http": {b"defaultHealthyRetryPolicy": {b"numRetries": 5}}} - ) - else: - policy = json.dumps({"foo": "bar"}) - displayname = "My display name" - delivery = json.dumps( - {"http": {"defaultHealthyRetryPolicy": {"numRetries": 5}}} - ) + policy = json.dumps({"foo": "bar"}) + displayname = "My display name" + delivery = json.dumps({"http": {"defaultHealthyRetryPolicy": {"numRetries": 5}}}) conn.set_topic_attributes( TopicArn=topic_arn, AttributeName="Policy", AttributeValue=policy ) diff --git a/tests/test_sqs/test_sqs.py b/tests/test_sqs/test_sqs.py index f92bfa6bb..fca108ada 100644 --- a/tests/test_sqs/test_sqs.py +++ b/tests/test_sqs/test_sqs.py @@ -10,7 +10,6 @@ import hashlib import boto import boto3 import botocore.exceptions -import six import sys import sure # noqa from boto.exception import SQSError @@ -587,9 +586,9 @@ def test_get_queue_attributes(): response["Attributes"]["ApproximateNumberOfMessages"].should.equal("0") response["Attributes"]["ApproximateNumberOfMessagesDelayed"].should.equal("0") response["Attributes"]["ApproximateNumberOfMessagesNotVisible"].should.equal("0") - response["Attributes"]["CreatedTimestamp"].should.be.a(six.string_types) + response["Attributes"]["CreatedTimestamp"].should.be.a(str) response["Attributes"]["DelaySeconds"].should.equal("0") - response["Attributes"]["LastModifiedTimestamp"].should.be.a(six.string_types) + response["Attributes"]["LastModifiedTimestamp"].should.be.a(str) response["Attributes"]["MaximumMessageSize"].should.equal("262144") response["Attributes"]["MessageRetentionPeriod"].should.equal("345600") response["Attributes"]["QueueArn"].should.equal(