Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/botocore/serialize.py: 23%

385 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 06:03 +0000

1# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"). You 

4# may not use this file except in compliance with the License. A copy of 

5# the License is located at 

6# 

7# http://aws.amazon.com/apache2.0/ 

8# 

9# or in the "license" file accompanying this file. This file is 

10# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 

11# ANY KIND, either express or implied. See the License for the specific 

12# language governing permissions and limitations under the License. 

13"""Protocol input serializes. 

14 

15This module contains classes that implement input serialization 

16for the various AWS protocol types. 

17 

18These classes essentially take user input, a model object that 

19represents what the expected input should look like, and it returns 

20a dictionary that contains the various parts of a request. A few 

21high level design decisions: 

22 

23 

24* Each protocol type maps to a separate class, all inherit from 

25 ``Serializer``. 

26* The return value for ``serialize_to_request`` (the main entry 

27 point) returns a dictionary that represents a request. This 

28 will have keys like ``url_path``, ``query_string``, etc. This 

29 is done so that it's a) easy to test and b) not tied to a 

30 particular HTTP library. See the ``serialize_to_request`` docstring 

31 for more details. 

32 

33Unicode 

34------- 

35 

36The input to the serializers should be text (str/unicode), not bytes, 

37with the exception of blob types. Those are assumed to be binary, 

38and if a str/unicode type is passed in, it will be encoded as utf-8. 

39""" 

40import base64 

41import calendar 

42import datetime 

43import json 

44import re 

45from xml.etree import ElementTree 

46 

47from botocore import validate 

48from botocore.compat import formatdate 

49from botocore.utils import ( 

50 has_header, 

51 is_json_value_header, 

52 parse_to_aware_datetime, 

53 percent_encode, 

54) 

55 

56# From the spec, the default timestamp format if not specified is iso8601. 

57DEFAULT_TIMESTAMP_FORMAT = 'iso8601' 

58ISO8601 = '%Y-%m-%dT%H:%M:%SZ' 

59# Same as ISO8601, but with microsecond precision. 

60ISO8601_MICRO = '%Y-%m-%dT%H:%M:%S.%fZ' 

61 

62 

63def create_serializer(protocol_name, include_validation=True): 

64 # TODO: Unknown protocols. 

65 serializer = SERIALIZERS[protocol_name]() 

66 if include_validation: 

67 validator = validate.ParamValidator() 

68 serializer = validate.ParamValidationDecorator(validator, serializer) 

69 return serializer 

70 

71 

72class Serializer: 

73 DEFAULT_METHOD = 'POST' 

74 # Clients can change this to a different MutableMapping 

75 # (i.e OrderedDict) if they want. This is used in the 

76 # compliance test to match the hash ordering used in the 

77 # tests. 

78 MAP_TYPE = dict 

79 DEFAULT_ENCODING = 'utf-8' 

80 

81 def serialize_to_request(self, parameters, operation_model): 

82 """Serialize parameters into an HTTP request. 

83 

84 This method takes user provided parameters and a shape 

85 model and serializes the parameters to an HTTP request. 

86 More specifically, this method returns information about 

87 parts of the HTTP request, it does not enforce a particular 

88 interface or standard for an HTTP request. It instead returns 

89 a dictionary of: 

90 

91 * 'url_path' 

92 * 'host_prefix' 

93 * 'query_string' 

94 * 'headers' 

95 * 'body' 

96 * 'method' 

97 

98 It is then up to consumers to decide how to map this to a Request 

99 object of their HTTP library of choice. Below is an example 

100 return value:: 

101 

102 {'body': {'Action': 'OperationName', 

103 'Bar': 'val2', 

104 'Foo': 'val1', 

105 'Version': '2014-01-01'}, 

106 'headers': {}, 

107 'method': 'POST', 

108 'query_string': '', 

109 'host_prefix': 'value.', 

110 'url_path': '/'} 

111 

112 :param parameters: The dictionary input parameters for the 

113 operation (i.e the user input). 

114 :param operation_model: The OperationModel object that describes 

115 the operation. 

116 """ 

