Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/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

641 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 decimal 

45import json 

46import math 

47import re 

48import struct 

49from xml.etree import ElementTree 

50 

51from botocore import validate 

52from botocore.compat import formatdate 

53from botocore.exceptions import ParamValidationError 

54from botocore.useragent import register_feature_id 

55from botocore.utils import ( 

56 has_header, 

57 is_json_value_header, 

58 parse_to_aware_datetime, 

59 percent_encode, 

60) 

61 

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

63DEFAULT_TIMESTAMP_FORMAT = 'iso8601' 

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

65# Same as ISO8601, but with microsecond precision. 

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

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

68 

69TIMESTAMP_PRECISION_DEFAULT = 'default' 

70TIMESTAMP_PRECISION_MILLISECOND = 'millisecond' 

71TIMESTAMP_PRECISION_OPTIONS = ( 

72 TIMESTAMP_PRECISION_DEFAULT, 

73 TIMESTAMP_PRECISION_MILLISECOND, 

74) 

75 

76 

77def create_serializer( 

78 protocol_name, 

79 include_validation=True, 

80 timestamp_precision=TIMESTAMP_PRECISION_DEFAULT, 

81): 

82 """Create a serializer for the given protocol. 

83 :param protocol_name: The protocol name to create a serializer for. 

84 :type protocol_name: str 

85 :param include_validation: Whether to include parameter validation. 

86 :type include_validation: bool 

87 :param timestamp_precision: Timestamp precision level. 

88 - 'default': Microseconds for ISO timestamps, seconds for Unix and RFC 

89 - 'millisecond': Millisecond precision (ISO/Unix), seconds for RFC 

90 :type timestamp_precision: str 

91 :return: A serializer instance for the given protocol. 

92 """ 

93 # TODO: Unknown protocols. 

94 serializer = SERIALIZERS[protocol_name]( 

95 timestamp_precision=timestamp_precision 

96 ) 

97 if include_validation: 

98 validator = validate.ParamValidator() 

99 serializer = validate.ParamValidationDecorator(validator, serializer) 

100 return serializer 

101 

102 

103class Serializer: 

104 DEFAULT_METHOD = 'POST' 

105 # Clients can change this to a different MutableMapping 

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

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

108 # tests. 

109 MAP_TYPE = dict 

110 DEFAULT_ENCODING = 'utf-8' 

111 

112 def __init__(self, timestamp_precision=TIMESTAMP_PRECISION_DEFAULT): 

113 if timestamp_precision not in TIMESTAMP_PRECISION_OPTIONS: 

114 raise ValueError( 

115 f"Invalid timestamp precision found while creating serializer: {timestamp_precision}" 

116 ) 

117 self._timestamp_precision = timestamp_precision 

118 

119 def serialize_to_request(self, parameters, operation_model): 

120 """Serialize parameters into an HTTP request. 

121 

122 This method takes user provided parameters and a shape 

123 model and serializes the parameters to an HTTP request. 

124 More specifically, this method returns information about 

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

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

127 a dictionary of: 

128 

129 * 'url_path' 

130 * 'host_prefix' 

131 * 'query_string' 

132 * 'headers' 

133 * 'body' 

134 * 'method' 

135 

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

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

138 return value:: 

139 

140 {'body': {'Action': 'OperationName', 

141 'Bar': 'val2', 

142 'Foo': 'val1', 

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

144 'headers': {}, 

145 'method': 'POST', 

146 'query_string': '', 

147 'host_prefix': 'value.', 

148 'url_path': '/'} 

149 

150 :param parameters: The dictionary input parameters for the 

151 operation (i.e the user input). 

152 :param operation_model: The OperationModel object that describes 

153 the operation. 

154 """ 

155 raise NotImplementedError("serialize_to_request") 

156 

157 def _create_default_request(self): 

158 # Creates a boilerplate default request dict that subclasses 

159 # can use as a starting point. 

160 serialized = { 

161 'url_path': '/', 

162 'query_string': '', 

163 'method': self.DEFAULT_METHOD, 

164 'headers': {}, 

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

166 'body': b'', 

167 } 

168 return serialized 

169 

170 # Some extra utility methods subclasses can use. 

171 

172 def _timestamp_iso8601(self, value): 

173 """Return ISO8601 timestamp with precision based on timestamp_precision.""" 

174 # Smithy's standard is milliseconds, so we truncate the timestamp if the millisecond flag is set to true 

175 if self._timestamp_precision == TIMESTAMP_PRECISION_MILLISECOND: 

176 milliseconds = value.microsecond // 1000 

177 return ( 

178 value.strftime('%Y-%m-%dT%H:%M:%S') + f'.{milliseconds:03d}Z' 

179 ) 

180 else: 

181 # Otherwise we continue supporting microseconds in iso8601 for legacy reasons 

182 if value.microsecond > 0: 

183 timestamp_format = ISO8601_MICRO 

184 else: 

