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

646 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_LEGACY = 'legacy' 

72TIMESTAMP_PRECISION_OPTIONS = ( 

73 TIMESTAMP_PRECISION_DEFAULT, 

74 TIMESTAMP_PRECISION_MILLISECOND, 

75 TIMESTAMP_PRECISION_LEGACY, 

76) 

77 

78 

79def create_serializer( 

80 protocol_name, 

81 include_validation=True, 

82 timestamp_precision=TIMESTAMP_PRECISION_DEFAULT, 

83): 

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

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

86 :type protocol_name: str 

87 :param include_validation: Whether to include parameter validation. 

88 :type include_validation: bool 

89 :param timestamp_precision: Timestamp precision level. 

90 - 'default': Full precision of the provided value (up to 

91 microseconds) for ISO and Unix timestamps, seconds for RFC 

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

93 - 'legacy': Behavior prior to sub-second Unix timestamp support. 

94 Microseconds for ISO timestamps, seconds for Unix and RFC 

95 :type timestamp_precision: str 

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

97 """ 

98 # TODO: Unknown protocols. 

99 serializer = SERIALIZERS[protocol_name]( 

100 timestamp_precision=timestamp_precision 

101 ) 

102 if include_validation: 

103 validator = validate.ParamValidator() 

104 serializer = validate.ParamValidationDecorator(validator, serializer) 

105 return serializer 

106 

107 

108class Serializer: 

109 DEFAULT_METHOD = 'POST' 

110 # Clients can change this to a different MutableMapping 

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

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

113 # tests. 

114 MAP_TYPE = dict 

115 DEFAULT_ENCODING = 'utf-8' 

116 

117 def __init__(self, timestamp_precision=TIMESTAMP_PRECISION_DEFAULT): 

118 if timestamp_precision not in TIMESTAMP_PRECISION_OPTIONS: 

119 raise ValueError( 

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

121 ) 

122 self._timestamp_precision = timestamp_precision 

123 

124 def serialize_to_request(self, parameters, operation_model): 

125 """Serialize parameters into an HTTP request. 

126 

127 This method takes user provided parameters and a shape 

128 model and serializes the parameters to an HTTP request. 

129 More specifically, this method returns information about 

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

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

132 a dictionary of: 

133 

134 * 'url_path' 

135 * 'host_prefix' 

136 * 'query_string' 

137 * 'headers' 

138 * 'body' 

139 * 'method' 

140 

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

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

143 return value:: 

144 

