From cad185c74da5ec14cbd3f3212ad9e6f7b8303c6d Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Thu, 16 Feb 2017 22:51:04 -0500 Subject: [PATCH] Cleanup headers and encoding. --- moto/apigateway/models.py | 2 +- moto/apigateway/responses.py | 40 +++++----- moto/awslambda/models.py | 2 +- moto/awslambda/responses.py | 23 +++--- moto/core/models.py | 10 ++- moto/core/responses.py | 10 +-- moto/core/utils.py | 28 ++++++- moto/datapipeline/responses.py | 2 +- moto/dynamodb/responses.py | 2 +- moto/dynamodb2/responses.py | 2 +- moto/ecs/responses.py | 2 +- moto/events/responses.py | 2 +- moto/kinesis/responses.py | 2 +- moto/kms/responses.py | 2 +- moto/opsworks/responses.py | 2 +- moto/s3/responses.py | 102 +++++++++++++----------- moto/swf/responses.py | 2 +- tests/test_sns/test_publishing.py | 2 +- tests/test_sns/test_publishing_boto3.py | 2 +- 19 files changed, 138 insertions(+), 101 deletions(-) diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py index be0bfa43..bab0bc1d 100644 --- a/moto/apigateway/models.py +++ b/moto/apigateway/models.py @@ -331,7 +331,7 @@ class RestAPI(object): def update_integration_mocks(self, stage_name): stage_url = STAGE_URL.format(api_id=self.id, region_name=self.region_name, stage_name=stage_name) - responses.add_callback(responses.GET, stage_url, callback=self.resource_callback) + responses.add_callback(responses.GET, stage_url.lower(), callback=self.resource_callback) def create_stage(self, name, deployment_id,variables=None,description='',cacheClusterEnabled=None,cacheClusterSize=None): if variables is None: diff --git a/moto/apigateway/responses.py b/moto/apigateway/responses.py index e8c353f4..a7bb28c6 100644 --- a/moto/apigateway/responses.py +++ b/moto/apigateway/responses.py @@ -10,11 +10,11 @@ from .exceptions import StageNotFoundException class APIGatewayResponse(BaseResponse): def _get_param(self, key): - return json.loads(self.body.decode("ascii")).get(key) + return json.loads(self.body).get(key) def _get_param_with_default_value(self, key, default): - jsonbody = json.loads(self.body.decode("ascii")) + jsonbody = json.loads(self.body) if key in jsonbody: return jsonbody.get(key) @@ -30,14 +30,14 @@ class APIGatewayResponse(BaseResponse): if self.method == 'GET': apis = self.backend.list_apis() - return 200, headers, json.dumps({"item": [ + return 200, {}, json.dumps({"item": [ api.to_dict() for api in apis ]}) elif self.method == 'POST': name = self._get_param('name') description = self._get_param('description') rest_api = self.backend.create_rest_api(name, description) - return 200, headers, json.dumps(rest_api.to_dict()) + return 200, {}, json.dumps(rest_api.to_dict()) def restapis_individual(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -45,10 +45,10 @@ class APIGatewayResponse(BaseResponse): if self.method == 'GET': rest_api = self.backend.get_rest_api(function_id) - return 200, headers, json.dumps(rest_api.to_dict()) + return 200, {}, json.dumps(rest_api.to_dict()) elif self.method == 'DELETE': rest_api = self.backend.delete_rest_api(function_id) - return 200, headers, json.dumps(rest_api.to_dict()) + return 200, {}, json.dumps(rest_api.to_dict()) def resources(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -56,7 +56,7 @@ class APIGatewayResponse(BaseResponse): if self.method == 'GET': resources = self.backend.list_resources(function_id) - return 200, headers, json.dumps({"item": [ + return 200, {}, json.dumps({"item": [ resource.to_dict() for resource in resources ]}) @@ -72,7 +72,7 @@ class APIGatewayResponse(BaseResponse): resource = self.backend.create_resource(function_id, resource_id, path_part) elif self.method == 'DELETE': resource = self.backend.delete_resource(function_id, resource_id) - return 200, headers, json.dumps(resource.to_dict()) + return 200, {}, json.dumps(resource.to_dict()) def resource_methods(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -83,11 +83,11 @@ class APIGatewayResponse(BaseResponse): if self.method == 'GET': method = self.backend.get_method(function_id, resource_id, method_type) - return 200, headers, json.dumps(method) + return 200, {}, json.dumps(method) elif self.method == 'PUT': authorization_type = self._get_param("authorizationType") method = self.backend.create_method(function_id, resource_id, method_type, authorization_type) - return 200, headers, json.dumps(method) + return 200, {}, json.dumps(method) def resource_method_responses(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -103,7 +103,7 @@ class APIGatewayResponse(BaseResponse): method_response = self.backend.create_method_response(function_id, resource_id, method_type, response_code) elif self.method == 'DELETE': method_response = self.backend.delete_method_response(function_id, resource_id, method_type, response_code) - return 200, headers, json.dumps(method_response) + return 200, {}, json.dumps(method_response) def restapis_stages(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -123,9 +123,9 @@ class APIGatewayResponse(BaseResponse): cacheClusterEnabled=cacheClusterEnabled, cacheClusterSize=cacheClusterSize) elif self.method == 'GET': stages = self.backend.get_stages(function_id) - return 200, headers, json.dumps({"item": stages}) + return 200, {}, json.dumps({"item": stages}) - return 200, headers, json.dumps(stage_response) + return 200, {}, json.dumps(stage_response) def stages(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -137,11 +137,11 @@ class APIGatewayResponse(BaseResponse): try: stage_response = self.backend.get_stage(function_id, stage_name) except StageNotFoundException as error: - return error.code, headers,'{{"message":"{0}","code":"{1}"}}'.format(error.message,error.error_type) + return error.code, {},'{{"message":"{0}","code":"{1}"}}'.format(error.message,error.error_type) elif self.method == 'PATCH': patch_operations = self._get_param('patchOperations') stage_response = self.backend.update_stage(function_id, stage_name, patch_operations) - return 200, headers, json.dumps(stage_response) + return 200, {}, json.dumps(stage_response) def integrations(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -159,7 +159,7 @@ class APIGatewayResponse(BaseResponse): integration_response = self.backend.create_integration(function_id, resource_id, method_type, integration_type, uri, request_templates=request_templates) elif self.method == 'DELETE': integration_response = self.backend.delete_integration(function_id, resource_id, method_type) - return 200, headers, json.dumps(integration_response) + return 200, {}, json.dumps(integration_response) def integration_responses(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -182,7 +182,7 @@ class APIGatewayResponse(BaseResponse): integration_response = self.backend.delete_integration_response( function_id, resource_id, method_type, status_code ) - return 200, headers, json.dumps(integration_response) + return 200, {}, json.dumps(integration_response) def deployments(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -190,13 +190,13 @@ class APIGatewayResponse(BaseResponse): if self.method == 'GET': deployments = self.backend.get_deployments(function_id) - return 200, headers, json.dumps({"item": deployments}) + return 200, {}, json.dumps({"item": deployments}) elif self.method == 'POST': name = self._get_param("stageName") description = self._get_param_with_default_value("description","") stage_variables = self._get_param_with_default_value('variables',{}) deployment = self.backend.create_deployment(function_id, name, description,stage_variables) - return 200, headers, json.dumps(deployment) + return 200, {}, json.dumps(deployment) def individual_deployment(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -208,4 +208,4 @@ class APIGatewayResponse(BaseResponse): deployment = self.backend.get_deployment(function_id, deployment_id) elif self.method == 'DELETE': deployment = self.backend.delete_deployment(function_id, deployment_id) - return 200, headers, json.dumps(deployment) + return 200, {}, json.dumps(deployment) diff --git a/moto/awslambda/models.py b/moto/awslambda/models.py index 069717ca..e8595cc2 100644 --- a/moto/awslambda/models.py +++ b/moto/awslambda/models.py @@ -196,7 +196,7 @@ class LambdaBackend(BaseBackend): def __init__(self): self._functions = {} - + def has_function(self, function_name): return function_name in self._functions diff --git a/moto/awslambda/responses.py b/moto/awslambda/responses.py index 708a8796..0cd7c57e 100644 --- a/moto/awslambda/responses.py +++ b/moto/awslambda/responses.py @@ -36,6 +36,7 @@ class LambdaResponse(BaseResponse): raise ValueError("Cannot handle request") def _invoke(self, request, full_url, headers): + response_headers = {} lambda_backend = self.get_lambda_backend(full_url) path = request.path if hasattr(request, 'path') else request.path_url @@ -43,15 +44,15 @@ class LambdaResponse(BaseResponse): if lambda_backend.has_function(function_name): fn = lambda_backend.get_function(function_name) - payload = fn.invoke(request, headers) - headers['Content-Length'] = str(len(payload)) - return 202, headers, payload + payload = fn.invoke(request, response_headers) + response_headers['Content-Length'] = str(len(payload)) + return 202, response_headers, payload else: - return 404, headers, "{}" + return 404, response_headers, "{}" def _list_functions(self, request, full_url, headers): lambda_backend = self.get_lambda_backend(full_url) - return 200, headers, json.dumps({ + return 200, {}, json.dumps({ "Functions": [fn.get_configuration() for fn in lambda_backend.list_functions()], # "NextMarker": str(uuid.uuid4()), }) @@ -62,10 +63,10 @@ class LambdaResponse(BaseResponse): try: fn = lambda_backend.create_function(spec) except ValueError as e: - return 400, headers, json.dumps({"Error": {"Code": e.args[0], "Message": e.args[1]}}) + return 400, {}, json.dumps({"Error": {"Code": e.args[0], "Message": e.args[1]}}) else: config = fn.get_configuration() - return 201, headers, json.dumps(config) + return 201, {}, json.dumps(config) def _delete_function(self, request, full_url, headers): lambda_backend = self.get_lambda_backend(full_url) @@ -75,9 +76,9 @@ class LambdaResponse(BaseResponse): if lambda_backend.has_function(function_name): lambda_backend.delete_function(function_name) - return 204, headers, "" + return 204, {}, "" else: - return 404, headers, "{}" + return 404, {}, "{}" def _get_function(self, request, full_url, headers): lambda_backend = self.get_lambda_backend(full_url) @@ -88,9 +89,9 @@ class LambdaResponse(BaseResponse): if lambda_backend.has_function(function_name): fn = lambda_backend.get_function(function_name) code = fn.get_code() - return 200, headers, json.dumps(code) + return 200, {}, json.dumps(code) else: - return 404, headers, "{}" + return 404, {}, "{}" def get_lambda_backend(self, full_url): from moto.awslambda.models import lambda_backends diff --git a/moto/core/models.py b/moto/core/models.py index fa6b7483..9570a86d 100644 --- a/moto/core/models.py +++ b/moto/core/models.py @@ -8,7 +8,11 @@ import re from moto.packages.responses import responses from moto.packages.httpretty import HTTPretty from .responses import metadata_response -from .utils import convert_regex_to_flask_path, convert_flask_to_responses_response +from .utils import ( + convert_httpretty_response, + convert_regex_to_flask_path, + convert_flask_to_responses_response, +) class BaseMockAWS(object): nested_count = 0 @@ -93,14 +97,14 @@ class HttprettyMockAWS(BaseMockAWS): HTTPretty.register_uri( method=method, uri=re.compile(key), - body=value, + body=convert_httpretty_response(value), ) # Mock out localhost instance metadata HTTPretty.register_uri( method=method, uri=re.compile('http://169.254.169.254/latest/meta-data/.*'), - body=metadata_response + body=convert_httpretty_response(metadata_response), ) def disable_patching(self): diff --git a/moto/core/responses.py b/moto/core/responses.py index 337227d3..05c882ba 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -123,14 +123,14 @@ class BaseResponse(_TemplateEnvironmentMixin): for key, value in request.form.items(): querystring[key] = [value, ] + if isinstance(self.body, six.binary_type): + self.body = self.body.decode('utf-8') + if not querystring: querystring.update(parse_qs(urlparse(full_url).query, keep_blank_values=True)) if not querystring: if 'json' in request.headers.get('content-type', []) and self.aws_service_spec: - if isinstance(self.body, six.binary_type): - decoded = json.loads(self.body.decode('utf-8')) - else: - decoded = json.loads(self.body) + decoded = json.loads(self.body) target = request.headers.get('x-amz-target') or request.headers.get('X-Amz-Target') service, method = target.split('.') @@ -154,7 +154,7 @@ class BaseResponse(_TemplateEnvironmentMixin): self.headers = request.headers if 'host' not in self.headers: self.headers['host'] = urlparse(full_url).netloc - self.response_headers = headers + self.response_headers = {"server": "amazon.com"} def get_region_from_url(self, full_url): match = re.search(self.region_regex, full_url) diff --git a/moto/core/utils.py b/moto/core/utils.py index 0f4b20b6..451d1a76 100644 --- a/moto/core/utils.py +++ b/moto/core/utils.py @@ -79,6 +79,29 @@ def convert_regex_to_flask_path(url_path): return url_path +class convert_httpretty_response(object): + + def __init__(self, callback): + self.callback = callback + + @property + def __name__(self): + # For instance methods, use class and method names. Otherwise + # use module and method name + if inspect.ismethod(self.callback): + outer = self.callback.__self__.__class__.__name__ + else: + outer = self.callback.__module__ + return "{0}.{1}".format(outer, self.callback.__name__) + + def __call__(self, request, url, headers, **kwargs): + result = self.callback(request, url, headers) + status, headers, response = result + if 'server' not in headers: + headers["server"] = "amazon.com" + return status, headers, response + + class convert_flask_to_httpretty_response(object): def __init__(self, callback): @@ -119,8 +142,11 @@ class convert_flask_to_responses_response(object): return "{0}.{1}".format(outer, self.callback.__name__) def __call__(self, request, *args, **kwargs): + for key, val in request.headers.items(): + if isinstance(val, six.binary_type): + request.headers[key] = val.decode("utf-8") + result = self.callback(request, request.url, request.headers) - # result is a status, headers, response tuple status, headers, response = result return status, headers, response diff --git a/moto/datapipeline/responses.py b/moto/datapipeline/responses.py index 70d19d18..2607f685 100644 --- a/moto/datapipeline/responses.py +++ b/moto/datapipeline/responses.py @@ -12,7 +12,7 @@ class DataPipelineResponse(BaseResponse): def parameters(self): # TODO this should really be moved to core/responses.py if self.body: - return json.loads(self.body.decode("utf-8")) + return json.loads(self.body) else: return self.querystring diff --git a/moto/dynamodb/responses.py b/moto/dynamodb/responses.py index 226d5d11..59cff039 100644 --- a/moto/dynamodb/responses.py +++ b/moto/dynamodb/responses.py @@ -51,7 +51,7 @@ class DynamoHandler(BaseResponse): return status, self.response_headers, dynamo_json_dump({'__type': type_}) def call_action(self): - body = self.body.decode('utf-8') + body = self.body if 'GetSessionToken' in body: return 200, self.response_headers, sts_handler() diff --git a/moto/dynamodb2/responses.py b/moto/dynamodb2/responses.py index 081afc2c..0957bfa8 100644 --- a/moto/dynamodb2/responses.py +++ b/moto/dynamodb2/responses.py @@ -52,7 +52,7 @@ class DynamoHandler(BaseResponse): return status, self.response_headers, dynamo_json_dump({'__type': type_}) def call_action(self): - body = self.body.decode('utf-8') + body = self.body if 'GetSessionToken' in body: return 200, self.response_headers, sts_handler() diff --git a/moto/ecs/responses.py b/moto/ecs/responses.py index ce90de37..a8c0ddda 100644 --- a/moto/ecs/responses.py +++ b/moto/ecs/responses.py @@ -14,7 +14,7 @@ class EC2ContainerServiceResponse(BaseResponse): @property def request_params(self): try: - return json.loads(self.body.decode()) + return json.loads(self.body) except ValueError: return {} diff --git a/moto/events/responses.py b/moto/events/responses.py index 7d63388b..75e70370 100644 --- a/moto/events/responses.py +++ b/moto/events/responses.py @@ -19,7 +19,7 @@ class EventsHandler(BaseResponse): } def load_body(self): - decoded_body = self.body.decode('utf-8') + decoded_body = self.body return json.loads(decoded_body or '{}') def error(self, type_, message='', status=400): diff --git a/moto/kinesis/responses.py b/moto/kinesis/responses.py index d0a90a61..9bc9fe94 100644 --- a/moto/kinesis/responses.py +++ b/moto/kinesis/responses.py @@ -11,7 +11,7 @@ class KinesisResponse(BaseResponse): @property def parameters(self): - return json.loads(self.body.decode("utf-8")) + return json.loads(self.body) @property def kinesis_backend(self): diff --git a/moto/kms/responses.py b/moto/kms/responses.py index bc928f6f..7f0659a6 100644 --- a/moto/kms/responses.py +++ b/moto/kms/responses.py @@ -22,7 +22,7 @@ class KmsResponse(BaseResponse): @property def parameters(self): - return json.loads(self.body.decode("utf-8")) + return json.loads(self.body) @property def kms_backend(self): diff --git a/moto/opsworks/responses.py b/moto/opsworks/responses.py index 47fed301..4e097915 100644 --- a/moto/opsworks/responses.py +++ b/moto/opsworks/responses.py @@ -10,7 +10,7 @@ class OpsWorksResponse(BaseResponse): @property def parameters(self): - return json.loads(self.body.decode("utf-8")) + return json.loads(self.body) @property def opsworks_backend(self): diff --git a/moto/s3/responses.py b/moto/s3/responses.py index 3fbd058f..07be98e7 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -104,10 +104,10 @@ class ResponseObject(_TemplateEnvironmentMixin): try: response = self._bucket_response(request, full_url, headers) except S3ClientError as s3error: - response = s3error.code, headers, s3error.description + response = s3error.code, {}, s3error.description if isinstance(response, six.string_types): - return 200, headers, response.encode("utf-8") + return 200, {}, response.encode("utf-8") else: status_code, headers, response_content = response return status_code, headers, response_content.encode("utf-8") @@ -133,8 +133,9 @@ class ResponseObject(_TemplateEnvironmentMixin): # Flask server body = request.data if body is None: - body = '' - body = body.decode('utf-8') + body = b'' + if isinstance(body, six.binary_type): + body = body.decode('utf-8') if method == 'HEAD': return self._bucket_response_head(bucket_name, headers) @@ -151,7 +152,7 @@ class ResponseObject(_TemplateEnvironmentMixin): def _bucket_response_head(self, bucket_name, headers): self.backend.get_bucket(bucket_name) - return 200, headers, "" + return 200, {}, "" def _bucket_response_get(self, bucket_name, querystring, headers): if 'uploads' in querystring: @@ -173,7 +174,7 @@ class ResponseObject(_TemplateEnvironmentMixin): elif 'lifecycle' in querystring: bucket = self.backend.get_bucket(bucket_name) if not bucket.rules: - return 404, headers, "NoSuchLifecycleConfiguration" + return 404, {}, "NoSuchLifecycleConfiguration" template = self.response_template(S3_BUCKET_LIFECYCLE_CONFIGURATION) return template.render(rules=bucket.rules) elif 'versioning' in querystring: @@ -184,8 +185,8 @@ class ResponseObject(_TemplateEnvironmentMixin): policy = self.backend.get_bucket_policy(bucket_name) if not policy: template = self.response_template(S3_NO_POLICY) - return 404, headers, template.render(bucket_name=bucket_name) - return 200, headers, policy + return 404, {}, template.render(bucket_name=bucket_name) + return 200, {}, policy elif 'website' in querystring: website_configuration = self.backend.get_bucket_website_configuration(bucket_name) return website_configuration @@ -211,7 +212,7 @@ class ResponseObject(_TemplateEnvironmentMixin): version_id_marker=version_id_marker ) template = self.response_template(S3_BUCKET_GET_VERSIONS) - return 200, headers, template.render( + return 200, {}, template.render( key_list=versions, bucket=bucket, prefix='', @@ -220,14 +221,14 @@ class ResponseObject(_TemplateEnvironmentMixin): is_truncated='false', ) elif querystring.get('list-type', [None])[0] == '2': - return 200, headers, self._handle_list_objects_v2(bucket_name, querystring) + return 200, {}, self._handle_list_objects_v2(bucket_name, querystring) bucket = self.backend.get_bucket(bucket_name) prefix = querystring.get('prefix', [None])[0] delimiter = querystring.get('delimiter', [None])[0] result_keys, result_folders = self.backend.prefix_query(bucket, prefix, delimiter) template = self.response_template(S3_BUCKET_GET_RESPONSE) - return 200, headers, template.render( + return 200, {}, template.render( bucket=bucket, prefix=prefix, delimiter=delimiter, @@ -286,7 +287,7 @@ class ResponseObject(_TemplateEnvironmentMixin): template = self.response_template(S3_BUCKET_VERSIONING) return template.render(bucket_versioning_status=ver.group(1)) else: - return 404, headers, "" + return 404, {}, "" elif 'lifecycle' in querystring: rules = xmltodict.parse(body)['LifecycleConfiguration']['Rule'] if not isinstance(rules, list): @@ -315,27 +316,27 @@ class ResponseObject(_TemplateEnvironmentMixin): else: raise template = self.response_template(S3_BUCKET_CREATE_RESPONSE) - return 200, headers, template.render(bucket=new_bucket) + return 200, {}, template.render(bucket=new_bucket) def _bucket_response_delete(self, body, bucket_name, querystring, headers): if 'policy' in querystring: self.backend.delete_bucket_policy(bucket_name, body) - return 204, headers, "" + return 204, {}, "" elif 'lifecycle' in querystring: bucket = self.backend.get_bucket(bucket_name) bucket.delete_lifecycle() - return 204, headers, "" + return 204, {}, "" removed_bucket = self.backend.delete_bucket(bucket_name) if removed_bucket: # Bucket exists template = self.response_template(S3_DELETE_BUCKET_SUCCESS) - return 204, headers, template.render(bucket=removed_bucket) + return 204, {}, template.render(bucket=removed_bucket) else: # Tried to delete a bucket that still has keys template = self.response_template(S3_DELETE_BUCKET_WITH_ITEMS_ERROR) - return 409, headers, template.render(bucket=removed_bucket) + return 409, {}, template.render(bucket=removed_bucket) def _bucket_response_post(self, request, body, bucket_name, headers): path = request.path if hasattr(request, 'path') else request.path_url @@ -349,7 +350,7 @@ class ResponseObject(_TemplateEnvironmentMixin): else: # HTTPretty, build new form object form = {} - for kv in body.decode('utf-8').split('&'): + for kv in body.split('&'): k, v = kv.split('=') form[k] = v @@ -365,7 +366,7 @@ class ResponseObject(_TemplateEnvironmentMixin): metadata = metadata_from_headers(form) new_key.set_metadata(metadata) - return 200, headers, "" + return 200, {}, "" def _bucket_response_delete_keys(self, request, body, bucket_name, headers): template = self.response_template(S3_DELETE_KEYS_RESPONSE) @@ -382,9 +383,10 @@ class ResponseObject(_TemplateEnvironmentMixin): else: error_names.append(key_name) - return 200, headers, template.render(deleted=deleted_names, delete_errors=error_names) + return 200, {}, template.render(deleted=deleted_names, delete_errors=error_names) def _handle_range_header(self, request, headers, response_content): + response_headers = {} length = len(response_content) last = length - 1 _, rspec = request.headers.get('range').split('=') @@ -399,28 +401,29 @@ class ResponseObject(_TemplateEnvironmentMixin): begin = length - min(end, length) end = last else: - return 400, headers, "" + return 400, response_headers, "" if begin < 0 or end > last or begin > min(end, last): - return 416, headers, "" - headers['content-range'] = "bytes {0}-{1}/{2}".format( + return 416, response_headers, "" + response_headers['content-range'] = "bytes {0}-{1}/{2}".format( begin, end, length) - return 206, headers, response_content[begin:end + 1] + return 206, response_headers, response_content[begin:end + 1] def key_response(self, request, full_url, headers): + response_headers = {} try: response = self._key_response(request, full_url, headers) except S3ClientError as s3error: - response = s3error.code, headers, s3error.description + response = s3error.code, {}, s3error.description if isinstance(response, six.string_types): status_code = 200 response_content = response else: - status_code, headers, response_content = response + status_code, response_headers, response_content = response if status_code == 200 and 'range' in request.headers: - return self._handle_range_header(request, headers, response_content) - return status_code, headers, response_content + return self._handle_range_header(request, response_headers, response_content) + return status_code, response_headers, response_content def _key_response(self, request, full_url, headers): parsed_url = urlparse(full_url) @@ -455,11 +458,12 @@ class ResponseObject(_TemplateEnvironmentMixin): raise NotImplementedError("Method {0} has not been impelemented in the S3 backend yet".format(method)) def _key_response_get(self, bucket_name, query, key_name, headers): + response_headers = {} if query.get('uploadId'): upload_id = query['uploadId'][0] parts = self.backend.list_multipart(bucket_name, upload_id) template = self.response_template(S3_MULTIPART_LIST_RESPONSE) - return 200, headers, template.render( + return 200, response_headers, template.render( bucket_name=bucket_name, key_name=key_name, upload_id=upload_id, @@ -471,13 +475,14 @@ class ResponseObject(_TemplateEnvironmentMixin): bucket_name, key_name, version_id=version_id) if 'acl' in query: template = self.response_template(S3_OBJECT_ACL_RESPONSE) - return 200, headers, template.render(obj=key) + return 200, response_headers, template.render(obj=key) - headers.update(key.metadata) - headers.update(key.response_dict) - return 200, headers, key.value + response_headers.update(key.metadata) + response_headers.update(key.response_dict) + return 200, response_headers, key.value def _key_response_put(self, request, body, bucket_name, query, key_name, headers): + response_headers = {} if query.get('uploadId') and query.get('partNumber'): upload_id = query['uploadId'][0] part_number = int(query['partNumber'][0]) @@ -501,8 +506,8 @@ class ResponseObject(_TemplateEnvironmentMixin): key = self.backend.set_part( bucket_name, upload_id, part_number, body) response = "" - headers.update(key.response_dict) - return 200, headers, response + response_headers.update(key.response_dict) + return 200, response_headers, response storage_class = request.headers.get('x-amz-storage-class', 'STANDARD') acl = self._acl_from_headers(request.headers) @@ -511,7 +516,7 @@ class ResponseObject(_TemplateEnvironmentMixin): key = self.backend.get_key(bucket_name, key_name) # TODO: Support the XML-based ACL format key.set_acl(acl) - return 200, headers, "" + return 200, response_headers, "" if 'x-amz-copy-source' in request.headers: # Copy key @@ -526,8 +531,8 @@ class ResponseObject(_TemplateEnvironmentMixin): metadata = metadata_from_headers(request.headers) new_key.set_metadata(metadata, replace=True) template = self.response_template(S3_OBJECT_COPY_RESPONSE) - headers.update(new_key.response_dict) - return 200, headers, template.render(key=new_key) + response_headers.update(new_key.response_dict) + return 200, response_headers, template.render(key=new_key) streaming_request = hasattr(request, 'streaming') and request.streaming closing_connection = headers.get('connection') == 'close' if closing_connection and streaming_request: @@ -546,18 +551,19 @@ class ResponseObject(_TemplateEnvironmentMixin): new_key.set_acl(acl) template = self.response_template(S3_OBJECT_RESPONSE) - headers.update(new_key.response_dict) - return 200, headers, template.render(key=new_key) + response_headers.update(new_key.response_dict) + return 200, response_headers, template.render(key=new_key) def _key_response_head(self, bucket_name, query, key_name, headers): + response_headers = {} version_id = query.get('versionId', [None])[0] key = self.backend.get_key(bucket_name, key_name, version_id=version_id) if key: - headers.update(key.metadata) - headers.update(key.response_dict) - return 200, headers, "" + response_headers.update(key.metadata) + response_headers.update(key.response_dict) + return 200, response_headers, "" else: - return 404, headers, "" + return 404, response_headers, "" def _acl_from_headers(self, headers): canned_acl = headers.get('x-amz-acl', '') @@ -595,10 +601,10 @@ class ResponseObject(_TemplateEnvironmentMixin): if query.get('uploadId'): upload_id = query['uploadId'][0] self.backend.cancel_multipart(bucket_name, upload_id) - return 204, headers, "" + return 204, {}, "" self.backend.delete_key(bucket_name, key_name) template = self.response_template(S3_DELETE_OBJECT_SUCCESS) - return 204, headers, template.render() + return 204, {}, template.render() def _complete_multipart_body(self, body): ps = minidom.parseString(body).getElementsByTagName('Part') @@ -620,7 +626,7 @@ class ResponseObject(_TemplateEnvironmentMixin): key_name=key_name, upload_id=multipart.id, ) - return 200, headers, response + return 200, {}, response if query.get('uploadId'): body = self._complete_multipart_body(body) @@ -640,7 +646,7 @@ class ResponseObject(_TemplateEnvironmentMixin): if key.expiry_date is not None: r = 200 key.restore(int(days)) - return r, headers, "" + return r, {}, "" else: raise NotImplementedError("Method POST had only been implemented for multipart uploads and restore operations, so far") diff --git a/moto/swf/responses.py b/moto/swf/responses.py index 47d00901..92d4957f 100644 --- a/moto/swf/responses.py +++ b/moto/swf/responses.py @@ -16,7 +16,7 @@ class SWFResponse(BaseResponse): # SWF parameters are passed through a JSON body, so let's ease retrieval @property def _params(self): - return json.loads(self.body.decode("utf-8")) + return json.loads(self.body) def _check_int(self, parameter): if not isinstance(parameter, int): diff --git a/tests/test_sns/test_publishing.py b/tests/test_sns/test_publishing.py index 8f8bfb0a..dae7e2b8 100644 --- a/tests/test_sns/test_publishing.py +++ b/tests/test_sns/test_publishing.py @@ -70,7 +70,7 @@ def test_publish_to_http(): last_request = responses.calls[-1].request last_request.method.should.equal("POST") - parse_qs(last_request.body.decode('utf-8')).should.equal({ + parse_qs(last_request.body).should.equal({ "Type": ["Notification"], "MessageId": [message_id], "TopicArn": ["arn:aws:sns:{0}:123456789012:some-topic".format(conn.region.name)], diff --git a/tests/test_sns/test_publishing_boto3.py b/tests/test_sns/test_publishing_boto3.py index b3752264..e31b969f 100644 --- a/tests/test_sns/test_publishing_boto3.py +++ b/tests/test_sns/test_publishing_boto3.py @@ -75,7 +75,7 @@ def test_publish_to_http(): last_request = responses.calls[-2].request last_request.method.should.equal("POST") - parse_qs(last_request.body.decode('utf-8')).should.equal({ + parse_qs(last_request.body).should.equal({ "Type": ["Notification"], "MessageId": [message_id], "TopicArn": ["arn:aws:sns:{0}:123456789012:some-topic".format(conn._client_config.region_name)],