185 timestamp_format = ISO8601 

186 return value.strftime(timestamp_format) 

187 

188 def _timestamp_unixtimestamp(self, value): 

189 """Return unix timestamp with precision based on timestamp_precision.""" 

190 # As of the addition of the precision flag, we support millisecond precision here as well 

191 if self._timestamp_precision == TIMESTAMP_PRECISION_MILLISECOND: 

192 base_timestamp = calendar.timegm(value.timetuple()) 

193 milliseconds = (value.microsecond // 1000) / 1000.0 

194 return base_timestamp + milliseconds 

195 else: 

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

197 

198 def _timestamp_rfc822(self, value): 

199 """Return RFC822 timestamp (always second precision - RFC doesn't support sub-second).""" 

200 # RFC 2822 doesn't support sub-second precision, so always use second precision format 

201 if isinstance(value, datetime.datetime): 

202 value = int(calendar.timegm(value.timetuple())) 

203 return formatdate(value, usegmt=True) 

204 

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

206 if timestamp_format is None: 

207 timestamp_format = self.TIMESTAMP_FORMAT 

208 timestamp_format = timestamp_format.lower() 

209 datetime_obj = parse_to_aware_datetime(value) 

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

211 final_value = converter(datetime_obj) 

212 return final_value 

213 

214 def _get_serialized_name(self, shape, default_name): 

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

216 # Otherwise it will return the passed in default_name. 

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

218 

219 def _get_base64(self, value): 

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

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

222 # via the default encoding. 

223 if isinstance(value, str): 

224 value = value.encode(self.DEFAULT_ENCODING) 

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

226 

227 def _expand_host_prefix(self, parameters, operation_model): 

228 operation_endpoint = operation_model.endpoint 

229 if ( 

230 operation_endpoint is None 

231 or 'hostPrefix' not in operation_endpoint 

232 ): 

233 return None 

234 

235 host_prefix_expression = operation_endpoint['hostPrefix'] 

236 if operation_model.input_shape is None: 

237 return host_prefix_expression 

238 input_members = operation_model.input_shape.members 

239 host_labels = [ 

240 member 

241 for member, shape in input_members.items() 

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

243 ] 

244 format_kwargs = {} 

245 bad_labels = [] 

246 for name in host_labels: 

247 param = parameters[name] 

248 if not HOST_PREFIX_RE.match(param): 

249 bad_labels.append(name) 

250 format_kwargs[name] = param 

251 if bad_labels: 

252 raise ParamValidationError( 

253 report=( 

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

255 "Must contain only alphanumeric characters, hyphen, " 

256 "or period." 

257 ) 

258 ) 

259 return host_prefix_expression.format(**format_kwargs) 

260 

261 def _is_shape_flattened(self, shape): 

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

263 

264 def _handle_float(self, value): 

265 if value == float("Infinity"): 

266 value = "Infinity" 

267 elif value == float("-Infinity"): 

268 value = "-Infinity" 

269 elif math.isnan(value): 

270 value = "NaN" 

271 return value 

272 

273 def _handle_query_compatible_trait(self, operation_model, serialized): 

274 if operation_model.service_model.is_query_compatible: 

275 serialized['headers']['x-amzn-query-mode'] = 'true' 

276 

277 

278class QuerySerializer(Serializer): 

279 TIMESTAMP_FORMAT = 'iso8601' 

280 

281 def serialize_to_request(self, parameters, operation_model): 

282 shape = operation_model.input_shape 

283 serialized = self._create_default_request() 

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

285 'method', self.DEFAULT_METHOD 

286 ) 

287 serialized['headers'] = { 

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

289 } 

290 # The query serializer only deals with body params so 

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

292 body_params = self.MAP_TYPE() 

293 body_params['Action'] = operation_model.name 

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

295 if shape is not None: 

296 self._serialize(body_params, parameters, shape) 

297 serialized['body'] = body_params 

298 

299 host_prefix = self._expand_host_prefix(parameters, operation_model) 

300 if host_prefix is not None: 

301 serialized['host_prefix'] = host_prefix 

302 

303 return serialized 

304 

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

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

307 # final serialized parameters. 

308 # value: The current user input value. 

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

310 # input. 

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

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

313 method = getattr( 

314 self, 

315 f'_serialize_type_{shape.type_name}', 

316 self._default_serialize, 

317 ) 

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

319 

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

321 members = shape.members 

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

323 member_shape = members[key] 

324 member_prefix = self._get_serialized_name(member_shape, key) 

325 if prefix: 

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

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

328 

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

330 if not value: 

331 # The query protocol serializes empty lists. 

332 serialized[prefix] = '' 

333 return 

334 if self._is_shape_flattened(shape): 

335 list_prefix = prefix 

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

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

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

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

340 else: 

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

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

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

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

345 element_shape = shape.member 

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

347 

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

349 if self._is_shape_flattened(shape): 

350 full_prefix = prefix 

351 else: 

