-
Notifications
You must be signed in to change notification settings - Fork 341
[http-client-python] Add XML deserialization tests for EnumerationResults payload #9778
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0db58c9
bd1f951
833e54e
8caa873
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
|---|---|---|
|
|
@@ -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>' | ||
|
|
||
| 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"}) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @copilot what if blobs is xml.unwrapped True
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added |
||
| 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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): | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot like if our enumerationResults model followed this model pattern -- https://github.com/l0lawrence/azure-sdk-for-python/blob/14028a4655c9055e234fb142a91da64fdd0b82f4/sdk/storage/azure-storage-blob/azure/storage/blob/_generated/models/_models_py3.py#L1566
There was a problem hiding this comment.
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_listin commit 833e54e. It uses XML<Blobs><BlobPrefixes /></Blobs>whereblobsis aBlobsSegmentmodel containingblob_prefixes: list[BlobPrefix]. The test asserts thatresult.blobs.blob_prefixes == []— an empty<BlobPrefixes />element inside a container correctly deserializes to an empty list.There was a problem hiding this comment.
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_patternin commit 8caa873. It follows the linked Azure SDK model —BlobsSegmenthas bothblob_prefixes: list[BlobPrefix](unwrapped) andblob_items: list[BlobItem](unwrapped). With<Blobs />, no matching children are found so both lists areNone.