Skip to content
This repository was archived by the owner on May 22, 2026. It is now read-only.

Commit f684c1d

Browse files
authored
Sync from typespec main (2026-04-01) (64d27b7) (#3416)
* Update dependencies * Sync shared files from typespec repo (2026-04-01 00:02:41) * Add changelog * Update dependencies (2026-04-01 00:04:55) * Regenerate for typespec-python (2026-04-01 00:10:34) --------- Co-authored-by: AutoPrFromHttpClientPython <AutoPrFromHttpClientPython>
1 parent 824dd02 commit f684c1d

186 files changed

Lines changed: 28126 additions & 11529 deletions

File tree

  • .chronus/changes
  • packages
    • autorest.python
    • typespec-python
      • test
        • azure/generated
          • authentication-api-key/authentication/apikey/_utils
          • authentication-http-custom/authentication/http/custom/_utils
          • authentication-noauth-union/authentication/noauth/union/_utils
          • authentication-oauth2/authentication/oauth2/_utils
          • authentication-union/authentication/union/_utils
          • azure-client-generator-core-access/specs/azure/clientgenerator/core/access/_utils
          • azure-client-generator-core-alternate-type/specs/azure/clientgenerator/core/alternatetype/_utils
          • azure-client-generator-core-api-version-header/client/alternateapiversion/service/header/_utils
          • azure-client-generator-core-api-version-path/client/alternateapiversion/service/path/_utils
          • azure-client-generator-core-api-version-query/client/alternateapiversion/service/query/_utils
          • azure-client-generator-core-client-default-value/specs/azure/clientgenerator/core/clientdefaultvalue/_utils
          • azure-client-generator-core-client-initialization-default/specs/azure/clientgenerator/core/clientinitialization/default/_utils
          • azure-client-generator-core-client-initialization-individuallyparent/specs/azure/clientgenerator/core/clientinitialization/individuallyparent/_utils
          • azure-client-generator-core-client-initialization-individually/specs/azure/clientgenerator/core/clientinitialization/individually/_utils
          • azure-client-generator-core-client-location-move-method-parameter-to-client/specs/azure/clientgenerator/core/clientlocation/parameter/_utils
          • azure-client-generator-core-client-location-move-to-existing-sub-client/specs/azure/clientgenerator/core/clientlocation/subclient/_utils
          • azure-client-generator-core-client-location-move-to-new-sub-client/specs/azure/clientgenerator/core/clientlocation/newsubclient/_utils
          • azure-client-generator-core-client-location-move-to-root-client/specs/azure/clientgenerator/core/clientlocation/rootclient/_utils
          • azure-client-generator-core-deserialize-empty-string-as-null/specs/azure/clientgenerator/core/emptystring/_utils
          • azure-client-generator-core-flatten-property/specs/azure/clientgenerator/core/flattenproperty/_utils
          • azure-client-generator-core-hierarchy-building/specs/azure/clientgenerator/core/hierarchybuilding/_utils
          • azure-client-generator-core-override/specs/azure/clientgenerator/core/override/_utils
          • azure-client-generator-core-usage/specs/azure/clientgenerator/core/usage/_utils
          • azure-core-basic/specs/azure/core/basic/_utils
          • azure-core-lro-rpc/specs/azure/core/lro/rpc/_utils
          • azure-core-lro-standard/specs/azure/core/lro/standard/_utils
          • azure-core-model/specs/azure/core/model/_utils
          • azure-core-page/specs/azure/core/page/_utils
          • azure-core-scalar/specs/azure/core/scalar/_utils
          • azure-core-traits/specs/azure/core/traits/_utils
          • azure-encode-duration/specs/azure/encode/duration/_utils
          • azure-example-basic/specs/azure/example/basic/_utils
          • azure-payload-pageable/specs/azure/payload/pageable/_utils
          • azure-resource-manager-common-properties/azure/resourcemanager/commonproperties/_utils
          • azure-resource-manager-large-header/azure/resourcemanager/largeheader/_utils
          • azure-resource-manager-method-subscription-id/azure/resourcemanager/methodsubscriptionid/_utils
          • azure-resource-manager-multi-service-shared-models/azure/resourcemanager/multiservicesharedmodels/combined/_utils
          • azure-resource-manager-multi-service/azure/resourcemanager/multiservice/combined/_utils
          • azure-resource-manager-non-resource/azure/resourcemanager/nonresource/_utils
          • azure-resource-manager-operation-templates/azure/resourcemanager/operationtemplates/_utils
          • azure-resource-manager-resources/azure/resourcemanager/resources/_utils
          • azure-special-headers-client-request-id/azure/specialheaders/xmsclientrequestid/_utils
          • azure-versioning-previewversion/specs/azure/versioning/previewversion/_utils
          • client-namespace/client/clientnamespace
          • client-naming-enum-conflict/client/naming/enumconflict/_utils
          • client-naming/client/naming/main/_utils
          • client-overload/client/overload/_utils
          • client-structure-clientoperationgroup/client/structure/clientoperationgroup/_utils
          • client-structure-default/client/structure/service/_utils
          • client-structure-multiclient/client/structure/multiclient/_utils
          • client-structure-renamedoperation/client/structure/renamedoperation/_utils
          • client-structure-twooperationgroup/client/structure/twooperationgroup/_utils
          • encode-array/encode/array/_utils
          • encode-bytes/encode/bytes/_utils
          • encode-datetime/encode/datetime/_utils
          • encode-duration/encode/duration/_utils
          • encode-numeric/encode/numeric/_utils
          • generation-subdir/generation/subdir/_generated/_utils
          • headasbooleanfalse/headasbooleanfalse/_utils
          • headasbooleantrue/headasbooleantrue/_utils
          • parameters-basic/parameters/basic/_utils
          • parameters-body-optionality/parameters/bodyoptionality/_utils
          • parameters-collection-format/parameters/collectionformat/_utils
          • parameters-path/parameters/path/_utils
          • parameters-query/parameters/query/_utils
          • parameters-spread/parameters/spread/_utils
          • payload-content-negotiation/payload/contentnegotiation/_utils
          • payload-json-merge-patch/payload/jsonmergepatch/_utils
          • payload-media-type/payload/mediatype/_utils
          • payload-multipart/payload/multipart/_utils
          • payload-pageable/payload/pageable/_utils
          • payload-xml/payload/xml/_utils
          • resiliency-srv-driven1/resiliency/srv/driven1/_utils
          • resiliency-srv-driven2/resiliency/srv/driven2/_utils
          • response-status-code-range/response/statuscoderange/_utils
          • routes/routes/_utils
          • serialization-encoded-name-json/serialization/encodedname/json/_utils
          • server-endpoint-not-defined/server/endpoint/notdefined/_utils
          • server-path-multiple/server/path/multiple/_utils
          • server-path-single/server/path/single/_utils
          • server-versions-not-versioned/server/versions/notversioned/_utils
          • server-versions-versioned/server/versions/versioned/_utils
          • service-multi-service/service/multiservice/_utils
          • setuppy-authentication-union/setuppy/authentication/union/_utils
          • special-headers-conditional-request/specialheaders/conditionalrequest/_utils
          • special-headers-repeatability/specialheaders/repeatability/_utils
          • special-words/specialwords/_utils
          • specs-documentation/specs/documentation/_utils
          • streaming-jsonl/streaming/jsonl/_utils
          • typetest-array/typetest/array/_utils
          • typetest-dictionary/typetest/dictionary/_utils
          • typetest-discriminatedunion/typetest/discriminatedunion/_utils
          • typetest-enum-extensible/typetest/enum/extensible/_utils
          • typetest-enum-fixed/typetest/enum/fixed/_utils
          • typetest-model-empty/typetest/model/empty/_utils
          • typetest-model-enumdiscriminator/typetest/model/enumdiscriminator/_utils
          • typetest-model-nesteddiscriminator/typetest/model/nesteddiscriminator/_utils
          • typetest-model-notdiscriminated/typetest/model/notdiscriminated/_utils
          • typetest-model-recursive/typetest/model/recursive/_utils
          • typetest-model-singlediscriminator/typetest/model/singlediscriminator/_utils
          • typetest-model-usage/typetest/model/usage/_utils
          • typetest-model-visibility/typetest/model/visibility/_utils
          • typetest-property-additionalproperties/typetest/property/additionalproperties/_utils
          • typetest-property-nullable/typetest/property/nullable/_utils
          • typetest-property-optional/typetest/property/optional/_utils
          • typetest-property-valuetypes/typetest/property/valuetypes/_utils
          • typetest-scalar/typetest/scalar/_utils
          • typetest-union/typetest/union/_utils
          • versioning-added/versioning/added/_utils
          • versioning-madeoptional/versioning/madeoptional/_utils
          • versioning-removed/versioning/removed/_utils
          • versioning-renamedfrom/versioning/renamedfrom/_utils
          • versioning-returntypechangedfrom/versioning/returntypechangedfrom/_utils
          • versioning-typechangedfrom/versioning/typechangedfrom/_utils
        • generic_mock_api_tests
        • unbranded/generated

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
changeKind: internal
3+
packages:
4+
- "@autorest/python"
5+
- "@azure-tools/typespec-python"
6+
---
7+
8+
Add mock API test cases for XML scenarios introduced in https://github.com/microsoft/typespec/pull/10063, covering: renamed property, nested model, renamed nested model, wrapped primitive with custom item names, model array variants (wrapped/unwrapped/renamed), renamed attribute, namespace, and namespace-on-properties. Fix XML serialization/deserialization bugs: unwrapped model array element naming, namespace key mismatch between DPG models and the runtime template, default-namespace propagation semantics, and handling of Python-reserved namespace prefixes.

packages/autorest.python/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
},
3030
"homepage": "https://github.com/Azure/autorest.python/blob/main/README.md",
3131
"dependencies": {
32-
"@typespec/http-client-python": "https://artprodcus3.artifacts.visualstudio.com/A0fb41ef4-5012-48a9-bf39-4ee3de03ee35/29ec6040-b234-4e31-b139-33dc4287b756/_apis/artifact/cGlwZWxpbmVhcnRpZmFjdDovL2F6dXJlLXNkay9wcm9qZWN0SWQvMjllYzYwNDAtYjIzNC00ZTMxLWIxMzktMzNkYzQyODdiNzU2L2J1aWxkSWQvNjA3ODg3OS9hcnRpZmFjdE5hbWUvYnVpbGRfYXJ0aWZhY3RzX3B5dGhvbg2/content?format=file&subPath=%2Fpackages%2Ftypespec-http-client-python-0.28.2.tgz",
32+
"@typespec/http-client-python": "https://artprodcus3.artifacts.visualstudio.com/A0fb41ef4-5012-48a9-bf39-4ee3de03ee35/29ec6040-b234-4e31-b139-33dc4287b756/_apis/artifact/cGlwZWxpbmVhcnRpZmFjdDovL2F6dXJlLXNkay9wcm9qZWN0SWQvMjllYzYwNDAtYjIzNC00ZTMxLWIxMzktMzNkYzQyODdiNzU2L2J1aWxkSWQvNjA4ODMyMy9hcnRpZmFjdE5hbWUvYnVpbGRfYXJ0aWZhY3RzX3B5dGhvbg2/content?format=file&subPath=%2Fpackages%2Ftypespec-http-client-python-0.28.2.tgz",
3333
"@autorest/system-requirements": "~1.0.2",
3434
"fs-extra": "~11.2.0",
3535
"tsx": "^4.21.0"

packages/typespec-python/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
"js-yaml": "~4.1.0",
6868
"semver": "~7.6.2",
6969
"tsx": "^4.21.0",
70-
"@typespec/http-client-python": "https://artprodcus3.artifacts.visualstudio.com/A0fb41ef4-5012-48a9-bf39-4ee3de03ee35/29ec6040-b234-4e31-b139-33dc4287b756/_apis/artifact/cGlwZWxpbmVhcnRpZmFjdDovL2F6dXJlLXNkay9wcm9qZWN0SWQvMjllYzYwNDAtYjIzNC00ZTMxLWIxMzktMzNkYzQyODdiNzU2L2J1aWxkSWQvNjA3ODg3OS9hcnRpZmFjdE5hbWUvYnVpbGRfYXJ0aWZhY3RzX3B5dGhvbg2/content?format=file&subPath=%2Fpackages%2Ftypespec-http-client-python-0.28.2.tgz",
70+
"@typespec/http-client-python": "https://artprodcus3.artifacts.visualstudio.com/A0fb41ef4-5012-48a9-bf39-4ee3de03ee35/29ec6040-b234-4e31-b139-33dc4287b756/_apis/artifact/cGlwZWxpbmVhcnRpZmFjdDovL2F6dXJlLXNkay9wcm9qZWN0SWQvMjllYzYwNDAtYjIzNC00ZTMxLWIxMzktMzNkYzQyODdiNzU2L2J1aWxkSWQvNjA4ODMyMy9hcnRpZmFjdE5hbWUvYnVpbGRfYXJ0aWZhY3RzX3B5dGhvbg2/content?format=file&subPath=%2Fpackages%2Ftypespec-http-client-python-0.28.2.tgz",
7171
"fs-extra": "~11.2.0"
7272
},
7373
"devDependencies": {

packages/typespec-python/test/azure/generated/authentication-api-key/authentication/apikey/_utils/model_base.py

Lines changed: 155 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -600,57 +600,9 @@ def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
600600
for rest_field in self._attr_to_rest_field.values()
601601
if rest_field._default is not _UNSET
602602
}
603-
if args: # pylint: disable=too-many-nested-blocks
603+
if args:
604604
if isinstance(args[0], ET.Element):
605-
existed_attr_keys = []
606-
model_meta = getattr(self, "_xml", {})
607-
608-
for rf in self._attr_to_rest_field.values():
609-
prop_meta = getattr(rf, "_xml", {})
610-
xml_name = prop_meta.get("name", rf._rest_name)
611-
xml_ns = prop_meta.get("ns", model_meta.get("ns", None))
612-
if xml_ns:
613-
xml_name = "{" + xml_ns + "}" + xml_name
614-
615-
# attribute
616-
if prop_meta.get("attribute", False) and args[0].get(xml_name) is not None:
617-
existed_attr_keys.append(xml_name)
618-
dict_to_pass[rf._rest_name] = _deserialize(rf._type, args[0].get(xml_name))
619-
continue
620-
621-
# unwrapped element is array
622-
if prop_meta.get("unwrapped", False):
623-
# unwrapped array could either use prop items meta/prop meta
624-
if prop_meta.get("itemsName"):
625-
xml_name = prop_meta.get("itemsName")
626-
xml_ns = prop_meta.get("itemNs")
627-
if xml_ns:
628-
xml_name = "{" + xml_ns + "}" + xml_name
629-
items = args[0].findall(xml_name) # pyright: ignore
630-
if len(items) > 0:
631-
existed_attr_keys.append(xml_name)
632-
dict_to_pass[rf._rest_name] = _deserialize(rf._type, items)
633-
elif not rf._is_optional:
634-
existed_attr_keys.append(xml_name)
635-
dict_to_pass[rf._rest_name] = []
636-
continue
637-
638-
# text element is primitive type
639-
if prop_meta.get("text", False):
640-
if args[0].text is not None:
641-
dict_to_pass[rf._rest_name] = _deserialize(rf._type, args[0].text)
642-
continue
643-
644-
# wrapped element could be normal property or array, it should only have one element
645-
item = args[0].find(xml_name)
646-
if item is not None:
647-
existed_attr_keys.append(xml_name)
648-
dict_to_pass[rf._rest_name] = _deserialize(rf._type, item)
649-
650-
# rest thing is additional properties
651-
for e in args[0]:
652-
if e.tag not in existed_attr_keys:
653-
dict_to_pass[e.tag] = _convert_element(e)
605+
dict_to_pass.update(self._init_from_xml(args[0]))
654606
else:
655607
dict_to_pass.update(
656608
{k: _create_value(_get_rest_field(self._attr_to_rest_field, k), v) for k, v in args[0].items()}
@@ -669,6 +621,69 @@ def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
669621
)
670622
super().__init__(dict_to_pass)
671623