117 raise NotImplementedError("serialize_to_request") 

118 

119 def _create_default_request(self): 

120 # Creates a boilerplate default request dict that subclasses 

121 # can use as a starting point. 

122 serialized = { 

123 'url_path': '/', 

124 'query_string': '', 

125 'method': self.DEFAULT_METHOD, 

126 'headers': {}, 

127 # An empty body is represented as an empty byte string. 

128 'body': b'', 

129 } 

130 return serialized 

131 

132 # Some extra utility methods subclasses can use. 

133 

134 def _timestamp_iso8601(self, value): 

135 if value.microsecond > 0: 

136 timestamp_format = ISO8601_MICRO 

137 else: 

138 timestamp_format = ISO8601 

139 return value.strftime(timestamp_format) 

140 

141 def _timestamp_unixtimestamp(self, value): 

142 return int(calendar.timegm(value.timetuple())) 

143 

144 def _timestamp_rfc822(self, value): 

145 if isinstance(value, datetime.datetime): 

146 value = self._timestamp_unixtimestamp(value) 

147 return formatdate(value, usegmt=True) 

148 

149 def _convert_timestamp_to_str(self, value, timestamp_format=None): 

150 if timestamp_format is None: 

151 timestamp_format = self.TIMESTAMP_FORMAT 

152 timestamp_format = timestamp_format.lower() 

153 datetime_obj = parse_to_aware_datetime(value) 

154 converter = getattr(self, f'_timestamp_{timestamp_format}') 

155 final_value = converter(datetime_obj) 

156 return final_value 

157 

158 def _get_serialized_name(self, shape, default_name): 

159 # Returns the serialized name for the shape if it exists. 

160 # Otherwise it will return the passed in default_name. 

161 return shape.serialization.get('name', default_name) 

162 

163 def _get_base64(self, value): 

164 # Returns the base64-encoded version of value, handling 

165 # both strings and bytes. The returned value is a string 

166 # via the default encoding. 

167 if isinstance(value, str): 

168 value = value.encode(self.DEFAULT_ENCODING) 

169 return base64.b64encode(value).strip().decode(self.DEFAULT_ENCODING) 

170 

171 def _expand_host_prefix(self, parameters, operation_model): 

172 operation_endpoint = operation_model.endpoint 

173 if ( 

174 operation_endpoint is None 

175 or 'hostPrefix' not in operation_endpoint 

176 ): 

177 return None 

178 

179 host_prefix_expression = operation_endpoint['hostPrefix'] 

180 input_members = operation_model.input_shape.members 

181 host_labels = [ 

182 member 

183 for member, shape in input_members.items() 

184 if shape.serialization.get('hostLabel') 

185 ] 

186 format_kwargs = {name: parameters[name] for name in host_labels} 

187 

188 return host_prefix_expression.format(**format_kwargs) 

189 

190 

191class QuerySerializer(Serializer): 

192 

193 TIMESTAMP_FORMAT = 'iso8601' 

194 

195 def serialize_to_request(self, parameters, operation_model): 

196 shape = operation_model.input_shape 

197 serialized = self._create_default_request() 

198 serialized['method'] = operation_model.http.get( 

199 'method', self.DEFAULT_METHOD 

200 ) 

201 serialized['headers'] = { 

202 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8' 

203 } 

204 # The query serializer only deals with body params so 

205 # that's what we hand off the _serialize_* methods. 

206 body_params = self.MAP_TYPE() 

207 body_params['Action'] = operation_model.name 

208 body_params['Version'] = operation_model.metadata['apiVersion'] 

209 if shape is not None: 

210 self._serialize(body_params, parameters, shape) 

211 serialized['body'] = body_params 

212 

213 host_prefix = self._expand_host_prefix(parameters, operation_model) 

214 if host_prefix is not None: 

215 serialized['host_prefix'] = host_prefix 

216 

217 return serialized 

218 

219 def _serialize(self, serialized, value, shape, prefix=''): 

