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

395 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:51 +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.exceptions import ParamValidationError 

50from botocore.utils import ( 

51 has_header, 

52 is_json_value_header, 

53 parse_to_aware_datetime, 

54 percent_encode, 

55) 

56 

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

58DEFAULT_TIMESTAMP_FORMAT = 'iso8601' 

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

60# Same as ISO8601, but with microsecond precision. 

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

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

63 

64 

65def create_serializer(protocol_name, include_validation=True): 

66 # TODO: Unknown protocols. 

67 serializer = SERIALIZERS[protocol_name]() 

68 if include_validation: 

69 validator = validate.ParamValidator() 

70 serializer = validate.ParamValidationDecorator(validator, serializer) 

71 return serializer 

72 

73 

74class Serializer: 

75 DEFAULT_METHOD = 'POST' 

76 # Clients can change this to a different MutableMapping 

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

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

79 # tests. 

80 MAP_TYPE = dict 

81 DEFAULT_ENCODING = 'utf-8' 

82 

83 def serialize_to_request(self, parameters, operation_model): 

84 """Serialize parameters into an HTTP request. 

85 

86 This method takes user provided parameters and a shape 

87 model and serializes the parameters to an HTTP request. 

88 More specifically, this method returns information about 

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

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

91 a dictionary of: 

92 

93 * 'url_path' 

94 * 'host_prefix' 

95 * 'query_string' 

96 * 'headers' 

97 * 'body' 

98 * 'method' 

99 

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

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

102 return value:: 

103 

104 {'body': {'Action': 'OperationName', 

105 'Bar': 'val2', 

106 'Foo': 'val1', 

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

108 'headers': {}, 

109 'method': 'POST', 

110 'query_string': '', 

111 'host_prefix': 'value.', 

112 'url_path': '/'} 

113 

114 :param parameters: The dictionary input parameters for the 

115 operation (i.e the user input). 

116 :param operation_model: The OperationModel object that describes 

117 the operation. 

118 """ 

119 raise NotImplementedError("serialize_to_request") 

120 

121 def _create_default_request(self): 

122 # Creates a boilerplate default request dict that subclasses 

123 # can use as a starting point. 

124 serialized = { 

125 'url_path': '/', 

126 'query_string': '', 

127 'method': self.DEFAULT_METHOD, 

128 'headers': {}, 

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

130 'body': b'', 

131 } 

132 return serialized 

133 

134 # Some extra utility methods subclasses can use. 

135 

136 def _timestamp_iso8601(self, value): 

137 if value.microsecond > 0: 

138 timestamp_format = ISO8601_MICRO 

139 else: 

140 timestamp_format = ISO8601 

141 return value.strftime(timestamp_format) 

142 

143 def _timestamp_unixtimestamp(self, value): 

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

145 

146 def _timestamp_rfc822(self, value): 

147 if isinstance(value, datetime.datetime): 

148 value = self._timestamp_unixtimestamp(value) 

149 return formatdate(value, usegmt=True) 

150 

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

152 if timestamp_format is None: 

153 timestamp_format = self.TIMESTAMP_FORMAT 

154 timestamp_format = timestamp_format.lower() 

155 datetime_obj = parse_to_aware_datetime(value) 

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

157 final_value = converter(datetime_obj) 

158 return final_value 

159 

160 def _get_serialized_name(self, shape, default_name): 

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

162 # Otherwise it will return the passed in default_name. 

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

164 

165 def _get_base64(self, value): 

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

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

168 # via the default encoding. 

169 if isinstance(value, str): 

170 value = value.encode(self.DEFAULT_ENCODING) 

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

172 

173 def _expand_host_prefix(self, parameters, operation_model): 

174 operation_endpoint = operation_model.endpoint 

175 if ( 

176 operation_endpoint is None 

177 or 'hostPrefix' not in operation_endpoint 

178 ): 

179 return None 

180 