145 {'body': {'Action': 'OperationName', 

146 'Bar': 'val2', 

147 'Foo': 'val1', 

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

149 'headers': {}, 

150 'method': 'POST', 

151 'query_string': '', 

152 'host_prefix': 'value.', 

153 'url_path': '/'} 

154 

155 :param parameters: The dictionary input parameters for the 

156 operation (i.e the user input). 

157 :param operation_model: The OperationModel object that describes 

158 the operation. 

159 """ 

160 raise NotImplementedError("serialize_to_request") 

161 

162 def _create_default_request(self): 

163 # Creates a boilerplate default request dict that subclasses 

164 # can use as a starting point. 

165 serialized = { 

166 'url_path': '/', 

167 'query_string': '', 

168 'method': self.DEFAULT_METHOD, 

169 'headers': {}, 

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

171 'body': b'', 

172 } 

173 return serialized 

174 

175 # Some extra utility methods subclasses can use. 

176 

177 def _timestamp_iso8601(self, value): 

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

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

180 if self._timestamp_precision == TIMESTAMP_PRECISION_MILLISECOND: 

181 milliseconds = value.microsecond // 1000 

182 return ( 

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

184 ) 

185 else: 

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

187 if value.microsecond > 0: 

188 timestamp_format = ISO8601_MICRO 

189 else: 

190 timestamp_format = ISO8601 

191 return value.strftime(timestamp_format) 

192 

193 def _timestamp_unixtimestamp(self, value): 

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

195 timestamp = calendar.timegm(value.timetuple()) 

196 if self._timestamp_precision == TIMESTAMP_PRECISION_MILLISECOND: 

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

198 return timestamp + milliseconds 

199 elif self._timestamp_precision == TIMESTAMP_PRECISION_LEGACY: 

200 return timestamp 

201 elif value.microsecond > 0: 

202 # Add the microseconds as integers before dividing so the float is only 

203 # rounded once. Dividing first and then adding rounds twice, which can 

204 # produce a slightly different value, e.g. 1 + 3691 / 10**6 gives 

205 # 1.0036909999999999 instead of 1.003691. 

206 return (timestamp * 10**6 + value.microsecond) / 10**6 

207 return timestamp 

208 

209 def _timestamp_rfc822(self, value): 

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

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

212 if isinstance(value, datetime.datetime): 

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

214 return formatdate(value, usegmt=True) 

215 

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

217 if timestamp_format is None: 

218 timestamp_format = self.TIMESTAMP_FORMAT 

219 timestamp_format = timestamp_format.lower() 

220 datetime_obj = parse_to_aware_datetime(value) 

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

222 final_value = converter(datetime_obj) 

223 return final_value 

224 

225 def _get_serialized_name(self, shape, default_name): 

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

227 # Otherwise it will return the passed in default_name. 

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

229 

230 def _get_base64(self, value): 

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

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

233 # via the default encoding. 

234 if isinstance(value, str): 

235 value = value.encode(self.DEFAULT_ENCODING) 

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

237 

238 def _expand_host_prefix(self, parameters, operation_model): 

239 operation_endpoint = operation_model.endpoint 

240 if ( 

241 operation_endpoint is None 

242 or 'hostPrefix' not in operation_endpoint 

243 ): 

244 return None 

245 

246 host_prefix_expression = operation_endpoint['hostPrefix'] 

247 if operation_model.input_shape is None: 

248 return host_prefix_expression 

249 input_members = operation_model.input_shape.members 

250 host_labels = [ 

251 member 

252 for member, shape in input_members.items() 

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

254 ] 

255 format_kwargs = {} 

256 bad_labels = [] 

257 for name in host_labels: 

258 param = parameters[name] 

259 if not HOST_PREFIX_RE.match(param): 

260 bad_labels.append(name) 

261 format_kwargs[name] = param 

262 if bad_labels: 

263 raise ParamValidationError( 

264 report=( 

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

266 "Must contain only alphanumeric characters, hyphen, " 

267 "or period." 

268 ) 

269 ) 

270 return host_prefix_expression.format(**format_kwargs) 

271 

272 def _is_shape_flattened(self, shape): 

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

274 

275 def _handle_float(self, value): 

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

277 value = "Infinity" 

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

279 value = "-Infinity" 

280 elif math.isnan(value): 

281 value = "NaN" 

282 return value 

283 

284 def _handle_query_compatible_trait(self, operation_model, serialized): 

285 if operation_model.service_model.is_query_compatible: 

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

287 

288 

289class QuerySerializer(Serializer): 

290 TIMESTAMP_FORMAT = 'iso8601' 

291 

292 def serialize_to_request(self, parameters, operation_model): 

293 shape = operation_model.input_shape 

294 serialized = self._create_default_request() 

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

296 'method', self.DEFAULT_METHOD 

297 ) 

298 serialized['headers'] = { 

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

300 } 

301 # The query serializer only deals with body params so 

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

303 body_params = self.MAP_TYPE() 

304 body_params['Action'] = operation_model.name 

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

306 if shape is not None: 

307 self._serialize(body_params, parameters, shape) 

308 serialized['body'] = body_params 

309 

310 host_prefix = self._expand_host_prefix(parameters, operation_model) 

311 if host_prefix is not None: 

312 serialized['host_prefix'] = host_prefix 

313 

314 return serialized 

315 

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

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

318 # final serialized parameters. 

319 # value: The current user input value. 

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

321 # input. 

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

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

324 method = getattr( 

325 self, 

326 f'_serialize_type_{shape.type_name}', 

327 self._default_serialize, 

328 ) 

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

330 

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

332 members = shape.members 

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

334 member_shape = members[key] 

335 member_prefix = self._get_serialized_name(member_shape, key) 

336 if prefix: 

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

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

339 

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

341 if not value: 

342 # The query protocol serializes empty lists. 

343 serialized[prefix] = '' 

344 return 

345 if self._is_shape_flattened(shape): 

346 list_prefix = prefix 

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

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

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

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

351 else: 

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

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

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

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

356 element_shape = shape.member 

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

358 

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

360 if self._is_shape_flattened(shape): 

361 full_prefix = prefix 

362 else: 

363 full_prefix = f'{prefix}.entry' 

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

365 key_shape = shape.key 

366 value_shape = shape.value 

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

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

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

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

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

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

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

374 

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

376 # Blob args must be base64 encoded. 

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

378 

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

380 serialized[prefix] = self._convert_timestamp_to_str( 

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

382 ) 

383 

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

385 if value: 

386 serialized[prefix] = 'true' 

387 else: 

388 serialized[prefix] = 'false' 

389 

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

391 serialized[prefix] = value 

392 

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

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

395 

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

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

398 

399 

400class EC2Serializer(QuerySerializer): 

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

402 

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

404 serializer. This class encapsulates those differences. The model 

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

406 to worry about wiring this class up correctly. 

407 

408 """ 

409 

410 def _get_serialized_name(self, shape, default_name): 

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

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

413 if 'queryName' in shape.serialization: 

414 return shape.serialization['queryName'] 

415 elif 'name' in shape.serialization: 

416 # A locationName is always capitalized 

417 # on input for the ec2 protocol. 

418 name = shape.serialization['name'] 

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

420 else: 

421 return default_name 

422 

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

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

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

426 element_shape = shape.member 

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

428 

429 

430class JSONSerializer(Serializer): 

431 TIMESTAMP_FORMAT = 'unixtimestamp' 

432 

433 def serialize_to_request(self, parameters, operation_model): 

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

435 operation_model.metadata['targetPrefix'], 

436 operation_model.name, 

437 ) 