220 # serialized: The dict that is incrementally added to with the 

221 # final serialized parameters. 

222 # value: The current user input value. 

223 # shape: The shape object that describes the structure of the 

224 # input. 

225 # prefix: The incrementally built up prefix for the serialized 

226 # key (i.e Foo.bar.members.1). 

227 method = getattr( 

228 self, 

229 f'_serialize_type_{shape.type_name}', 

230 self._default_serialize, 

231 ) 

232 method(serialized, value, shape, prefix=prefix) 

233 

234 def _serialize_type_structure(self, serialized, value, shape, prefix=''): 

235 members = shape.members 

236 for key, value in value.items(): 

237 member_shape = members[key] 

238 member_prefix = self._get_serialized_name(member_shape, key) 

239 if prefix: 

240 member_prefix = f'{prefix}.{member_prefix}' 

241 self._serialize(serialized, value, member_shape, member_prefix) 

242 

243 def _serialize_type_list(self, serialized, value, shape, prefix=''): 

244 if not value: 

245 # The query protocol serializes empty lists. 

246 serialized[prefix] = '' 

247 return 

248 if self._is_shape_flattened(shape): 

249 list_prefix = prefix 

250 if shape.member.serialization.get('name'): 

251 name = self._get_serialized_name(shape.member, default_name='') 

252 # Replace '.Original' with '.{name}'. 

253 list_prefix = '.'.join(prefix.split('.')[:-1] + [name]) 

254 else: 

255 list_name = shape.member.serialization.get('name', 'member') 

256 list_prefix = f'{prefix}.{list_name}' 

257 for i, element in enumerate(value, 1): 

258 element_prefix = f'{list_prefix}.{i}' 

259 element_shape = shape.member 

260 self._serialize(serialized, element, element_shape, element_prefix) 

261 

262 def _serialize_type_map(self, serialized, value, shape, prefix=''): 

263 if self._is_shape_flattened(shape): 

264 full_prefix = prefix 

265 else: 

266 full_prefix = '%s.entry' % prefix 

267 template = full_prefix + '.{i}.{suffix}' 

268 key_shape = shape.key 

269 value_shape = shape.value 

270 key_suffix = self._get_serialized_name(key_shape, default_name='key') 

271 value_suffix = self._get_serialized_name(value_shape, 'value') 

272 for i, key in enumerate(value, 1): 

273 key_prefix = template.format(i=i, suffix=key_suffix) 

274 value_prefix = template.format(i=i, suffix=value_suffix) 

275 self._serialize(serialized, key, key_shape, key_prefix) 

276 self._serialize(serialized, value[key], value_shape, value_prefix) 

277 

278 def _serialize_type_blob(self, serialized, value, shape, prefix=''): 

279 # Blob args must be base64 encoded. 

280 serialized[prefix] = self._get_base64(value) 

281 

282 def _serialize_type_timestamp(self, serialized, value, shape, prefix=''): 

283 serialized[prefix] = self._convert_timestamp_to_str( 

284 value, shape.serialization.get('timestampFormat') 

285 ) 

286 

287 def _serialize_type_boolean(self, serialized, value, shape, prefix=''): 

288 if value: 

289 serialized[prefix] = 'true' 

290 else: 

291 serialized[prefix] = 'false' 

292 

293 def _default_serialize(self, serialized, value, shape, prefix=''): 

294 serialized[prefix] = value 

295 

296 def _is_shape_flattened(self, shape): 

297 return shape.serialization.get('flattened') 

298 

299 

300class EC2Serializer(QuerySerializer): 

301 """EC2 specific customizations to the query protocol serializers. 

302 

303 The EC2 model is almost, but not exactly, similar to the query protocol 

304 serializer. This class encapsulates those differences. The model 

305 will have be marked with a ``protocol`` of ``ec2``, so you don't need 

306 to worry about wiring this class up correctly. 

307 

308 """ 

309 

310 def _get_serialized_name(self, shape, default_name): 

