localstack-core 4.7.1.dev49__py3-none-any.whl → 4.10.1.dev12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. localstack/aws/api/cloudformation/__init__.py +18 -4
  2. localstack/aws/api/cloudwatch/__init__.py +41 -1
  3. localstack/aws/api/config/__init__.py +4 -0
  4. localstack/aws/api/core.py +6 -2
  5. localstack/aws/api/dynamodb/__init__.py +30 -0
  6. localstack/aws/api/ec2/__init__.py +1522 -65
  7. localstack/aws/api/iam/__init__.py +7 -0
  8. localstack/aws/api/kinesis/__init__.py +19 -0
  9. localstack/aws/api/kms/__init__.py +6 -0
  10. localstack/aws/api/lambda_/__init__.py +13 -0
  11. localstack/aws/api/logs/__init__.py +15 -0
  12. localstack/aws/api/redshift/__init__.py +9 -3
  13. localstack/aws/api/route53/__init__.py +5 -0
  14. localstack/aws/api/s3/__init__.py +12 -0
  15. localstack/aws/api/s3control/__init__.py +54 -0
  16. localstack/aws/api/ssm/__init__.py +2 -0
  17. localstack/aws/api/transcribe/__init__.py +17 -0
  18. localstack/aws/client.py +7 -2
  19. localstack/aws/forwarder.py +52 -5
  20. localstack/aws/handlers/analytics.py +1 -1
  21. localstack/aws/handlers/internal_requests.py +6 -1
  22. localstack/aws/handlers/logging.py +12 -2
  23. localstack/aws/handlers/metric_handler.py +41 -1
  24. localstack/aws/handlers/service.py +40 -20
  25. localstack/aws/mocking.py +2 -2
  26. localstack/aws/patches.py +2 -2
  27. localstack/aws/protocol/parser.py +459 -32
  28. localstack/aws/protocol/serializer.py +689 -69
  29. localstack/aws/protocol/service_router.py +120 -20
  30. localstack/aws/protocol/validate.py +1 -1
  31. localstack/aws/scaffold.py +1 -1
  32. localstack/aws/skeleton.py +4 -2
  33. localstack/aws/spec-patches.json +58 -0
  34. localstack/aws/spec.py +37 -16
  35. localstack/cli/exceptions.py +1 -1
  36. localstack/cli/localstack.py +6 -6
  37. localstack/cli/lpm.py +3 -4
  38. localstack/cli/plugins.py +1 -1
  39. localstack/cli/profiles.py +1 -2
  40. localstack/config.py +25 -18
  41. localstack/constants.py +4 -29
  42. localstack/dev/kubernetes/__main__.py +130 -7
  43. localstack/dev/run/configurators.py +1 -4
  44. localstack/dev/run/paths.py +1 -1
  45. localstack/dns/plugins.py +5 -1
  46. localstack/dns/server.py +13 -4
  47. localstack/logging/format.py +3 -3
  48. localstack/packages/api.py +9 -8
  49. localstack/packages/core.py +2 -2
  50. localstack/packages/plugins.py +0 -8
  51. localstack/runtime/analytics.py +3 -0
  52. localstack/runtime/hooks.py +1 -1
  53. localstack/runtime/init.py +2 -2
  54. localstack/runtime/main.py +5 -5
  55. localstack/runtime/patches.py +2 -2
  56. localstack/services/apigateway/helpers.py +1 -4
  57. localstack/services/apigateway/legacy/helpers.py +7 -8
  58. localstack/services/apigateway/legacy/integration.py +4 -3
  59. localstack/services/apigateway/legacy/invocations.py +6 -5
  60. localstack/services/apigateway/legacy/provider.py +148 -68
  61. localstack/services/apigateway/legacy/templates.py +1 -1
  62. localstack/services/apigateway/next_gen/execute_api/handlers/method_request.py +7 -2
  63. localstack/services/apigateway/next_gen/execute_api/handlers/resource_router.py +1 -2
  64. localstack/services/apigateway/next_gen/execute_api/integrations/aws.py +3 -0
  65. localstack/services/apigateway/next_gen/execute_api/integrations/http.py +3 -3
  66. localstack/services/apigateway/next_gen/execute_api/template_mapping.py +2 -2
  67. localstack/services/apigateway/next_gen/execute_api/test_invoke.py +114 -9
  68. localstack/services/apigateway/next_gen/provider.py +5 -0
  69. localstack/services/apigateway/resource_providers/aws_apigateway_resource.py +1 -1
  70. localstack/services/cloudformation/api_utils.py +4 -8
  71. localstack/services/cloudformation/cfn_utils.py +1 -1
  72. localstack/services/cloudformation/engine/entities.py +14 -4
  73. localstack/services/cloudformation/engine/template_deployer.py +6 -4
  74. localstack/services/cloudformation/engine/transformers.py +6 -4
  75. localstack/services/cloudformation/engine/v2/change_set_model.py +201 -13
  76. localstack/services/cloudformation/engine/v2/change_set_model_describer.py +52 -3
  77. localstack/services/cloudformation/engine/v2/change_set_model_executor.py +117 -76
  78. localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +205 -52
  79. localstack/services/cloudformation/engine/v2/change_set_model_transform.py +350 -116
  80. localstack/services/cloudformation/engine/v2/change_set_model_validator.py +56 -14
  81. localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +1 -0
  82. localstack/services/cloudformation/engine/v2/resolving.py +7 -5
  83. localstack/services/cloudformation/engine/yaml_parser.py +9 -2
  84. localstack/services/cloudformation/provider.py +7 -5
  85. localstack/services/cloudformation/resource_provider.py +7 -1
  86. localstack/services/cloudformation/resources.py +24149 -0
  87. localstack/services/cloudformation/service_models.py +2 -2
  88. localstack/services/cloudformation/v2/entities.py +19 -9
  89. localstack/services/cloudformation/v2/provider.py +336 -106
  90. localstack/services/cloudformation/v2/types.py +13 -7
  91. localstack/services/cloudformation/v2/utils.py +4 -1
  92. localstack/services/cloudwatch/alarm_scheduler.py +4 -1
  93. localstack/services/cloudwatch/provider.py +18 -13
  94. localstack/services/cloudwatch/provider_v2.py +25 -28
  95. localstack/services/dynamodb/packages.py +2 -1
  96. localstack/services/dynamodb/provider.py +42 -0
  97. localstack/services/dynamodb/server.py +2 -2
  98. localstack/services/dynamodb/v2/provider.py +42 -0
  99. localstack/services/ecr/resource_providers/aws_ecr_repository.py +5 -2
  100. localstack/services/edge.py +1 -1
  101. localstack/services/es/provider.py +2 -2
  102. localstack/services/events/event_rule_engine.py +31 -13
  103. localstack/services/events/models.py +4 -5
  104. localstack/services/events/provider.py +17 -14
  105. localstack/services/events/target.py +17 -9
  106. localstack/services/events/v1/provider.py +5 -5
  107. localstack/services/firehose/provider.py +14 -4
  108. localstack/services/iam/provider.py +11 -116
  109. localstack/services/iam/resources/policy_simulator.py +133 -0
  110. localstack/services/kinesis/models.py +15 -2
  111. localstack/services/kinesis/provider.py +86 -3
  112. localstack/services/kms/provider.py +14 -5
  113. localstack/services/lambda_/api_utils.py +6 -3
  114. localstack/services/lambda_/invocation/docker_runtime_executor.py +1 -1
  115. localstack/services/lambda_/invocation/event_manager.py +1 -1
  116. localstack/services/lambda_/invocation/internal_sqs_queue.py +5 -9
  117. localstack/services/lambda_/invocation/lambda_models.py +10 -7
  118. localstack/services/lambda_/invocation/lambda_service.py +5 -1
  119. localstack/services/lambda_/packages.py +1 -1
  120. localstack/services/lambda_/provider.py +4 -3
  121. localstack/services/lambda_/provider_utils.py +1 -1
  122. localstack/services/logs/provider.py +36 -19
  123. localstack/services/moto.py +2 -1
  124. localstack/services/opensearch/cluster.py +15 -7
  125. localstack/services/opensearch/packages.py +26 -7
  126. localstack/services/opensearch/provider.py +8 -2
  127. localstack/services/opensearch/versions.py +56 -7
  128. localstack/services/plugins.py +11 -7
  129. localstack/services/providers.py +10 -2
  130. localstack/services/redshift/provider.py +0 -21
  131. localstack/services/s3/constants.py +5 -2
  132. localstack/services/s3/cors.py +4 -4
  133. localstack/services/s3/models.py +1 -1
  134. localstack/services/s3/notifications.py +55 -39
  135. localstack/services/s3/presigned_url.py +35 -54
  136. localstack/services/s3/provider.py +73 -15
  137. localstack/services/s3/utils.py +42 -22
  138. localstack/services/s3/validation.py +46 -32
  139. localstack/services/s3/website_hosting.py +4 -2
  140. localstack/services/ses/provider.py +18 -8
  141. localstack/services/sns/constants.py +7 -1
  142. localstack/services/sns/executor.py +9 -2
  143. localstack/services/sns/provider.py +8 -5
  144. localstack/services/sns/publisher.py +31 -16
  145. localstack/services/sns/v2/models.py +167 -0
  146. localstack/services/sns/v2/provider.py +867 -0
  147. localstack/services/sns/v2/utils.py +130 -0
  148. localstack/services/sqs/constants.py +1 -1
  149. localstack/services/sqs/developer_api.py +205 -0
  150. localstack/services/sqs/models.py +48 -5
  151. localstack/services/sqs/provider.py +38 -311
  152. localstack/services/sqs/query_api.py +6 -2
  153. localstack/services/sqs/utils.py +121 -2
  154. localstack/services/ssm/provider.py +1 -1
  155. localstack/services/stepfunctions/asl/component/intrinsic/member.py +1 -1
  156. localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison.py +5 -11
  157. localstack/services/stepfunctions/asl/component/state/state_choice/state_choice.py +2 -2
  158. localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +2 -2
  159. localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/state_parallel.py +1 -1
  160. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task.py +2 -2
  161. localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +1 -1
  162. localstack/services/stepfunctions/asl/component/state/state_pass/state_pass.py +2 -2
  163. localstack/services/stepfunctions/asl/component/state/state_succeed/state_succeed.py +1 -1
  164. localstack/services/stepfunctions/asl/component/state/state_wait/state_wait.py +1 -1
  165. localstack/services/stepfunctions/asl/eval/environment.py +1 -1
  166. localstack/services/stepfunctions/asl/jsonata/jsonata.py +1 -1
  167. localstack/services/stepfunctions/backend/execution.py +2 -1
  168. localstack/services/stores.py +1 -1
  169. localstack/services/transcribe/provider.py +6 -1
  170. localstack/state/codecs.py +61 -0
  171. localstack/state/core.py +11 -5
  172. localstack/state/pickle.py +10 -49
  173. localstack/testing/aws/cloudformation_utils.py +1 -1
  174. localstack/testing/pytest/cloudformation/fixtures.py +3 -3
  175. localstack/testing/pytest/cloudformation/transformers.py +0 -0
  176. localstack/testing/pytest/container.py +4 -5
  177. localstack/testing/pytest/fixtures.py +33 -31
  178. localstack/testing/pytest/in_memory_localstack.py +0 -4
  179. localstack/testing/pytest/marking.py +38 -11
  180. localstack/testing/pytest/stepfunctions/utils.py +4 -3
  181. localstack/testing/pytest/util.py +1 -1
  182. localstack/testing/pytest/validation_tracking.py +1 -2
  183. localstack/testing/snapshots/transformer_utility.py +6 -1
  184. localstack/utils/analytics/events.py +2 -2
  185. localstack/utils/analytics/metadata.py +6 -4
  186. localstack/utils/analytics/metrics/counter.py +8 -15
  187. localstack/utils/analytics/publisher.py +1 -2
  188. localstack/utils/analytics/service_providers.py +19 -0
  189. localstack/utils/analytics/service_request_aggregator.py +2 -2
  190. localstack/utils/archives.py +11 -11
  191. localstack/utils/asyncio.py +2 -2
  192. localstack/utils/aws/arns.py +24 -29
  193. localstack/utils/aws/aws_responses.py +8 -8
  194. localstack/utils/aws/aws_stack.py +2 -3
  195. localstack/utils/aws/dead_letter_queue.py +1 -5
  196. localstack/utils/aws/message_forwarding.py +1 -2
  197. localstack/utils/aws/request_context.py +4 -5
  198. localstack/utils/aws/resources.py +1 -1
  199. localstack/utils/aws/templating.py +1 -1
  200. localstack/utils/batch_policy.py +3 -3
  201. localstack/utils/bootstrap.py +21 -13
  202. localstack/utils/catalog/catalog.py +139 -0
  203. localstack/utils/catalog/catalog_loader.py +119 -0
  204. localstack/utils/catalog/common.py +58 -0
  205. localstack/utils/catalog/plugins.py +28 -0
  206. localstack/utils/cloudwatch/cloudwatch_util.py +5 -5
  207. localstack/utils/collections.py +7 -8
  208. localstack/utils/config_listener.py +1 -1
  209. localstack/utils/container_networking.py +2 -3
  210. localstack/utils/container_utils/container_client.py +135 -136
  211. localstack/utils/container_utils/docker_cmd_client.py +85 -69
  212. localstack/utils/container_utils/docker_sdk_client.py +69 -66
  213. localstack/utils/crypto.py +10 -10
  214. localstack/utils/diagnose.py +3 -4
  215. localstack/utils/docker_utils.py +9 -5
  216. localstack/utils/files.py +33 -13
  217. localstack/utils/functions.py +4 -3
  218. localstack/utils/http.py +11 -11
  219. localstack/utils/json.py +20 -6
  220. localstack/utils/kinesis/kinesis_connector.py +2 -1
  221. localstack/utils/net.py +15 -9
  222. localstack/utils/no_exit_argument_parser.py +2 -2
  223. localstack/utils/numbers.py +9 -2
  224. localstack/utils/objects.py +7 -6
  225. localstack/utils/patch.py +10 -3
  226. localstack/utils/run.py +12 -11
  227. localstack/utils/scheduler.py +11 -11
  228. localstack/utils/server/tcp_proxy.py +2 -2
  229. localstack/utils/serving.py +3 -4
  230. localstack/utils/strings.py +15 -16
  231. localstack/utils/sync.py +126 -1
  232. localstack/utils/tagging.py +8 -6
  233. localstack/utils/testutil.py +8 -8
  234. localstack/utils/threads.py +2 -2
  235. localstack/utils/time.py +12 -4
  236. localstack/utils/urls.py +1 -3
  237. localstack/utils/xray/traceid.py +1 -1
  238. localstack/version.py +16 -3
  239. {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/METADATA +18 -14
  240. {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/RECORD +248 -239
  241. {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/entry_points.txt +8 -4
  242. localstack_core-4.10.1.dev12.dist-info/plux.json +1 -0
  243. localstack/packages/terraform.py +0 -46
  244. localstack/services/cloudformation/deploy.html +0 -144
  245. localstack/services/cloudformation/deploy_ui.py +0 -47
  246. localstack/services/cloudformation/plugins.py +0 -12
  247. localstack_core-4.7.1.dev49.dist-info/plux.json +0 -1
  248. {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack +0 -0
  249. {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack-supervisor +0 -0
  250. {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack.bat +0 -0
  251. {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/WHEEL +0 -0
  252. {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/licenses/LICENSE.txt +0 -0
  253. {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/top_level.txt +0 -0
@@ -14,27 +14,34 @@ The different protocols have many similarities. The class hierarchy is
14
14
  designed such that the serializers share as much logic as possible.
15
15
  The class hierarchy looks as follows:
16
16
  ::
17
- ┌───────────────────┐
18
- │ResponseSerializer │
19
- └───────────────────┘
20
- ▲ ▲
21
- ┌──────────────────────┘ └──────────────────┐
22
- ┌────────────┴────────────┐ ┌────────────┴─────────────┐ ┌─────────┴────────────┐
23
- │BaseXMLResponseSerializer│ BaseRestResponseSerializerJSONResponseSerializer│
24
- └─────────────────────────┘ └──────────────────────────┘ └──────────────────────┘
25
- ▲ ▲
26
- ┌──────────────────────┴─┐ ┌┴─────────────┴──────────┐ ┌┴──────────────┴──────────┐
27
- QueryResponseSerializer RestXMLResponseSerializer RestJSONResponseSerializer│
28
- └────────────────────────┘ └─────────────────────────┘ └──────────────────────────┘
29
-
30
- ┌──────────┴──────────┐
17
+ ┌────────────────────┐
18
+ ResponseSerializer │
19
+ └────────────────────┘
20
+ ▲ ▲
21
+ ┌─────────────────┬───────┘ └──────────────┬──────────────────────┐
22
+ ┌────────────┴────────────┐ │ ┌───────┴──────────────┐ │ ┌────────────┴─────────────┐
23
+ │BaseXMLResponseSerializer│ │JSONResponseSerializer│ │ │BaseCBORResponseSerializer│
24
+ └─────────────────────────┘ │ └──────────────────────┘ │ └──────────────────────────┘
25
+ ▲ ▲ ┌─────────────┴────────────┐ ┌─────┴─────────────────────┐
26
+ │ │ │BaseRestResponseSerializer│ │ │BaseRpcV2ResponseSerializer│ │ │
27
+ └──────────────────────────┘ └───────────────────────────┘
28
+ │ │ ▲ ▲ │ ▲ │ │
29
+ │ │ │ │ │ │ │ │
30
+ │ ┌─┴──────────────┴────────┐ ┌──┴───────────┴───────────┐ ┌──────────┴───────────┴────┐ │
31
+ │ │RestXMLResponseSerializer│ │RestJSONResponseSerializer│ │RpcV2CBORResponseSerializer│ │
32
+ │ └─────────────────────────┘ └──────────────────────────┘ └───────────────────────────┘ │
33
+ ┌─────┴──────────────────┐ ┌──────────┴─────────────┐
34
+ │QueryResponseSerializer │ │ CBORResponseSerializer │
35
+ └────────────────────────┘ └────────────────────────┘
36
+
37
+ ┌─────────┴───────────┐
31
38
  │EC2ResponseSerializer│
32
39
  └─────────────────────┘
33
40
  ::
34
41
 
35
42
  The ``ResponseSerializer`` contains the logic that is used among all the
36
- different protocols (``query``, ``json``, ``rest-json``, ``rest-xml``, and
37
- ``ec2``).
43
+ different protocols (``query``, ``json``, ``rest-json``, ``rest-xml``, ``cbor``
44
+ and ``ec2``).
38
45
  The protocols relate to each other in the following ways:
39
46
 
40
47
  * The ``query`` and the ``rest-xml`` protocols both have XML bodies in their
@@ -42,10 +49,14 @@ The protocols relate to each other in the following ways:
42
49
  type).
43
50
  * The ``json`` and the ``rest-json`` protocols both have JSON bodies in their
44
51
  responses which are serialized the same way.
52
+ * The ``cbor`` protocol is not properly defined in the spec, but mirrors the
53
+ ``json`` protocol.
45
54
  * The ``rest-json`` and ``rest-xml`` protocols serialize some metadata in
46
- the HTTP response's header fields
55
+ the HTTP response's header fields.
47
56
  * The ``ec2`` protocol is basically similar to the ``query`` protocol with a
48
57
  specific error response formatting.
58
+ * The ``smithy-rpc-v2-cbor`` protocol defines a specific way to route request
59
+ to services via the RPC v2 trait, and encodes its body with the CBOR format.
49
60
 
50
61
  The serializer classes in this module correspond directly to the different
51
62
  protocols. ``#create_serializer`` shows the explicit mapping between the
@@ -54,13 +65,23 @@ The classes are structured as follows:
54
65
 
55
66
  * The ``ResponseSerializer`` contains all the basic logic for the
56
67
  serialization which is shared among all different protocols.
57
- * The ``BaseXMLResponseSerializer`` and the ``JSONResponseSerializer``
58
- contain the logic for the XML and the JSON serialization respectively.
68
+ * The ``BaseXMLResponseSerializer``, ``JSONResponseSerializer`` and
69
+ ``BaseCBORResponseSerializer`` contain the logic for the XML, JSON
70
+ and the CBOR serialization respectively.
59
71
  * The ``BaseRestResponseSerializer`` contains the logic for the REST
60
72
  protocol specifics (i.e. specific HTTP header serializations).
73
+ * The ``BaseRpcV2ResponseSerializer`` contains the logic for the RPC v2
74
+ protocol specifics (i.e. pretty bare, does not has any specific
75
+ about body serialization).
61
76
  * The ``RestXMLResponseSerializer`` and the ``RestJSONResponseSerializer``
62
77
  inherit the ReST specific logic from the ``BaseRestResponseSerializer``
63
78
  and the XML / JSON body serialization from their second super class.
79
+ * The ``RpcV2CBORResponseSerializer`` inherits the RPC v2 specific logic
80
+ from the ``BaseRpcV2ResponseSerializer`` and the CBOR body serialization
81
+ from its second super class.
82
+ * The ``CBORResponseSerializer`` contains the logic specific to the
83
+ non-official ``cbor`` protocol, mirroring the ``json`` protocol but
84
+ with CBOR encoded body
64
85
 
65
86
  The services and their protocols are defined by using AWS's Smithy
66
87
  (a language to define services in a - somewhat - protocol-agnostic
@@ -73,21 +94,32 @@ be sent back to the calling client.
73
94
 
74
95
  import abc
75
96
  import base64
97
+ import datetime
76
98
  import functools
77
99
  import json
78
100
  import logging
101
+ import math
79
102
  import string
103
+ import struct
80
104
  from abc import ABC
81
105
  from binascii import crc32
82
106
  from collections.abc import Iterable, Iterator
83
- from datetime import datetime
84
107
  from email.utils import formatdate
85
108
  from struct import pack
86
- from typing import Any
109
+ from typing import IO, Any
87
110
  from xml.etree import ElementTree as ETree
88
111
 
89
112
  import xmltodict
90
- from botocore.model import ListShape, MapShape, OperationModel, ServiceModel, Shape, StructureShape
113
+ from botocore.model import (
114
+ ListShape,
115
+ MapShape,
116
+ OperationModel,
117
+ ServiceModel,
118
+ Shape,
119
+ ShapeResolver,
120
+ StringShape,
121
+ StructureShape,
122
+ )
91
123
  from botocore.serialize import ISO8601, ISO8601_MICRO
92
124
  from botocore.utils import calculate_md5, is_json_value_header, parse_to_aware_datetime
93
125
 
@@ -261,7 +293,11 @@ class ResponseSerializer(abc.ABC):
261
293
  f"Error to serialize ({error.__class__.__name__ if error else None}) is not a ServiceException."
262
294
  )
263
295
  shape = operation_model.service_model.shape_for_error_code(error.code)
264
- serialized_response.status_code = error.status_code
296
+ serialized_response.status_code = self._get_error_status_code(
297
+ error=error,
298
+ headers=headers,
299
+ service_model=operation_model.service_model,
300
+ )
265
301
 
266
302
  self._serialize_error(
267
303
  error, serialized_response, shape, operation_model, mime_type, request_id
@@ -506,7 +542,7 @@ class ResponseSerializer(abc.ABC):
506
542
  # Some extra utility methods subclasses can use.
507
543
 
508
544
  @staticmethod
509
- def _timestamp_iso8601(value: datetime) -> str:
545
+ def _timestamp_iso8601(value: datetime.datetime) -> str:
510
546
  if value.microsecond > 0:
511
547
  timestamp_format = ISO8601_MICRO
512
548
  else:
@@ -514,20 +550,22 @@ class ResponseSerializer(abc.ABC):
514
550
  return value.strftime(timestamp_format)
515
551
 
516
552
  @staticmethod
517
- def _timestamp_unixtimestamp(value: datetime) -> float:
553
+ def _timestamp_unixtimestamp(value: datetime.datetime) -> float:
518
554
  return value.timestamp()
519
555
 
520
- def _timestamp_rfc822(self, value: datetime) -> str:
521
- if isinstance(value, datetime):
556
+ def _timestamp_rfc822(self, value: datetime.datetime) -> str:
557
+ if isinstance(value, datetime.datetime):
522
558
  value = self._timestamp_unixtimestamp(value)
523
559
  return formatdate(value, usegmt=True)
524
560
 
525
- def _convert_timestamp_to_str(self, value: int | str | datetime, timestamp_format=None) -> str:
561
+ def _convert_timestamp_to_str(
562
+ self, value: int | str | datetime.datetime, timestamp_format=None
563
+ ) -> str:
526
564
  if timestamp_format is None:
527
565
  timestamp_format = self.TIMESTAMP_FORMAT
528
566
  timestamp_format = timestamp_format.lower()
529
567
  datetime_obj = parse_to_aware_datetime(value)
530
- converter = getattr(self, "_timestamp_%s" % timestamp_format)
568
+ converter = getattr(self, f"_timestamp_{timestamp_format}")
531
569
  final_value = converter(datetime_obj)
532
570
  return final_value
533
571
 
@@ -580,6 +618,60 @@ class ResponseSerializer(abc.ABC):
580
618
  def _get_error_message(self, error: Exception) -> str | None:
581
619
  return str(error) if error is not None and str(error) != "None" else None
582
620
 
621
+ def _get_error_status_code(
622
+ self, error: ServiceException, headers: Headers, service_model: ServiceModel
623
+ ) -> int:
624
+ return error.status_code
625
+
626
+
627
+ class QueryCompatibleProtocolMixin:
628
+ def _get_error_status_code(
629
+ self, error: ServiceException, headers: dict | Headers | None, service_model: ServiceModel
630
+ ) -> int:
631
+ # by default, some protocols (namely `json` and `smithy-rpc-v2-cbor`) might not define exception status code in
632
+ # their specs, so they are not defined in the `ServiceException` object and will use the default value of `400`
633
+ # But Query compatible service always do define them, so we get the wrong code for service that are
634
+ # multi-protocols like CloudWatch
635
+ # we need to verify if the service is compatible, and if the client has requested the query compatible error
636
+ # code to return the right value
637
+ if not service_model.is_query_compatible:
638
+ return error.status_code
639
+
640
+ if headers and headers.get("x-amzn-query-mode") == "true":
641
+ return error.status_code
642
+
643
+ # we only want to override status code 4XX
644
+ if 400 < error.status_code <= 499:
645
+ return 400
646
+
647
+ return error.status_code
648
+
649
+ def _add_query_compatible_error_header(self, response: Response, error: ServiceException):
650
+ """
651
+ Add an `x-amzn-query-error` header for client to translate errors codes from former `query` services
652
+ into other protocols.
653
+ """
654
+
655
+ sender_fault = "Sender" if error.sender_fault else "Receiver"
656
+ response.headers["x-amzn-query-error"] = f"{error.code};{sender_fault}"
657
+
658
+ def _get_error_code(
659
+ self, is_query_compatible: bool, error: ServiceException, shape: Shape | None = None
660
+ ):
661
+ # if the operation is query compatible, we need to add to use shape name
662
+ if is_query_compatible:
663
+ if shape:
664
+ code = shape.name
665
+ else:
666
+ # if the shape is not defined, we are using the Exception named to derive the `Code`, like you would
667
+ # from the shape. This allows us to have Exception that are valid in multi-protocols by defining its
668
+ # code and its name to be different
669
+ code = error.__class__.__name__
670
+ else:
671
+ code = error.code
672
+
673
+ return code
674
+
583
675
 
584
676
  class BaseXMLResponseSerializer(ResponseSerializer):
585
677
  """
@@ -689,7 +781,7 @@ class BaseXMLResponseSerializer(ResponseSerializer):
689
781
  name = shape.serialization.get("resultWrapper")
690
782
 
691
783
  try:
692
- method = getattr(self, "_serialize_type_%s" % shape.type_name, self._default_serialize)
784
+ method = getattr(self, f"_serialize_type_{shape.type_name}", self._default_serialize)
693
785
  method(xmlnode, params, shape, name, mime_type)
694
786
  except (TypeError, ValueError, AttributeError) as e:
695
787
  raise ProtocolSerializerError(
@@ -705,7 +797,7 @@ class BaseXMLResponseSerializer(ResponseSerializer):
705
797
  namespace_metadata = shape.serialization["xmlNamespace"]
706
798
  attribute_name = "xmlns"
707
799
  if namespace_metadata.get("prefix"):
708
- attribute_name += ":%s" % namespace_metadata["prefix"]
800
+ attribute_name += ":{}".format(namespace_metadata["prefix"])
709
801
  structure_node.attrib[attribute_name] = namespace_metadata["uri"]
710
802
  for key, value in params.items():
711
803
  if value is None:
@@ -1136,6 +1228,15 @@ class QueryResponseSerializer(BaseXMLResponseSerializer):
1136
1228
  request_id_element = ETree.SubElement(response_metadata, "RequestId")
1137
1229
  request_id_element.text = request_id
1138
1230
 
1231
+ def _prepare_additional_traits_in_response(
1232
+ self, response: Response, operation_model: OperationModel, request_id: str
1233
+ ):
1234
+ response.headers["x-amzn-RequestId"] = request_id
1235
+ response = super()._prepare_additional_traits_in_response(
1236
+ response, operation_model, request_id
1237
+ )
1238
+ return response
1239
+
1139
1240
 
1140
1241
  class EC2ResponseSerializer(QueryResponseSerializer):
1141
1242
  """
@@ -1193,7 +1294,7 @@ class EC2ResponseSerializer(QueryResponseSerializer):
1193
1294
  request_id_element.text = request_id
1194
1295
 
1195
1296
 
1196
- class JSONResponseSerializer(ResponseSerializer):
1297
+ class JSONResponseSerializer(QueryCompatibleProtocolMixin, ResponseSerializer):
1197
1298
  """
1198
1299
  The ``JSONResponseSerializer`` is responsible for the serialization of responses from services with the ``json``
1199
1300
  protocol. It implements the JSON response body serialization, which is also used by the
@@ -1220,25 +1321,48 @@ class JSONResponseSerializer(ResponseSerializer):
1220
1321
  # TODO implement different service-specific serializer configurations
1221
1322
  # - currently we set both, the `__type` member as well as the `X-Amzn-Errortype` header
1222
1323
  # - the specification defines that it's either the __type field OR the header
1223
- response.headers["X-Amzn-Errortype"] = error.code
1224
- body["__type"] = error.code
1324
+ # this depends on the JSON protocol version as well. If json-1.0 the Error should be the full shape ID, like
1325
+ # com.amazon.coral.service#ExceptionName
1326
+ # if json-1.1, it should only be the name
1327
+
1328
+ is_query_compatible = operation_model.service_model.is_query_compatible
1329
+ code = self._get_error_code(is_query_compatible, error, shape)
1330
+
1331
+ response.headers["X-Amzn-Errortype"] = code
1332
+
1333
+ # the `__type` field is not defined in default botocore error shapes
1334
+ body["__type"] = code
1225
1335
 
1226
1336
  if shape:
1227
1337
  remaining_params = {}
1228
1338
  # TODO add a possibility to serialize simple non-modelled errors (like S3 NoSuchBucket#BucketName)
1229
1339
  for member in shape.members:
1230
1340
  if hasattr(error, member):
1231
- remaining_params[member] = getattr(error, member)
1341
+ value = getattr(error, member)
1342
+
1232
1343
  # Default error message fields can sometimes have different casing in the specs
1233
1344
  elif member.lower() in ["code", "message"] and hasattr(error, member.lower()):
1234
- remaining_params[member] = getattr(error, member.lower())
1345
+ value = getattr(error, member.lower())
1346
+
1347
+ else:
1348
+ continue
1349
+
1350
+ if value is None:
1351
+ # do not serialize a value that is set to `None`
1352
+ continue
1353
+
1354
+ # if the value is falsy (empty string, empty list) and not in the Shape required members, AWS will
1355
+ # not serialize it, and it will not be part of the response body.
1356
+ if value or member in shape.required_members:
1357
+ remaining_params[member] = value
1358
+
1235
1359
  self._serialize(body, remaining_params, shape, None, mime_type)
1236
1360
 
1237
- # Only set the message if it has not been set with the shape members
1361
+ # this is a workaround, some Error Shape do not define a `Message` field, but it is always returned
1362
+ # this could be solved at the same time as the `__type` field
1238
1363
  if "message" not in body and "Message" not in body:
1239
- message = self._get_error_message(error)
1240
- if message is not None:
1241
- body["message"] = message
1364
+ if error_message := self._get_error_message(error):
1365
+ body["message"] = error_message
1242
1366
 
1243
1367
  if mime_type in self.CBOR_TYPES:
1244
1368
  response.set_response(cbor2_dumps(body, datetime_as_timestamp=True))
@@ -1246,6 +1370,9 @@ class JSONResponseSerializer(ResponseSerializer):
1246
1370
  else:
1247
1371
  response.set_json(body)
1248
1372
 
1373
+ if is_query_compatible:
1374
+ self._add_query_compatible_error_header(response, error)
1375
+
1249
1376
  def _serialize_response(
1250
1377
  self,
1251
1378
  parameters: dict,
@@ -1261,7 +1388,7 @@ class JSONResponseSerializer(ResponseSerializer):
1261
1388
  else:
1262
1389
  json_version = operation_model.metadata.get("jsonVersion")
1263
1390
  if json_version is not None:
1264
- response.headers["Content-Type"] = "application/x-amz-json-%s" % json_version
1391
+ response.headers["Content-Type"] = f"application/x-amz-json-{json_version}"
1265
1392
  response.set_response(
1266
1393
  self._serialize_body_params(parameters, shape, operation_model, mime_type, request_id)
1267
1394
  )
@@ -1286,7 +1413,7 @@ class JSONResponseSerializer(ResponseSerializer):
1286
1413
  def _serialize(self, body: dict, value: Any, shape, key: str | None, mime_type: str):
1287
1414
  """This method dynamically invokes the correct `_serialize_type_*` method for each shape type."""
1288
1415
  try:
1289
- method = getattr(self, "_serialize_type_%s" % shape.type_name, self._default_serialize)
1416
+ method = getattr(self, f"_serialize_type_{shape.type_name}", self._default_serialize)
1290
1417
  method(body, value, shape, key, mime_type)
1291
1418
  except (TypeError, ValueError, AttributeError) as e:
1292
1419
  raise ProtocolSerializerError(
@@ -1377,7 +1504,7 @@ class JSONResponseSerializer(ResponseSerializer):
1377
1504
  def _prepare_additional_traits_in_response(
1378
1505
  self, response: Response, operation_model: OperationModel, request_id: str
1379
1506
  ):
1380
- response.headers["x-amzn-requestid"] = request_id
1507
+ response.headers.setdefault("x-amzn-RequestId", request_id)
1381
1508
  response = super()._prepare_additional_traits_in_response(
1382
1509
  response, operation_model, request_id
1383
1510
  )
@@ -1407,6 +1534,504 @@ class RestJSONResponseSerializer(BaseRestResponseSerializer, JSONResponseSeriali
1407
1534
  serialized.headers["Content-Type"] = mime_type
1408
1535
 
1409
1536
 
1537
+ class BaseCBORResponseSerializer(ResponseSerializer):
1538
+ """
1539
+ The ``BaseCBORResponseSerializer`` performs the basic logic for the CBOR response serialization.
1540
+
1541
+ There are two types of map/list in CBOR, indefinite length types and "defined" ones:
1542
+ You can use the `\xbf` byte marker to indicate a map with indefinite length, then `\xff` to indicate the end
1543
+ of the map.
1544
+ You can also use, for example, `\xa4` to indicate a map with exactly 4 things in it, so `\xff` is not
1545
+ required at the end.
1546
+ AWS, for both Kinesis and `smithy-rpc-v2-cbor` services, is using indefinite data structures when returning
1547
+ responses.
1548
+
1549
+ The CBOR serializer cannot serialize an exception if it is not defined in our specs.
1550
+ LocalStack defines a way to have user-defined exception by subclassing `CommonServiceException`, so it needs to be
1551
+ able to encode those, as well as InternalError
1552
+ We are creating a default botocore structure shape (`_DEFAULT_ERROR_STRUCTURE_SHAPE`) to be used in such cases.
1553
+ """
1554
+
1555
+ SUPPORTED_MIME_TYPES = [APPLICATION_CBOR, APPLICATION_AMZ_CBOR_1_1]
1556
+
1557
+ UNSIGNED_INT_MAJOR_TYPE = 0
1558
+ NEGATIVE_INT_MAJOR_TYPE = 1
1559
+ BLOB_MAJOR_TYPE = 2
1560
+ STRING_MAJOR_TYPE = 3
1561
+ LIST_MAJOR_TYPE = 4
1562
+ MAP_MAJOR_TYPE = 5
1563
+ TAG_MAJOR_TYPE = 6
1564
+ FLOAT_AND_SIMPLE_MAJOR_TYPE = 7
1565
+
1566
+ INDEFINITE_ITEM_ADDITIONAL_INFO = 31
1567
+ BREAK_CODE = b"\xff"
1568
+ USE_INDEFINITE_DATA_STRUCTURE = True
1569
+
1570
+ _ERROR_TYPE_SHAPE = StringShape(shape_name="__type", shape_model={"type": "string"})
1571
+
1572
+ _DEFAULT_ERROR_STRUCTURE_SHAPE = StructureShape(
1573
+ shape_name="DefaultErrorStructure",
1574
+ shape_model={
1575
+ "type": "structure",
1576
+ "members": {
1577
+ "message": {"shape": "ErrorMessage"},
1578
+ "__type": {"shape": "ErrorType"},
1579
+ },
1580
+ "error": {"code": "DefaultErrorStructure", "httpStatusCode": 400, "senderFault": True},
1581
+ "exception": True,
1582
+ },
1583
+ shape_resolver=ShapeResolver(
1584
+ shape_map={
1585
+ "ErrorMessage": {"type": "string"},
1586
+ "ErrorType": {"type": "string"},
1587
+ },
1588
+ ),
1589
+ )
1590
+
1591
+ def _serialize_data_item(
1592
+ self, serialized: bytearray, value: Any, shape: Shape | None, name: str | None = None
1593
+ ) -> None:
1594
+ method = getattr(self, f"_serialize_type_{shape.type_name}")
1595
+ if method is None:
1596
+ raise ValueError(
1597
+ f"Unrecognized C2J type: {shape.type_name}, unable to serialize request"
1598
+ )
1599
+ method(serialized, value, shape, name)
1600
+
1601
+ def _serialize_type_integer(
1602
+ self, serialized: bytearray, value: int, shape: Shape | None, name: str | None = None
1603
+ ) -> None:
1604
+ if value >= 0:
1605
+ major_type = self.UNSIGNED_INT_MAJOR_TYPE
1606
+ else:
1607
+ major_type = self.NEGATIVE_INT_MAJOR_TYPE
1608
+ # The only differences in serializing negative and positive integers is
1609
+ # that for negative, we set the major type to 1 and set the value to -1
1610
+ # minus the value
1611
+ value = -1 - value
1612
+ additional_info, num_bytes = self._get_additional_info_and_num_bytes(value)
1613
+ initial_byte = self._get_initial_byte(major_type, additional_info)
1614
+ if num_bytes == 0:
1615
+ serialized.extend(initial_byte)
1616
+ else:
1617
+ serialized.extend(initial_byte + value.to_bytes(num_bytes, "big"))
1618
+
1619
+ def _serialize_type_long(
1620
+ self, serialized: bytearray, value: int, shape: Shape, name: str | None = None
1621
+ ) -> None:
1622
+ self._serialize_type_integer(serialized, value, shape, name)
1623
+
1624
+ def _serialize_type_blob(
1625
+ self,
1626
+ serialized: bytearray,
1627
+ value: str | bytes | IO[bytes],
1628
+ shape: Shape | None,
1629
+ name: str | None = None,
1630
+ ) -> None:
1631
+ if isinstance(value, str):
1632
+ value = value.encode("utf-8")
1633
+ elif not isinstance(value, (bytes, bytearray)):
1634
+ # We support file-like objects for blobs; these already have been
1635
+ # validated to ensure they have a read method
1636
+ value = value.read()
1637
+ length = len(value)
1638
+ additional_info, num_bytes = self._get_additional_info_and_num_bytes(length)
1639
+ initial_byte = self._get_initial_byte(self.BLOB_MAJOR_TYPE, additional_info)
1640
+ if num_bytes == 0:
1641
+ serialized.extend(initial_byte)
1642
+ else:
1643
+ serialized.extend(initial_byte + length.to_bytes(num_bytes, "big"))
1644
+ serialized.extend(value)
1645
+
1646
+ def _serialize_type_string(
1647
+ self, serialized: bytearray, value: str, shape: Shape | None, name: str | None = None
1648
+ ) -> None:
1649
+ encoded = value.encode("utf-8")
1650
+ length = len(encoded)
1651
+ additional_info, num_bytes = self._get_additional_info_and_num_bytes(length)
1652
+ initial_byte = self._get_initial_byte(self.STRING_MAJOR_TYPE, additional_info)
1653
+ if num_bytes == 0:
1654
+ serialized.extend(initial_byte + encoded)
1655
+ else:
1656
+ serialized.extend(initial_byte + length.to_bytes(num_bytes, "big") + encoded)
1657
+
1658
+ def _serialize_type_list(
1659
+ self, serialized: bytearray, value: list, shape: Shape | None, name: str | None = None
1660
+ ) -> None:
1661
+ initial_bytes, closing_bytes = self._get_bytes_for_data_structure(
1662
+ value, self.LIST_MAJOR_TYPE
1663
+ )
1664
+ serialized.extend(initial_bytes)
1665
+
1666
+ for item in value:
1667
+ self._serialize_data_item(serialized, item, shape.member)
1668
+
1669
+ if closing_bytes is not None:
1670
+ serialized.extend(closing_bytes)
1671
+
1672
+ def _serialize_type_map(
1673
+ self, serialized: bytearray, value: dict, shape: Shape | None, name: str | None = None
1674
+ ) -> None:
1675
+ initial_bytes, closing_bytes = self._get_bytes_for_data_structure(
1676
+ value, self.MAP_MAJOR_TYPE
1677
+ )
1678
+ serialized.extend(initial_bytes)
1679
+
1680
+ for key_item, item in value.items():
1681
+ self._serialize_data_item(serialized, key_item, shape.key)
1682
+ self._serialize_data_item(serialized, item, shape.value)
1683
+
1684
+ if closing_bytes is not None:
1685
+ serialized.extend(closing_bytes)
1686
+
1687
+ def _serialize_type_structure(
1688
+ self,
1689
+ serialized: bytearray,
1690
+ value: dict,
1691
+ shape: Shape | None,
1692
+ name: str | None = None,
1693
+ shape_members: dict[str, Shape] | None = None,
1694
+ ) -> None:
1695
+ # `_serialize_type_structure` has a different signature other `_serialize_type_*` methods as it accepts
1696
+ # `shape_members`. This is because sometimes, the `StructureShape` does not have some members defined in the
1697
+ # specs, and we want to be able to pass arbitrary members to serialize undocumented members.
1698
+ # see `_serialize_error_structure` for its specific usage
1699
+
1700
+ if name is not None:
1701
+ # For nested structures, we need to serialize the key first
1702
+ self._serialize_data_item(serialized, name, shape.key_shape)
1703
+
1704
+ # Remove `None` values from the dictionary
1705
+ value = {k: v for k, v in value.items() if v is not None}
1706
+
1707
+ initial_bytes, closing_bytes = self._get_bytes_for_data_structure(
1708
+ value, self.MAP_MAJOR_TYPE
1709
+ )
1710
+ serialized.extend(initial_bytes)
1711
+ members = shape_members or shape.members
1712
+ for member_key, member_value in value.items():
1713
+ member_shape = members[member_key]
1714
+ if "name" in member_shape.serialization:
1715
+ member_key = member_shape.serialization["name"]
1716
+ if member_value is not None:
1717
+ self._serialize_type_string(serialized, member_key, None, None)
1718
+ self._serialize_data_item(serialized, member_value, member_shape)
1719
+
1720
+ if closing_bytes is not None:
1721
+ serialized.extend(closing_bytes)
1722
+
1723
+ def _serialize_type_timestamp(
1724
+ self,
1725
+ serialized: bytearray,
1726
+ value: int | str | datetime.datetime,
1727
+ shape: Shape | None,
1728
+ name: str | None = None,
1729
+ ) -> None:
1730
+ # https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html#timestamp-type-serialization
1731
+ tag = 1 # Use tag 1 for unix timestamp
1732
+ initial_byte = self._get_initial_byte(self.TAG_MAJOR_TYPE, tag)
1733
+ serialized.extend(initial_byte) # Tagging the timestamp
1734
+
1735
+ # we encode the timestamp as a double, like the Go SDK
1736
+ # https://github.com/aws/aws-sdk-go-v2/blob/5d7c17325a2581afae4455c150549174ebfd9428/internal/protocoltest/smithyrpcv2cbor/serializers.go#L664-L669
1737
+ # Currently, the Botocore serializer using unsigned integers, but it does not conform to the Smithy specs:
1738
+ # > This protocol uses epoch-seconds, also known as Unix timestamps, with millisecond
1739
+ # > (1/1000th of a second) resolution.
1740
+ timestamp = float(self._convert_timestamp_to_str(value))
1741
+ initial_byte = self._get_initial_byte(self.FLOAT_AND_SIMPLE_MAJOR_TYPE, 27)
1742
+ serialized.extend(initial_byte + struct.pack(">d", timestamp))
1743
+
1744
+ def _serialize_type_float(
1745
+ self, serialized: bytearray, value: float, shape: Shape | None, name: str | None = None
1746
+ ) -> None:
1747
+ if self._is_special_number(value):
1748
+ serialized.extend(
1749
+ self._get_bytes_for_special_numbers(value)
1750
+ ) # Handle special values like NaN or Infinity
1751
+ else:
1752
+ initial_byte = self._get_initial_byte(self.FLOAT_AND_SIMPLE_MAJOR_TYPE, 26)
1753
+ serialized.extend(initial_byte + struct.pack(">f", value))
1754
+
1755
+ def _serialize_type_double(
1756
+ self, serialized: bytearray, value: float, shape: Shape | None, name: str | None = None
1757
+ ) -> None:
1758
+ if self._is_special_number(value):
1759
+ serialized.extend(
1760
+ self._get_bytes_for_special_numbers(value)
1761
+ ) # Handle special values like NaN or Infinity
1762
+ else:
1763
+ initial_byte = self._get_initial_byte(self.FLOAT_AND_SIMPLE_MAJOR_TYPE, 27)
1764
+ serialized.extend(initial_byte + struct.pack(">d", value))
1765
+
1766
+ def _serialize_type_boolean(
1767
+ self, serialized: bytearray, value: bool, shape: Shape | None, name: str | None = None
1768
+ ) -> None:
1769
+ additional_info = 21 if value else 20
1770
+ serialized.extend(self._get_initial_byte(self.FLOAT_AND_SIMPLE_MAJOR_TYPE, additional_info))
1771
+
1772
+ @staticmethod
1773
+ def _get_additional_info_and_num_bytes(value: int) -> tuple[int, int]:
1774
+ # Values under 24 can be stored in the initial byte and don't need further
1775
+ # encoding
1776
+ if value < 24:
1777
+ return value, 0
1778
+ # Values between 24 and 255 (inclusive) can be stored in 1 byte and
1779
+ # correspond to additional info 24
1780
+ elif value < 256:
1781
+ return 24, 1
1782
+ # Values up to 65535 can be stored in two bytes and correspond to additional
1783
+ # info 25
1784
+ elif value < 65536:
1785
+ return 25, 2
1786
+ # Values up to 4294967296 can be stored in four bytes and correspond to
1787
+ # additional info 26
1788
+ elif value < 4294967296:
1789
+ return 26, 4
1790
+ # The maximum number of bytes in a definite length data items is 8 which
1791
+ # to additional info 27
1792
+ else:
1793
+ return 27, 8
1794
+
1795
+ def _get_initial_byte(self, major_type: int, additional_info: int) -> bytes:
1796
+ # The highest order three bits are the major type, so we need to bitshift the
1797
+ # major type by 5
1798
+ major_type_bytes = major_type << 5
1799
+ return (major_type_bytes | additional_info).to_bytes(1, "big")
1800
+
1801
+ @staticmethod
1802
+ def _is_special_number(value: int | float) -> bool:
1803
+ return any(
1804
+ [
1805
+ value == float("inf"),
1806
+ value == float("-inf"),
1807
+ math.isnan(value),
1808
+ ]
1809
+ )
1810
+
1811
+ def _get_bytes_for_special_numbers(self, value: int | float) -> bytes:
1812
+ additional_info = 25
1813
+ initial_byte = self._get_initial_byte(self.FLOAT_AND_SIMPLE_MAJOR_TYPE, additional_info)
1814
+ if value == float("inf"):
1815
+ return initial_byte + struct.pack(">H", 0x7C00)
1816
+ elif value == float("-inf"):
1817
+ return initial_byte + struct.pack(">H", 0xFC00)
1818
+ elif math.isnan(value):
1819
+ return initial_byte + struct.pack(">H", 0x7E00)
1820
+
1821
+ def _get_bytes_for_data_structure(
1822
+ self, value: list | dict, major_type: int
1823
+ ) -> tuple[bytes, bytes | None]:
1824
+ if self.USE_INDEFINITE_DATA_STRUCTURE:
1825
+ additional_info = self.INDEFINITE_ITEM_ADDITIONAL_INFO
1826
+ return self._get_initial_byte(major_type, additional_info), self.BREAK_CODE
1827
+ else:
1828
+ length = len(value)
1829
+ additional_info, num_bytes = self._get_additional_info_and_num_bytes(length)
1830
+ initial_byte = self._get_initial_byte(major_type, additional_info)
1831
+ if num_bytes != 0:
1832
+ initial_byte = initial_byte + length.to_bytes(num_bytes, "big")
1833
+
1834
+ return initial_byte, None
1835
+
1836
+ def _serialize_error_structure(
1837
+ self, body: bytearray, shape: Shape | None, error: ServiceException, code: str
1838
+ ):
1839
+ if not shape:
1840
+ shape = self._DEFAULT_ERROR_STRUCTURE_SHAPE
1841
+ shape_members = shape.members
1842
+ else:
1843
+ # we need to manually add the `__type` field to the shape members as it is not part of the specs
1844
+ # we do a shallow copy of the shape members
1845
+ shape_members = shape.members.copy()
1846
+ shape_members["__type"] = self._ERROR_TYPE_SHAPE
1847
+
1848
+ # Error responses in the rpcv2Cbor protocol MUST be serialized identically to standard responses with one
1849
+ # additional component to distinguish which error is contained: a body field named __type.
1850
+ params = {"__type": code}
1851
+
1852
+ for member in shape_members:
1853
+ if hasattr(error, member):
1854
+ value = getattr(error, member)
1855
+
1856
+ # Default error message fields can sometimes have different casing in the specs
1857
+ elif member.lower() in ["code", "message"] and hasattr(error, member.lower()):
1858
+ value = getattr(error, member.lower())
1859
+
1860
+ else:
1861
+ continue
1862
+
1863
+ if value is None:
1864
+ # do not serialize a value that is set to `None`
1865
+ continue
1866
+
1867
+ # if the value is falsy (empty string, empty list) and not in the Shape required members, AWS will
1868
+ # not serialize it, and it will not be part of the response body.
1869
+ if value or member in shape.required_members:
1870
+ params[member] = value
1871
+
1872
+ self._serialize_type_structure(body, params, shape, None, shape_members=shape_members)
1873
+
1874
+
1875
+ class CBORResponseSerializer(BaseCBORResponseSerializer):
1876
+ """
1877
+ The ``CBORResponseSerializer`` is responsible for the serialization of responses from services with the ``cbor``
1878
+ protocol. It implements the CBOR response body serialization, which is only currently used by Kinesis and is derived
1879
+ conceptually from the ``JSONResponseSerializer``
1880
+ """
1881
+
1882
+ TIMESTAMP_FORMAT = "unixtimestamp"
1883
+
1884
+ def _serialize_error(
1885
+ self,
1886
+ error: ServiceException,
1887
+ response: Response,
1888
+ shape: StructureShape,
1889
+ operation_model: OperationModel,
1890
+ mime_type: str,
1891
+ request_id: str,
1892
+ ) -> None:
1893
+ body = bytearray()
1894
+ response.content_type = mime_type
1895
+ response.headers["X-Amzn-Errortype"] = error.code
1896
+
1897
+ self._serialize_error_structure(body, shape, error, code=error.code)
1898
+
1899
+ response.set_response(bytes(body))
1900
+
1901
+ def _serialize_response(
1902
+ self,
1903
+ parameters: dict,
1904
+ response: Response,
1905
+ shape: Shape | None,
1906
+ shape_members: dict,
1907
+ operation_model: OperationModel,
1908
+ mime_type: str,
1909
+ request_id: str,
1910
+ ) -> None:
1911
+ response.content_type = mime_type
1912
+ response.set_response(
1913
+ self._serialize_body_params(parameters, shape, operation_model, mime_type, request_id)
1914
+ )
1915
+
1916
+ def _serialize_body_params(
1917
+ self,
1918
+ params: dict,
1919
+ shape: Shape,
1920
+ operation_model: OperationModel,
1921
+ mime_type: str,
1922
+ request_id: str,
1923
+ ) -> bytes | None:
1924
+ if shape is None:
1925
+ return b""
1926
+ body = bytearray()
1927
+ self._serialize_data_item(body, params, shape)
1928
+ return bytes(body)
1929
+
1930
+ def _prepare_additional_traits_in_response(
1931
+ self, response: Response, operation_model: OperationModel, request_id: str
1932
+ ) -> Response:
1933
+ response.headers["x-amzn-RequestId"] = request_id
1934
+ response = super()._prepare_additional_traits_in_response(
1935
+ response, operation_model, request_id
1936
+ )
1937
+ return response
1938
+
1939
+
1940
+ class BaseRpcV2ResponseSerializer(ResponseSerializer):
1941
+ """
1942
+ The BaseRpcV2ResponseSerializer performs the basic logic for the RPC V2 response serialization.
1943
+ The only variance between the various RPCv2 protocols is the way the body is serialized for regular responses,
1944
+ and the way they will encode exceptions.
1945
+ """
1946
+
1947
+ def _serialize_response(
1948
+ self,
1949
+ parameters: dict,
1950
+ response: Response,
1951
+ shape: Shape | None,
1952
+ shape_members: dict,
1953
+ operation_model: OperationModel,
1954
+ mime_type: str,
1955
+ request_id: str,
1956
+ ) -> None:
1957
+ response.content_type = mime_type
1958
+ response.set_response(
1959
+ self._serialize_body_params(parameters, shape, operation_model, mime_type, request_id)
1960
+ )
1961
+
1962
+ def _serialize_body_params(
1963
+ self,
1964
+ params: dict,
1965
+ shape: Shape,
1966
+ operation_model: OperationModel,
1967
+ mime_type: str,
1968
+ request_id: str,
1969
+ ) -> bytes | None:
1970
+ raise NotImplementedError
1971
+
1972
+
1973
+ class RpcV2CBORResponseSerializer(
1974
+ QueryCompatibleProtocolMixin, BaseRpcV2ResponseSerializer, BaseCBORResponseSerializer
1975
+ ):
1976
+ """
1977
+ The RpcV2CBORResponseSerializer implements the CBOR body serialization part for the RPC v2 protocol, and implements the
1978
+ specific exception serialization.
1979
+ https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html
1980
+ """
1981
+
1982
+ # the Smithy spec defines that only `application/cbor` is supported for RPC v2 CBOR
1983
+ SUPPORTED_MIME_TYPES = [APPLICATION_CBOR]
1984
+ TIMESTAMP_FORMAT = "unixtimestamp"
1985
+
1986
+ def _serialize_body_params(
1987
+ self,
1988
+ params: dict,
1989
+ shape: Shape,
1990
+ operation_model: OperationModel,
1991
+ mime_type: str,
1992
+ request_id: str,
1993
+ ) -> bytes | None:
1994
+ if shape is None:
1995
+ return b""
1996
+ body = bytearray()
1997
+ self._serialize_data_item(body, params, shape)
1998
+ return bytes(body)
1999
+
2000
+ def _serialize_error(
2001
+ self,
2002
+ error: ServiceException,
2003
+ response: Response,
2004
+ shape: StructureShape,
2005
+ operation_model: OperationModel,
2006
+ mime_type: str,
2007
+ request_id: str,
2008
+ ) -> None:
2009
+ body = bytearray()
2010
+ response.content_type = mime_type # can only be 'application/cbor'
2011
+
2012
+ # Responses for the rpcv2Cbor protocol SHOULD NOT contain the X-Amzn-ErrorType header.
2013
+ # Type information is always serialized in the payload. This is different from the `json` protocol
2014
+ is_query_compatible = operation_model.service_model.is_query_compatible
2015
+ code = self._get_error_code(is_query_compatible, error, shape)
2016
+
2017
+ self._serialize_error_structure(body, shape, error, code=code)
2018
+
2019
+ response.set_response(bytes(body))
2020
+
2021
+ if is_query_compatible:
2022
+ self._add_query_compatible_error_header(response, error)
2023
+
2024
+ def _prepare_additional_traits_in_response(
2025
+ self, response: Response, operation_model: OperationModel, request_id: str
2026
+ ):
2027
+ response.headers["x-amzn-RequestId"] = request_id
2028
+ response.headers["Smithy-Protocol"] = "rpc-v2-cbor"
2029
+ response = super()._prepare_additional_traits_in_response(
2030
+ response, operation_model, request_id
2031
+ )
2032
+ return response
2033
+
2034
+
1410
2035
  class S3ResponseSerializer(RestXMLResponseSerializer):
1411
2036
  """
1412
2037
  The ``S3ResponseSerializer`` adds some minor logic to handle S3 specific peculiarities with the error response
@@ -1573,7 +2198,7 @@ class S3ResponseSerializer(RestXMLResponseSerializer):
1573
2198
  root.attrib["xmlns"] = self.XML_NAMESPACE
1574
2199
 
1575
2200
  @staticmethod
1576
- def _timestamp_iso8601(value: datetime) -> str:
2201
+ def _timestamp_iso8601(value: datetime.datetime) -> str:
1577
2202
  """
1578
2203
  This is very specific to S3, S3 returns an ISO8601 timestamp but with milliseconds always set to 000
1579
2204
  Some SDKs are very picky about the length
@@ -1700,26 +2325,12 @@ class SqsJsonResponseSerializer(JSONResponseSerializer):
1700
2325
  "QueueNameExists": "QueueAlreadyExists",
1701
2326
  }
1702
2327
 
1703
- def _serialize_error(
1704
- self,
1705
- error: ServiceException,
1706
- response: Response,
1707
- shape: StructureShape,
1708
- operation_model: OperationModel,
1709
- mime_type: str,
1710
- request_id: str,
1711
- ) -> None:
1712
- """
1713
- Overrides _serialize_error as SQS has a special header for query API legacy reason: 'x-amzn-query-error',
1714
- which contained the exception code as well as a Sender field.
1715
- Ex: 'x-amzn-query-error': 'InvalidParameterValue;Sender'
1716
- """
1717
- # TODO: for body["__type"] = error.code, it seems AWS differs from what we send for SQS
1718
- # AWS: "com.amazon.coral.service#InvalidParameterValueException"
1719
- # or AWS: "com.amazonaws.sqs#BatchRequestTooLong"
1720
- # LocalStack: "InvalidParameterValue"
1721
- super()._serialize_error(error, response, shape, operation_model, mime_type, request_id)
1722
- # We need to add a prefix to certain errors, as they have been deleted in the specs. These will not change
2328
+ # TODO: on body error serialization (body["__type"]),it seems AWS differs from what we send for SQS
2329
+ # AWS: "com.amazon.coral.service#InvalidParameterValueException"
2330
+ # or AWS: "com.amazonaws.sqs#BatchRequestTooLong"
2331
+ # LocalStack: "InvalidParameterValue"
2332
+
2333
+ def _add_query_compatible_error_header(self, response: Response, error: ServiceException):
1723
2334
  if error.code in self.JSON_TO_QUERY_ERROR_CODES:
1724
2335
  code = self.JSON_TO_QUERY_ERROR_CODES[error.code]
1725
2336
  elif error.code in self.QUERY_PREFIXED_ERRORS:
@@ -1727,6 +2338,7 @@ class SqsJsonResponseSerializer(JSONResponseSerializer):
1727
2338
  else:
1728
2339
  code = error.code
1729
2340
 
2341
+ # SQS exceptions all have sender fault set to False, so we hardcode it to `Sender`
1730
2342
  response.headers["x-amzn-query-error"] = f"{code};Sender"
1731
2343
 
1732
2344
 
@@ -1744,11 +2356,14 @@ def gen_amzn_requestid():
1744
2356
 
1745
2357
 
1746
2358
  @functools.cache
1747
- def create_serializer(service: ServiceModel) -> ResponseSerializer:
2359
+ def create_serializer(
2360
+ service: ServiceModel, protocol: ProtocolName | None = None
2361
+ ) -> ResponseSerializer:
1748
2362
  """
1749
2363
  Creates the right serializer for the given service model.
1750
2364
 
1751
2365
  :param service: to create the serializer for
2366
+ :param protocol: the protocol for the serializer. If not provided, fallback to the service's default protocol
1752
2367
  :return: ResponseSerializer which can handle the protocol of the service
1753
2368
  """
1754
2369
 
@@ -1768,17 +2383,22 @@ def create_serializer(service: ServiceModel) -> ResponseSerializer:
1768
2383
  "rest-json": RestJSONResponseSerializer,
1769
2384
  "rest-xml": RestXMLResponseSerializer,
1770
2385
  "ec2": EC2ResponseSerializer,
2386
+ "smithy-rpc-v2-cbor": RpcV2CBORResponseSerializer,
2387
+ # TODO: implement multi-protocol support for Kinesis, so that it can uses the `cbor` protocol and remove
2388
+ # CBOR handling from JSONResponseParser
2389
+ # this is not an "official" protocol defined from the spec, but is derived from ``json``
1771
2390
  }
2391
+ service_protocol = protocol or service.protocol
1772
2392
 
1773
2393
  # Try to select a service- and protocol-specific serializer implementation
1774
2394
  if (
1775
2395
  service.service_name in service_specific_serializers
1776
- and service.protocol in service_specific_serializers[service.service_name]
2396
+ and service_protocol in service_specific_serializers[service.service_name]
1777
2397
  ):
1778
- return service_specific_serializers[service.service_name][service.protocol]()
2398
+ return service_specific_serializers[service.service_name][service_protocol]()
1779
2399
  else:
1780
2400
  # Otherwise, pick the protocol-specific serializer for the protocol of the service
1781
- return protocol_specific_serializers[service.protocol]()
2401
+ return protocol_specific_serializers[service_protocol]()
1782
2402
 
1783
2403
 
1784
2404
  def aws_response_serializer(
@@ -1809,7 +2429,7 @@ def aws_response_serializer(
1809
2429
  def _decorate(fn):
1810
2430
  service_model = load_service(service_name, protocol=protocol)
1811
2431
  operation_model = service_model.operation_model(operation)
1812
- serializer = create_serializer(service_model)
2432
+ serializer = create_serializer(service_model, protocol=protocol)
1813
2433
 
1814
2434
  def _proxy(*args, **kwargs) -> WerkzeugResponse:
1815
2435
  # extract request from function invocation (decorator can be used for methods as well as for functions).