Skip to content

Commit c151ffd

Browse files
committed
faster XML, better coverage and docs
Signed-off-by: Aleksei Stepanov <penguinolog@gmail.com>
1 parent f618551 commit c151ffd

File tree

4 files changed

+79
-18
lines changed

4 files changed

+79
-18
lines changed

README.rst

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,24 @@ This package includes:
6969
.. note:: for Windows `ProactorEventLoop` or another non-standard event loop should be used!
7070

7171
* `ExecResult` - class for execution results storage.
72-
Contains exit code, stdout, stderr and getters for decoding as JSON, YAML, string, bytearray and brief strings (up to 7 lines).
72+
Contains exit code, stdout, stderr and getters for decoding as JSON, YAML, XML (and LXML) element tree, string, bytearray
73+
and brief strings (up to 7 lines).
7374

74-
* `ExitCodes` - enumerator for standard Linux exit codes. BASH return codes (broduced from signal codes) also available.
75+
* `ExitCodes` - enumerator for standard Linux exit codes. BASH return codes (produced from signal codes) also available.
76+
77+
Installation
78+
============
79+
80+
Standard: `pip install exec-helpers`
81+
Extras:
82+
83+
* ``yaml`` - install `PyYaml` for yaml decoding (`PyYAML` is main decoder, `ruamel.YAML` also supported as fallback.)
84+
85+
* ``xml`` - install `defusedxml` for safe XML parsing to `xml.etree.ElementTree.Element`.
86+
87+
* ``lxml`` - install `lxml` for advanced XML parsing. Can be unsafe.
88+
89+
* ``ALL_FORMATS`` - install all parsers. When new parsers will be added, it will ne also supported.
7590

7691
Usage
7792
=====
@@ -105,7 +120,7 @@ Creation from scratch:
105120
)
106121
107122
Key is a main connection key (always tried first) and keys are alternate keys.
108-
Key filename is afilename or list of filenames with keys, which should be loaded.
123+
Key filename is a filename or list of filenames with keys, which should be loaded.
109124
Passphrase is an alternate password for keys, if it differs from main password.
110125
If main key now correct for username - alternate keys tried, if correct key found - it became main.
111126
If no working key - password is used and None is set as main key.
@@ -223,11 +238,14 @@ Execution result object has a set of useful properties:
223238

224239
* `stdout_json` - STDOUT decoded as JSON.
225240

226-
* `stdout_yaml` - STDOUT decoded as YAML. Accessible only if PyYAML or ruamel.YAML library installed. (Option ``yaml``)
241+
* `stdout_yaml` - STDOUT decoded as YAML. Accessible only if `PyYAML` or `ruamel.YAML` library installed.
242+
(Extras: ``yaml``)
227243

228-
* `stdout_xml` - STDOUT decoded as XML to `ElementTree` using `defusedxml` library. Accessible only if `defusedxml` library installed. (Option ``xml``)
244+
* `stdout_xml` - STDOUT decoded as XML to `ElementTree` using `defusedxml` library. Accessible only if `defusedxml` library installed.
245+
(Extras: ``xml``)
229246

230-
* `stdout_lxml` - STDOUT decoded as XML to `ElementTree` using `lxml` library. Accessible only if `lxml` library installed. (Option ``lxml``) Can be insecure.
247+
* `stdout_lxml` - STDOUT decoded as XML to `ElementTree` using `lxml` library. Accessible only if `lxml` library installed.
248+
(Extras: ``lxml``) Can be insecure.
231249

232250
* `timestamp` -> `typing.Optional(datetime.datetime)`. Timestamp for received exit code.
233251

exec_helpers/exec_result.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -514,9 +514,9 @@ def __deserialize(self, fmt: str) -> typing.Any:
514514
return yaml.safe_load(self.stdout_str)
515515
return ruamel_yaml.YAML(typ="safe").load(self.stdout_str) # nosec # Safe
516516
if fmt == "xml":
517-
return defusedxml.ElementTree.fromstring(bytes(self.stdout_bin))
517+
return defusedxml.ElementTree.fromstring(b"".join(self.stdout))
518518
if fmt == "lxml":
519-
return lxml.etree.fromstring(bytes(self.stdout_bin)) # nosec
519+
return lxml.etree.fromstring(b"".join(self.stdout)) # nosec
520520
except Exception as e:
521521
tmpl: str = f"{{self.cmd}} stdout is not valid {fmt}:\n{{stdout!r}}\n"
522522
LOGGER.exception(tmpl.format(self=self, stdout=self.stdout_str))