624+
def _init_from_xml(self, element: ET.Element) -> dict[str, typing.Any]:
625+
"""Deserialize an XML element into a dict mapping rest field names to values.
626+
627+
:param ET.Element element: The XML element to deserialize from.
628+
:returns: A dictionary of rest_name to deserialized value pairs.
629+
:rtype: dict
630+
"""
631+
result: dict[str, typing.Any] = {}
632+
model_meta = getattr(self, "_xml", {})
633+
existed_attr_keys: list[str] = []
634+
635+
for rf in self._attr_to_rest_field.values():
636+
prop_meta = getattr(rf, "_xml", {})
637+
xml_name = prop_meta.get("name", rf._rest_name)
638+
xml_ns = _resolve_xml_ns(prop_meta, model_meta)
639+
if xml_ns:
640+
xml_name = "{" + xml_ns + "}" + xml_name
641+
642+
# attribute
643+
if prop_meta.get("attribute", False) and element.get(xml_name) is not None:
644+
existed_attr_keys.append(xml_name)
645+
result[rf._rest_name] = _deserialize(rf._type, element.get(xml_name))
646+
continue
647+
648+
# unwrapped element is array
649+
if prop_meta.get("unwrapped", False):
650+
# unwrapped array could either use prop items meta/prop meta
651+
_items_name = prop_meta.get("itemsName")
652+
if _items_name:
653+
xml_name = _items_name
654+
_items_ns = prop_meta.get("itemsNs")
655+
if _items_ns is not None:
656+
xml_ns = _items_ns
657+
if xml_ns:
658+
xml_name = "{" + xml_ns + "}" + xml_name
659+
items = element.findall(xml_name) # pyright: ignore
660+
if len(items) > 0:
661+
existed_attr_keys.append(xml_name)
662+
result[rf._rest_name] = _deserialize(rf._type, items)
663+
elif not rf._is_optional:
664+
existed_attr_keys.append(xml_name)
665+
result[rf._rest_name] = []
666+
continue
667+
668+
# text element is primitive type
669+
if prop_meta.get("text", False):
670+
if element.text is not None:
671+
result[rf._rest_name] = _deserialize(rf._type, element.text)
672+
continue
673+
674+
# wrapped element could be normal property or array, it should only have one element
675+
item = element.find(xml_name)
676+
if item is not None:
677+
existed_attr_keys.append(xml_name)
678+
result[rf._rest_name] = _deserialize(rf._type, item)
679+
680+
# rest thing is additional properties
681+
for e in element:
682+
if e.tag not in existed_attr_keys:
683+
result[e.tag] = _convert_element(e)
684+
685+
return result
686+
672687
def copy(self) -> "Model":
673688
return Model(self.__dict__)
674689