181 host_prefix_expression = operation_endpoint['hostPrefix'] 

182 input_members = operation_model.input_shape.members 

183 host_labels = [ 

184 member 

185 for member, shape in input_members.items() 

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

187 ] 

188 format_kwargs = {} 

189 bad_labels = [] 

190 for name in host_labels: 

191 param = parameters[name] 

192 if not HOST_PREFIX_RE.match(param): 

193 bad_labels.append(name) 

194 format_kwargs[name] = param 

195 if bad_labels: 

196 raise ParamValidationError( 

197 report=( 

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

199 "Must contain only alphanumeric characters, hyphen, " 

200 "or period." 

201 ) 

202 ) 

203 return host_prefix_expression.format(**format_kwargs) 

204 

205 

206class QuerySerializer(Serializer): 

207 TIMESTAMP_FORMAT = 'iso8601' 

208 

209 def serialize_to_request(self, parameters, operation_model): 

210 shape = operation_model.input_shape 

211 serialized = self._create_default_request() 

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

213 'method', self.DEFAULT_METHOD 

214 ) 

215 serialized['headers'] = { 

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

217 } 

218 # The query serializer only deals with body params so 

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

220 body_params = self.MAP_TYPE() 

221 body_params['Action'] = operation_model.name 

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

223 if shape is not None: 

224 self._serialize(body_params, parameters, shape) 

225 serialized['body'] = body_params 

226 

227 host_prefix = self._expand_host_prefix(parameters, operation_model) 

228 if host_prefix is not None: 

229 serialized['host_prefix'] = host_prefix 

230 

231 return serialized 

232 

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

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

235 # final serialized parameters. 

236 # value: The current user input value. 

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

238 # input. 

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

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

241 method = getattr( 

242 self, 

243 f'_serialize_type_{shape.type_name}', 

244 self._default_serialize, 

245 ) 

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

247 

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

249 members = shape.members 

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

251 member_shape = members[key] 

252 member_prefix = self._get_serialized_name(member_shape, key) 

253 if prefix: 

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

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

256 

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

258 if not value: 

259 # The query protocol serializes empty lists. 

260 serialized[prefix] = '' 

261 return 

262 if self._is_shape_flattened(shape): 

263 list_prefix = prefix 

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

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

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

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

268 else: 

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

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

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

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

273 element_shape = shape.member 

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

275 

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

277 if self._is_shape_flattened(shape): 

278 full_prefix = prefix 

279 else: 

280 full_prefix = '%s.entry' % prefix 

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

282 key_shape = shape.key 

283 value_shape = shape.value 

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

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

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

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

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

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

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

291 

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

293 # Blob args must be base64 encoded. 

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

295 

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

297 serialized[prefix] = self._convert_timestamp_to_str( 

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

299 ) 

300 

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

302 if value: 

303 serialized[prefix] = 'true' 

304 else: 

305 serialized[prefix] = 'false' 

306 

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

308 serialized[prefix] = value 

309 

310 def _is_shape_flattened(self, shape): 

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

312 

313 

314class EC2Serializer(QuerySerializer): 

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

316 

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

318 serializer. This class encapsulates those differences. The model 

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

320 to worry about wiring this class up correctly. 

321 