352 full_prefix = f'{prefix}.entry' 

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

354 key_shape = shape.key 

355 value_shape = shape.value 

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

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

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

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

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

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

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

363 

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

365 # Blob args must be base64 encoded. 

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

367 

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

369 serialized[prefix] = self._convert_timestamp_to_str( 

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

371 ) 

372 

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

374 if value: 

375 serialized[prefix] = 'true' 

376 else: 

377 serialized[prefix] = 'false' 

378 

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

380 serialized[prefix] = value 

381 

382 def _serialize_type_float(self, serialized, value, shape, prefix=''): 

383 serialized[prefix] = self._handle_float(value) 

384 

385 def _serialize_type_double(self, serialized, value, shape, prefix=''): 

386 self._serialize_type_float(serialized, value, shape, prefix) 

387 

388 

389class EC2Serializer(QuerySerializer): 

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

391 

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

393 serializer. This class encapsulates those differences. The model 

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

395 to worry about wiring this class up correctly. 

396 

397 """ 

398 

399 def _get_serialized_name(self, shape, default_name): 

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

401 # Otherwise it will return the passed in capitalized default_name. 

402 if 'queryName' in shape.serialization: 

403 return shape.serialization['queryName'] 

404 elif 'name' in shape.serialization: 

405 # A locationName is always capitalized 

406 # on input for the ec2 protocol. 

407 name = shape.serialization['name'] 

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

409 else: 

410 return default_name 

411 

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

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

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

415 element_shape = shape.member 

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

417 

418 

419class JSONSerializer(Serializer): 

420 TIMESTAMP_FORMAT = 'unixtimestamp' 

421 

422 def serialize_to_request(self, parameters, operation_model): 

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

424 operation_model.metadata['targetPrefix'], 

425 operation_model.name, 

426 ) 

427 json_version = operation_model.metadata['jsonVersion'] 

428 serialized = self._create_default_request() 

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

430 'method', self.DEFAULT_METHOD 

431 ) 

432 serialized['headers'] = { 

433 'X-Amz-Target': target, 

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

435 } 

436 self._handle_query_compatible_trait(operation_model, serialized) 

437 

438 body = self.MAP_TYPE() 

439 input_shape = operation_model.input_shape 

440 if input_shape is not None: 

441 self._serialize(body, parameters, input_shape) 

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

443 

444 host_prefix = self._expand_host_prefix(parameters, operation_model) 

445 if host_prefix is not None: 

446 serialized['host_prefix'] = host_prefix 

447 

448 return serialized 

449 

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

451 method = getattr( 

452 self, 

453 f'_serialize_type_{shape.type_name}', 

454 self._default_serialize, 

455 ) 

456 method(serialized, value, shape, key) 

457 

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

459 if shape.is_document_type: 

460 serialized[key] = value 

461 else: 

462 if key is not None: 

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

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

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

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

467 # dictionary we just created. 

468 new_serialized = self.MAP_TYPE() 

469 serialized[key] = new_serialized 

470 serialized = new_serialized 

471 members = shape.members 

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

473 member_shape = members[member_key] 

474 if 'name' in member_shape.serialization: 

475 member_key = member_shape.serialization['name'] 

476 self._serialize( 

477 serialized, member_value, member_shape, member_key 

478 ) 

479 

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

481 map_obj = self.MAP_TYPE() 

482 serialized[key] = map_obj 

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

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

485 

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

487 list_obj = [] 

488 serialized[key] = list_obj 

489 for list_item in value: 

490 wrapper = {} 

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

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

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

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

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

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

497 

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

499 serialized[key] = value 

500 

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

502 serialized[key] = self._convert_timestamp_to_str( 

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

504 ) 

505 

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

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

508 

509 def _serialize_type_float(self, serialized, value, shape, prefix=''): 

510 if isinstance(value, decimal.Decimal): 

511 value = float(value) 

512 serialized[prefix] = self._handle_float(value) 

513 

514 def _serialize_type_double(self, serialized, value, shape, prefix=''): 

515 self._serialize_type_float(serialized, value, shape, prefix) 

516 

517 

518class CBORSerializer(Serializer): 

519 UNSIGNED_INT_MAJOR_TYPE = 0 

520 NEGATIVE_INT_MAJOR_TYPE = 1 

521 BLOB_MAJOR_TYPE = 2 

522 STRING_MAJOR_TYPE = 3 

523 LIST_MAJOR_TYPE = 4 

524 MAP_MAJOR_TYPE = 5 

525 TAG_MAJOR_TYPE = 6 

526 FLOAT_AND_SIMPLE_MAJOR_TYPE = 7 

527 

528 def _serialize_data_item(self, serialized, value, shape, key=None): 

529 method = getattr(self, f'_serialize_type_{shape.type_name}') 

530 if method is None: 

531 raise ValueError( 

532 f"Unrecognized C2J type: {shape.type_name}, unable to " 

533 f"serialize request" 

534 ) 

535 method(serialized, value, shape, key) 

536 

537 def _serialize_type_integer(self, serialized, value, shape, key): 

538 if value >= 0: 

539 major_type = self.UNSIGNED_INT_MAJOR_TYPE 

540 else: 

541 major_type = self.NEGATIVE_INT_MAJOR_TYPE 

542 # The only differences in serializing negative and positive integers is 

543 # that for negative, we set the major type to 1 and set the value to -1 

544 # minus the value 

545 value = -1 - value 

546 additional_info, num_bytes = self._get_additional_info_and_num_bytes( 

547 value 

548 ) 

549 initial_byte = self._get_initial_byte(major_type, additional_info) 

550 if num_bytes == 0: 

551 serialized.extend(initial_byte) 

552 else: 

553 serialized.extend(initial_byte + value.to_bytes(num_bytes, "big")) 

554 

555 def _serialize_type_long(self, serialized, value, shape, key): 

556 self._serialize_type_integer(serialized, value, shape, key) 

557 

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

559 if isinstance(value, str): 

560 value = value.encode('utf-8') 

561 elif not isinstance(value, (bytes, bytearray)): 

562 # We support file-like objects for blobs; these already have been 

563 # validated to ensure they have a read method 

564 value = value.read() 

565 length = len(value) 

566 additional_info, num_bytes = self._get_additional_info_and_num_bytes( 

567 length 

568 ) 

569 initial_byte = self._get_initial_byte( 

570 self.BLOB_MAJOR_TYPE, additional_info 

571 ) 

572 if num_bytes == 0: 

573 serialized.extend(initial_byte) 

574 else: 

575 serialized.extend(initial_byte + length.to_bytes(num_bytes, "big")) 

576 serialized.extend(value) 

577 

578 def _serialize_type_string(self, serialized, value, shape, key): 

579 encoded = value.encode('utf-8') 

580 length = len(encoded) 

581 additional_info, num_bytes = self._get_additional_info_and_num_bytes( 

582 length 

583 ) 

584 initial_byte = self._get_initial_byte( 

585 self.STRING_MAJOR_TYPE, additional_info 

586 ) 

587 if num_bytes == 0: 

588 serialized.extend(initial_byte + encoded) 

589 else: 

590 serialized.extend( 

591 initial_byte + length.to_bytes(num_bytes, "big") + encoded 

592 ) 

593 

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

595 length = len(value) 

596 additional_info, num_bytes = self._get_additional_info_and_num_bytes( 

597 length 

598 ) 

599 initial_byte = self._get_initial_byte( 

600 self.LIST_MAJOR_TYPE, additional_info 

601 ) 

602 if num_bytes == 0: 

603 serialized.extend(initial_byte) 

604 else: 

605 serialized.extend(initial_byte + length.to_bytes(num_bytes, "big")) 

606 for item in value: 

607 self._serialize_data_item(serialized, item, shape.member) 

608 

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

610 length = len(value) 

611 additional_info, num_bytes = self._get_additional_info_and_num_bytes( 

612 length 

613 ) 

614 initial_byte = self._get_initial_byte( 

615 self.MAP_MAJOR_TYPE, additional_info 

616 ) 

617 if num_bytes == 0: 

618 serialized.extend(initial_byte) 

619 else: 

620 serialized.extend(initial_byte + length.to_bytes(num_bytes, "big")) 

621 for key_item, item in value.items(): 

622 self._serialize_data_item(serialized, key_item, shape.key) 

623 self._serialize_data_item(serialized, item, shape.value) 

624 

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

626 if key is not None: 

627 # For nested structures, we need to serialize the key first 

628 self._serialize_data_item(serialized, key, shape.key_shape) 

629 

630 # Remove `None` values from the dictionary 

631 value = {k: v for k, v in value.items() if v is not None} 

632 

633 map_length = len(value) 

634 additional_info, num_bytes = self._get_additional_info_and_num_bytes( 

635 map_length 

636 ) 

637 initial_byte = self._get_initial_byte( 

638 self.MAP_MAJOR_TYPE, additional_info 

639 ) 

640 if num_bytes == 0: 

641 serialized.extend(initial_byte) 

642 else: 

643 serialized.extend( 

644 initial_byte + map_length.to_bytes(num_bytes, "big") 

645 ) 

646 

647 members = shape.members 

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

649 member_shape = members[member_key] 

650 if 'name' in member_shape.serialization: 

651 member_key = member_shape.serialization['name'] 

652 if member_value is not None: 

653 self._serialize_type_string(serialized, member_key, None, None) 

654 self._serialize_data_item( 

655 serialized, member_value, member_shape 

656 ) 

657 

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

659 timestamp = self._convert_timestamp_to_str(value) 

660 tag = 1 # Use tag 1 for unix timestamp 

661 initial_byte = self._get_initial_byte(self.TAG_MAJOR_TYPE, tag) 

662 serialized.extend(initial_byte) # Tagging the timestamp 

663 additional_info, num_bytes = self._get_additional_info_and_num_bytes( 

664 timestamp 

665 ) 

666 

667 if num_bytes == 0: 

668 initial_byte = self._get_initial_byte( 

669 self.UNSIGNED_INT_MAJOR_TYPE, timestamp 

670 ) 

671 serialized.extend(initial_byte) 

672 else: 

673 initial_byte = self._get_initial_byte( 

674 self.UNSIGNED_INT_MAJOR_TYPE, additional_info 

675 ) 

676 serialized.extend( 

677 initial_byte + timestamp.to_bytes(num_bytes, "big") 

678 ) 

679 

680 def _serialize_type_float(self, serialized, value, shape, key): 

681 if self._is_special_number(value): 

682 serialized.extend( 

683 self._get_bytes_for_special_numbers(value) 

684 ) # Handle special values like NaN or Infinity 

685 else: 

686 initial_byte = self._get_initial_byte( 

687 self.FLOAT_AND_SIMPLE_MAJOR_TYPE, 26 

688 ) 

689 serialized.extend(initial_byte + struct.pack(">f", value)) 

690 

691 def _serialize_type_double(self, serialized, value, shape, key): 

692 if self._is_special_number(value): 

693 serialized.extend( 

694 self._get_bytes_for_special_numbers(value) 

695 ) # Handle special values like NaN or Infinity 

696 else: 

697 initial_byte = self._get_initial_byte( 

698 self.FLOAT_AND_SIMPLE_MAJOR_TYPE, 27 

699 ) 

700 serialized.extend(initial_byte + struct.pack(">d", value)) 

701 

702 def _serialize_type_boolean(self, serialized, value, shape, key): 

703 additional_info = 21 if value else 20 

704 serialized.extend( 

705 self._get_initial_byte( 

706 self.FLOAT_AND_SIMPLE_MAJOR_TYPE, additional_info 

707 ) 

708 ) 

709 

710 def _get_additional_info_and_num_bytes(self, value): 

711 # Values under 24 can be stored in the initial byte and don't need further 

712 # encoding 

713 if value < 24: 

714 return value, 0 

715 # Values between 24 and 255 (inclusive) can be stored in 1 byte and 

716 # correspond to additional info 24 

717 elif value < 256: 

718 return 24, 1 

719 # Values up to 65535 can be stored in two bytes and correspond to additional 

720 # info 25 

721 elif value < 65536: 

722 return 25, 2 

723 # Values up to 4294967296 can be stored in four bytes and correspond to 

724 # additional info 26 

725 elif value < 4294967296: 

726 return 26, 4 

727 # The maximum number of bytes in a definite length data items is 8 which 

728 # to additional info 27 

729 else: 

730 return 27, 8 

731 

732 def _get_initial_byte(self, major_type, additional_info): 

733 # The highest order three bits are the major type, so we need to bitshift the 

734 # major type by 5 

735 major_type_bytes = major_type << 5 

736 return (major_type_bytes | additional_info).to_bytes(1, "big") 

737 

738 def _is_special_number(self, value): 

739 return any( 

740 [ 

741 value == float('inf'), 

742 value == float('-inf'), 

743 math.isnan(value), 

744 ] 

745 ) 

746 

747 def _get_bytes_for_special_numbers(self, value): 

748 additional_info = 25 

749 initial_byte = self._get_initial_byte( 

750 self.FLOAT_AND_SIMPLE_MAJOR_TYPE, additional_info 

751 ) 

752 if value == float('inf'): 

753 return initial_byte + struct.pack(">H", 0x7C00) 

754 elif value == float('-inf'): 

755 return initial_byte + struct.pack(">H", 0xFC00) 

756 elif math.isnan(value): 

757 return initial_byte + struct.pack(">H", 0x7E00) 

758 

759 

760class BaseRestSerializer(Serializer): 

761 """Base class for rest protocols. 

