smithy-xml 0.1.0__tar.gz

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.
@@ -0,0 +1,8 @@
1
+ {
2
+ "changes": [
3
+ {
4
+ "type": "feature",
5
+ "description": "Added initial support for XML deserialization in Smithy clients."
6
+ }
7
+ ]
8
+ }
@@ -0,0 +1,36 @@
1
+ # Eclipse
2
+ .classpath
3
+ .project
4
+ .settings/
5
+
6
+ # Intellij
7
+ .idea/
8
+ *.iml
9
+ *.iws
10
+
11
+ # VS Code
12
+ .vscode/
13
+
14
+ # Mac
15
+ .DS_Store
16
+
17
+ # Maven
18
+ target/
19
+ **/dependency-reduced-pom.xml
20
+
21
+ # Gradle
22
+ **/.gradle
23
+ build/
24
+ */out/
25
+ **/*/out/
26
+
27
+ # Python
28
+ __pycache__
29
+ *.swp
30
+ .mypy_cache
31
+ .coverage
32
+ coverage.xml
33
+ htmlcov
34
+ *.egg-info
35
+ dist
36
+ .venv
@@ -0,0 +1,6 @@
1
+ # Changelog
2
+
3
+ ## v0.1.0
4
+
5
+ ### Features
6
+ * Added initial support for XML deserialization in Smithy clients.
@@ -0,0 +1 @@
1
+ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
@@ -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,4 @@
1
+ # smithy-xml
2
+
3
+ This package provides generic XML serialization and deserialization support
4
+ for Smithy clients and servers.
@@ -0,0 +1,50 @@
1
+ [project]
2
+ name = "smithy-xml"
3
+ dynamic = ["version"]
4
+ requires-python = ">=3.12"
5
+ authors = [
6
+ {name = "Amazon Web Services"},
7
+ ]
8
+ description = "XML serialization and deserialization support for Smithy tooling."
9
+ readme = "README.md"
10
+ license = {text = "Apache License 2.0"}
11
+ keywords = ["smithy", "sdk", "xml"]
12
+ classifiers = [
13
+ "Development Status :: 2 - Pre-Alpha",
14
+ "Intended Audience :: Developers",
15
+ "Intended Audience :: System Administrators",
16
+ "Natural Language :: English",
17
+ "License :: OSI Approved :: Apache Software License",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
+ "Programming Language :: Python :: Implementation :: CPython",
26
+ "Topic :: Software Development :: Libraries"
27
+ ]
28
+ dependencies = [
29
+ "smithy-core",
30
+ ]
31
+
32
+ [project.urls]
33
+ "Changelog" = "https://github.com/smithy-lang/smithy-python/blob/develop/packages/smithy-xml/CHANGELOG.md"
34
+ "Code" = "https://github.com/smithy-lang/smithy-python/blob/develop/packages/smithy-xml/"
35
+ "Issue tracker" = "https://github.com/smithy-lang/smithy-python/issues"
36
+
37
+ [build-system]
38
+ requires = ["hatchling"]
39
+ build-backend = "hatchling.build"
40
+
41
+ [tool.hatch.version]
42
+ path = "src/smithy_xml/__init__.py"
43
+
44
+ [tool.hatch.build]
45
+ exclude = [
46
+ "tests",
47
+ ]
48
+
49
+ [tool.ruff]
50
+ src = ["src"]
@@ -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,2 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
@@ -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
@@ -0,0 +1 @@
1
+ Marker
@@ -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."""