438 json_version = operation_model.metadata['jsonVersion'] 

439 serialized = self._create_default_request() 

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

441 'method', self.DEFAULT_METHOD 

442 ) 

443 serialized['headers'] = { 

444 'X-Amz-Target': target, 

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

446 } 

447 self._handle_query_compatible_trait(operation_model, serialized) 

448 

449 body = self.MAP_TYPE() 

450 input_shape = operation_model.input_shape 

451 if input_shape is not None: 

452 self._serialize(body, parameters, input_shape) 

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

454 

455 host_prefix = self._expand_host_prefix(parameters, operation_model) 

456 if host_prefix is not None: 

457 serialized['host_prefix'] = host_prefix 

458 

459 return serialized 

460 

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

462 method = getattr( 

463 self, 

464 f'_serialize_type_{shape.type_name}', 

465 self._default_serialize, 

466 ) 

467 method(serialized, value, shape, key) 

468 

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

470 if shape.is_document_type: 

471 serialized[key] = value 

472 else: 

473 if key is not None: 

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

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

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

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

478 # dictionary we just created. 

479 new_serialized = self.MAP_TYPE() 

480 serialized[key] = new_serialized 

481 serialized = new_serialized 

482 members = shape.members 

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

484 member_shape = members[member_key] 

485 if 'name' in member_shape.serialization: 

486 member_key = member_shape.serialization['name'] 

487 self._serialize( 

488 serialized, member_value, member_shape, member_key 

489 ) 

490 

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

492 map_obj = self.MAP_TYPE() 

493 serialized[key] = map_obj 

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

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

496 

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

498 list_obj = [] 

499 serialized[key] = list_obj 

500 for list_item in value: 

501 wrapper = {} 

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

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

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

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

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

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

508 

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

510 serialized[key] = value 

511 

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

513 serialized[key] = self._convert_timestamp_to_str( 

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

515 ) 

516 

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

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

519 

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

521 if isinstance(value, decimal.Decimal): 

522 value = float(value) 

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

524 

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

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

527 

528 

529class CBORSerializer(Serializer): 

530 UNSIGNED_INT_MAJOR_TYPE = 0 

531 NEGATIVE_INT_MAJOR_TYPE = 1 

532 BLOB_MAJOR_TYPE = 2 

