Codebase list python-procrunner / 31e5d57
Deprecate array access to the return object The procrunner return object is a subprocess.CompletedProcess object and should be accessed as such. Array access is now deprecated. Markus Gerstel 3 years ago
2 changed file(s) with 124 addition(s) and 46 deletion(s). Raw diff Collapse all Expand all
77 import sys
88 import time
99 import timeit
10 import warnings
1011 from multiprocessing import Pipe
1112 from threading import Thread
1213
300301 return command
301302
302303
303 class ReturnObject(dict, subprocess.CompletedProcess):
304 class ReturnObject(subprocess.CompletedProcess):
304305 """
305306 A subprocess.CompletedProcess-like object containing the executed
306307 command, stdout and stderr (both as bytestrings), and the exitcode.
310311 exited with a non-zero exit code.
311312 """
312313
313 def __init__(self, *arg, **kw):
314 super().__init__(*arg, **kw)
315 self.args = self["command"]
316 self.returncode = self["exitcode"]
317 self.stdout = self["stdout"]
318 self.stderr = self["stderr"]
314 def __init__(self, exitcode=None, command=None, stdout=None, stderr=None, **kw):
315 super().__init__(
316 args=command, returncode=exitcode, stdout=stdout, stderr=stderr
317 )
318 self._extras = {
319 "timeout": kw.get("timeout"),
320 "runtime": kw.get("runtime"),
321 "time_start": kw.get("time_start"),
322 "time_end": kw.get("time_end"),
323 }
324
325 def __getitem__(self, key):
326 warnings.warn(
327 "dictionary access to a procrunner return object is deprecated",
328 DeprecationWarning,
329 stacklevel=2,
330 )
331 if key in self._extras:
332 return self._extras[key]
333 if not hasattr(self, key):
334 raise KeyError(f"Unknown attribute {key}")
335 return getattr(self, key)
319336
320337 def __eq__(self, other):
321338 """Override equality operator to account for added fields"""
327344 """This object is not immutable, so mark it as unhashable"""
328345 return None
329346
330 def __ne__(self, other):
331 """Overrides the default implementation (unnecessary in Python 3)"""
332 return not self.__eq__(other)
347 @property
348 def cmd(self):
349 warnings.warn(
350 "procrunner return object .cmd is deprecated, use .args",
351 DeprecationWarning,
352 stacklevel=2,
353 )
354 return self.args
355
356 @property
357 def command(self):
358 warnings.warn(
359 "procrunner return object .command is deprecated, use .args",
360 DeprecationWarning,
361 stacklevel=2,
362 )
363 return self.args
364
365 @property
366 def exitcode(self):
367 warnings.warn(
368 "procrunner return object .exitcode is deprecated, use .returncode",
369 DeprecationWarning,
370 stacklevel=2,
371 )
372 return self.returncode
373
374 @property
375 def timeout(self):
376 warnings.warn(
377 "procrunner return object .timeout is deprecated",
378 DeprecationWarning,
379 stacklevel=2,
380 )
381 return self._extras["timeout"]
382
383 @property
384 def runtime(self):
385 warnings.warn(
386 "procrunner return object .runtime is deprecated",
387 DeprecationWarning,
388 stacklevel=2,
389 )
390 return self._extras["runtime"]
391
392 @property
393 def time_start(self):
394 warnings.warn(
395 "procrunner return object .time_start is deprecated",
396 DeprecationWarning,
397 stacklevel=2,
398 )
399 return self._extras["time_start"]
400
401 @property
402 def time_end(self):
403 warnings.warn(
404 "procrunner return object .time_end is deprecated",
405 DeprecationWarning,
406 stacklevel=2,
407 )
408 return self._extras["time_end"]
409
410 def update(self, dictionary):
411 self._extras.update(dictionary)
333412
334413
335414 def run(
521600 time_end = time.strftime("%Y-%m-%d %H:%M:%S GMT", time.gmtime())
522601
523602 result = ReturnObject(
524 {
525 "exitcode": p.returncode,
526 "command": command,
527 "stdout": stdout,
528 "stderr": stderr,
529 "timeout": timeout_encountered,
530 "runtime": runtime,
531 "time_start": time_start,
532 "time_end": time_end,
533 }
603 exitcode=p.returncode,
604 command=command,
605 stdout=stdout,
606 stderr=stderr,
607 timeout=timeout_encountered,
608 runtime=runtime,
609 time_start=time_start,
610 time_end=time_end,
534611 )
535612 if stdin is not None:
536613 result.update(
9292 assert not mock_process.terminate.called
9393 assert not mock_process.kill.called
9494 for key in expected:
95 assert actual[key] == expected[key]
95 with pytest.warns(DeprecationWarning):
96 assert actual[key] == expected[key]
9697 assert actual.args == tuple(command)
9798 assert actual.returncode == mock_process.returncode
9899 assert actual.stdout == mock.sentinel.proc_stdout
259260
260261 def test_return_object_semantics():
261262 ro = procrunner.ReturnObject(
262 {
263 "command": mock.sentinel.command,
264 "exitcode": 0,
265 "stdout": mock.sentinel.stdout,
266 "stderr": mock.sentinel.stderr,
267 }
268 )
269 assert ro["command"] == mock.sentinel.command
263 command=mock.sentinel.command,
264 exitcode=0,
265 stdout=mock.sentinel.stdout,
266 stderr=mock.sentinel.stderr,
267 )
268 with pytest.warns(DeprecationWarning):
269 assert ro["command"] == mock.sentinel.command
270270 assert ro.args == mock.sentinel.command
271 assert ro["exitcode"] == 0
271 with pytest.warns(DeprecationWarning):
272 assert ro["exitcode"] == 0
272273 assert ro.returncode == 0
273 assert ro["stdout"] == mock.sentinel.stdout
274 with pytest.warns(DeprecationWarning):
275 assert ro["stdout"] == mock.sentinel.stdout
274276 assert ro.stdout == mock.sentinel.stdout
275 assert ro["stderr"] == mock.sentinel.stderr
277 with pytest.warns(DeprecationWarning):
278 assert ro["stderr"] == mock.sentinel.stderr
276279 assert ro.stderr == mock.sentinel.stderr
277280
278281 with pytest.raises(KeyError):
279 ro["unknownkey"]
282 with pytest.warns(DeprecationWarning):
283 ro["unknownkey"]
280284 ro.update({"unknownkey": mock.sentinel.key})
281 assert ro["unknownkey"] == mock.sentinel.key
285 with pytest.warns(DeprecationWarning):
286 assert ro["unknownkey"] == mock.sentinel.key
282287
283288
284289 def test_return_object_check_function_passes_on_success():
285290 ro = procrunner.ReturnObject(
286 {
287 "command": mock.sentinel.command,
288 "exitcode": 0,
289 "stdout": mock.sentinel.stdout,
290 "stderr": mock.sentinel.stderr,
291 }
291 command=mock.sentinel.command,
292 exitcode=0,
293 stdout=mock.sentinel.stdout,
294 stderr=mock.sentinel.stderr,
292295 )
293296 ro.check_returncode()
294297
295298
296299 def test_return_object_check_function_raises_on_error():
297300 ro = procrunner.ReturnObject(
298 {
299 "command": mock.sentinel.command,
300 "exitcode": 1,
301 "stdout": mock.sentinel.stdout,
302 "stderr": mock.sentinel.stderr,
303 }
301 command=mock.sentinel.command,
302 exitcode=1,
303 stdout=mock.sentinel.stdout,
304 stderr=mock.sentinel.stderr,
304305 )
305306 with pytest.raises(Exception) as e:
306307 ro.check_returncode()