Skip to content

Commit 361d3c5

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

File tree

5 files changed

+45
-1
lines changed

5 files changed

+45
-1
lines changed

README.rst

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

204204
* `stdout_yaml` - STDOUT decoded as YAML.
205205

206+
* `stdout_xml` - STDOUT decoded as XML to `ElementTree` using `defusedxml` library.
207+
206208
* `timestamp` -> `typing.Optional(datetime.datetime)`. Timestamp for received exit code.
207209

208210
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: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,21 @@
2525
import json
2626
import logging
2727
import threading
28-
import typing # noqa # pylint: disable=unused-import
28+
import typing
29+
2930

3031
# External Dependencies
32+
import defusedxml.ElementTree # type: ignore
3133
import six
3234
import yaml
3335

3436
# Exec-Helpers Implementation
3537
from exec_helpers import exceptions
3638
from exec_helpers import proc_enums
3739

40+
if typing.TYPE_CHECKING:
41+
import xml.etree.ElementTree # nosec # noqa # pylint: disable=unused-import
42+
3843
__all__ = ("ExecResult",)
3944

4045
logger = logging.getLogger(__name__)
@@ -489,6 +494,8 @@ def __deserialize(self, fmt): # type: (typing.Text) -> typing.Any
489494
if yaml.__with_libyaml__:
490495
return yaml.load(self.stdout_str, Loader=yaml.CSafeLoader) # nosec # Safe
491496
return yaml.safe_load(self.stdout_str)
497+
if fmt == "xml":
498+
return defusedxml.ElementTree.fromstring(bytes(self.stdout_bin))
492499
except Exception:
493500
tmpl = " stdout is not valid {fmt}:\n" "{{stdout!r}}\n".format(fmt=fmt)
494501
logger.exception(self.cmd + tmpl.format(stdout=self.stdout_str))
@@ -502,6 +509,7 @@ def stdout_json(self): # type: () -> typing.Any
502509
"""JSON from stdout.
503510
504511
:rtype: typing.Any
512+
:raises DeserializeValueError: STDOUT can not be deserialized as JSON
505513
"""
506514
with self.stdout_lock:
507515
return self.__deserialize(fmt="json")
@@ -511,10 +519,21 @@ def stdout_yaml(self): # type: () -> typing.Any
511519
"""YAML from stdout.
512520
513521
:rtype: typing.Any
522+
:raises DeserializeValueError: STDOUT can not be deserialized as YAML
514523
"""
515524
with self.stdout_lock:
516525
return self.__deserialize(fmt="yaml")
517526

527+
@property
528+
def stdout_xml(self): # type: () -> xml.etree.ElementTree.Element
529+
"""YAML from stdout.
530+
531+
:rtype: xml.etree.ElementTree.Element
532+
:raises DeserializeValueError: STDOUT can not be deserialized as XML
533+
"""
534+
with self.stdout_lock:
535+
return self.__deserialize(fmt="xml") # type: ignore
536+
518537
def __dir__(self): # type: () -> typing.List[typing.Text]
519538
"""Override dir for IDE and as source for getitem checks."""
520539
return [
@@ -532,6 +551,7 @@ def __dir__(self): # type: () -> typing.List[typing.Text]
532551
"stderr_lines",
533552
"stdout_json",
534553
"stdout_yaml",
554+
"stdout_xml",
535555
"lock",
536556
]
537557

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ typing >= 3.6; python_version < "3.7" # PSF
88
futures>=3.1; python_version == "2.7"
99
enum34>=1.1; python_version == "2.7"
1010
psutil >= 5.0
11+
defusedxml

test/test_exec_result.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import datetime
2424
import unittest
25+
import xml.etree.ElementTree
2526

2627
import mock
2728

@@ -301,3 +302,16 @@ def test_indexed_lines_access(self):
301302
_ = result.stdout_lines["aaa"] # noqa
302303
with self.assertRaises(TypeError):
303304
_ = result.stdout_lines[1, "aaa"] # noqa
305+
306+
def test_stdout_xml(self):
307+
result = exec_helpers.ExecResult(
308+
"test",
309+
stdout=[
310+
b"<?xml version='1.0'?>\n",
311+
b'<data>123</data>\n',
312+
]
313+
)
314+
expect = xml.etree.ElementTree.fromstring(b"<?xml version='1.0'?>\n<data>123</data>\n")
315+
self.assertEqual(
316+
xml.etree.ElementTree.tostring(expect), xml.etree.ElementTree.tostring(result.stdout_xml)
317+
)

0 commit comments

Comments
 (0)