533 STRING_MAJOR_TYPE = 3 

534 LIST_MAJOR_TYPE = 4 

535 MAP_MAJOR_TYPE = 5 

536 TAG_MAJOR_TYPE = 6 

537 FLOAT_AND_SIMPLE_MAJOR_TYPE = 7 

538 

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

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

541 if method is None: 

542 raise ValueError( 

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

544 f"serialize request" 

545 ) 

546 method(serialized, value, shape, key) 

547 

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

549 if value >= 0: 

550 major_type = self.UNSIGNED_INT_MAJOR_TYPE 

551 else: 

552 major_type = self.NEGATIVE_INT_MAJOR_TYPE 

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

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

555 # minus the value 

556 value = -1 - value 

557 additional_info, num_bytes = self._get_additional_info_and_num_bytes( 

558 value 

559 ) 

560 initial_byte = self._get_initial_byte(major_type, additional_info) 

561 if num_bytes == 0: 

562 serialized.extend(initial_byte) 

563 else: 

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

565 

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

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

568 

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

570 if isinstance(value, str): 

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

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

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

574 # validated to ensure they have a read method 

575 value = value.read() 

576 length = len(value) 

577 additional_info, num_bytes = self._get_additional_info_and_num_bytes( 

578 length 

579 ) 

580 initial_byte = self._get_initial_byte( 

581 self.BLOB_MAJOR_TYPE, additional_info 

582 ) 

583 if num_bytes == 0: 

584 serialized.extend(initial_byte) 

585 else: 

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

587 serialized.extend(value) 

588 

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

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

591 length = len(encoded) 

592 additional_info, num_bytes = self._get_additional_info_and_num_bytes( 

593 length 

594 ) 

595 initial_byte = self._get_initial_byte( 

596 self.STRING_MAJOR_TYPE, additional_info 

597 ) 

598 if num_bytes == 0: 

599 serialized.extend(initial_byte + encoded) 

600 else: 

601 serialized.extend( 

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

603 ) 

604 

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

606 length = len(value) 

607 additional_info, num_bytes = self._get_additional_info_and_num_bytes( 

608 length 

609 ) 

610 initial_byte = self._get_initial_byte( 

611 self.LIST_MAJOR_TYPE, additional_info 

612 ) 

613 if num_bytes == 0: 

614 serialized.extend(initial_byte) 

615 else: 

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

617 for item in value: 

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

619 

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

621 length = len(value) 

622 additional_info, num_bytes = self._get_additional_info_and_num_bytes( 

623 length 

624 ) 

625 initial_byte = self._get_initial_byte( 

626 self.MAP_MAJOR_TYPE, additional_info 

627 ) 

628 if num_bytes == 0: 

629 serialized.extend(initial_byte) 

630 else: 

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

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

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

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

635 

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

637 if key is not None: 

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

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

640 

641 # Remove `None` values from the dictionary 

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

643 

644 map_length = len(value) 

645 additional_info, num_bytes = self._get_additional_info_and_num_bytes( 

646 map_length 

647 ) 

648 initial_byte = self._get_initial_byte( 

649 self.MAP_MAJOR_TYPE, additional_info 

650 ) 

651 if num_bytes == 0: 

652 serialized.extend(initial_byte) 

653 else: 

654 serialized.extend( 

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

656 ) 

657 

658 members = shape.members 

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

660 member_shape = members[member_key] 

661 if 'name' in member_shape.serialization: 

662 member_key = member_shape.serialization['name'] 

663 if member_value is not None: 

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

665 self._serialize_data_item( 

666 serialized, member_value, member_shape 

667 ) 

668 

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

670 timestamp = self._convert_timestamp_to_str(value) 

671 tag = 1 # Use tag 1 for unix timestamp 

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

673 serialized.extend(initial_byte) # Tagging the timestamp 

674 # Tag 1 permits either an integer or a floating-point epoch seconds 

675 # value; a float is used when sub-second precision is present. 

676 if isinstance(timestamp, float): 

677 self._serialize_type_double(serialized, timestamp, shape, key) 

678 else: 

679 self._serialize_type_integer(serialized, timestamp, shape, key) 