setup.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,11 @@ def get_simple_vars_from_src(src):
230230

231231
KEYWORDS = ["logging", "debugging", "development"]
232232

233-
setup_args = dict(
233+
XML_DEPS = ["defusedxml"]
234+
LXML_DEPS = ["lxml!=3.7.0"]
235+
YAML_DEPS = ["PyYAML>=3.12"]
236+
237+
SETUP_ARGS = dict(
234238
name="exec-helpers",
235239
author=VARIABLES["__author__"],
236240
author_email=VARIABLES["__author_email__"],
@@ -259,20 +263,21 @@ def get_simple_vars_from_src(src):
259263
use_scm_version={'write_to': 'exec_helpers/_version.py'},
260264
install_requires=REQUIRED,
261265
extras_require={
262-
"xml": ["defusedxml"],
263-
"lxml": ["lxml!=3.7.0"],
264-
"yaml": ["PyYAML>=3.12"],
266+
"xml": XML_DEPS,
267+
"lxml": LXML_DEPS,
268+
"yaml": YAML_DEPS,
269+
"ALL_FORMATS": XML_DEPS + LXML_DEPS + YAML_DEPS
265270
},
266271
package_data={"exec_helpers": ["py.typed"]},
267272
)
268273
if cythonize is not None:
269-
setup_args["ext_modules"] = EXT_MODULES
270-
setup_args["cmdclass"] = dict(build_ext=AllowFailRepair)
274+
SETUP_ARGS["ext_modules"] = EXT_MODULES
275+
SETUP_ARGS["cmdclass"] = dict(build_ext=AllowFailRepair)
271276

272277
try:
273-
setuptools.setup(**setup_args)
278+
setuptools.setup(**SETUP_ARGS)
274279
except BuildFailed:
275280
print("*" * 80 + "\n" "* Build Failed!\n" "* Use clear scripts version.\n" "*" * 80 + "\n")
276-
del setup_args["ext_modules"]
277-
del setup_args["cmdclass"]
278-
setuptools.setup(**setup_args)
281+
del SETUP_ARGS["ext_modules"]
282+
del SETUP_ARGS["cmdclass"]
283+
setuptools.setup(**SETUP_ARGS)

test/test_exec_result.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,3 +337,41 @@ def test_stdout_yaml_ruamel(self):
337337
expect = {"test": "data"}
338338
result = result.stdout_yaml
339339
self.assertEqual(expect, result)
340+
341+
342+
class TestExecResultNoExtras(unittest.TestCase):
343+
def setUp(self) -> None:
344+
self._orig_yaml, exec_helpers.exec_result.yaml = exec_helpers.exec_result.yaml, None
345+
self._orig_ruamel_yaml, exec_helpers.exec_result.ruamel_yaml = exec_helpers.exec_result.ruamel_yaml, None
346+
self._orig_lxml, exec_helpers.exec_result.lxml = exec_helpers.exec_result.lxml, None
347+
self._orig_defusedxml, exec_helpers.exec_result.defusedxml = exec_helpers.exec_result.defusedxml, None
348+
349+
def tearDown(self) -> None:
350+
exec_helpers.exec_result.yaml = self._orig_yaml
351+
exec_helpers.exec_result.ruamel_yaml = self._orig_ruamel_yaml
352+
exec_helpers.exec_result.lxml = self._orig_lxml
353+
exec_helpers.exec_result.defusedxml = self._orig_defusedxml
354+
355+
def test_stdout_yaml(self):
356+
result = exec_helpers.ExecResult(
357+
"test",
358+
stdout=[
359+
b"{test: data}\n"
360+
]
361+
)
362+
with self.assertRaises(AttributeError):
363+
getattr(result, 'stdout_yaml') # noqa: B009
364+
365+
def test_stdout_xmls(self):
366+
result = exec_helpers.ExecResult(
367+
"test",
368+
stdout=[
369+
b"<?xml version='1.0'?>\n",
370+
b'<data>123</data>\n',
371+
]
372+
)
373+
with self.assertRaises(AttributeError):
374+
getattr(result, 'stdout_xml') # noqa: B009
375+
376+
with self.assertRaises(AttributeError):
377+
getattr(result, 'stdout_lxml') # noqa: B009

0 commit comments

Comments
 (0)