762 

763 The only variance between the various rest protocols is the 

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

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

766 

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

768 

769 """ 

770 

771 QUERY_STRING_TIMESTAMP_FORMAT = 'iso8601' 

772 HEADER_TIMESTAMP_FORMAT = 'rfc822' 

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

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

775 # to put the serialized value. 

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

777 

778 def serialize_to_request(self, parameters, operation_model): 

779 serialized = self._create_default_request() 

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

781 'method', self.DEFAULT_METHOD 

782 ) 

783 shape = operation_model.input_shape 

784 

785 host_prefix = self._expand_host_prefix(parameters, operation_model) 

786 if host_prefix is not None: 

787 serialized['host_prefix'] = host_prefix 

788 

789 if shape is None: 

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

791 return serialized 

792 shape_members = shape.members 

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

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

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

796 # query_string_kwargs because they are templated, so we need 

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

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

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

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

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

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

803 partitioned = { 

804 'uri_path_kwargs': self.MAP_TYPE(), 

805 'query_string_kwargs': self.MAP_TYPE(), 

806 'body_kwargs': self.MAP_TYPE(), 

807 'headers': self.MAP_TYPE(), 

808 } 

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

810 if param_value is None: 

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

812 continue 

813 self._partition_parameters( 

814 partitioned, param_name, param_value, shape_members 

815 ) 

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

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

818 ) 

819 

820 if 'authPath' in operation_model.http: 

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

822 operation_model.http['authPath'], 

823 partitioned['uri_path_kwargs'], 

824 ) 

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

826 # where the requestUri path already has query parameters. 

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

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

829 if partitioned['headers']: 

830 serialized['headers'] = partitioned['headers'] 

831 self._serialize_payload( 

832 partitioned, parameters, serialized, shape, shape_members 

833 ) 

834 self._serialize_content_type(serialized, shape, shape_members) 

835 

836 return serialized 

837 

838 def _render_uri_template(self, uri_template, params): 

839 # We need to handle two cases:: 

840 # 

841 # /{Bucket}/foo 

842 # /{Key+}/bar 

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

844 # be one greedy key. 

845 encoded_params = {} 

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

847 if template_param.endswith('+'): 

848 encoded_params[template_param] = percent_encode( 

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

850 ) 

851 else: 

852 encoded_params[template_param] = percent_encode( 

853 params[template_param] 

854 ) 

855 return uri_template.format(**encoded_params) 

856 

857 def _serialize_payload( 

858 self, partitioned, parameters, serialized, shape, shape_members 

859 ): 

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

861 # parameters - The user input params. 

862 # serialized - The final serialized request dict. 

863 # shape - Describes the expected input shape 

864 # shape_members - The members of the input struct shape 

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

866 if self._has_streaming_payload(payload_member, shape_members): 

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

868 # value of the payload. 

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

870 body_payload = self._encode_payload(body_payload) 

871 serialized['body'] = body_payload 

872 elif payload_member is not None: 

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

874 # member to they body. 

875 body_params = parameters.get(payload_member) 

876 if body_params is not None: 

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

878 body_params, shape_members[payload_member] 

879 ) 

880 else: 

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

882 elif partitioned['body_kwargs']: 

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

884 partitioned['body_kwargs'], shape 

885 ) 

886 elif self._requires_empty_body(shape): 

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

888 

889 def _serialize_empty_body(self): 

890 return b'' 

891 

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

893 """ 

894 Some protocols require varied Content-Type headers 

895 depending on user input. This allows subclasses to apply 

896 this conditionally. 

897 """ 

898 pass 

899 

900 def _requires_empty_body(self, shape): 

901 """ 

902 Some protocols require a specific body to represent an empty 

903 payload. This allows subclasses to apply this conditionally. 

904 """ 

905 return False 

906 

907 def _has_streaming_payload(self, payload, shape_members): 

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

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

910 'blob', 

911 'string', 

912 ) 

913 

914 def _encode_payload(self, body): 

915 if isinstance(body, str): 

916 return body.encode(self.DEFAULT_ENCODING) 

917 return body 

918 

919 def _partition_parameters( 

920 self, partitioned, param_name, param_value, shape_members 

921 ): 

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

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

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

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

926 member = shape_members[param_name] 

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

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

929 if location == 'uri': 

930 uri_path_value = self._get_uri_and_query_string_value( 

931 param_value, member 

932 ) 

933 partitioned['uri_path_kwargs'][key_name] = uri_path_value 

934 elif location == 'querystring': 

935 if isinstance(param_value, dict): 

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

937 elif member.type_name == 'list': 

938 new_param = [ 

939 self._get_uri_and_query_string_value(value, member.member) 

940 for value in param_value 

941 ] 

942 partitioned['query_string_kwargs'][key_name] = new_param 

943 else: 

944 new_param = self._get_uri_and_query_string_value( 

945 param_value, member 

946 ) 

947 partitioned['query_string_kwargs'][key_name] = new_param 

948 elif location == 'header': 

949 shape = shape_members[param_name] 

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

951 # Empty lists should not be set on the headers 

952 return 

953 partitioned['headers'][key_name] = self._convert_header_value( 

954 shape, param_value 

955 ) 

956 elif location == 'headers': 

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

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

959 header_prefix = key_name 

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

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

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

963 # plus the key provided by the user. 

964 self._do_serialize_header_map( 

965 header_prefix, partitioned['headers'], param_value 

966 ) 

967 else: 

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

969 

970 def _get_uri_and_query_string_value(self, param_value, member): 

971 if member.type_name == 'boolean': 

972 return str(param_value).lower() 

973 elif member.type_name == 'timestamp': 

974 timestamp_format = member.serialization.get( 

975 'timestampFormat', self.QUERY_STRING_TIMESTAMP_FORMAT 

976 ) 

977 return self._convert_timestamp_to_str( 

978 param_value, timestamp_format 

979 ) 

980 elif member.type_name in ['float', 'double']: 

981 return str(self._handle_float(param_value)) 

982 return param_value 

983 

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

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

986 full_key = header_prefix + key 

987 headers[full_key] = val 

988 

989 def _serialize_body_params(self, params, shape): 

990 raise NotImplementedError('_serialize_body_params') 

991 

992 def _convert_header_value(self, shape, value): 

993 if shape.type_name == 'timestamp': 

994 datetime_obj = parse_to_aware_datetime(value) 

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

996 timestamp_format = shape.serialization.get( 

997 'timestampFormat', self.HEADER_TIMESTAMP_FORMAT 

998 ) 

999 return str( 

1000 self._convert_timestamp_to_str(timestamp, timestamp_format) 

1001 ) 

1002 elif shape.type_name == 'list': 

1003 if shape.member.type_name == "string": 

1004 converted_value = [ 

1005 self._escape_header_list_string(v) 

1006 for v in value 

1007 if v is not None 

1008 ] 

1009 else: 

1010 converted_value = [ 

1011 self._convert_header_value(shape.member, v) 

1012 for v in value 

1013 if v is not None 

1014 ] 

1015 return ",".join(converted_value) 

1016 elif is_json_value_header(shape): 

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

1018 # the header. 

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

1020 elif shape.type_name == 'boolean': 

1021 return str(value).lower() 

1022 elif shape.type_name in ['float', 'double']: 

1023 return str(self._handle_float(value)) 

1024 else: 

1025 return str(value) 

1026 

1027 def _escape_header_list_string(self, value): 

1028 # Escapes a header list string by wrapping it in double quotes if it contains 

1029 # a comma or a double quote, and escapes any internal double quotes. 

1030 if '"' in value or ',' in value: 

1031 return '"' + value.replace('"', '\\"') + '"' 

1032 else: 

1033 return value 

1034 

1035 

1036class BaseRpcV2Serializer(Serializer): 

1037 """Base class for RPCv2 protocols. 

