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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

395 statements  

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""" 

40 

41import base64 

42import calendar 

43import datetime 

44import json 

45import re 

46from xml.etree import ElementTree 

47 

48from botocore import validate 

49from botocore.compat import formatdate 

50from botocore.exceptions import ParamValidationError 

51from botocore.utils import ( 

52 has_header, 

53 is_json_value_header, 

54 parse_to_aware_datetime, 

55 percent_encode, 

56) 

57 

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

59DEFAULT_TIMESTAMP_FORMAT = 'iso8601' 

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

61# Same as ISO8601, but with microsecond precision. 

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

63HOST_PREFIX_RE = re.compile(r"^[A-Za-z0-9\.\-]+$") 

64 

65 

66def create_serializer(protocol_name, include_validation=True): 

67 # TODO: Unknown protocols. 

68 serializer = SERIALIZERS[protocol_name]() 

69 if include_validation: 

70 validator = validate.ParamValidator() 

71 serializer = validate.ParamValidationDecorator(validator, serializer) 

72 return serializer 

73 

74 

75class Serializer: 

76 DEFAULT_METHOD = 'POST' 

77 # Clients can change this to a different MutableMapping 

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

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

80 # tests. 

81 MAP_TYPE = dict 

82 DEFAULT_ENCODING = 'utf-8' 

83 

84 def serialize_to_request(self, parameters, operation_model): 

85 """Serialize parameters into an HTTP request. 

86 

87 This method takes user provided parameters and a shape 

88 model and serializes the parameters to an HTTP request. 

89 More specifically, this method returns information about 

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

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

92 a dictionary of: 

93 

94 * 'url_path' 

95 * 'host_prefix' 

96 * 'query_string' 

97 * 'headers' 

98 * 'body' 

99 * 'method' 

100 

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

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

103 return value:: 

104 