680 

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

682 if self._is_special_number(value): 

683 serialized.extend( 

684 self._get_bytes_for_special_numbers(value) 

685 ) # Handle special values like NaN or Infinity 

686 else: 

687 initial_byte = self._get_initial_byte( 

688 self.FLOAT_AND_SIMPLE_MAJOR_TYPE, 26 

689 ) 

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

691 

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

693 if self._is_special_number(value): 

694 serialized.extend( 

695 self._get_bytes_for_special_numbers(value) 

696 ) # Handle special values like NaN or Infinity 

697 else: 

698 initial_byte = self._get_initial_byte( 

699 self.FLOAT_AND_SIMPLE_MAJOR_TYPE, 27 

700 ) 

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

702 

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

704 additional_info = 21 if value else 20 

705 serialized.extend( 

706 self._get_initial_byte( 

707 self.FLOAT_AND_SIMPLE_MAJOR_TYPE, additional_info 

708 ) 

709 ) 

710 

711 def _get_additional_info_and_num_bytes(self, value): 

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

713 # encoding 

714 if value < 24: 

715 return value, 0 

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

717 # correspond to additional info 24 

718 elif value < 256: 

719 return 24, 1 

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

721 # info 25 

722 elif value < 65536: 

723 return 25, 2 

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

725 # additional info 26 

726 elif value < 4294967296: 

727 return 26, 4 

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

729 # to additional info 27 

730 else: 

731 return 27, 8 

732 

733 def _get_initial_byte(self, major_type, additional_info): 

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

735 # major type by 5 

736 major_type_bytes = major_type << 5 

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

738 

739 def _is_special_number(self, value): 

740 return any( 

741 [ 

742 value == float('inf'), 

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

744 math.isnan(value), 

745 ] 

746 ) 

747 

748 def _get_bytes_for_special_numbers(self, value): 

749 additional_info = 25 

750 initial_byte = self._get_initial_byte( 

751 self.FLOAT_AND_SIMPLE_MAJOR_TYPE, additional_info 

752 ) 

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

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

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

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

757 elif math.isnan(value): 

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

759 

760 

761class BaseRestSerializer(Serializer): 

762 """Base class for rest protocols. 

763 

764 The only variance between the various rest protocols is the 

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

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

767 

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

769 

770 """ 

771 

772 QUERY_STRING_TIMESTAMP_FORMAT = 'iso8601' 

773 HEADER_TIMESTAMP_FORMAT = 'rfc822' 

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

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

776 # to put the serialized value. 

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

778 

779 def serialize_to_request(self, parameters, operation_model): 

780 serialized = self._create_default_request() 

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

782 'method', self.DEFAULT_METHOD 

783 ) 

784 shape = operation_model.input_shape 

785 

786 host_prefix = self._expand_host_prefix(parameters, operation_model) 

787 if host_prefix is not None: 

788 serialized['host_prefix'] = host_prefix 

789 

790 if shape is None: 

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

792 return serialized 

793 shape_members = shape.members 

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

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

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

797 # query_string_kwargs because they are templated, so we need 

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

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

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

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

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

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

804 partitioned = { 

805 'uri_path_kwargs': self.MAP_TYPE(), 

806 'query_string_kwargs': self.MAP_TYPE(), 

807 'body_kwargs': self.MAP_TYPE(), 

808 'headers': self.MAP_TYPE(), 

809 } 

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

811 if param_value is None: 

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

813 continue 

814 self._partition_parameters( 

815 partitioned, param_name, param_value, shape_members 

816 ) 

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

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

819 ) 

820 

821 if 'authPath' in operation_model.http: 

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

823 operation_model.http['authPath'], 

824 partitioned['uri_path_kwargs'], 

825 ) 

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

827 # where the requestUri path already has query parameters. 

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

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

830 if partitioned['headers']: 

831 serialized['headers'] = partitioned['headers'] 

832 self._serialize_payload( 

833 partitioned, parameters, serialized, shape, shape_members 

834 ) 

835 self._serialize_content_type(serialized, shape, shape_members) 

836 

837 return serialized 

838 

839 def _render_uri_template(self, uri_template, params): 