1038 

1039 The only variance between the various RPCv2 protocols is the 

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

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

1042 

1043 Subclasses must implement the ``_serialize_body_params`` and 

1044 ``_serialize_headers`` methods. 

1045 

1046 """ 

1047 

1048 def serialize_to_request(self, parameters, operation_model): 

1049 serialized = self._create_default_request() 

1050 service_name = operation_model.service_model.metadata['targetPrefix'] 

1051 operation_name = operation_model.name 

1052 serialized['url_path'] = ( 

1053 f'/service/{service_name}/operation/{operation_name}' 

1054 ) 

1055 

1056 input_shape = operation_model.input_shape 

1057 if input_shape is not None: 

1058 self._serialize_payload(parameters, serialized, input_shape) 

1059 

1060 self._serialize_headers(serialized, operation_model) 

1061 

1062 return serialized 

1063 

1064 def _serialize_payload(self, parameters, serialized, shape): 

1065 body_payload = self._serialize_body_params(parameters, shape) 

1066 serialized['body'] = body_payload 

1067 

1068 def _serialize_headers(self, serialized, operation_model): 

1069 raise NotImplementedError("_serialize_headers") 

1070 

1071 def _serialize_body_params(self, parameters, shape): 

1072 raise NotImplementedError("_serialize_body_params") 

1073 

1074 

1075class RestJSONSerializer(BaseRestSerializer, JSONSerializer): 

1076 def _serialize_empty_body(self): 

1077 return b'{}' 

1078 

1079 def _requires_empty_body(self, shape): 

1080 """ 