311 # Returns the serialized name for the shape if it exists. 

312 # Otherwise it will return the passed in default_name. 

313 if 'queryName' in shape.serialization: 

314 return shape.serialization['queryName'] 

315 elif 'name' in shape.serialization: 

316 # A locationName is always capitalized 

317 # on input for the ec2 protocol. 

318 name = shape.serialization['name'] 

319 return name[0].upper() + name[1:] 

320 else: 

321 return default_name 

322 

323 def _serialize_type_list(self, serialized, value, shape, prefix=''): 

324 for i, element in enumerate(value, 1): 

325 element_prefix = f'{prefix}.{i}' 

326 element_shape = shape.member 

327 self._serialize(serialized, element, element_shape, element_prefix) 

328 

329 

330class JSONSerializer(Serializer): 

331 TIMESTAMP_FORMAT = 'unixtimestamp' 

332 

333 def serialize_to_request(self, parameters, operation_model): 

334 target = '{}.{}'.format( 

335 operation_model.metadata['targetPrefix'], 

336 operation_model.name, 

337 ) 

338 json_version = operation_model.metadata['jsonVersion'] 

339 serialized = self._create_default_request() 

340 serialized['method'] = operation_model.http.get( 

341 'method', self.DEFAULT_METHOD 

342 ) 

343 serialized['headers'] = { 

344 'X-Amz-Target': target, 

345 'Content-Type': 'application/x-amz-json-%s' % json_version, 

346 } 

347 body = self.MAP_TYPE() 

348 input_shape = operation_model.input_shape 

349 if input_shape is not None: 

350 self._serialize(body, parameters, input_shape) 

351 serialized['body'] = json.dumps(body).encode(self.DEFAULT_ENCODING) 

352 

353 host_prefix = self._expand_host_prefix(parameters, operation_model) 

354 if host_prefix is not None: 

355 serialized['host_prefix'] = host_prefix 

356 

357 return serialized 

358 

359 def _serialize(self, serialized, value, shape, key=None): 

360 method = getattr( 

361 self, 

362 '_serialize_type_%s' % shape.type_name, 

363 self._default_serialize, 

364 ) 

365 method(serialized, value, shape, key) 

366 

367 def _serialize_type_structure(self, serialized, value, shape, key): 

368 if shape.is_document_type: 

369 serialized[key] = value 

370 else: 

371 if key is not None: 

372 # If a key is provided, this is a result of a recursive 

373 # call so we need to add a new child dict as the value 

374 # of the passed in serialized dict. We'll then add 

375 # all the structure members as key/vals in the new serialized 

376 # dictionary we just created. 

377 new_serialized = self.MAP_TYPE() 

378 serialized[key] = new_serialized 

379 serialized = new_serialized 

380 members = shape.members 

381 for member_key, member_value in value.items(): 

382 member_shape = members[member_key] 

383 if 'name' in member_shape.serialization: 

384 member_key = member_shape.serialization['name'] 

385 self._serialize( 

386 serialized, member_value, member_shape, member_key 

387 ) 

388 

389 def _serialize_type_map(self, serialized, value, shape, key): 

390 map_obj = self.MAP_TYPE() 

391 serialized[key] = map_obj 

392 for sub_key, sub_value in value.items(): 

393 self._serialize(map_obj, sub_value, shape.value, sub_key) 

394 

395 def _serialize_type_list(self, serialized, value, shape, key): 

396 list_obj = [] 

397 serialized[key] = list_obj 

398 for list_item in value: 

399 wrapper = {} 

400 # The JSON list serialization is the only case where we aren't 

401 # setting a key on a dict. We handle this by using 

402 # a __current__ key on a wrapper dict to serialize each 

403 # list item before appending it to the serialized list. 

404 self._serialize(wrapper, list_item, shape.member, "__current__") 

405 list_obj.append(wrapper["__current__"]) 

406 

407 def _default_serialize(self, serialized, value, shape, key): 

408 serialized[key] = value 

409 

410 def _serialize_type_timestamp(self, serialized, value, shape, key): 