840 # We need to handle two cases:: 

841 # 

842 # /{Bucket}/foo 

843 # /{Key+}/bar 

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

845 # be one greedy key. 

846 encoded_params = {} 

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

848 if template_param.endswith('+'): 

849 encoded_params[template_param] = percent_encode( 

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

851 ) 

852 else: 

853 encoded_params[template_param] = percent_encode( 

854 params[template_param] 

855 ) 

856 return uri_template.format(**encoded_params) 

857 

858 def _serialize_payload( 

859 self, partitioned, parameters, serialized, shape, shape_members 

860 ): 

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

862 # parameters - The user input params. 

863 # serialized - The final serialized request dict. 

864 # shape - Describes the expected input shape 

865 # shape_members - The members of the input struct shape 

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

867 if self._has_streaming_payload(payload_member, shape_members): 

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

869 # value of the payload. 

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

871 body_payload = self._encode_payload(body_payload) 

872 serialized['body'] = body_payload 

873 elif payload_member is not None: 

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

875 # member to they body. 

876 body_params = parameters.get(payload_member) 

877 if body_params is not None: 

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

879 body_params, shape_members[payload_member] 

880 ) 

881 else: 

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

883 elif partitioned['body_kwargs']: 

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

885 partitioned['body_kwargs'], shape 

886 ) 

887 elif self._requires_empty_body(shape): 

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

889 

890 def _serialize_empty_body(self): 

891 return b'' 

892 

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

894 """ 

895 Some protocols require varied Content-Type headers 

896 depending on user input. This allows subclasses to apply 

897 this conditionally. 

898 """ 

899 pass 

900 

901 def _requires_empty_body(self, shape): 

902 """ 

903 Some protocols require a specific body to represent an empty 

904 payload. This allows subclasses to apply this conditionally. 

905 """ 

906 return False 

907 

908 def _has_streaming_payload(self, payload, shape_members): 

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

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

911 'blob', 

912 'string', 

913 ) 

914 

915 def _encode_payload(self, body): 

916 if isinstance(body, str): 

917 return body.encode(self.DEFAULT_ENCODING) 

918 return body 

919 

920 def _partition_parameters( 

921 self, partitioned, param_name, param_value, shape_members 

922 ): 

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

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

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

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

927 member = shape_members[param_name] 

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

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

930 if location == 'uri': 

931 uri_path_value = self._get_uri_and_query_string_value( 

932 param_value, member 

933 ) 

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

935 elif location == 'querystring': 

936 if isinstance(param_value, dict): 

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

938 elif member.type_name == 'list': 

939 new_param = [ 

940 self._get_uri_and_query_string_value(value, member.member) 

941 for value in param_value 

942 ] 

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

944 else: 

945 new_param = self._get_uri_and_query_string_value( 

946 param_value, member 

947 ) 

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

949 elif location == 'header': 

950 shape = shape_members[param_name] 

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

952 # Empty lists should not be set on the headers 

953 return 

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

955 shape, param_value 

956 ) 

957 elif location == 'headers': 

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

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

960 header_prefix = key_name 

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

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

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

964 # plus the key provided by the user. 

965 self._do_serialize_header_map( 

966 header_prefix, partitioned['headers'], param_value 

967 ) 

968 else: 

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

970 

971 def _get_uri_and_query_string_value(self, param_value, member): 

972 if member.type_name == 'boolean': 

973 return str(param_value).lower() 

974 elif member.type_name == 'timestamp': 

975 timestamp_format = member.serialization.get( 

976 'timestampFormat', self.QUERY_STRING_TIMESTAMP_FORMAT 

977 ) 

978 return self._convert_timestamp_to_str( 

979 param_value, timestamp_format 

980 ) 

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

982 return str(self._handle_float(param_value)) 

983 return param_value 

984 

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

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

987 full_key = header_prefix + key 

988 headers[full_key] = val 

989 

990 def _serialize_body_params(self, params, shape): 

991 raise NotImplementedError('_serialize_body_params') 

992 

993 def _convert_header_value(self, shape, value): 

994 if shape.type_name == 'timestamp': 

995 datetime_obj = parse_to_aware_datetime(value) 

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