1081 Serialize an empty JSON object whenever the shape has 

1082 members not targeting a location. 

1083 """ 

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

1085 if 'location' not in val.serialization: 

1086 return True 

1087 return False 

1088 

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

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

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

1092 if self._has_streaming_payload(payload, shape_members): 

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

1094 return 

1095 

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

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

1098 if has_body and not has_content_type: 

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

1100 

1101 def _serialize_body_params(self, params, shape): 

1102 serialized_body = self.MAP_TYPE() 

1103 self._serialize(serialized_body, params, shape) 

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

1105 

1106 

1107class RestXMLSerializer(BaseRestSerializer): 

1108 TIMESTAMP_FORMAT = 'iso8601' 

1109 

1110 def _serialize_body_params(self, params, shape): 

1111 root_name = shape.serialization['name'] 

1112 pseudo_root = ElementTree.Element('') 

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

1114 real_root = list(pseudo_root)[0] 

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

1116 

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

1118 method = getattr( 

1119 self, 

1120 f'_serialize_type_{shape.type_name}', 

1121 self._default_serialize, 

1122 ) 

1123 method(xmlnode, params, shape, name) 

1124 

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

1126 structure_node = ElementTree.SubElement(xmlnode, name) 

1127 

1128 self._add_xml_namespace(shape, structure_node) 

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

1130 member_shape = shape.members[key] 

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

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

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

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

1135 # *current* node. 

1136 if value is None: 

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

1138 return 

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

1140 # xmlAttributes must have a serialization name. 

1141 xml_attribute_name = member_shape.serialization['name'] 

1142 structure_node.attrib[xml_attribute_name] = value 

1143 continue 

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

1145 

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

1147 member_shape = shape.member 

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

1149 element_name = name 

1150 list_node = xmlnode 

1151 else: 

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

1153 list_node = ElementTree.SubElement(xmlnode, name) 

1154 self._add_xml_namespace(shape, list_node) 

1155 for item in params: 

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

1157 

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

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

1160 # we serialize this as: 

1161 # <MyMap> 

1162 # <entry> 

1163 # <key>key1</key> 

1164 # <value>val1</value> 

1165 # </entry> 

1166 # </MyMap> 

1167 if not self._is_shape_flattened(shape): 

1168 node = ElementTree.SubElement(xmlnode, name) 

1169 self._add_xml_namespace(shape, node) 

1170 

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

1172 sub_node = ( 

1173 ElementTree.SubElement(xmlnode, name) 

1174 if self._is_shape_flattened(shape) 

1175 else ElementTree.SubElement(node, 'entry') 

1176 ) 

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

1178 val_name = self._get_serialized_name( 

1179 shape.value, default_name='value' 

1180 ) 

1181 self._serialize(shape.key, key, sub_node, key_name) 

1182 self._serialize(shape.value, value, sub_node, val_name) 

1183 

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

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

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

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

1188 node = ElementTree.SubElement(xmlnode, name) 

1189 if params: 

1190 str_value = 'true' 

1191 else: 

1192 str_value = 'false' 

1193 node.text = str_value 

1194 self._add_xml_namespace(shape, node) 

1195 

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

1197 node = ElementTree.SubElement(xmlnode, name) 

1198 node.text = self._get_base64(params) 

1199 self._add_xml_namespace(shape, node) 

1200 

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

1202 node = ElementTree.SubElement(xmlnode, name) 

1203 node.text = str( 

1204 self._convert_timestamp_to_str( 

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

1206 ) 

1207 ) 

1208 self._add_xml_namespace(shape, node) 

1209 

1210 def _serialize_type_float(self, xmlnode, params, shape, name): 

1211 node = ElementTree.SubElement(xmlnode, name) 

1212 node.text = str(self._handle_float(params)) 

1213 self._add_xml_namespace(shape, node) 

1214 

1215 def _serialize_type_double(self, xmlnode, params, shape, name): 

1216 self._serialize_type_float(xmlnode, params, shape, name) 

1217 

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

1219 node = ElementTree.SubElement(xmlnode, name) 

1220 node.text = str(params) 

1221 self._add_xml_namespace(shape, node) 

1222 

1223 def _add_xml_namespace(self, shape, structure_node): 

1224 if 'xmlNamespace' in shape.serialization: 

1225 namespace_metadata = shape.serialization['xmlNamespace'] 

1226 attribute_name = 'xmlns' 

1227 if isinstance(namespace_metadata, dict): 

1228 if namespace_metadata.get('prefix'): 

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

1230 structure_node.attrib[attribute_name] = namespace_metadata[ 

1231 'uri' 

1232 ] 

1233 elif isinstance(namespace_metadata, str): 

1234 structure_node.attrib[attribute_name] = namespace_metadata 

1235 

1236 

1237class RpcV2CBORSerializer(BaseRpcV2Serializer, CBORSerializer): 

1238 TIMESTAMP_FORMAT = 'unixtimestamp' 

1239 

1240 def serialize_to_request(self, parameters, operation_model): 

1241 register_feature_id('PROTOCOL_RPC_V2_CBOR') 

1242 return super().serialize_to_request(parameters, operation_model) 

1243 

1244 def _serialize_body_params(self, parameters, input_shape): 

1245 body = bytearray() 

1246 self._serialize_data_item(body, parameters, input_shape) 

1247 return bytes(body) 

1248 

1249 def _serialize_headers(self, serialized, operation_model): 

1250 serialized['headers']['smithy-protocol'] = 'rpc-v2-cbor' 

1251 

1252 if operation_model.has_event_stream_output: 

1253 header_val = 'application/vnd.amazon.eventstream' 

1254 else: 

1255 header_val = 'application/cbor' 

1256 self._handle_query_compatible_trait(operation_model, serialized) 

1257 

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

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

1260 

1261 serialized['headers']['Accept'] = header_val 

1262 if not has_content_type and has_body: 

1263 serialized['headers']['Content-Type'] = header_val 

1264 

1265 

1266SERIALIZERS = { 

1267 'ec2': EC2Serializer, 

1268 'query': QuerySerializer, 

1269 'json': JSONSerializer, 

1270 'rest-json': RestJSONSerializer, 

1271 'rest-xml': RestXMLSerializer, 

1272 'smithy-rpc-v2-cbor': RpcV2CBORSerializer, 

1273}