411 serialized[key] = self._convert_timestamp_to_str( 

412 value, shape.serialization.get('timestampFormat') 

413 ) 

414 

415 def _serialize_type_blob(self, serialized, value, shape, key): 

416 serialized[key] = self._get_base64(value) 

417 

418 

419class BaseRestSerializer(Serializer): 

420 """Base class for rest protocols. 

421 

422 The only variance between the various rest protocols is the 

423 way that the body is serialized. All other aspects (headers, uri, etc.) 

424 are the same and logic for serializing those aspects lives here. 

425 

426 Subclasses must implement the ``_serialize_body_params`` method. 

427 

428 """ 

429 

430 QUERY_STRING_TIMESTAMP_FORMAT = 'iso8601' 

431 HEADER_TIMESTAMP_FORMAT = 'rfc822' 

432 # This is a list of known values for the "location" key in the 

433 # serialization dict. The location key tells us where on the request 

434 # to put the serialized value. 

435 KNOWN_LOCATIONS = ['uri', 'querystring', 'header', 'headers'] 

436 

437 def serialize_to_request(self, parameters, operation_model): 

438 serialized = self._create_default_request() 

439 serialized['method'] = operation_model.http.get( 

440 'method', self.DEFAULT_METHOD 

441 ) 

442 shape = operation_model.input_shape 

443 if shape is None: 

444 serialized['url_path'] = operation_model.http['requestUri'] 

445 return serialized 

446 shape_members = shape.members 

447 # While the ``serialized`` key holds the final serialized request 

448 # data, we need interim dicts for the various locations of the 

449 # request. We need this for the uri_path_kwargs and the 

450 # query_string_kwargs because they are templated, so we need 

451 # to gather all the needed data for the string template, 

452 # then we render the template. The body_kwargs is needed 

453 # because once we've collected them all, we run them through 

454 # _serialize_body_params, which for rest-json, creates JSON, 

455 # and for rest-xml, will create XML. This is what the 

456 # ``partitioned`` dict below is for. 

457 partitioned = { 

458 'uri_path_kwargs': self.MAP_TYPE(), 

459 'query_string_kwargs': self.MAP_TYPE(), 

460 'body_kwargs': self.MAP_TYPE(), 

461 'headers': self.MAP_TYPE(), 

462 } 

463 for param_name, param_value in parameters.items(): 

464 if param_value is None: 

465 # Don't serialize any parameter with a None value. 

466 continue 

467 self._partition_parameters( 

468 partitioned, param_name, param_value, shape_members 

469 ) 

470 serialized['url_path'] = self._render_uri_template( 

471 operation_model.http['requestUri'], partitioned['uri_path_kwargs'] 

472 ) 

473 

474 if 'authPath' in operation_model.http: 

475 serialized['auth_path'] = self._render_uri_template( 

476 operation_model.http['authPath'], 

477 partitioned['uri_path_kwargs'], 

478 ) 

479 # Note that we lean on the http implementation to handle the case 

480 # where the requestUri path already has query parameters. 

481 # The bundled http client, requests, already supports this. 

482 serialized['query_string'] = partitioned['query_string_kwargs'] 

483 if partitioned['headers']: 

484 serialized['headers'] = partitioned['headers'] 

485 self._serialize_payload( 

486 partitioned, parameters, serialized, shape, shape_members 

487 ) 

488 self._serialize_content_type(serialized, shape, shape_members) 

489 

490 host_prefix = self._expand_host_prefix(parameters, operation_model) 

491 if host_prefix is not None: 

492 serialized['host_prefix'] = host_prefix 

493 

494 return serialized 

495 

496 def _render_uri_template(self, uri_template, params): 

497 # We need to handle two cases:: 

498 # 

499 # /{Bucket}/foo 

500 # /{Key+}/bar 

501 # A label ending with '+' is greedy. There can only 

502 # be one greedy key. 

503 encoded_params = {} 

504 for template_param in re.findall(r'{(.*?)}', uri_template): 