997 timestamp_format = shape.serialization.get( 

998 'timestampFormat', self.HEADER_TIMESTAMP_FORMAT 

999 ) 

1000 return str( 

1001 self._convert_timestamp_to_str(timestamp, timestamp_format) 

1002 ) 

1003 elif shape.type_name == 'list': 

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

1005 converted_value = [ 

1006 self._escape_header_list_string(v) 

1007 for v in value 

1008 if v is not None 

1009 ] 

1010 else: 

1011 converted_value = [ 

1012 self._convert_header_value(shape.member, v) 

1013 for v in value 

1014 if v is not None 

1015 ] 

1016 return ",".join(converted_value) 

1017 elif is_json_value_header(shape): 

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

1019 # the header. 

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

1021 elif shape.type_name == 'boolean': 

1022 return str(value).lower() 

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

1024 return str(self._handle_float(value)) 

1025 else: 

1026 return str(value) 

1027 

1028 def _escape_header_list_string(self, value): 

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

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

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

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

1033 else: 

1034 return value 

1035 

1036 

1037class BaseRpcV2Serializer(Serializer): 

1038 """Base class for RPCv2 protocols. 

1039 

1040 The only variance between the various RPCv2 protocols is the 

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

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

1043 

1044 Subclasses must implement the ``_serialize_body_params`` and 

1045 ``_serialize_headers`` methods. 

1046 

1047 """ 

1048 

1049 def serialize_to_request(self, parameters, operation_model): 

1050 serialized = self._create_default_request() 

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

1052 operation_name = operation_model.name 

1053 serialized['url_path'] = ( 

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

1055 ) 

1056 

1057 input_shape = operation_model.input_shape 

1058 if input_shape is not None: 

1059 self._serialize_payload(parameters, serialized, input_shape) 

1060 

1061 host_prefix = self._expand_host_prefix(parameters, operation_model) 

1062 if host_prefix is not None: 

1063 serialized['host_prefix'] = host_prefix 

1064 

1065 self._serialize_headers(serialized, operation_model) 

1066 

1067 return serialized 

1068 

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

1070 body_payload = self._serialize_body_params(parameters, shape) 

1071 serialized['body'] = body_payload 

1072 

1073 def _serialize_headers(self, serialized, operation_model): 

1074 raise NotImplementedError("_serialize_headers") 

1075 

1076 def _serialize_body_params(self, parameters, shape): 

1077 raise NotImplementedError("_serialize_body_params") 

1078 

1079 

1080class RestJSONSerializer(BaseRestSerializer, JSONSerializer): 

1081 def _serialize_empty_body(self): 

1082 return b'{}' 

1083 

1084 def _requires_empty_body(self, shape): 

1085 """ 

1086 Serialize an empty JSON object whenever the shape has 

1087 members not targeting a location. 

1088 """ 

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

1090 if 'location' not in val.serialization: 

1091 return True 

1092 return False 

1093 

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

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

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

1097 if self._has_streaming_payload(payload, shape_members): 

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

1099 return 

1100 

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

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

1103 if has_body and not has_content_type: 

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

1105 

1106 def _serialize_body_params(self, params, shape): 

1107 serialized_body = self.MAP_TYPE() 

1108 self._serialize(serialized_body, params, shape) 

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

1110 

1111 

1112class RestXMLSerializer(BaseRestSerializer): 

1113 TIMESTAMP_FORMAT = 'iso8601' 

1114 

1115 def _serialize_body_params(self, params, shape): 

1116 root_name = shape.serialization['name'] 

1117 pseudo_root = ElementTree.Element('') 

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

1119 real_root = list(pseudo_root)[0] 

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

1121 

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

1123 method = getattr( 

1124 self, 

1125 f'_serialize_type_{shape.type_name}', 

1126 self._default_serialize, 

1127 ) 

1128 method(xmlnode, params, shape, name) 

1129 

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

1131 structure_node = ElementTree.SubElement(xmlnode, name) 

1132 

1133 self._add_xml_namespace(shape, structure_node) 

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

1135 member_shape = shape.members[key] 

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

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

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

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

