Codebase list python-procrunner / 1774296
Remove ReturnObject(), completing #60 Markus Gerstel 2 years ago
4 changed file(s) with 18 addition(s) and 208 deletion(s). Raw diff Collapse all Expand all
44 3.0.0 (2022-01-??)
55 ------------------
66 * Drop Python 3.6 support
7 * The run() function now returns a subprocess.CompletedProcess object,
8 which no longer allows array access operations
9 (those were deprecated in `#60 <https://github.com/DiamondLightSource/python-procrunner/pull/60>`_)
710
811 2.3.1 (2021-10-25)
912 ------------------
3838 #
3939 # Returns:
4040 #
41 # ReturnObject(
41 # subprocess.CompletedProcess(
4242 # args=('/bin/ls', '/some/path/containing spaces'),
4343 # returncode=2,
4444 # stdout=b'',
4545 # stderr=b'/bin/ls: cannot access /some/path/containing spaces: No such file or directory\n'
4646 # )
47 #
48 # which also offers (albeit deprecated)
49 #
50 # result.runtime == 0.12990689277648926
51 # result.time_end == '2017-11-12 19:54:49 GMT'
52 # result.time_start == '2017-11-12 19:54:49 GMT'
53 # result.timeout == False
5447
5548 __version__ = "2.3.1"
5649
306299 return command
307300
308301
309 class ReturnObject(subprocess.CompletedProcess):
310 """
311 A subprocess.CompletedProcess-like object containing the executed
312 command, stdout and stderr (both as bytestrings), and the exitcode.
313 The check_returncode() function raises an exception if the process
314 exited with a non-zero exit code.
315 """
316
317 def __init__(self, exitcode=None, command=None, stdout=None, stderr=None, **kw):
318 super().__init__(
319 args=command, returncode=exitcode, stdout=stdout, stderr=stderr
320 )
321 self._extras = {
322 "timeout": kw.get("timeout"),
323 "runtime": kw.get("runtime"),
324 "time_start": kw.get("time_start"),
325 "time_end": kw.get("time_end"),
326 }
327
328 def __getitem__(self, key):
329 warnings.warn(
330 "dictionary access to a procrunner return object is deprecated",
331 DeprecationWarning,
332 stacklevel=2,
333 )
334 if key in self._extras:
335 return self._extras[key]
336 if not hasattr(self, key):
337 raise KeyError(f"Unknown attribute {key}")
338 return getattr(self, key)
339
340 def __eq__(self, other):
341 """Override equality operator to account for added fields"""
342 if type(other) is type(self):
343 return self.__dict__ == other.__dict__
344 return False
345
346 def __hash__(self):
347 """This object is not immutable, so mark it as unhashable"""
348 return None
349
350 @property
351 def cmd(self):
352 warnings.warn(
353 "procrunner return object .cmd is deprecated, use .args",
354 DeprecationWarning,
355 stacklevel=2,
356 )
357 return self.args
358
359 @property
360 def command(self):
361 warnings.warn(
362 "procrunner return object .command is deprecated, use .args",
363 DeprecationWarning,
364 stacklevel=2,
365 )
366 return self.args
367
368 @property
369 def exitcode(self):
370 warnings.warn(
371 "procrunner return object .exitcode is deprecated, use .returncode",
372 DeprecationWarning,
373 stacklevel=2,
374 )
375 return self.returncode
376
377 @property
378 def timeout(self):
379 warnings.warn(
380 "procrunner return object .timeout is deprecated",
381 DeprecationWarning,
382 stacklevel=2,
383 )
384 return self._extras["timeout"]
385
386 @property
387 def runtime(self):
388 warnings.warn(
389 "procrunner return object .runtime is deprecated",
390 DeprecationWarning,
391 stacklevel=2,
392 )
393 return self._extras["runtime"]
394
395 @property
396 def time_start(self):
397 warnings.warn(
398 "procrunner return object .time_start is deprecated",
399 DeprecationWarning,
400 stacklevel=2,
401 )
402 return self._extras["time_start"]
403
404 @property
405 def time_end(self):
406 warnings.warn(
407 "procrunner return object .time_end is deprecated",
408 DeprecationWarning,
409 stacklevel=2,
410 )
411 return self._extras["time_end"]
412
413 def update(self, dictionary):
414 self._extras.update(dictionary)
415
416
417302 def _deprecate_argument_calling(f):
418303 @functools.wraps(f)
419304 def wrapper(*args, **kwargs):
444329 win32resolve=True,
445330 working_directory=None,
446331 raise_timeout_exception=False,
447 ):
332 ) -> subprocess.CompletedProcess:
448333 """
449334 Run an external process.
450335
479364 as a subprocess.CompletedProcess object.
480365 """
481366
482 time_start = time.strftime("%Y-%m-%d %H:%M:%S GMT", time.gmtime())
483367 logger.debug("Starting external process: %s", command)
484368
485369 if stdin is None:
654538 cmd=command, timeout=timeout, output=stdout, stderr=stderr
655539 )
656540
657 time_end = time.strftime("%Y-%m-%d %H:%M:%S GMT", time.gmtime())
658 result = ReturnObject(
659 exitcode=p.returncode,
660 command=command,
661 stdout=stdout,
662 stderr=stderr,
663 timeout=timeout_encountered,
664 runtime=runtime,
665 time_start=time_start,
666 time_end=time_end,
541 return subprocess.CompletedProcess(
542 args=command, returncode=p.returncode, stdout=stdout, stderr=stderr
667543 )
668 if stdin is not None:
669 result.update(
670 {
671 "stdin_bytes_sent": stdin.bytes_sent(),
672 "stdin_bytes_remain": stdin.bytes_remaining(),
673 }
674 )
675
676 return result
0 from __future__ import annotations
1
02 import copy
13 import os
24 import pathlib
7173
7274 mock_streamreader.side_effect = streamreader_processing
7375 mock_subprocess.Popen.return_value = mock_process
74
75 expected = {
76 "stderr": mock.sentinel.proc_stderr,
77 "stdout": mock.sentinel.proc_stdout,
78 "exitcode": mock_process.returncode,
79 "command": tuple(command),
80 "runtime": mock.ANY,
81 "timeout": False,
82 "time_start": mock.ANY,
83 "time_end": mock.ANY,
84 }
8576
8677 actual = procrunner.run(
8778 command,
119110 )
120111 assert not mock_process.terminate.called
121112 assert not mock_process.kill.called
122 for key in expected:
123 with pytest.warns(DeprecationWarning):
124 assert actual[key] == expected[key]
125 assert actual.args == tuple(command)
126 assert actual.returncode == mock_process.returncode
127 assert actual.stdout == mock.sentinel.proc_stdout
128 assert actual.stderr == mock.sentinel.proc_stderr
113 assert actual == mock_subprocess.CompletedProcess.return_value
114 mock_subprocess.CompletedProcess.assert_called_once_with(
115 args=tuple(command),
116 returncode=mock_process.returncode,
117 stdout=mock.sentinel.proc_stdout,
118 stderr=mock.sentinel.proc_stderr,
119 )
129120
130121
131122 @mock.patch("procrunner.subprocess")
303294 callback.assert_not_called()
304295 aggregator.flush()
305296 callback.assert_called_once_with("morestuff")
306
307
308 def test_return_object_semantics():
309 ro = procrunner.ReturnObject(
310 command=mock.sentinel.command,
311 exitcode=0,
312 stdout=mock.sentinel.stdout,
313 stderr=mock.sentinel.stderr,
314 )
315 with pytest.warns(DeprecationWarning):
316 assert ro["command"] == mock.sentinel.command
317 assert ro.args == mock.sentinel.command
318 with pytest.warns(DeprecationWarning):
319 assert ro["exitcode"] == 0
320 assert ro.returncode == 0
321 with pytest.warns(DeprecationWarning):
322 assert ro["stdout"] == mock.sentinel.stdout
323 assert ro.stdout == mock.sentinel.stdout
324 with pytest.warns(DeprecationWarning):
325 assert ro["stderr"] == mock.sentinel.stderr
326 assert ro.stderr == mock.sentinel.stderr
327
328 with pytest.raises(KeyError):
329 with pytest.warns(DeprecationWarning):
330 ro["unknownkey"]
331 ro.update({"unknownkey": mock.sentinel.key})
332 with pytest.warns(DeprecationWarning):
333 assert ro["unknownkey"] == mock.sentinel.key
334
335
336 def test_return_object_check_function_passes_on_success():
337 ro = procrunner.ReturnObject(
338 command=mock.sentinel.command,
339 exitcode=0,
340 stdout=mock.sentinel.stdout,
341 stderr=mock.sentinel.stderr,
342 )
343 ro.check_returncode()
344
345
346 def test_return_object_check_function_raises_on_error():
347 ro = procrunner.ReturnObject(
348 command=mock.sentinel.command,
349 exitcode=1,
350 stdout=mock.sentinel.stdout,
351 stderr=mock.sentinel.stderr,
352 )
353 with pytest.raises(Exception) as e:
354 ro.check_returncode()
355 assert repr(mock.sentinel.command) in str(e.value)
356 assert "1" in str(e.value)
0 from __future__ import annotations
1
02 import os
13 import subprocess
24 import sys
8991 assert runtime < 3
9092 return
9193 runtime = timeit.default_timer() - start
92 with pytest.warns(DeprecationWarning, match="\\.timeout"):
93 assert result.timeout
9494 assert runtime < 3
9595 assert not result.stdout
9696 assert not result.stderr