505 if template_param.endswith('+'): 

506 encoded_params[template_param] = percent_encode( 

507 params[template_param[:-1]], safe='/~' 

508 ) 

509 else: 

510 encoded_params[template_param] = percent_encode( 

511 params[template_param] 

512 ) 

513 return uri_template.format(**encoded_params) 

514 

515 def _serialize_payload( 

516 self, partitioned, parameters, serialized, shape, shape_members 

517 ): 

518 # partitioned - The user input params partitioned by location. 

519 # parameters - The user input params. 

520 # serialized - The final serialized request dict. 

521 # shape - Describes the expected input shape 

522 # shape_members - The members of the input struct shape 

523 payload_member = shape.serialization.get('payload') 

524 if self._has_streaming_payload(payload_member, shape_members): 

525 # If it's streaming, then the body is just the 

526 # value of the payload. 

527 body_payload = parameters.get(payload_member, b'') 

528 body_payload = self._encode_payload(body_payload) 

529 serialized['body'] = body_payload 

530 elif payload_member is not None: 

531 # If there's a payload member, we serialized that 

532 # member to they body. 

533 body_params = parameters.get(payload_member) 

534 if body_params is not None: 

535 serialized['body'] = self._serialize_body_params( 

536 body_params, shape_members[payload_member] 

537 ) 

538 else: 

539 serialized['body'] = self._serialize_empty_body() 

540 elif partitioned['body_kwargs']: 

541 serialized['body'] = self._serialize_body_params( 

542 partitioned['body_kwargs'], shape 

543 ) 

544 elif self._requires_empty_body(shape): 

545 serialized['body'] = self._serialize_empty_body() 

546 

547 def _serialize_empty_body(self): 

548 return b'' 

549 

550 def _serialize_content_type(self, serialized, shape, shape_members): 

551 """ 

552 Some protocols require varied Content-Type headers 

553 depending on user input. This allows subclasses to apply 

554 this conditionally. 

555 """ 

556 pass 

557 

558 def _requires_empty_body(self, shape): 

559 """ 

560 Some protocols require a specific body to represent an empty 

561 payload. This allows subclasses to apply this conditionally. 

562 """ 

563 return False 

564 

565 def _has_streaming_payload(self, payload, shape_members): 

566 """Determine if payload is streaming (a blob or string).""" 

567 return payload is not None and shape_members[payload].type_name in ( 

568 'blob', 

569 'string', 

570 ) 

571 

572 def _encode_payload(self, body): 

573 if isinstance(body, str): 

574 return body.encode(self.DEFAULT_ENCODING) 

575 return body 

576 

577 def _partition_parameters( 

578 self, partitioned, param_name, param_value, shape_members 

579 ): 

580 # This takes the user provided input parameter (``param``) 

581 # and figures out where they go in the request dict. 

582 # Some params are HTTP headers, some are used in the URI, some 

583 # are in the request body. This method deals with this. 

584 member = shape_members[param_name] 

585 location = member.serialization.get('location') 

586 key_name = member.serialization.get('name', param_name) 

587 if location == 'uri': 

588 partitioned['uri_path_kwargs'][key_name] = param_value 

589 elif location == 'querystring': 

590 if isinstance(param_value, dict): 

591 partitioned['query_string_kwargs'].update(param_value) 

592 elif isinstance(param_value, bool): 

593 bool_str = str(param_value).lower() 

594 partitioned['query_string_kwargs'][key_name] = bool_str 

595 elif member.type_name == 'timestamp': 

596 timestamp_format = member.serialization.get( 

597 'timestampFormat', self.QUERY_STRING_TIMESTAMP_FORMAT 

598 ) 

599 timestamp = self._convert_timestamp_to_str( 

600 param_value, timestamp_format 

601 ) 

602 partitioned['query_string_kwargs'][key_name] = timestamp 

603 else: 

604 partitioned['query_string_kwargs'][key_name] = param_value 

605 elif location == 'header': 

606 shape = shape_members[param_name] 