1140 # *current* node. 

1141 if value is None: 

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

1143 return 

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

1145 # xmlAttributes must have a serialization name. 

1146 xml_attribute_name = member_shape.serialization['name'] 

1147 structure_node.attrib[xml_attribute_name] = value 

1148 continue 

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

1150 

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

1152 member_shape = shape.member 

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

1154 element_name = name 

1155 list_node = xmlnode 

1156 else: 

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

1158 list_node = ElementTree.SubElement(xmlnode, name) 

1159 self._add_xml_namespace(shape, list_node) 

1160 for item in params: 

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

1162 

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

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

1165 # we serialize this as: 

1166 # <MyMap> 

1167 # <entry> 

1168 # <key>key1</key> 

1169 # <value>val1</value> 

1170 # </entry> 

1171 # </MyMap> 

1172 if not self._is_shape_flattened(shape): 

1173 node = ElementTree.SubElement(xmlnode, name) 

1174 self._add_xml_namespace(shape, node) 

1175 

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

1177 sub_node = ( 

1178 ElementTree.SubElement(xmlnode, name) 

1179 if self._is_shape_flattened(shape) 

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

1181 ) 

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

1183 val_name = self._get_serialized_name( 

1184 shape.value, default_name='value' 

1185 ) 

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

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

1188 

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

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

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

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

1193 node = ElementTree.SubElement(xmlnode, name) 

1194 if params: 

1195 str_value = 'true' 

1196 else: 

1197 str_value = 'false' 

1198 node.text = str_value 

1199 self._add_xml_namespace(shape, node) 

1200 

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

1202 node = ElementTree.SubElement(xmlnode, name) 

1203 node.text = self._get_base64(params) 

1204 self._add_xml_namespace(shape, node) 

1205 

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

1207 node = ElementTree.SubElement(xmlnode, name) 

1208 node.text = str( 

1209 self._convert_timestamp_to_str( 

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

1211 ) 

1212 ) 

1213 self._add_xml_namespace(shape, node) 

1214 

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

1216 node = ElementTree.SubElement(xmlnode, name) 

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

1218 self._add_xml_namespace(shape, node) 

1219 

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

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

1222 

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

1224 node = ElementTree.SubElement(xmlnode, name) 

1225 node.text = str(params) 

1226 self._add_xml_namespace(shape, node) 

1227 

1228 def _add_xml_namespace(self, shape, structure_node): 

1229 if 'xmlNamespace' in shape.serialization: 

1230 namespace_metadata = shape.serialization['xmlNamespace'] 

1231 attribute_name = 'xmlns' 

1232 if isinstance(namespace_metadata, dict): 

1233 if namespace_metadata.get('prefix'): 

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

1235 structure_node.attrib[attribute_name] = namespace_metadata[ 

1236 'uri' 

1237 ] 

1238 elif isinstance(namespace_metadata, str): 

1239 structure_node.attrib[attribute_name] = namespace_metadata 

1240 

1241 

1242class RpcV2CBORSerializer(BaseRpcV2Serializer, CBORSerializer): 

1243 TIMESTAMP_FORMAT = 'unixtimestamp' 

1244 

1245 def serialize_to_request(self, parameters, operation_model): 

1246 register_feature_id('PROTOCOL_RPC_V2_CBOR') 

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

1248 

1249 def _serialize_body_params(self, parameters, input_shape): 

1250 body = bytearray() 

1251 self._serialize_data_item(body, parameters, input_shape) 

1252 return bytes(body) 

1253 

1254 def _serialize_headers(self, serialized, operation_model): 

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

1256 

1257 if operation_model.has_event_stream_output: 

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

1259 else: 

1260 header_val = 'application/cbor' 

1261 self._handle_query_compatible_trait(operation_model, serialized) 

1262 

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

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

1265 

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

1267 if not has_content_type and has_body: 

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

1269 

1270 

1271SERIALIZERS = { 

1272 'ec2': EC2Serializer, 

1273 'query': QuerySerializer, 

1274 'json': JSONSerializer, 

1275 'rest-json': RestJSONSerializer, 

1276 'rest-xml': RestXMLSerializer, 

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

1278}