105 {'body': {'Action': 'OperationName', 

106 'Bar': 'val2', 

107 'Foo': 'val1', 

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

109 'headers': {}, 

110 'method': 'POST', 

111 'query_string': '', 

112 'host_prefix': 'value.', 

113 'url_path': '/'} 

114 

115 :param parameters: The dictionary input parameters for the 

116 operation (i.e the user input). 

117 :param operation_model: The OperationModel object that describes 

118 the operation. 

119 """ 

120 raise NotImplementedError("serialize_to_request") 

121 

122 def _create_default_request(self): 

123 # Creates a boilerplate default request dict that subclasses 

124 # can use as a starting point. 

125 serialized = { 

126 'url_path': '/', 

127 'query_string': '', 

128 'method': self.DEFAULT_METHOD, 

129 'headers': {}, 

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

131 'body': b'', 

132 } 

133 return serialized 

134 

135 # Some extra utility methods subclasses can use. 

136 

137 def _timestamp_iso8601(self, value): 

138 if value.microsecond > 0: 

139 timestamp_format = ISO8601_MICRO 

140 else: 

141 timestamp_format = ISO8601 

142 return value.strftime(timestamp_format) 

143 

144 def _timestamp_unixtimestamp(self, value): 

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

146 

147 def _timestamp_rfc822(self, value): 

148 if isinstance(value, datetime.datetime): 

149 value = self._timestamp_unixtimestamp(value) 

150 return formatdate(value, usegmt=True) 

151 

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

153 if timestamp_format is None: 

154 timestamp_format = self.TIMESTAMP_FORMAT 

155 timestamp_format = timestamp_format.lower() 

156 datetime_obj = parse_to_aware_datetime(value) 

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

158 final_value = converter(datetime_obj) 

159 return final_value 

160 

161 def _get_serialized_name(self, shape, default_name): 

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

163 # Otherwise it will return the passed in default_name. 

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

165 

166 def _get_base64(self, value): 

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

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

169 # via the default encoding. 

170 if isinstance(value, str): 

171 value = value.encode(self.DEFAULT_ENCODING) 

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

173 

174 def _expand_host_prefix(self, parameters, operation_model): 

175 operation_endpoint = operation_model.endpoint 

176 if ( 

177 operation_endpoint is None 

178 or 'hostPrefix' not in operation_endpoint 

179 ): 

180 return None 

181 

182 host_prefix_expression = operation_endpoint['hostPrefix'] 

183 input_members = operation_model.input_shape.members 

184 host_labels = [ 

185 member 

186 for member, shape in input_members.items() 

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

188 ] 

189 format_kwargs = {} 

190 bad_labels = [] 

191 for name in host_labels: 

192 param = parameters[name] 

193 if not HOST_PREFIX_RE.match(param): 

194 bad_labels.append(name) 

195 format_kwargs[name] = param 

196 if bad_labels: 

197 raise ParamValidationError( 

198 report=( 

199 f"Invalid value for parameter(s): {', '.join(bad_labels)}. " 

200 "Must contain only alphanumeric characters, hyphen, " 

201 "or period." 

202 ) 

203 ) 

204 return host_prefix_expression.format(**format_kwargs) 

205 

206 

207class QuerySerializer(Serializer): 

208 TIMESTAMP_FORMAT = 'iso8601' 

209 

210 def serialize_to_request(self, parameters, operation_model): 

211 shape = operation_model.input_shape 

212 serialized = self._create_default_request() 

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

214 'method', self.DEFAULT_METHOD 

215 ) 

216 serialized['headers'] = { 

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

218 } 

219 # The query serializer only deals with body params so 

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

221 body_params = self.MAP_TYPE() 

222 body_params['Action'] = operation_model.name 

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

224 if shape is not None: 

225 self._serialize(body_params, parameters, shape) 

226 serialized['body'] = body_params 

227 

228 host_prefix = self._expand_host_prefix(parameters, operation_model) 

229 if host_prefix is not None: 

230 serialized['host_prefix'] = host_prefix 

231 

232 return serialized 

233 

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

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

236 # final serialized parameters. 

237 # value: The current user input value. 

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

239 # input. 

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

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

242 method = getattr( 

243 self, 

244 f'_serialize_type_{shape.type_name}', 

245 self._default_serialize, 

246 ) 

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

248 

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

250 members = shape.members 

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

252 member_shape = members[key] 

253 member_prefix = self._get_serialized_name(member_shape, key) 

254 if prefix: 

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

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

257 

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

259 if not value: 

260 # The query protocol serializes empty lists. 

261 serialized[prefix] = '' 

262 return 

263 if self._is_shape_flattened(shape): 

264 list_prefix = prefix 

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

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

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

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

269 else: 

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

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

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

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

274 element_shape = shape.member 

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

276 

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

278 if self._is_shape_flattened(shape): 

279 full_prefix = prefix 

280 else: 

281 full_prefix = f'{prefix}.entry' 

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

283 key_shape = shape.key 

284 value_shape = shape.value 

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

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

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

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

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

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

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

292 

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

294 # Blob args must be base64 encoded. 

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

296 

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

298 serialized[prefix] = self._convert_timestamp_to_str( 

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

300 ) 

301 

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

303 if value: 

304 serialized[prefix] = 'true' 

305 else: 

306 serialized[prefix] = 'false' 

307 

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

309 serialized[prefix] = value 

310 

311 def _is_shape_flattened(self, shape): 

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

313 

314 

315class EC2Serializer(QuerySerializer): 

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

317 

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

319 serializer. This class encapsulates those differences. The model 

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

321 to worry about wiring this class up correctly. 

322 

323 """ 

324 

325 def _get_serialized_name(self, shape, default_name): 

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

327 # Otherwise it will return the passed in default_name. 

328 if 'queryName' in shape.serialization: 

329 return shape.serialization['queryName'] 

330 elif 'name' in shape.serialization: 

331 # A locationName is always capitalized 

332 # on input for the ec2 protocol. 

333 name = shape.serialization['name'] 

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

335 else: 

336 return default_name 

337 

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

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

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

341 element_shape = shape.member 

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

343 

344 

345class JSONSerializer(Serializer): 

346 TIMESTAMP_FORMAT = 'unixtimestamp' 

347 

348 def serialize_to_request(self, parameters, operation_model): 

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

350 operation_model.metadata['targetPrefix'], 

351 operation_model.name, 

352 ) 