322 """ 

323 

324 def _get_serialized_name(self, shape, default_name): 

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

326 # Otherwise it will return the passed in default_name. 

327 if 'queryName' in shape.serialization: 

328 return shape.serialization['queryName'] 

329 elif 'name' in shape.serialization: 

330 # A locationName is always capitalized 

331 # on input for the ec2 protocol. 

332 name = shape.serialization['name'] 

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

334 else: 

335 return default_name 

336 

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

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

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

340 element_shape = shape.member 

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

342 

343 

344class JSONSerializer(Serializer): 

345 TIMESTAMP_FORMAT = 'unixtimestamp' 

346 

347 def serialize_to_request(self, parameters, operation_model): 

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

349 operation_model.metadata['targetPrefix'], 

350 operation_model.name, 

351 ) 

352 json_version = operation_model.metadata['jsonVersion'] 

353 serialized = self._create_default_request() 

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

355 'method', self.DEFAULT_METHOD 

356 ) 

357 serialized['headers'] = { 

358 'X-Amz-Target': target, 

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

360 } 

361 body = self.MAP_TYPE() 

362 input_shape = operation_model.input_shape 

363 if input_shape is not None: 

364 self._serialize(body, parameters, input_shape) 

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

366 

367 host_prefix = self._expand_host_prefix(parameters, operation_model) 

368 if host_prefix is not None: 

369 serialized['host_prefix'] = host_prefix 

370 

371 return serialized 

372 

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

374 method = getattr( 

375 self, 

376 '_serialize_type_%s' % shape.type_name, 

377 self._default_serialize, 

378 ) 

379 method(serialized, value, shape, key) 

380 

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

382 if shape.is_document_type: 

383 serialized[key] = value 

384 else: 

385 if key is not None: 

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

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

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

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

390 # dictionary we just created. 

391 new_serialized = self.MAP_TYPE() 

392 serialized[key] = new_serialized 

393 serialized = new_serialized 

394 members = shape.members 

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

396 member_shape = members[member_key] 

397 if 'name' in member_shape.serialization: 

398 member_key = member_shape.serialization['name'] 

399 self._serialize( 

400 serialized, member_value, member_shape, member_key 

401 ) 

402 

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

404 map_obj = self.MAP_TYPE() 

405 serialized[key] = map_obj 

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

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

408 

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

410 list_obj = [] 

411 serialized[key] = list_obj 

412 for list_item in value: 

413 wrapper = {} 

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

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

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

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

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

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

420 

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

422 serialized[key] = value 

423 

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

425 serialized[key] = self._convert_timestamp_to_str( 

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

427 ) 

428 

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

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

431 

432 

433class BaseRestSerializer(Serializer): 

434 """Base class for rest protocols. 

435 

436 The only variance between the various rest protocols is the 

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

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

439 

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

441 

442 """ 

443 

444 QUERY_STRING_TIMESTAMP_FORMAT = 'iso8601' 

445 HEADER_TIMESTAMP_FORMAT = 'rfc822' 

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

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

448 # to put the serialized value. 

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

450 

451 def serialize_to_request(self, parameters, operation_model): 

452 serialized = self._create_default_request() 

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

454 'method', self.DEFAULT_METHOD 

455 ) 

456 shape = operation_model.input_shape 

457 if shape is None: 

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

459 return serialized 

460 shape_members = shape.members 

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

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

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

464 # query_string_kwargs because they are templated, so we need 

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

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

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

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

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

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

471 partitioned = { 

472 'uri_path_kwargs': self.MAP_TYPE(), 

473 'query_string_kwargs': self.MAP_TYPE(), 

474 'body_kwargs': self.MAP_TYPE(), 

475 'headers': self.MAP_TYPE(), 

476 } 

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

478 if param_value is None: 

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

480 continue 

481 self._partition_parameters( 

482 partitioned, param_name, param_value, shape_members 

483 ) 

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

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

486 ) 

487 

488 if 'authPath' in operation_model.http: 

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

490 operation_model.http['authPath'], 

491 partitioned['uri_path_kwargs'], 

492 ) 

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

494 # where the requestUri path already has query parameters. 

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

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

497 if partitioned['headers']: 

498 serialized['headers'] = partitioned['headers'] 

499 self._serialize_payload( 

500 partitioned, parameters, serialized, shape, shape_members 

501 ) 

502 self._serialize_content_type(serialized, shape, shape_members) 

503 

504 host_prefix = self._expand_host_prefix(parameters, operation_model) 

505 if host_prefix is not None: 

506 serialized['host_prefix'] = host_prefix 

507 

508 return serialized 

509 

510 def _render_uri_template(self, uri_template, params): 

511 # We need to handle two cases:: 

512 # 

513 # /{Bucket}/foo 

514 # /{Key+}/bar 

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

516 # be one greedy key. 

517 encoded_params = {} 

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

519 if template_param.endswith('+'): 

520 encoded_params[template_param] = percent_encode( 

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

522 ) 

523 else: 

524 encoded_params[template_param] = percent_encode( 

525 params[template_param] 

526 ) 

527 return uri_template.format(**encoded_params) 

528 

529 def _serialize_payload( 

530 self, partitioned, parameters, serialized, shape, shape_members 

531 ): 

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

533 # parameters - The user input params. 

534 # serialized - The final serialized request dict. 

535 # shape - Describes the expected input shape 

536 # shape_members - The members of the input struct shape 

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

538 if self._has_streaming_payload(payload_member, shape_members): 

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

540 # value of the payload. 

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

542 body_payload = self._encode_payload(body_payload) 

543 serialized['body'] = body_payload 

544 elif payload_member is not None: 

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

546 # member to they body. 

547 body_params = parameters.get(payload_member) 

548 if body_params is not None: 

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

550 body_params, shape_members[payload_member] 

551 ) 

552 else: 

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

554 elif partitioned['body_kwargs']: 

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

556 partitioned['body_kwargs'], shape 

557 ) 

558 elif self._requires_empty_body(shape): 

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

560 

561 def _serialize_empty_body(self): 

562 return b'' 

563 

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

565 """ 

