Skip to content

Commit 3039252

Browse files
committed
fix: go back to xmltodict for now, mspconfig migration is... complex...
1 parent 7a37b1d commit 3039252

3 files changed

Lines changed: 188 additions & 192 deletions

File tree

Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
from __future__ import annotations
22

3-
from pydantic import ConfigDict
4-
from pydantic_xml import BaseXmlModel, attr, element, wrapped
5-
6-
from .const import XML_NS
3+
from pydantic import BaseModel, ConfigDict, Field
4+
from xmltodict import parse as xml_parse
75

86
# Example Filter Diagnostics XML:
97
#
@@ -32,56 +30,39 @@
3230
# </Response>
3331

3432

35-
class FilterDiagnosticsParameter(BaseXmlModel, tag="Parameter", ns="api", nsmap=XML_NS):
36-
"""Individual diagnostic parameter with name, type, and value."""
37-
33+
class FilterDiagnosticsParameter(BaseModel):
3834
model_config = ConfigDict(from_attributes=True)
3935

40-
name: str = attr()
41-
data_type: str = attr(name="dataType")
42-
value: int
43-
36+
name: str = Field(alias="@name")
37+
dataType: str = Field(alias="@dataType")
38+
value: int = Field(alias="#text")
4439

45-
class FilterDiagnostics(BaseXmlModel, tag="Response", ns="api", nsmap=XML_NS):
46-
"""Filter diagnostics response containing diagnostic parameters.
47-
48-
The XML structure has a Parameters wrapper element containing Parameter children:
49-
<Response>
50-
<Name>FilterDiagnostics</Name>
51-
<Parameters>
52-
<Parameter name="..." dataType="...">value</Parameter>
53-
...
54-
</Parameters>
55-
</Response>
56-
"""
5740

41+
class FilterDiagnosticsParameters(BaseModel):
5842
model_config = ConfigDict(from_attributes=True)
5943

60-
name: str = element(tag="Name")
61-
parameters: list[FilterDiagnosticsParameter] = wrapped("Parameters", element(tag="Parameter", default_factory=list))
44+
parameter: list[FilterDiagnosticsParameter] = Field(alias="Parameter")
6245

63-
def get_param_by_name(self, name: str) -> int:
64-
"""Get parameter value by name.
6546

66-
Args:
67-
name: Name of the parameter to retrieve
47+
class FilterDiagnostics(BaseModel):
48+
model_config = ConfigDict(from_attributes=True)
6849

69-
Returns:
70-
The integer value of the parameter
50+
name: str = Field(alias="Name")
51+
# parameters: FilterDiagnosticsParameters = Field(alias="Parameters")
52+
parameters: list[FilterDiagnosticsParameter] = Field(alias="Parameters")
7153

72-
Raises:
73-
IndexError: If parameter name not found
74-
"""
54+
def get_param_by_name(self, name: str) -> int:
7555
return [param.value for param in self.parameters if param.name == name][0]
7656

7757
@staticmethod
7858
def load_xml(xml: str) -> FilterDiagnostics:
79-
"""Load filter diagnostics from XML string.
80-
81-
Args:
82-
xml: XML string containing filter diagnostics data
83-
84-
Returns:
85-
Parsed FilterDiagnostics instance
86-
"""
87-
return FilterDiagnostics.from_xml(xml)
59+
data = xml_parse(
60+
xml,
61+
# Some things will be lists or not depending on if a pool has more than one of that piece of equipment. Here we are coercing
62+
# everything that *could* be a list into a list to make the parsing more consistent.
63+
force_list=("Parameter"),
64+
)
65+
# The XML nests the Parameter entries under a Parameters entry, this is annoying to work with. Here we are adjusting the data to
66+
# remove that extra level in the data
67+
data["Response"]["Parameters"] = data["Response"]["Parameters"]["Parameter"]
68+
return FilterDiagnostics.model_validate(data["Response"])
Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from __future__ import annotations
22

3-
from pydantic import ConfigDict, computed_field
4-
from pydantic_xml import BaseXmlModel, attr, element, wrapped
3+
from typing import Any
4+
from xml.etree.ElementTree import Element
5+
6+
from pydantic import BaseModel, ConfigDict, Field, model_validator
57

68
from .const import XML_NS
79

@@ -19,45 +21,23 @@
1921
# </Response>
2022

2123

22-
class LeadMessageParameter(BaseXmlModel, tag="Parameter", ns="api", nsmap=XML_NS):
23-
"""Individual parameter in lead message."""
24-
25-
name: str = attr()
26-
value: int
27-
28-
29-
class LeadMessage(BaseXmlModel, tag="Response", ns="api", nsmap=XML_NS):
30-
"""Lead message containing protocol parameters.
31-
32-
Lead messages are sent at the start of communication to establish
33-
protocol parameters like message size and block count.
34-
"""
35-
24+
class LeadMessage(BaseModel):
3625
model_config = ConfigDict(from_attributes=True)
3726

38-
name: str = element(tag="Name")
39-
parameters: list[LeadMessageParameter] = wrapped("Parameters", element(tag="Parameter", default_factory=list))
40-
41-
@computed_field # type: ignore[prop-decorator]
42-
@property
43-
def source_op_id(self) -> int:
44-
"""Extract SourceOpId from parameters."""
45-
return next((p.value for p in self.parameters if p.name == "SourceOpId"), 0)
46-
47-
@computed_field # type: ignore[prop-decorator]
48-
@property
49-
def msg_size(self) -> int:
50-
"""Extract MsgSize from parameters."""
51-
return next((p.value for p in self.parameters if p.name == "MsgSize"), 0)
52-
53-
@computed_field # type: ignore[prop-decorator]
54-
@property
55-
def msg_block_count(self) -> int:
56-
"""Extract MsgBlockCount from parameters."""
57-
return next((p.value for p in self.parameters if p.name == "MsgBlockCount"), 0)
58-
59-
@computed_field # type: ignore[prop-decorator]
60-
@property
61-
def type(self) -> int:
62-
"""Extract Type from parameters."""
63-
return next((p.value for p in self.parameters if p.name == "Type"), 0)
27+
source_op_id: int = Field(alias="SourceOpId")
28+
msg_size: int = Field(alias="MsgSize")
29+
msg_block_count: int = Field(alias="MsgBlockCount")
30+
type: int = Field(alias="Type")
31+
32+
@model_validator(mode="before")
33+
@classmethod
34+
def parse_xml_element(cls, data: Any) -> dict[str, Any]:
35+
"""Parse XML Element into dict format for Pydantic validation."""
36+
if isinstance(data, Element):
37+
# Parse the Parameter elements from the XML
38+
result = {}
39+
for param in data.findall(".//api:Parameter", XML_NS):
40+
if name := param.get("name"):
41+
result[name] = int(param.text) if param.text else 0
42+
return result
43+
return data

0 commit comments

Comments
 (0)