353 json_version = operation_model.metadata['jsonVersion'] 

354 serialized = self._create_default_request() 

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

356 'method', self.DEFAULT_METHOD 

357 ) 

358 serialized['headers'] = { 

359 'X-Amz-Target': target, 

360 'Content-Type': f'application/x-amz-json-{json_version}', 

361 } 

362 body = self.MAP_TYPE() 

363 input_shape = operation_model.input_shape 

364 if input_shape is not None: 

365 self._serialize(body, parameters, input_shape) 

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

367 

368 host_prefix = self._expand_host_prefix(parameters, operation_model) 

369 if host_prefix is not None: 

370 serialized['host_prefix'] = host_prefix 

371 

372 return serialized 

373 

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

375 method = getattr( 

376 self, 

377 f'_serialize_type_{shape.type_name}', 

378 self._default_serialize, 

379 ) 

380 method(serialized, value, shape, key) 

381 

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

383 if shape.is_document_type: 

384 serialized[key] = value 

385 else: 

386 if key is not None: 

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

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

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

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

391 # dictionary we just created. 

392 new_serialized = self.MAP_TYPE() 

393 serialized[key] = new_serialized 

394 serialized = new_serialized 

395 members = shape.members 

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

397 member_shape = members[member_key] 

398 if 'name' in member_shape.serialization: 

399 member_key = member_shape.serialization['name'] 

400 self._serialize( 

401 serialized, member_value, member_shape, member_key 

402 ) 

403 

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

405 map_obj = self.MAP_TYPE() 

406 serialized[key] = map_obj 

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

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

409 

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

411 list_obj = [] 

412 serialized[key] = list_obj 

413 for list_item in value: 

414 wrapper = {} 

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

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

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

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

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

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

421 

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

423 serialized[key] = value 

424 

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

426 serialized[key] = self._convert_timestamp_to_str( 

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

428 ) 

429 

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

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

432 

433 

434class BaseRestSerializer(Serializer): 

435 """Base class for rest protocols. 

436 

437 The only variance between the various rest protocols is the 

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

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

440 

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

442 

443 """ 

444 

445 QUERY_STRING_TIMESTAMP_FORMAT = 'iso8601' 

446 HEADER_TIMESTAMP_FORMAT = 'rfc822' 

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

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

449 # to put the serialized value. 

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

451 

452 def serialize_to_request(self, parameters, operation_model): 

453 serialized = self._create_default_request() 

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

455 'method', self.DEFAULT_METHOD 

456 ) 

457 shape = operation_model.input_shape 

458 if shape is None: 

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

460 return serialized 

461 shape_members = shape.members 

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

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

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

465 # query_string_kwargs because they are templated, so we need 

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

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

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

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

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

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

472 partitioned = { 

473 'uri_path_kwargs': self.MAP_TYPE(), 

474 'query_string_kwargs': self.MAP_TYPE(), 

475 'body_kwargs': self.MAP_TYPE(), 

476 'headers': self.MAP_TYPE(), 

477 } 

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

479 if param_value is None: 

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

481 continue 

482 self._partition_parameters( 

483 partitioned, param_name, param_value, shape_members 

484 ) 

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

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

487 ) 

488 

489 if 'authPath' in operation_model.http: 

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

491 operation_model.http['authPath'], 

492 partitioned['uri_path_kwargs'], 

493 ) 

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

495 # where the requestUri path already has query parameters. 

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

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

498 if partitioned['headers']: 

499 serialized['headers'] = partitioned['headers'] 

500 self._serialize_payload( 

501 partitioned, parameters, serialized, shape, shape_members 

502 ) 

503 self._serialize_content_type(serialized, shape, shape_members) 

504 

505 host_prefix = self._expand_host_prefix(parameters, operation_model) 

506 if host_prefix is not None: 

507 serialized['host_prefix'] = host_prefix 

508 

509 return serialized 

510 

511 def _render_uri_template(self, uri_template, params): 

512 # We need to handle two cases:: 

513 # 

514 # /{Bucket}/foo 

515 # /{Key+}/bar 

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

517 # be one greedy key. 

518 encoded_params = {} 

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

520 if template_param.endswith('+'): 

521 encoded_params[template_param] = percent_encode( 

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

523 ) 

524 else: 

525 encoded_params[template_param] = percent_encode( 

526 params[template_param] 

527 ) 

528 return uri_template.format(**encoded_params) 

529 

530 def _serialize_payload( 

531 self, partitioned, parameters, serialized, shape, shape_members 

532 ): 

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

534 # parameters - The user input params. 

535 # serialized - The final serialized request dict. 

536 # shape - Describes the expected input shape 

537 # shape_members - The members of the input struct shape 

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

539 if self._has_streaming_payload(payload_member, shape_members): 

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

541 # value of the payload. 

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

543 body_payload = self._encode_payload(body_payload) 

544 serialized['body'] = body_payload 

545 elif payload_member is not None: 

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

547 # member to they body. 

548 body_params = parameters.get(payload_member) 

549 if body_params is not None: 

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

551 body_params, shape_members[payload_member] 

552 ) 

553 else: 

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

555 elif partitioned['body_kwargs']: 

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

557 partitioned['body_kwargs'], shape 

558 ) 

559 elif self._requires_empty_body(shape): 

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

561 

562 def _serialize_empty_body(self): 

563 return b'' 

564 

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

566 """ 

567 Some protocols require varied Content-Type headers 

568 depending on user input. This allows subclasses to apply 

569 this conditionally. 

570 """ 

571 pass 

572 

573 def _requires_empty_body(self, shape): 

574 """ 

575 Some protocols require a specific body to represent an empty 

576 payload. This allows subclasses to apply this conditionally. 

577 """ 

578 return False 

579 

580 def _has_streaming_payload(self, payload, shape_members): 

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

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

583 'blob', 

584 'string', 

585 ) 

586 

587 def _encode_payload(self, body): 

588 if isinstance(body, str): 

589 return body.encode(self.DEFAULT_ENCODING) 

590 return body 

591 

592 def _partition_parameters( 

593 self, partitioned, param_name, param_value, shape_members 

594 ): 

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

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

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

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

599 member = shape_members[param_name] 

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

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

602 if location == 'uri': 

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

604 elif location == 'querystring': 

605 if isinstance(param_value, dict): 

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

607 elif isinstance(param_value, bool): 

608 bool_str = str(param_value).lower() 

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

610 elif member.type_name == 'timestamp': 

611 timestamp_format = member.serialization.get( 

612 'timestampFormat', self.QUERY_STRING_TIMESTAMP_FORMAT 

613 ) 

614 timestamp = self._convert_timestamp_to_str( 

615 param_value, timestamp_format 

616 ) 

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

618 else: 

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

620 elif location == 'header': 

621 shape = shape_members[param_name] 

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

623 # Empty lists should not be set on the headers 

624 return 

625 value = self._convert_header_value(shape, param_value) 

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

627 elif location == 'headers': 

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

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

630 header_prefix = key_name 

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

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

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

634 # plus the key provided by the user. 

635 self._do_serialize_header_map( 

636 header_prefix, partitioned['headers'], param_value 

637 ) 

638 else: 

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

640 

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

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

643 full_key = header_prefix + key 

644 headers[full_key] = val 

645 

646 def _serialize_body_params(self, params, shape): 

647 raise NotImplementedError('_serialize_body_params') 

648 

649 def _convert_header_value(self, shape, value): 

650 if shape.type_name == 'timestamp': 

651 datetime_obj = parse_to_aware_datetime(value) 

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

653 timestamp_format = shape.serialization.get( 

654 'timestampFormat', self.HEADER_TIMESTAMP_FORMAT 

655 ) 

