smithy-xml 0.1.0__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.
- smithy_xml/__init__.py +65 -0
- smithy_xml/_private/__init__.py +2 -0
- smithy_xml/_private/deserializers.py +380 -0
- smithy_xml/_private/readers.py +55 -0
- smithy_xml/py.typed +1 -0
- smithy_xml/settings.py +20 -0
- smithy_xml-0.1.0.dist-info/METADATA +33 -0
- smithy_xml-0.1.0.dist-info/RECORD +10 -0
- smithy_xml-0.1.0.dist-info/WHEEL +4 -0
- smithy_xml-0.1.0.dist-info/licenses/NOTICE +1 -0
smithy_xml/__init__.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
from io import BytesIO
|
|
5
|
+
from xml.etree.ElementTree import iterparse
|
|
6
|
+
|
|
7
|
+
from smithy_core.codecs import Codec
|
|
8
|
+
from smithy_core.deserializers import ShapeDeserializer
|
|
9
|
+
from smithy_core.interfaces import BytesReader, BytesWriter
|
|
10
|
+
from smithy_core.serializers import ShapeSerializer
|
|
11
|
+
from smithy_core.types import TimestampFormat
|
|
12
|
+
|
|
13
|
+
from ._private.deserializers import XMLShapeDeserializer as _XMLShapeDeserializer
|
|
14
|
+
from ._private.readers import XMLEventReader as _XMLEventReader
|
|
15
|
+
from .settings import XMLSettings
|
|
16
|
+
|
|
17
|
+
__version__ = "0.1.0"
|
|
18
|
+
__all__ = ("XMLCodec", "XMLSettings")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class XMLCodec(Codec):
|
|
22
|
+
"""A codec for converting shapes to/from XML."""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
use_timestamp_format: bool = True,
|
|
27
|
+
default_timestamp_format: TimestampFormat = TimestampFormat.DATE_TIME,
|
|
28
|
+
default_namespace: str | None = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Initializes an XMLCodec.
|
|
31
|
+
|
|
32
|
+
:param use_timestamp_format: Whether the codec should use the
|
|
33
|
+
`smithy.api#timestampFormat` trait, if present.
|
|
34
|
+
:param default_timestamp_format: The default timestamp format to use if the
|
|
35
|
+
`smithy.api#timestampFormat` trait is not enabled or not present.
|
|
36
|
+
:param default_namespace: Default XML namespace (`xmlns`) applied to the root
|
|
37
|
+
element during serialization.
|
|
38
|
+
"""
|
|
39
|
+
self._settings = XMLSettings(
|
|
40
|
+
use_timestamp_format=use_timestamp_format,
|
|
41
|
+
default_timestamp_format=default_timestamp_format,
|
|
42
|
+
default_namespace=default_namespace,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def media_type(self) -> str:
|
|
47
|
+
return "application/xml"
|
|
48
|
+
|
|
49
|
+
def create_serializer(self, sink: BytesWriter) -> ShapeSerializer:
|
|
50
|
+
raise NotImplementedError("XML serialization is not supported")
|
|
51
|
+
|
|
52
|
+
def create_deserializer(
|
|
53
|
+
self,
|
|
54
|
+
source: bytes | BytesReader,
|
|
55
|
+
*,
|
|
56
|
+
wrapper_elements: tuple[str, ...] = (),
|
|
57
|
+
) -> ShapeDeserializer:
|
|
58
|
+
if isinstance(source, bytes):
|
|
59
|
+
source = BytesIO(source)
|
|
60
|
+
reader = _XMLEventReader(
|
|
61
|
+
iterparse(source, events=("start", "end")) # noqa: S314
|
|
62
|
+
)
|
|
63
|
+
return _XMLShapeDeserializer(
|
|
64
|
+
settings=self._settings, reader=reader, wrapper_elements=wrapper_elements
|
|
65
|
+
)
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import datetime
|
|
5
|
+
from base64 import b64decode
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from decimal import Decimal
|
|
8
|
+
from xml.etree.ElementTree import Element
|
|
9
|
+
|
|
10
|
+
from smithy_core.deserializers import ShapeDeserializer, SpecificShapeDeserializer
|
|
11
|
+
from smithy_core.documents import Document
|
|
12
|
+
from smithy_core.exceptions import SmithyError
|
|
13
|
+
from smithy_core.schemas import Schema
|
|
14
|
+
from smithy_core.shapes import ShapeID, ShapeType
|
|
15
|
+
from smithy_core.traits import (
|
|
16
|
+
TimestampFormatTrait,
|
|
17
|
+
XMLAttributeTrait,
|
|
18
|
+
XMLFlattenedTrait,
|
|
19
|
+
XMLNameTrait,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from ..settings import XMLSettings
|
|
23
|
+
from .readers import XMLEvent, XMLEventReader
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _local_name(tag: str) -> str:
|
|
27
|
+
"""Strip namespace URI from an element tag: {uri}local -> local."""
|
|
28
|
+
if tag.startswith("{"):
|
|
29
|
+
return tag.split("}", 1)[1]
|
|
30
|
+
return tag
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _expected_root_name(schema: Schema) -> str | None:
|
|
34
|
+
"""Get the expected root element name for root validation."""
|
|
35
|
+
if schema.shape_type not in (ShapeType.STRUCTURE, ShapeType.UNION):
|
|
36
|
+
return None
|
|
37
|
+
if xml_name := schema.get_trait(XMLNameTrait):
|
|
38
|
+
return xml_name.value
|
|
39
|
+
return schema.id.name
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _validate_element_name(expected: str, elem: Element) -> None:
|
|
43
|
+
"""Raise XMLParseError if the element's local name doesn't match expected."""
|
|
44
|
+
found = _local_name(elem.tag)
|
|
45
|
+
if found != expected:
|
|
46
|
+
raise XMLParseError(f"Expected element '{expected}', got '{found}'")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _xml_member_name(member_schema: Schema) -> str:
|
|
50
|
+
"""Get the XML element name for a member, respecting @xmlName."""
|
|
51
|
+
if xml_name := member_schema.get_trait(XMLNameTrait):
|
|
52
|
+
return xml_name.value
|
|
53
|
+
return member_schema.expect_member_name()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _parse_xml_float(text: str) -> float:
|
|
57
|
+
"""Parse an XML float string, handling NaN and Infinity."""
|
|
58
|
+
match text:
|
|
59
|
+
case "NaN":
|
|
60
|
+
return float("nan")
|
|
61
|
+
case "Infinity":
|
|
62
|
+
return float("inf")
|
|
63
|
+
case "-Infinity":
|
|
64
|
+
return float("-inf")
|
|
65
|
+
case _:
|
|
66
|
+
return float(text)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class XMLParseError(SmithyError):
|
|
70
|
+
def __init__(self, message: str) -> None:
|
|
71
|
+
super().__init__(f"Error parsing XML: {message}")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class XMLShapeDeserializer(ShapeDeserializer):
|
|
75
|
+
"""Deserializer that reads XML from a streaming pull parser."""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
settings: XMLSettings,
|
|
80
|
+
reader: XMLEventReader,
|
|
81
|
+
wrapper_elements: tuple[str, ...] = (),
|
|
82
|
+
) -> None:
|
|
83
|
+
self._settings = settings
|
|
84
|
+
self._reader = reader
|
|
85
|
+
self._is_root = not bool(wrapper_elements)
|
|
86
|
+
self._xml_names: dict[ShapeID, dict[str, Schema]] = {}
|
|
87
|
+
self._preconsumed_start: Element | None = None
|
|
88
|
+
|
|
89
|
+
# Wrapper elements are protocol transport containers (e.g. awsQuery's
|
|
90
|
+
# <OpResponse><OpResult>). The last wrapper's start element is kept
|
|
91
|
+
# so that the next read can reuse it.
|
|
92
|
+
for wrapper in wrapper_elements:
|
|
93
|
+
event = next(self._reader)
|
|
94
|
+
if event.type != "start":
|
|
95
|
+
raise XMLParseError(f"Expected start element, got '{event.type}'")
|
|
96
|
+
_validate_element_name(wrapper, event.elem)
|
|
97
|
+
self._preconsumed_start = event.elem
|
|
98
|
+
|
|
99
|
+
def is_null(self) -> bool:
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
def read_null(self) -> None:
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
def read_boolean(self, schema: Schema) -> bool:
|
|
106
|
+
text = self._read_text()
|
|
107
|
+
match text:
|
|
108
|
+
case "true":
|
|
109
|
+
return True
|
|
110
|
+
case "false":
|
|
111
|
+
return False
|
|
112
|
+
case _:
|
|
113
|
+
raise XMLParseError(f"Expected 'true' or 'false', got '{text}'")
|
|
114
|
+
|
|
115
|
+
def read_blob(self, schema: Schema) -> bytes:
|
|
116
|
+
return b64decode(self._read_text())
|
|
117
|
+
|
|
118
|
+
def read_integer(self, schema: Schema) -> int:
|
|
119
|
+
return int(self._read_text())
|
|
120
|
+
|
|
121
|
+
def read_float(self, schema: Schema) -> float:
|
|
122
|
+
return _parse_xml_float(self._read_text())
|
|
123
|
+
|
|
124
|
+
def read_big_decimal(self, schema: Schema) -> Decimal:
|
|
125
|
+
return Decimal(self._read_text())
|
|
126
|
+
|
|
127
|
+
def read_string(self, schema: Schema) -> str:
|
|
128
|
+
return self._read_text()
|
|
129
|
+
|
|
130
|
+
def read_document(self, schema: Schema) -> Document:
|
|
131
|
+
raise NotImplementedError("XML does not support document types")
|
|
132
|
+
|
|
133
|
+
def read_timestamp(self, schema: Schema) -> datetime.datetime:
|
|
134
|
+
format = self._settings.default_timestamp_format
|
|
135
|
+
if self._settings.use_timestamp_format:
|
|
136
|
+
if format_trait := schema.get_trait(TimestampFormatTrait):
|
|
137
|
+
format = format_trait.format
|
|
138
|
+
|
|
139
|
+
text = self._read_text()
|
|
140
|
+
return format.deserialize(text)
|
|
141
|
+
|
|
142
|
+
def read_struct(
|
|
143
|
+
self,
|
|
144
|
+
schema: Schema,
|
|
145
|
+
consumer: Callable[[Schema, "ShapeDeserializer"], None],
|
|
146
|
+
) -> None:
|
|
147
|
+
xml_names = self._get_xml_names(schema)
|
|
148
|
+
start_from_wrapper = self._preconsumed_start is not None
|
|
149
|
+
start_elem = self._consume_start_event()
|
|
150
|
+
if self._is_root:
|
|
151
|
+
self._is_root = False
|
|
152
|
+
expected = _expected_root_name(schema)
|
|
153
|
+
if expected is not None:
|
|
154
|
+
_validate_element_name(expected, start_elem)
|
|
155
|
+
|
|
156
|
+
# Wrapper elements are protocol transport containers, not modeled structs,
|
|
157
|
+
# so their attributes cannot be deserialized as struct members.
|
|
158
|
+
if not start_from_wrapper:
|
|
159
|
+
for member_schema in schema.members.values():
|
|
160
|
+
if member_schema.get_trait(XMLAttributeTrait) is None:
|
|
161
|
+
continue
|
|
162
|
+
expected_attr_name = _xml_member_name(member_schema)
|
|
163
|
+
for attr_name, attr_value in start_elem.attrib.items():
|
|
164
|
+
attr_local_name = _local_name(attr_name)
|
|
165
|
+
if attr_local_name == expected_attr_name:
|
|
166
|
+
consumer(
|
|
167
|
+
member_schema,
|
|
168
|
+
_AttributeDeserializer(attr_value, self._settings),
|
|
169
|
+
)
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
# Flattened members lack an enclosing element, so there is no way to
|
|
173
|
+
# know when all items have been parsed. Their events are collected
|
|
174
|
+
# during iteration and replayed through a bounded reader afterwards.
|
|
175
|
+
flattened_buffers: dict[str, list[XMLEvent]] = {}
|
|
176
|
+
flattened_names = {
|
|
177
|
+
xml_name: member_schema
|
|
178
|
+
for xml_name, member_schema in xml_names.items()
|
|
179
|
+
if member_schema.get_trait(XMLFlattenedTrait) is not None
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
while self._reader.peek().type != "end":
|
|
183
|
+
tag = _local_name(self._reader.peek().elem.tag)
|
|
184
|
+
|
|
185
|
+
if tag in flattened_names:
|
|
186
|
+
flattened_buffers.setdefault(tag, []).extend(self._buffer_element())
|
|
187
|
+
elif tag in xml_names:
|
|
188
|
+
consumer(xml_names[tag], self)
|
|
189
|
+
else:
|
|
190
|
+
# Skip unknown tag
|
|
191
|
+
self._consume_start_event()
|
|
192
|
+
self._skip_to_end()
|
|
193
|
+
|
|
194
|
+
next(self._reader)
|
|
195
|
+
|
|
196
|
+
for tag, events in flattened_buffers.items():
|
|
197
|
+
member_schema = flattened_names[tag]
|
|
198
|
+
buffered_de = XMLShapeDeserializer(
|
|
199
|
+
self._settings,
|
|
200
|
+
XMLEventReader(iter(events)),
|
|
201
|
+
)
|
|
202
|
+
consumer(member_schema, buffered_de)
|
|
203
|
+
|
|
204
|
+
def read_list(
|
|
205
|
+
self,
|
|
206
|
+
schema: Schema,
|
|
207
|
+
consumer: Callable[["ShapeDeserializer"], None],
|
|
208
|
+
) -> None:
|
|
209
|
+
is_flattened = schema.get_trait(XMLFlattenedTrait) is not None
|
|
210
|
+
if not is_flattened:
|
|
211
|
+
self._consume_start_event()
|
|
212
|
+
while self._reader.peek().type != "end":
|
|
213
|
+
consumer(self)
|
|
214
|
+
else:
|
|
215
|
+
while self._reader.has_next():
|
|
216
|
+
consumer(self)
|
|
217
|
+
|
|
218
|
+
if not is_flattened:
|
|
219
|
+
next(self._reader)
|
|
220
|
+
|
|
221
|
+
def read_map(
|
|
222
|
+
self,
|
|
223
|
+
schema: Schema,
|
|
224
|
+
consumer: Callable[[str, "ShapeDeserializer"], None],
|
|
225
|
+
) -> None:
|
|
226
|
+
is_flattened = schema.get_trait(XMLFlattenedTrait) is not None
|
|
227
|
+
key_schema = schema.members["key"]
|
|
228
|
+
value_schema = schema.members["value"]
|
|
229
|
+
key_tag = _xml_member_name(key_schema)
|
|
230
|
+
value_tag = _xml_member_name(value_schema)
|
|
231
|
+
|
|
232
|
+
if not is_flattened:
|
|
233
|
+
self._consume_start_event()
|
|
234
|
+
while self._reader.peek().type != "end":
|
|
235
|
+
self._read_map_entry(key_tag, value_tag, consumer)
|
|
236
|
+
else:
|
|
237
|
+
while self._reader.has_next():
|
|
238
|
+
self._read_map_entry(key_tag, value_tag, consumer)
|
|
239
|
+
|
|
240
|
+
if not is_flattened:
|
|
241
|
+
next(self._reader)
|
|
242
|
+
|
|
243
|
+
def _read_text(self) -> str:
|
|
244
|
+
"""Consume a complete element (start through end) and return its text."""
|
|
245
|
+
elem = self._consume_start_event()
|
|
246
|
+
self._skip_to_end()
|
|
247
|
+
# elem.text is populated only after consuming the "end" event
|
|
248
|
+
return elem.text or ""
|
|
249
|
+
|
|
250
|
+
def _consume_start_event(self) -> Element:
|
|
251
|
+
"""Consume and return the next start element.
|
|
252
|
+
|
|
253
|
+
If a start element was pre-consumed (e.g. from consuming wrapper elements),
|
|
254
|
+
it is returned first and cleared.
|
|
255
|
+
"""
|
|
256
|
+
if self._preconsumed_start is not None:
|
|
257
|
+
elem = self._preconsumed_start
|
|
258
|
+
self._preconsumed_start = None
|
|
259
|
+
return elem
|
|
260
|
+
event = next(self._reader)
|
|
261
|
+
if event.type != "start":
|
|
262
|
+
raise XMLParseError(f"Expected start element, got '{event.type}'")
|
|
263
|
+
return event.elem
|
|
264
|
+
|
|
265
|
+
def _skip_to_end(self) -> None:
|
|
266
|
+
"""Skip to the matching end event. Assumes start was already consumed."""
|
|
267
|
+
depth = 1
|
|
268
|
+
while depth > 0:
|
|
269
|
+
event = next(self._reader)
|
|
270
|
+
if event.type == "start":
|
|
271
|
+
depth += 1
|
|
272
|
+
elif event.type == "end":
|
|
273
|
+
depth -= 1
|
|
274
|
+
|
|
275
|
+
def _buffer_element(self) -> list[XMLEvent]:
|
|
276
|
+
"""Buffer a complete element's events (start through matching end)."""
|
|
277
|
+
events: list[XMLEvent] = []
|
|
278
|
+
event = next(self._reader)
|
|
279
|
+
events.append(event)
|
|
280
|
+
depth = 1
|
|
281
|
+
while depth > 0:
|
|
282
|
+
event = next(self._reader)
|
|
283
|
+
events.append(event)
|
|
284
|
+
if event.type == "start":
|
|
285
|
+
depth += 1
|
|
286
|
+
elif event.type == "end":
|
|
287
|
+
depth -= 1
|
|
288
|
+
return events
|
|
289
|
+
|
|
290
|
+
def _get_xml_names(self, schema: Schema) -> dict[str, Schema]:
|
|
291
|
+
"""Get or build the XML element name -> member schema mapping for a shape."""
|
|
292
|
+
if schema.id in self._xml_names:
|
|
293
|
+
return self._xml_names[schema.id]
|
|
294
|
+
result: dict[str, Schema] = {}
|
|
295
|
+
for member_schema in schema.members.values():
|
|
296
|
+
if member_schema.get_trait(XMLAttributeTrait) is not None:
|
|
297
|
+
continue
|
|
298
|
+
xml_name = _xml_member_name(member_schema)
|
|
299
|
+
result[xml_name] = member_schema
|
|
300
|
+
self._xml_names[schema.id] = result
|
|
301
|
+
return result
|
|
302
|
+
|
|
303
|
+
def _read_map_entry(
|
|
304
|
+
self,
|
|
305
|
+
key_tag: str,
|
|
306
|
+
value_tag: str,
|
|
307
|
+
consumer: Callable[[str, "ShapeDeserializer"], None],
|
|
308
|
+
) -> None:
|
|
309
|
+
"""Read one map entry element and emit key/value pairs via consumer."""
|
|
310
|
+
self._consume_start_event()
|
|
311
|
+
|
|
312
|
+
key: str | None = None
|
|
313
|
+
while self._reader.peek().type != "end":
|
|
314
|
+
child_tag = _local_name(self._reader.peek().elem.tag)
|
|
315
|
+
if child_tag == key_tag:
|
|
316
|
+
key = self._read_text()
|
|
317
|
+
elif child_tag == value_tag:
|
|
318
|
+
if key is None:
|
|
319
|
+
raise XMLParseError(
|
|
320
|
+
"Map key element must appear before value element"
|
|
321
|
+
)
|
|
322
|
+
consumer(key, self)
|
|
323
|
+
else:
|
|
324
|
+
# Skip unknown child tag
|
|
325
|
+
self._consume_start_event()
|
|
326
|
+
self._skip_to_end()
|
|
327
|
+
|
|
328
|
+
next(self._reader)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class _AttributeDeserializer(SpecificShapeDeserializer):
|
|
332
|
+
"""Deserializer for a value extracted from an XML attribute string."""
|
|
333
|
+
|
|
334
|
+
def __init__(self, value: str, settings: XMLSettings) -> None:
|
|
335
|
+
self._value = value
|
|
336
|
+
self._settings = settings
|
|
337
|
+
|
|
338
|
+
def read_string(self, schema: Schema) -> str:
|
|
339
|
+
return self._value
|
|
340
|
+
|
|
341
|
+
def read_boolean(self, schema: Schema) -> bool:
|
|
342
|
+
match self._value:
|
|
343
|
+
case "true":
|
|
344
|
+
return True
|
|
345
|
+
case "false":
|
|
346
|
+
return False
|
|
347
|
+
case _:
|
|
348
|
+
raise XMLParseError(f"Expected 'true' or 'false', got '{self._value}'")
|
|
349
|
+
|
|
350
|
+
def read_byte(self, schema: Schema) -> int:
|
|
351
|
+
return self.read_integer(schema)
|
|
352
|
+
|
|
353
|
+
def read_short(self, schema: Schema) -> int:
|
|
354
|
+
return self.read_integer(schema)
|
|
355
|
+
|
|
356
|
+
def read_integer(self, schema: Schema) -> int:
|
|
357
|
+
return int(self._value)
|
|
358
|
+
|
|
359
|
+
def read_long(self, schema: Schema) -> int:
|
|
360
|
+
return self.read_integer(schema)
|
|
361
|
+
|
|
362
|
+
def read_big_integer(self, schema: Schema) -> int:
|
|
363
|
+
return self.read_integer(schema)
|
|
364
|
+
|
|
365
|
+
def read_float(self, schema: Schema) -> float:
|
|
366
|
+
return _parse_xml_float(self._value)
|
|
367
|
+
|
|
368
|
+
def read_double(self, schema: Schema) -> float:
|
|
369
|
+
return self.read_float(schema)
|
|
370
|
+
|
|
371
|
+
def read_big_decimal(self, schema: Schema) -> Decimal:
|
|
372
|
+
return Decimal(self._value)
|
|
373
|
+
|
|
374
|
+
def read_timestamp(self, schema: Schema) -> datetime.datetime:
|
|
375
|
+
format = self._settings.default_timestamp_format
|
|
376
|
+
if self._settings.use_timestamp_format:
|
|
377
|
+
if format_trait := schema.get_trait(TimestampFormatTrait):
|
|
378
|
+
format = format_trait.format
|
|
379
|
+
|
|
380
|
+
return format.deserialize(self._value)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
from collections.abc import Iterator
|
|
5
|
+
from typing import NamedTuple
|
|
6
|
+
from xml.etree.ElementTree import Element
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class XMLEvent(NamedTuple):
|
|
10
|
+
type: str
|
|
11
|
+
elem: Element
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class XMLEventReader:
|
|
15
|
+
"""Buffered iterator over XML pull parser events with peek support.
|
|
16
|
+
|
|
17
|
+
Wraps an iterator of ``(event, element)`` tuples — either from
|
|
18
|
+
``iterparse`` (streaming from a byte source) or from an in-memory list
|
|
19
|
+
(for flattened member replay).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, events: Iterator[tuple[str, Element] | XMLEvent]) -> None:
|
|
23
|
+
self._iter = events
|
|
24
|
+
self._pending: XMLEvent | None = None
|
|
25
|
+
|
|
26
|
+
def __iter__(self):
|
|
27
|
+
return self
|
|
28
|
+
|
|
29
|
+
def __next__(self) -> XMLEvent:
|
|
30
|
+
if self._pending is not None:
|
|
31
|
+
result = self._pending
|
|
32
|
+
self._pending = None
|
|
33
|
+
return result
|
|
34
|
+
return self._next()
|
|
35
|
+
|
|
36
|
+
def _next(self) -> XMLEvent:
|
|
37
|
+
event = next(self._iter)
|
|
38
|
+
if isinstance(event, XMLEvent):
|
|
39
|
+
return event
|
|
40
|
+
event_type, elem = event
|
|
41
|
+
return XMLEvent(event_type, elem)
|
|
42
|
+
|
|
43
|
+
def has_next(self) -> bool:
|
|
44
|
+
if self._pending is not None:
|
|
45
|
+
return True
|
|
46
|
+
try:
|
|
47
|
+
self._pending = self._next()
|
|
48
|
+
return True
|
|
49
|
+
except StopIteration:
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
def peek(self) -> XMLEvent:
|
|
53
|
+
if self._pending is None:
|
|
54
|
+
self._pending = self._next()
|
|
55
|
+
return self._pending
|
smithy_xml/py.typed
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Marker
|
smithy_xml/settings.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from smithy_core.types import TimestampFormat
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(slots=True)
|
|
9
|
+
class XMLSettings:
|
|
10
|
+
"""Settings for the XML codec."""
|
|
11
|
+
|
|
12
|
+
use_timestamp_format: bool = True
|
|
13
|
+
"""Whether the codec should use the `smithy.api#timestampFormat` trait, if present."""
|
|
14
|
+
|
|
15
|
+
default_timestamp_format: TimestampFormat = TimestampFormat.DATE_TIME
|
|
16
|
+
"""The default timestamp format to use if the `smithy.api#timestampFormat` trait is
|
|
17
|
+
not enabled or not present."""
|
|
18
|
+
|
|
19
|
+
default_namespace: str | None = None
|
|
20
|
+
"""Default XML namespace (`xmlns`) applied to the root element during serialization."""
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: smithy-xml
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: XML serialization and deserialization support for Smithy tooling.
|
|
5
|
+
Project-URL: Changelog, https://github.com/smithy-lang/smithy-python/blob/develop/packages/smithy-xml/CHANGELOG.md
|
|
6
|
+
Project-URL: Code, https://github.com/smithy-lang/smithy-python/blob/develop/packages/smithy-xml/
|
|
7
|
+
Project-URL: Issue tracker, https://github.com/smithy-lang/smithy-python/issues
|
|
8
|
+
Author: Amazon Web Services
|
|
9
|
+
License: Apache License 2.0
|
|
10
|
+
License-File: NOTICE
|
|
11
|
+
Keywords: sdk,smithy,xml
|
|
12
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Natural Language :: English
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
26
|
+
Requires-Python: >=3.12
|
|
27
|
+
Requires-Dist: smithy-core
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# smithy-xml
|
|
31
|
+
|
|
32
|
+
This package provides generic XML serialization and deserialization support
|
|
33
|
+
for Smithy clients and servers.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
smithy_xml/__init__.py,sha256=Hae8bNbMlHlJrLDDiyRAiZlEk3CFMdK2gciipNXBMAE,2364
|
|
2
|
+
smithy_xml/py.typed,sha256=sow9soTwP9T_gEAQSVh7Gb8855h04Nwmhs2We-JRgZM,7
|
|
3
|
+
smithy_xml/settings.py,sha256=lHA3Igy-GPTuZKCFqwut5c0LOznps8pavYrXRr6Lycw,734
|
|
4
|
+
smithy_xml/_private/__init__.py,sha256=5idTbB990emq5l8fB_u7RfQB5qGATEBtb4PxBU2IIhs,109
|
|
5
|
+
smithy_xml/_private/deserializers.py,sha256=67wWVE8NaBoUqmRP_uqdQ8fimNvbhg7bVQaKKC5aUnE,13456
|
|
6
|
+
smithy_xml/_private/readers.py,sha256=xraoQQAr_r2WMpQD5wPwzNVkknhZ40w_LJX864kj0Z8,1536
|
|
7
|
+
smithy_xml-0.1.0.dist-info/METADATA,sha256=mMkSFmqIR4TxuDx6NcY7b7zRtXTASfXcV06nUkqQKQc,1462
|
|
8
|
+
smithy_xml-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
9
|
+
smithy_xml-0.1.0.dist-info/licenses/NOTICE,sha256=1CkO1kwu3Q_OHYTj-d-yiBJA_lNN73a4zSntavaD4oc,67
|
|
10
|
+
smithy_xml-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|