566 Some protocols require varied Content-Type headers 

567 depending on user input. This allows subclasses to apply 

568 this conditionally. 

569 """ 

570 pass 

571 

572 def _requires_empty_body(self, shape): 

573 """ 

574 Some protocols require a specific body to represent an empty 

575 payload. This allows subclasses to apply this conditionally. 

576 """ 

577 return False 

578 

579 def _has_streaming_payload(self, payload, shape_members): 

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

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

582 'blob', 

583 'string', 

584 ) 

585 

586 def _encode_payload(self, body): 

587 if isinstance(body, str): 

588 return body.encode(self.DEFAULT_ENCODING) 

589 return body 

590 

591 def _partition_parameters( 

592 self, partitioned, param_name, param_value, shape_members 

593 ): 

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

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

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

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

598 member = shape_members[param_name] 

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

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

601 if location == 'uri': 

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

603 elif location == 'querystring': 

604 if isinstance(param_value, dict): 

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

606 elif isinstance(param_value, bool): 

607 bool_str = str(param_value).lower() 

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

609 elif member.type_name == 'timestamp': 

610 timestamp_format = member.serialization.get( 

611 'timestampFormat', self.QUERY_STRING_TIMESTAMP_FORMAT 

612 ) 

613 timestamp = self._convert_timestamp_to_str( 

614 param_value, timestamp_format 

615 ) 

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

617 else: 

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

619 elif location == 'header': 

620 shape = shape_members[param_name] 

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

622 # Empty lists should not be set on the headers 

623 return 

624 value = self._convert_header_value(shape, param_value) 

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

626 elif location == 'headers': 

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

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

629 header_prefix = key_name 

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

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

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

633 # plus the key provided by the user. 

634 self._do_serialize_header_map( 

635 header_prefix, partitioned['headers'], param_value 

636 ) 

637 else: 

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

639 

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

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

642 full_key = header_prefix + key 

643 headers[full_key] = val 

644 

645 def _serialize_body_params(self, params, shape): 

646 raise NotImplementedError('_serialize_body_params') 

647 

648 def _convert_header_value(self, shape, value): 

649 if shape.type_name == 'timestamp': 

650 datetime_obj = parse_to_aware_datetime(value) 

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

652 timestamp_format = shape.serialization.get( 

653 'timestampFormat', self.HEADER_TIMESTAMP_FORMAT 

654 ) 

655 return self._convert_timestamp_to_str(timestamp, timestamp_format) 

656 elif shape.type_name == 'list': 

657 converted_value = [ 

658 self._convert_header_value(shape.member, v) 

659 for v in value 

660 if v is not None 

661 ] 

662 return ",".join(converted_value) 

663 elif is_json_value_header(shape): 

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

665 # the header. 

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

667 else: 

668 return value 

669 

670 

671class RestJSONSerializer(BaseRestSerializer, JSONSerializer): 

672 def _serialize_empty_body(self): 

673 return b'{}' 

674 

675 def _requires_empty_body(self, shape): 

676 """ 

