Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@typespec/http-client-python"
---

Add unit test for deserializing Azure Blob Storage EnumerationResults XML payload with attributes, empty list element, and empty string element.
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,186 @@ def __init__(self, *args, **kwargs):
assert isinstance(result.filter, CorrelationFilter)
assert result.filter.correlation_id == 12

def test_enumeration_results(self):
"""Test deserializing an Azure Blob Storage EnumerationResults XML payload."""
xml_payload = '<?xml version="1.0" encoding="utf-8"?><EnumerationResults ServiceEndpoint="https://service.blob.core.windows.net/" ContainerName="acontainer108f32e8"><Delimiter>/</Delimiter><Blobs /><NextMarker /></EnumerationResults>'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot what if the model we are deserializing could potential have a list of lists and both are empty, like blobs contains blobprefixes which expects an empty list

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added test_enumeration_results_nested_empty_list in commit 833e54e. It uses XML <Blobs><BlobPrefixes /></Blobs> where blobs is a BlobsSegment model containing blob_prefixes: list[BlobPrefix]. The test asserts that result.blobs.blob_prefixes == [] — an empty <BlobPrefixes /> element inside a container correctly deserializes to an empty list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added test_enumeration_results_azure_sdk_pattern in commit 8caa873. It follows the linked Azure SDK model — BlobsSegment has both blob_prefixes: list[BlobPrefix] (unwrapped) and blob_items: list[BlobItem] (unwrapped). With <Blobs />, no matching children are found so both lists are None.


class EnumerationResults(Model):
service_endpoint: str = rest_field(
name="ServiceEndpoint", xml={"name": "ServiceEndpoint", "attribute": True}
)
container_name: str = rest_field(name="ContainerName", xml={"name": "ContainerName", "attribute": True})
delimiter: str = rest_field(name="Delimiter", xml={"name": "Delimiter"})
blobs: list[str] = rest_field(name="Blobs", xml={"name": "Blobs"})
next_marker: str = rest_field(name="NextMarker", xml={"name": "NextMarker"})

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

_xml = {"name": "EnumerationResults"}

result = _deserialize_xml(EnumerationResults, xml_payload)

assert result.service_endpoint == "https://service.blob.core.windows.net/"
assert result.container_name == "acontainer108f32e8"
assert result.delimiter == "/"
assert result.blobs == []
assert result.next_marker == ""

def test_enumeration_results_nested_empty_list(self):
"""Test deserializing XML where a container element holds a nested empty list (e.g. Blobs/BlobPrefixes)."""
xml_payload = '<?xml version="1.0" encoding="utf-8"?><EnumerationResults ServiceEndpoint="https://service.blob.core.windows.net/" ContainerName="acontainer"><Delimiter>/</Delimiter><Blobs><BlobPrefixes /></Blobs><NextMarker /></EnumerationResults>'

class BlobPrefix(Model):
name: str = rest_field(name="Name", xml={"name": "Name"})

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

_xml = {"name": "BlobPrefix"}