607 if not param_value and shape.type_name == 'list': 

608 # Empty lists should not be set on the headers 

609 return 

610 value = self._convert_header_value(shape, param_value) 

611 partitioned['headers'][key_name] = str(value) 

612 elif location == 'headers': 

613 # 'headers' is a bit of an oddball. The ``key_name`` 

614 # is actually really a prefix for the header names: 

615 header_prefix = key_name 

616 # The value provided by the user is a dict so we'll be 

617 # creating multiple header key/val pairs. The key 

618 # name to use for each header is the header_prefix (``key_name``) 

619 # plus the key provided by the user. 

620 self._do_serialize_header_map( 

621 header_prefix, partitioned['headers'], param_value 

622 ) 

623 else: 

624 partitioned['body_kwargs'][param_name] = param_value 

625 

626 def _do_serialize_header_map(self, header_prefix, headers, user_input): 

627 for key, val in user_input.items(): 

628 full_key = header_prefix + key 

629 headers[full_key] = val 

630 

631 def _serialize_body_params(self, params, shape): 

632 raise NotImplementedError('_serialize_body_params') 

633 

634 def _convert_header_value(self, shape, value): 

635 if shape.type_name == 'timestamp': 

636 datetime_obj = parse_to_aware_datetime(value) 

637 timestamp = calendar.timegm(datetime_obj.utctimetuple()) 

638 timestamp_format = shape.serialization.get( 

639 'timestampFormat', self.HEADER_TIMESTAMP_FORMAT 

640 ) 

641 return self._convert_timestamp_to_str(timestamp, timestamp_format) 

642 elif shape.type_name == 'list': 

643 converted_value = [ 

644 self._convert_header_value(shape.member, v) 

645 for v in value 

646 if v is not None 

647 ] 

648 return ",".join(converted_value) 

649 elif is_json_value_header(shape): 

650 # Serialize with no spaces after separators to save space in 

651 # the header. 

652 return self._get_base64(json.dumps(value, separators=(',', ':'))) 

653 else: 

654 return value 

655 

656 

657class RestJSONSerializer(BaseRestSerializer, JSONSerializer): 

658 def _serialize_empty_body(self): 

659 return b'{}' 

660 

661 def _requires_empty_body(self, shape): 

662 """ 

663 Serialize an empty JSON object whenever the shape has 

664 members not targeting a location. 

665 """ 

666 for member, val in shape.members.items(): 

667 if 'location' not in val.serialization: 

668 return True 

669 return False 

670 

671 def _serialize_content_type(self, serialized, shape, shape_members): 

672 """Set Content-Type to application/json for all structured bodies.""" 

673 payload = shape.serialization.get('payload') 

674 if self._has_streaming_payload(payload, shape_members): 

675 # Don't apply content-type to streaming bodies 

676 return 

677 

678 has_body = serialized['body'] != b'' 

679 has_content_type = has_header('Content-Type', serialized['headers']) 

680 if has_body and not has_content_type: 

681 serialized['headers']['Content-Type'] = 'application/json' 

682 

683 def _serialize_body_params(self, params, shape): 

684 serialized_body = self.MAP_TYPE() 

685 self._serialize(serialized_body, params, shape) 

686 return json.dumps(serialized_body).encode(self.DEFAULT_ENCODING) 

687 

688 

689class RestXMLSerializer(BaseRestSerializer): 

690 TIMESTAMP_FORMAT = 'iso8601' 

691 

692 def _serialize_body_params(self, params, shape): 

693 root_name = shape.serialization['name'] 

694 pseudo_root = ElementTree.Element('') 

695 self._serialize(shape, params, pseudo_root, root_name) 

696 real_root = list(pseudo_root)[0] 

697 return ElementTree.tostring(real_root, encoding=self.DEFAULT_ENCODING) 

698 

699 def _serialize(self, shape, params, xmlnode, name): 

700 method = getattr( 

701 self, 

702 '_serialize_type_%s' % shape.type_name, 

703 self._default_serialize, 

704 ) 