@@ -721,7 +736,7 @@ def _deserialize(cls, data, exist_discriminators):
721736
model_meta = getattr(cls, "_xml", {})
722737
prop_meta = getattr(discriminator, "_xml", {})
723738
xml_name = prop_meta.get("name", discriminator._rest_name)
724-
xml_ns = prop_meta.get("ns", model_meta.get("ns", None))
739+
xml_ns = _resolve_xml_ns(prop_meta, model_meta)
725740
if xml_ns:
726741
xml_name = "{" + xml_ns + "}" + xml_name
727742

@@ -1190,6 +1205,56 @@ def serialize_xml(model: Model, exclude_readonly: bool = False) -> str:
11901205
return ET.tostring(_get_element(model, exclude_readonly), encoding="unicode") # type: ignore
11911206

11921207

1208+
def _get_xml_ns(meta: dict[str, typing.Any]) -> typing.Optional[str]:
1209+
"""Return the XML namespace from a metadata dict, checking both 'ns' (old-style) and 'namespace' (DPG) keys.
1210+
1211+
:param dict meta: The metadata dictionary to extract namespace from.
1212+
:returns: The namespace string if 'ns' or 'namespace' key is present, None otherwise.
1213+
:rtype: str or None
1214+
"""
1215+
ns = meta.get("ns")
1216+
if ns is None:
1217+
ns = meta.get("namespace")
1218+
return ns
1219+
1220+
1221+
def _resolve_xml_ns(
1222+
prop_meta: dict[str, typing.Any], model_meta: typing.Optional[dict[str, typing.Any]] = None
1223+
) -> typing.Optional[str]:
1224+
"""Resolve XML namespace for a property, falling back to model namespace when appropriate.
1225+
1226+
Checks the property metadata first; if no namespace is found and the model does not declare
1227+
an explicit prefix, falls back to the model-level namespace.
1228+
1229+
:param dict prop_meta: The property metadata dictionary.
1230+
:param dict model_meta: The model metadata dictionary, used as fallback.
1231+
:returns: The resolved namespace string, or None.
1232+
:rtype: str or None
1233+
"""
1234+
ns = _get_xml_ns(prop_meta)
1235+
if ns is None and model_meta is not None and not model_meta.get("prefix"):
1236+
ns = _get_xml_ns(model_meta)
1237+
return ns
1238+
1239+
1240+
def _set_xml_attribute(element: ET.Element, name: str, value: typing.Any, prop_meta: dict[str, typing.Any]) -> None:
1241+
"""Set an XML attribute on an element, handling namespace prefix registration.
1242+
1243+
:param ET.Element element: The element to set the attribute on.
1244+
:param str name: The default attribute name (wire name).
1245+
:param any value: The attribute value.
1246+
:param dict prop_meta: The property metadata dictionary.
1247+
"""
1248+
xml_name = prop_meta.get("name", name)
1249+
_attr_ns = _get_xml_ns(prop_meta)
1250+
if _attr_ns:
1251+
_attr_prefix = prop_meta.get("prefix")
1252+
if _attr_prefix:
1253+
_safe_register_namespace(_attr_prefix, _attr_ns)
1254+
xml_name = "{" + _attr_ns + "}" + xml_name
1255+
element.set(xml_name, _get_primitive_type_value(value))
1256+
1257+
11931258
def _get_element(
11941259
o: typing.Any,
11951260
exclude_readonly: bool = False,
@@ -1201,10 +1266,16 @@ def _get_element(
12011266

12021267
# if prop is a model, then use the prop element directly, else generate a wrapper of model
12031268
if wrapped_element is None:
1269+
# When serializing as an array item (parent_meta is set), check if the parent has an
1270+
# explicit itemsName. This ensures correct element names for unwrapped arrays (where
1271+
# the element tag is the property/items name, not the model type name).
1272+
_items_name = parent_meta.get("itemsName") if parent_meta is not None else None
1273+
element_name = _items_name if _items_name else (model_meta.get("name") or o.__class__.__name__)
1274+
_model_ns = _get_xml_ns(model_meta)
12041275
wrapped_element = _create_xml_element(
1205-
model_meta.get("name", o.__class__.__name__),
1276+
element_name,
12061277
model_meta.get("prefix"),
1207-
model_meta.get("ns"),
1278+
_model_ns,
12081279
)
12091280

12101281
readonly_props = []
@@ -1226,7 +1297,9 @@ def _get_element(
12261297
# additional properties will not have rest field, use the wire name as xml name
12271298
prop_meta = {"name": k}
12281299

1229-
# if no ns for prop, use model's
1300+
# Propagate model namespace to properties only for old-style "ns"-keyed models.
1301+
# DPG-generated models use the "namespace" key and explicitly declare namespace on
1302+
# each property that needs it, so propagation is intentionally skipped for them.
12301303
if prop_meta.get("ns") is None and model_meta.get("ns"):
12311304
prop_meta["ns"] = model_meta.get("ns")
12321305
prop_meta["prefix"] = model_meta.get("prefix")
@@ -1238,12 +1311,7 @@ def _get_element(
12381311
# text could only set on primitive type
12391312
wrapped_element.text = _get_primitive_type_value(v)
12401313
elif prop_meta.get("attribute", False):
1241-
xml_name = prop_meta.get("name", k)
1242-
if prop_meta.get("ns"):
1243-
ET.register_namespace(prop_meta.get("prefix"), prop_meta.get("ns")) # pyright: ignore
1244-
xml_name = "{" + prop_meta.get("ns") + "}" + xml_name # pyright: ignore
1245-
# attribute should be primitive type
1246-
wrapped_element.set(xml_name, _get_primitive_type_value(v))
1314+
_set_xml_attribute(wrapped_element, k, v, prop_meta)
12471315
else:
12481316
# other wrapped prop element
12491317
wrapped_element.append(_get_wrapped_element(v, exclude_readonly, prop_meta))
@@ -1252,14 +1320,15 @@ def _get_element(
12521320
return [_get_element(x, exclude_readonly, parent_meta) for x in o] # type: ignore
12531321
if isinstance(o, dict):
12541322
result = []
1323+
_dict_ns = _get_xml_ns(parent_meta) if parent_meta else None
12551324
for k, v in o.items():
12561325
result.append(
12571326
_get_wrapped_element(
12581327
v,
12591328
exclude_readonly,
12601329
{
12611330
"name": k,
1262-
"ns": parent_meta.get("ns") if parent_meta else None,
1331+
"ns": _dict_ns,
12631332
"prefix": parent_meta.get("prefix") if parent_meta else None,
12641333
},
12651334
)
@@ -1268,13 +1337,16 @@ def _get_element(
12681337

12691338
# primitive case need to create element based on parent_meta
12701339
if parent_meta:
1340+
_items_ns = parent_meta.get("itemsNs")
1341+
if _items_ns is None:
1342+
_items_ns = _get_xml_ns(parent_meta)
12711343
return _get_wrapped_element(
12721344
o,
12731345
exclude_readonly,
12741346
{
12751347
"name": parent_meta.get("itemsName", parent_meta.get("name")),
12761348
"prefix": parent_meta.get("itemsPrefix", parent_meta.get("prefix")),
1277-
"ns": parent_meta.get("itemsNs", parent_meta.get("ns")),
1349+
"ns": _items_ns,
12781350
},
12791351
)
12801352

@@ -1286,8 +1358,9 @@ def _get_wrapped_element(
12861358
exclude_readonly: bool,
12871359
meta: typing.Optional[dict[str, typing.Any]],
12881360
) -> ET.Element:
1361+
_meta_ns = _get_xml_ns(meta) if meta else None
12891362
wrapped_element = _create_xml_element(
1290-
meta.get("name") if meta else None, meta.get("prefix") if meta else None, meta.get("ns") if meta else None
1363+
meta.get("name") if meta else None, meta.get("prefix") if meta else None, _meta_ns
12911364
)
12921365
if isinstance(v, (dict, list)):
12931366
wrapped_element.extend(_get_element(v, exclude_readonly, meta))
@@ -1308,11 +1381,29 @@ def _get_primitive_type_value(v) -> str:
13081381
return str(v)
13091382

13101383

1384+
def _safe_register_namespace(prefix: str, ns: str) -> None:
1385+
"""Register an XML namespace prefix, handling reserved prefix patterns.
1386+
1387+
Some prefixes (e.g. 'ns2') match Python's reserved 'ns\\d+' pattern used for
1388+
auto-generated prefixes, causing register_namespace to raise ValueError.
1389+
Falls back to directly registering in the internal namespace map.
1390+
1391+
:param str prefix: The namespace prefix to register.
1392+
:param str ns: The namespace URI.
1393+
"""
1394+
try:
1395+
ET.register_namespace(prefix, ns)
1396+
except ValueError:
1397+
_ns_map = getattr(ET, "_namespace_map", None)
1398+
if _ns_map is not None:
1399+
_ns_map[ns] = prefix
1400+
1401+
13111402
def _create_xml_element(
13121403
tag: typing.Any, prefix: typing.Optional[str] = None, ns: typing.Optional[str] = None
13131404
) -> ET.Element:
13141405
if prefix and ns:
1315-
ET.register_namespace(prefix, ns)
1406+
_safe_register_namespace(prefix, ns)
13161407
if ns:
13171408
return ET.Element("{" + ns + "}" + tag)
13181409
return ET.Element(tag)

0 commit comments

Comments
 (0)