class BlobsSegment(Model):
blob_prefixes: list[BlobPrefix] = rest_field(
name="BlobPrefixes", xml={"name": "BlobPrefixes", "itemsName": "BlobPrefix"}
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

_xml = {"name": "Blobs"}

class EnumerationResults(Model):
service_endpoint: str = rest_field(
name="ServiceEndpoint", xml={"name": "ServiceEndpoint", "attribute": True}
)
container_name: str = rest_field(name="ContainerName", xml={"name": "ContainerName", "attribute": True})
delimiter: str = rest_field(name="Delimiter", xml={"name": "Delimiter"})
blobs: BlobsSegment = rest_field(name="Blobs", xml={"name": "Blobs"})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot what if blobs is xml.unwrapped True

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added test_enumeration_results_blobs_unwrapped in commit 8caa873. When a model-typed field (blobs: BlobsSegment) has unwrapped=True, the deserialization enters the unwrapped-list branch and stores the collected XML elements as-is — result.blobs becomes a list[ET.Element] instead of a BlobsSegment instance. The test documents this current behavior.

next_marker: str = rest_field(name="NextMarker", xml={"name": "NextMarker"})

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

_xml = {"name": "EnumerationResults"}

result = _deserialize_xml(EnumerationResults, xml_payload)

assert result.service_endpoint == "https://service.blob.core.windows.net/"
assert result.container_name == "acontainer"
assert result.delimiter == "/"
assert result.blobs.blob_prefixes == []
assert result.next_marker == ""

def test_enumeration_results_azure_sdk_pattern(self):
"""Test the real Azure SDK model pattern where BlobsSegment has two unwrapped list fields."""
# Both blob_prefixes and blob_items are unwrapped lists (items appear directly in <Blobs>).
# With <Blobs />, no matching children are found so both are None.
xml_payload = '<?xml version="1.0" encoding="utf-8"?><EnumerationResults ServiceEndpoint="https://service.blob.core.windows.net/" ContainerName="acontainer"><Delimiter>/</Delimiter><Blobs /><NextMarker /></EnumerationResults>'

class BlobPrefix(Model):
name: str = rest_field(name="Name", xml={"name": "Name"})

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

_xml = {"name": "BlobPrefix"}

class BlobItem(Model):
name: str = rest_field(name="Name", xml={"name": "Name"})

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

_xml = {"name": "Blob"}

class BlobsSegment(Model):
blob_prefixes: list[BlobPrefix] = rest_field(
name="blob_prefixes", xml={"name": "BlobPrefix", "unwrapped": True}
)
blob_items: list[BlobItem] = rest_field(name="blob_items", xml={"name": "Blob", "unwrapped": True})

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

_xml = {"name": "Blobs"}

class EnumerationResults(Model):
service_endpoint: str = rest_field(
name="ServiceEndpoint", xml={"name": "ServiceEndpoint", "attribute": True}
)
container_name: str = rest_field(name="ContainerName", xml={"name": "ContainerName", "attribute": True})
delimiter: str = rest_field(name="Delimiter", xml={"name": "Delimiter"})
blobs: BlobsSegment = rest_field(name="Blobs", xml={"name": "Blobs"})
next_marker: str = rest_field(name="NextMarker", xml={"name": "NextMarker"})

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

_xml = {"name": "EnumerationResults"}

result = _deserialize_xml(EnumerationResults, xml_payload)

assert result.service_endpoint == "https://service.blob.core.windows.net/"
assert result.container_name == "acontainer"
assert result.delimiter == "/"
assert isinstance(result.blobs, BlobsSegment)
# With <Blobs />, no <BlobPrefix> or <Blob> children exist → unwrapped empty lists stay None
assert result.blobs.blob_prefixes is None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want this to be None?

assert result.blobs.blob_items is None
assert result.next_marker == ""

def test_enumeration_results_blobs_unwrapped(self):
"""Test what happens when the blobs field itself is declared with unwrapped=True."""
# When a non-list model field uses unwrapped=True, the matching XML elements are collected
# as a list and stored as-is (the field receives a list of ET.Element objects).
xml_payload = '<?xml version="1.0" encoding="utf-8"?><EnumerationResults ServiceEndpoint="https://service.blob.core.windows.net/" ContainerName="acontainer"><Delimiter>/</Delimiter><Blobs /><NextMarker /></EnumerationResults>'

class BlobPrefix(Model):
name: str = rest_field(name="Name", xml={"name": "Name"})

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

_xml = {"name": "BlobPrefix"}

class BlobsSegment(Model):
blob_prefixes: list[BlobPrefix] = rest_field(
name="BlobPrefixes", xml={"name": "BlobPrefixes", "itemsName": "BlobPrefix"}
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

_xml = {"name": "Blobs"}

class EnumerationResults(Model):
service_endpoint: str = rest_field(
name="ServiceEndpoint", xml={"name": "ServiceEndpoint", "attribute": True}
)
container_name: str = rest_field(name="ContainerName", xml={"name": "ContainerName", "attribute": True})
delimiter: str = rest_field(name="Delimiter", xml={"name": "Delimiter"})
# unwrapped=True on a model-typed field: the deserialization collects matching XML
# elements as a list (rather than deserializing them into the model).
blobs: BlobsSegment = rest_field(name="Blobs", xml={"name": "Blobs", "unwrapped": True})
next_marker: str = rest_field(name="NextMarker", xml={"name": "NextMarker"})

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

_xml = {"name": "EnumerationResults"}

result = _deserialize_xml(EnumerationResults, xml_payload)

assert result.service_endpoint == "https://service.blob.core.windows.net/"
assert result.container_name == "acontainer"
assert result.delimiter == "/"
# unwrapped=True on a model field collects matching elements; <Blobs /> is found so it
# returns a list containing the raw ET.Element instead of a deserialized BlobsSegment.
assert isinstance(result.blobs, list)
assert len(result.blobs) == 1
assert isinstance(result.blobs[0], ET.Element)
assert result.next_marker == ""


class TestXmlSerialization:
def test_basic(self):
Expand Down
1 change: 1 addition & 0 deletions packages/http-client-python/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.