705 method(xmlnode, params, shape, name) 

706 

707 def _serialize_type_structure(self, xmlnode, params, shape, name): 

708 structure_node = ElementTree.SubElement(xmlnode, name) 

709 

710 if 'xmlNamespace' in shape.serialization: 

711 namespace_metadata = shape.serialization['xmlNamespace'] 

712 attribute_name = 'xmlns' 

713 if namespace_metadata.get('prefix'): 

714 attribute_name += ':%s' % namespace_metadata['prefix'] 

715 structure_node.attrib[attribute_name] = namespace_metadata['uri'] 

716 for key, value in params.items(): 

717 member_shape = shape.members[key] 

718 member_name = member_shape.serialization.get('name', key) 

719 # We need to special case member shapes that are marked as an 

720 # xmlAttribute. Rather than serializing into an XML child node, 

721 # we instead serialize the shape to an XML attribute of the 

722 # *current* node. 

723 if value is None: 

724 # Don't serialize any param whose value is None. 

725 return 

726 if member_shape.serialization.get('xmlAttribute'): 

727 # xmlAttributes must have a serialization name. 

728 xml_attribute_name = member_shape.serialization['name'] 

729 structure_node.attrib[xml_attribute_name] = value 

730 continue 

731 self._serialize(member_shape, value, structure_node, member_name) 

732 

733 def _serialize_type_list(self, xmlnode, params, shape, name): 

734 member_shape = shape.member 

735 if shape.serialization.get('flattened'): 

736 element_name = name 

737 list_node = xmlnode 

738 else: 

739 element_name = member_shape.serialization.get('name', 'member') 

740 list_node = ElementTree.SubElement(xmlnode, name) 

741 for item in params: 

742 self._serialize(member_shape, item, list_node, element_name) 

743 

744 def _serialize_type_map(self, xmlnode, params, shape, name): 

745 # Given the ``name`` of MyMap, and input of {"key1": "val1"} 

746 # we serialize this as: 

747 # <MyMap> 

748 # <entry> 

749 # <key>key1</key> 

750 # <value>val1</value> 

751 # </entry> 

752 # </MyMap> 

753 node = ElementTree.SubElement(xmlnode, name) 

754 # TODO: handle flattened maps. 

755 for key, value in params.items(): 

756 entry_node = ElementTree.SubElement(node, 'entry') 

757 key_name = self._get_serialized_name(shape.key, default_name='key') 

758 val_name = self._get_serialized_name( 

759 shape.value, default_name='value' 

760 ) 

761 self._serialize(shape.key, key, entry_node, key_name) 

762 self._serialize(shape.value, value, entry_node, val_name) 

763 

764 def _serialize_type_boolean(self, xmlnode, params, shape, name): 

765 # For scalar types, the 'params' attr is actually just a scalar 

766 # value representing the data we need to serialize as a boolean. 

767 # It will either be 'true' or 'false' 

768 node = ElementTree.SubElement(xmlnode, name) 

769 if params: 

770 str_value = 'true' 

771 else: 

772 str_value = 'false' 

773 node.text = str_value 

774 

775 def _serialize_type_blob(self, xmlnode, params, shape, name): 

776 node = ElementTree.SubElement(xmlnode, name) 

777 node.text = self._get_base64(params) 

778 

779 def _serialize_type_timestamp(self, xmlnode, params, shape, name): 

780 node = ElementTree.SubElement(xmlnode, name) 

781 node.text = self._convert_timestamp_to_str( 

782 params, shape.serialization.get('timestampFormat') 

783 ) 

784 

785 def _default_serialize(self, xmlnode, params, shape, name): 

786 node = ElementTree.SubElement(xmlnode, name) 

787 node.text = str(params) 

788 

789 

790SERIALIZERS = { 

791 'ec2': EC2Serializer, 

792 'query': QuerySerializer, 

793 'json': JSONSerializer, 

794 'rest-json': RestJSONSerializer, 

795 'rest-xml': RestXMLSerializer, 

796}