Skip to content

Commit 1df486c

Browse files
committed
support stdout_xml
* use defusedxml for security reasons Signed-off-by: Aleksei Stepanov <penguinolog@gmail.com>
1 parent 82da78d commit 1df486c

File tree

5 files changed

+41
-0
lines changed

5 files changed

+41
-0
lines changed

README.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ Execution result object has a set of useful properties:
225225

226226
* `stdout_yaml` - STDOUT decoded as YAML.
227227

228+
* `stdout_xml` - STDOUT decoded as XML to `ElementTree` using `defusedxml` library.
229+
228230
* `timestamp` -> `typing.Optional(datetime.datetime)`. Timestamp for received exit code.
229231

230232
SSHClient specific

doc/source/ExecResult.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@ API: ExecResult
147147
:rtype: ``typing.Any``
148148
:raises DeserializeValueError: STDOUT can not be deserialized as YAML
149149

150+
.. py:attribute:: stdout_xml
151+
152+
XML from stdout
153+
154+
:rtype: xml.etree.ElementTree.Element
155+
:raises DeserializeValueError: STDOUT can not be deserialized as XML
156+
150157
.. py:method:: read_stdout(src=None, log=None, verbose=False)
151158
152159
Read stdout file-like object to stdout.

exec_helpers/exec_result.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
import logging
2525
import threading
2626
import typing
27+
import xml.etree.ElementTree
2728

2829
# External Dependencies
30+
import defusedxml.ElementTree # type: ignore
2931
import yaml
3032

3133
# Exec-Helpers Implementation
@@ -492,6 +494,8 @@ def __deserialize(self, fmt: str) -> typing.Any:
492494
if yaml.__with_libyaml__:
493495
return yaml.load(self.stdout_str, Loader=yaml.CSafeLoader) # nosec # Safe
494496
return yaml.safe_load(self.stdout_str)
497+
if fmt == "xml":
498+
return defusedxml.ElementTree.fromstring(bytes(self.stdout_bin))
495499
except Exception as e:
496500
tmpl: str = f"{{self.cmd}} stdout is not valid {fmt}:\n{{stdout!r}}\n"
497501
LOGGER.exception(tmpl.format(self=self, stdout=self.stdout_str))
@@ -509,6 +513,7 @@ def stdout_json(self) -> typing.Any:
509513
"""JSON from stdout.
510514
511515
:rtype: typing.Any
516+
:raises DeserializeValueError: STDOUT can not be deserialized as JSON
512517
"""
513518
with self.stdout_lock:
514519
return self.__deserialize(fmt="json")
@@ -518,10 +523,21 @@ def stdout_yaml(self) -> typing.Any:
518523
"""YAML from stdout.
519524
520525
:rtype: typing.Any
526+
:raises DeserializeValueError: STDOUT can not be deserialized as YAML
521527
"""
522528
with self.stdout_lock:
523529
return self.__deserialize(fmt="yaml")
524530

531+
@property
532+
def stdout_xml(self) -> xml.etree.ElementTree.Element:
533+
"""YAML from stdout.
534+
535+
:rtype: xml.etree.ElementTree.Element
536+
:raises DeserializeValueError: STDOUT can not be deserialized as XML
537+
"""
538+
with self.stdout_lock:
539+
return self.__deserialize(fmt="xml") # type: ignore
540+
525541
def __dir__(self) -> typing.List[str]:
526542
"""Override dir for IDE and as source for getitem checks."""
527543
return [
@@ -539,6 +555,7 @@ def __dir__(self) -> typing.List[str]:
539555
"stderr_lines",
540556
"stdout_json",
541557
"stdout_yaml",
558+
"stdout_xml",
542559
"lock",
543560
]
544561

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ PyYAML>=3.12 # MIT
66
advanced-descriptors>=1.0 # Apache-2.0
77
typing >= 3.6 ; python_version < "3.7"
88
psutil >= 5.0
9+
defusedxml

test/test_exec_result.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
# Standard Library
2020
import datetime
2121
import unittest
22+
import xml.etree.ElementTree
2223
from unittest import mock
2324

2425
# Exec-Helpers Implementation
@@ -261,3 +262,16 @@ def test_indexed_lines_access(self):
261262
_ = result.stdout_lines["aaa"] # noqa
262263
with self.assertRaises(TypeError):
263264
_ = result.stdout_lines[1, "aaa"] # noqa
265+
266+
def test_stdout_xml(self):
267+
result = exec_helpers.ExecResult(
268+
"test",
269+
stdout=[
270+
b"<?xml version='1.0'?>\n",
271+
b'<data>123</data>\n',
272+
]
273+
)
274+
expect = xml.etree.ElementTree.fromstring(b"<?xml version='1.0'?>\n<data>123</data>\n")
275+
self.assertEqual(
276+
xml.etree.ElementTree.tostring(expect), xml.etree.ElementTree.tostring(result.stdout_xml)
277+
)

0 commit comments

Comments
 (0)