656 return self._convert_timestamp_to_str(timestamp, timestamp_format) 

657 elif shape.type_name == 'list': 

658 converted_value = [ 

659 self._convert_header_value(shape.member, v) 

660 for v in value 

661 if v is not None 

662 ] 

663 return ",".join(converted_value) 

664 elif is_json_value_header(shape): 

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

666 # the header. 

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

668 else: 

669 return value 

670 

671 

672class RestJSONSerializer(BaseRestSerializer, JSONSerializer): 

673 def _serialize_empty_body(self): 

674 return b'{}' 

675 

676 def _requires_empty_body(self, shape): 

677 """ 

678 Serialize an empty JSON object whenever the shape has 

679 members not targeting a location. 

680 """ 

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

682 if 'location' not in val.serialization: 

683 return True 

684 return False 

685 

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

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

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

689 if self._has_streaming_payload(payload, shape_members): 

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

691 return 

692 

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

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

695 if has_body and not has_content_type: 

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

697 

698 def _serialize_body_params(self, params, shape): 

699 serialized_body = self.MAP_TYPE() 

700 self._serialize(serialized_body, params, shape) 

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

702 

703 

704class RestXMLSerializer(BaseRestSerializer): 

705 TIMESTAMP_FORMAT = 'iso8601' 

706 

707 def _serialize_body_params(self, params, shape): 

708 root_name = shape.serialization['name'] 

709 pseudo_root = ElementTree.Element('') 

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

711 real_root = list(pseudo_root)[0] 

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

713 

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

715 method = getattr( 

716 self, 

717 f'_serialize_type_{shape.type_name}', 

718 self._default_serialize, 

719 ) 

720 method(xmlnode, params, shape, name) 

721 

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

723 structure_node = ElementTree.SubElement(xmlnode, name) 

724 

725 if 'xmlNamespace' in shape.serialization: 

726 namespace_metadata = shape.serialization['xmlNamespace'] 

727 attribute_name = 'xmlns' 

728 if namespace_metadata.get('prefix'): 

729 attribute_name += f":{namespace_metadata['prefix']}" 

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

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

732 member_shape = shape.members[key] 

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

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

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

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

737 # *current* node. 

738 if value is None: 

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

740 return 

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

742 # xmlAttributes must have a serialization name. 

743 xml_attribute_name = member_shape.serialization['name'] 

744 structure_node.attrib[xml_attribute_name] = value 

745 continue 

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

747 

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

749 member_shape = shape.member 

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

751 element_name = name 

752 list_node = xmlnode 

753 else: 

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

755 list_node = ElementTree.SubElement(xmlnode, name) 

756 for item in params: 

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

758 

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

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

761 # we serialize this as: 

762 # <MyMap> 

763 # <entry> 

764 # <key>key1</key> 

765 # <value>val1</value> 

766 # </entry> 

767 # </MyMap> 

768 node = ElementTree.SubElement(xmlnode, name) 

769 # TODO: handle flattened maps. 

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

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

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

773 val_name = self._get_serialized_name( 

774 shape.value, default_name='value' 

775 ) 

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

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

778 

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

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

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

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

783 node = ElementTree.SubElement(xmlnode, name) 

784 if params: 

785 str_value = 'true' 

786 else: 

787 str_value = 'false' 

788 node.text = str_value 

789 

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

791 node = ElementTree.SubElement(xmlnode, name) 

792 node.text = self._get_base64(params) 

793 

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

795 node = ElementTree.SubElement(xmlnode, name) 

796 node.text = self._convert_timestamp_to_str( 

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

798 ) 

799 

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

801 node = ElementTree.SubElement(xmlnode, name) 

802 node.text = str(params) 

803 

804 

805SERIALIZERS = { 

806 'ec2': EC2Serializer, 

807 'query': QuerySerializer, 

808 'json': JSONSerializer, 

809 'rest-json': RestJSONSerializer, 

810 'rest-xml': RestXMLSerializer, 

811}