677 Serialize an empty JSON object whenever the shape has 

678 members not targeting a location. 

679 """ 

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

681 if 'location' not in val.serialization: 

682 return True 

683 return False 

684 

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

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

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

688 if self._has_streaming_payload(payload, shape_members): 

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

690 return 

691 

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

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

694 if has_body and not has_content_type: 

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

696 

697 def _serialize_body_params(self, params, shape): 

698 serialized_body = self.MAP_TYPE() 

699 self._serialize(serialized_body, params, shape) 

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

701 

702 

703class RestXMLSerializer(BaseRestSerializer): 

704 TIMESTAMP_FORMAT = 'iso8601' 

705 

706 def _serialize_body_params(self, params, shape): 

707 root_name = shape.serialization['name'] 

708 pseudo_root = ElementTree.Element('') 

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

710 real_root = list(pseudo_root)[0] 

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

712 

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

714 method = getattr( 

715 self, 

716 '_serialize_type_%s' % shape.type_name, 

717 self._default_serialize, 

718 ) 

719 method(xmlnode, params, shape, name) 

720 

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

722 structure_node = ElementTree.SubElement(xmlnode, name) 

723 

724 if 'xmlNamespace' in shape.serialization: 

725 namespace_metadata = shape.serialization['xmlNamespace'] 

726 attribute_name = 'xmlns' 

727 if namespace_metadata.get('prefix'): 

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

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

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

731 member_shape = shape.members[key] 

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

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

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

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

736 # *current* node. 

737 if value is None: 

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

739 return 

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

741 # xmlAttributes must have a serialization name. 

742 xml_attribute_name = member_shape.serialization['name'] 

743 structure_node.attrib[xml_attribute_name] = value 

744 continue 

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

746 

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

748 member_shape = shape.member 

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

750 element_name = name 

751 list_node = xmlnode 

752 else: 

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

754 list_node = ElementTree.SubElement(xmlnode, name) 

755 for item in params: 

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

757 

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

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

760 # we serialize this as: 

761 # <MyMap> 

762 # <entry> 

763 # <key>key1</key> 

764 # <value>val1</value> 

765 # </entry> 

766 # </MyMap> 

767 node = ElementTree.SubElement(xmlnode, name) 

768 # TODO: handle flattened maps. 

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

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

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

772 val_name = self._get_serialized_name( 

773 shape.value, default_name='value' 

774 ) 

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

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

777 

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

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

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

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

782 node = ElementTree.SubElement(xmlnode, name) 

783 if params: 

784 str_value = 'true' 

785 else: 

786 str_value = 'false' 

787 node.text = str_value 

788 

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

790 node = ElementTree.SubElement(xmlnode, name) 

791 node.text = self._get_base64(params) 

792 

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

794 node = ElementTree.SubElement(xmlnode, name) 

795 node.text = self._convert_timestamp_to_str( 

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

797 ) 

798 

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

800 node = ElementTree.SubElement(xmlnode, name) 

801 node.text = str(params) 

802 

803 

804SERIALIZERS = { 

805 'ec2': EC2Serializer, 

806 'query': QuerySerializer, 

807 'json': JSONSerializer, 

808 'rest-json': RestJSONSerializer, 

809 'rest-xml': RestXMLSerializer, 

810}