• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

alorence / django-modern-rpc / 18088429320

29 Sep 2025 06:51AM UTC coverage: 98.998%. Remained the same
18088429320

push

github

alorence
Bump xmltodict from 0.15.0 to 1.0.2

Bumps [xmltodict](https://github.com/martinblech/xmltodict) from 0.15.0 to 1.0.2.
- [Release notes](https://github.com/martinblech/xmltodict/releases)
- [Changelog](https://github.com/martinblech/xmltodict/blob/master/CHANGELOG.md)
- [Commits](https://github.com/martinblech/xmltodict/compare/v0.15.0...v1.0.2)

---
updated-dependencies:
- dependency-name: xmltodict
  dependency-version: 1.0.2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

166 of 166 branches covered (100.0%)

1383 of 1397 relevant lines covered (99.0%)

27.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.53
modernrpc/xmlrpc/backends/marshalling.py
1
from __future__ import annotations
28✔
2

3
import base64
28✔
4
from collections import OrderedDict
28✔
5
from datetime import datetime
28✔
6
from typing import Any, Callable, Generic, Iterable, Protocol, TypeVar
28✔
7

8
from modernrpc.exceptions import RPCInvalidRequest
28✔
9
from modernrpc.helpers import first
28✔
10
from modernrpc.types import DictStrAny, RpcErrorResult
28✔
11
from modernrpc.xmlrpc.backends.constants import MAXINT, MININT
28✔
12
from modernrpc.xmlrpc.handler import XmlRpcRequest, XmlRpcResult
28✔
13

14
# Self is available in typing base module only from Python 3.11
15
try:
28✔
16
    from typing import Self
28✔
17
except ImportError:
15✔
18
    from typing_extensions import Self
15✔
19

20
try:
28✔
21
    # types.NoneType is available only with Python 3.10+
22
    from types import NoneType
28✔
23
except ImportError:
8✔
24
    NoneType = type(None)  # type: ignore[misc]
8✔
25

26

27
class ElementTypeProtocol(Protocol, Iterable):
28✔
28
    """
29
    Base protocol for XML element types. This reflects the API of both the xml.etree and the lxml library.
30
    Unfortunately, since both libraries share the same interface without inheriting a common base class, we
31
    have to define our own.
32
    """
33

34
    tag: str
28✔
35
    text: str
28✔
36

37
    def find(self, path: str, namespaces=None) -> Self | None: ...
4✔
38
    def findall(self, path: str, namespaces=None) -> list[Self]: ...
4✔
39
    def append(self, sub_element: Self) -> None: ...
4✔
40

41

42
ElementType = TypeVar("ElementType", bound=ElementTypeProtocol)
28✔
43

44
LoadFuncType = Callable[[ElementType], Any]
28✔
45
DumpFuncType = Callable[[Any], ElementType]
28✔
46

47

48
class EtreeElementUnmarshaller(Generic[ElementType]):
28✔
49
    def __init__(self, allow_none=True) -> None:
28✔
50
        self.allow_none = allow_none
28✔
51

52
        self.load_funcs: dict[str, LoadFuncType] = {
28✔
53
            "value": self.load_value,
54
            "nil": self.load_nil,
55
            "boolean": self.load_bool,
56
            "int": self.load_int,
57
            "i4": self.load_int,
58
            "double": self.load_float,
59
            "string": self.load_str,
60
            "dateTime.iso8601": self.load_datetime,
61
            "base64": self.load_base64,
62
            "array": self.load_array,
63
            "struct": self.load_struct,
64
        }
65

66
    def element_to_request(self, root: ElementType) -> XmlRpcRequest:
28✔
67
        if root.tag != "methodCall":
28✔
68
            raise RPCInvalidRequest("missing methodCall tag", data=root)
28✔
69

70
        method_name = root.find("./methodName")
28✔
71
        if method_name is None:
28✔
72
            raise RPCInvalidRequest("missing methodCall.methodName tag", data=root)
28✔
73

74
        params = root.find("./params")
28✔
75
        if params is None:
28✔
76
            return XmlRpcRequest(method_name=self.stripped_text(method_name))
28✔
77

78
        param_list = params.findall("./param")
28✔
79

80
        args = [self.dispatch(self.first_child(param)) for param in param_list]
28✔
81
        return XmlRpcRequest(method_name=self.stripped_text(method_name), args=args)
28✔
82

83
    @staticmethod
28✔
84
    def stripped_text(elt: ElementType) -> str:
28✔
85
        return elt.text.strip() if elt.text else ""
28✔
86

87
    @staticmethod
28✔
88
    def first_child(elt: ElementType) -> ElementType:
28✔
89
        try:
28✔
90
            return first(elt)
28✔
91
        except IndexError as ie:
×
92
            raise RPCInvalidRequest("missing child element", data=elt) from ie
×
93

94
    def dispatch(self, elt: ElementType) -> Any:
28✔
95
        try:
28✔
96
            load_func = self.load_funcs[elt.tag]
28✔
97
        except KeyError as exc:
28✔
98
            raise RPCInvalidRequest(f"Unsupported type {elt.tag}") from exc
28✔
99

100
        return load_func(elt)
28✔
101

102
    def load_value(self, element: ElementType) -> Any:
28✔
103
        return self.dispatch(self.first_child(element))
28✔
104

105
    def load_nil(self, _: ElementType) -> None:
28✔
106
        if self.allow_none:
28✔
107
            return
28✔
108
        raise ValueError("cannot marshal None unless allow_none is enabled")
×
109

110
    def load_int(self, elt: ElementType) -> int:
28✔
111
        return int(self.stripped_text(elt))
28✔
112

113
    def load_bool(self, elt: ElementType) -> bool:
28✔
114
        value = self.stripped_text(elt)
28✔
115
        if value not in ("0", "1"):
28✔
116
            raise TypeError(f"Invalid boolean value: only 0 and 1 are allowed, found {value}")
28✔
117
        return value == "1"
28✔
118

119
    def load_float(self, elt: ElementType) -> float:
28✔
120
        return float(self.stripped_text(elt))
28✔
121

122
    def load_str(self, elt: ElementType) -> str:
28✔
123
        return str(self.stripped_text(elt))
28✔
124

125
    def load_datetime(self, elt: ElementType) -> datetime:
28✔
126
        return datetime.strptime(self.stripped_text(elt), "%Y%m%dT%H:%M:%S")
28✔
127

128
    def load_base64(self, elt: ElementType) -> bytes:
28✔
129
        return base64.b64decode(self.stripped_text(elt))
28✔
130

131
    def load_array(self, elt: ElementType) -> list[Any]:
28✔
132
        return [self.dispatch(value_elt) for value_elt in elt.findall("./data/value")]
28✔
133

134
    def load_struct(self, elt: ElementType) -> DictStrAny:
28✔
135
        member_names_and_values = [self.load_struct_member(member) for member in elt.findall("./member")]
28✔
136
        return dict(member_names_and_values)
28✔
137

138
    def load_struct_member(self, member_elt: ElementType) -> tuple[str, Any]:
28✔
139
        member_name = member_elt.find("./name")
28✔
140
        if member_name is None:
28✔
141
            raise RPCInvalidRequest("missing member.name tag", data=member_elt)
×
142
        value = member_elt.find("./value")
28✔
143
        if value is None:
28✔
144
            raise RPCInvalidRequest("missing member.value tag", data=member_elt)
×
145

146
        return self.stripped_text(member_name), self.dispatch(value)
28✔
147

148

149
class EtreeElementMarshaller(Generic[ElementType]):
28✔
150
    def __init__(
28✔
151
        self,
152
        element_factory: Callable[[str], ElementType],
153
        sub_element_factory: Callable[[ElementType, str], ElementType],
154
        allow_none=True,
155
    ) -> None:
156
        self.element_factory = element_factory
28✔
157
        self.sub_element_factory = sub_element_factory
28✔
158

159
        self.allow_none = allow_none
28✔
160

161
        self.dump_funcs: dict[type, DumpFuncType] = {
28✔
162
            NoneType: self.dump_nil,
163
            bool: self.dump_bool,
164
            int: self.dump_int,
165
            float: self.dump_float,
166
            str: self.dump_str,
167
            bytes: self.dump_bytearray,
168
            bytearray: self.dump_bytearray,
169
            datetime: self.dump_datetime,
170
            list: self.dump_list,
171
            tuple: self.dump_list,
172
            dict: self.dump_dict,
173
            OrderedDict: self.dump_dict,
174
        }
175

176
    def result_to_element(self, result: XmlRpcResult) -> ElementType:
28✔
177
        """Convert an XmlRpcResult to an XML element."""
178
        root = self.element_factory("methodResponse")
28✔
179

180
        if isinstance(result, RpcErrorResult):
28✔
181
            fault = self.sub_element_factory(root, "fault")
28✔
182
            value = self.sub_element_factory(fault, "value")
28✔
183

184
            struct = self.sub_element_factory(value, "struct")
28✔
185

186
            # Add faultCode member
187
            member = self.sub_element_factory(struct, "member")
28✔
188
            name = self.sub_element_factory(member, "name")
28✔
189
            name.text = "faultCode"
28✔
190
            value = self.sub_element_factory(member, "value")
28✔
191
            int_el = self.sub_element_factory(value, "int")
28✔
192
            int_el.text = str(result.code)
28✔
193

194
            # Add faultString member
195
            member = self.sub_element_factory(struct, "member")
28✔
196
            name = self.sub_element_factory(member, "name")
28✔
197
            name.text = "faultString"
28✔
198
            value = self.sub_element_factory(member, "value")
28✔
199
            string = self.sub_element_factory(value, "string")
28✔
200
            string.text = result.message
28✔
201
        else:
202
            params = self.sub_element_factory(root, "params")
28✔
203
            param = self.sub_element_factory(params, "param")
28✔
204
            value = self.sub_element_factory(param, "value")
28✔
205

206
            # Add the result data
207
            value.append(self.dispatch(result.data))
28✔
208

209
        return root
28✔
210

211
    def dispatch(self, value: Any) -> ElementType:
28✔
212
        """Dispatch a value to the appropriate dump method."""
213
        try:
28✔
214
            dump_func = self.dump_funcs[type(value)]
28✔
215
        except KeyError as exc:
28✔
216
            raise TypeError(f"Unsupported type: {type(value)}") from exc
28✔
217

218
        return dump_func(value)
28✔
219

220
    def dump_nil(self, _: None) -> ElementType:
28✔
221
        if self.allow_none:
28✔
222
            return self.element_factory("nil")
28✔
223
        raise ValueError("cannot marshal None unless allow_none is enabled")
×
224

225
    def dump_bool(self, value: bool) -> ElementType:
28✔
226
        boolean = self.element_factory("boolean")
28✔
227
        boolean.text = "1" if value else "0"
28✔
228
        return boolean
28✔
229

230
    def dump_int(self, value: int) -> ElementType:
28✔
231
        if value > MAXINT or value < MININT:
28✔
232
            raise OverflowError("int value exceeds XML-RPC limits")
28✔
233
        int_el = self.element_factory("int")
28✔
234
        int_el.text = str(value)
28✔
235
        return int_el
28✔
236

237
    def dump_float(self, value: float) -> ElementType:
28✔
238
        double = self.element_factory("double")
28✔
239
        double.text = str(value)
28✔
240
        return double
28✔
241

242
    def dump_str(self, value: str) -> ElementType:
28✔
243
        string = self.element_factory("string")
28✔
244
        string.text = value
28✔
245
        return string
28✔
246

247
    def dump_datetime(self, value: datetime) -> ElementType:
28✔
248
        dt = self.element_factory("dateTime.iso8601")
28✔
249
        dt.text = value.strftime("%04Y%02m%02dT%H:%M:%S")
28✔
250
        return dt
28✔
251

252
    def dump_bytearray(self, value: bytes | bytearray) -> ElementType:
28✔
253
        b64 = self.element_factory("base64")
28✔
254
        b64.text = base64.b64encode(value).decode()
28✔
255
        return b64
28✔
256

257
    def dump_dict(self, value: dict) -> ElementType:
28✔
258
        struct = self.element_factory("struct")
28✔
259
        for key, val in value.items():
28✔
260
            member = self.sub_element_factory(struct, "member")
28✔
261
            name = self.sub_element_factory(member, "name")
28✔
262
            name.text = str(key)
28✔
263
            value_el = self.sub_element_factory(member, "value")
28✔
264
            value_el.append(self.dispatch(val))
28✔
265
        return struct
28✔
266

267
    def dump_list(self, value: list | tuple) -> ElementType:
28✔
268
        array = self.element_factory("array")
28✔
269
        data = self.sub_element_factory(array, "data")
28✔
270
        for val in value:
28✔
271
            value_el = self.sub_element_factory(data, "value")
28✔
272
            value_el.append(self.dispatch(val))
28✔
273
        return array
28✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc