From 86fd76fea7cff09161768d31da28765da91fcbda Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= <piotr.maslanka@henrietta.com.pl>
Date: Wed, 29 Apr 2020 16:00:12 +0200
Subject: [PATCH] add processes

---
 CHANGELOG.md            |  3 ++-
 docs/index.rst          |  1 +
 docs/processes.rst      |  4 ++++
 satella/__init__.py     |  2 +-
 satella/exceptions.py   | 10 +++++++++-
 satella/processes.py    | 29 +++++++++++++++++++++++++++++
 tests/test_processes.py | 10 ++++++++++
 7 files changed, 56 insertions(+), 3 deletions(-)
 create mode 100644 docs/processes.rst
 create mode 100644 satella/processes.py
 create mode 100644 tests/test_processes.py

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c4bad5f3..e48ef6a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,5 @@
 # v2.7.16
 
 * extended `Proxy`
-* made `Immutable` utilize `__slots__`
\ No newline at end of file
+* made `Immutable` utilize `__slots__`
+* added `satella.processes`
\ No newline at end of file
diff --git a/docs/index.rst b/docs/index.rst
index 28879607..2a7504fc 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -26,6 +26,7 @@ Visit the project's page at GitHub_!
            files
            time
            exceptions
+           processes
 
 
 Indices and tables
diff --git a/docs/processes.rst b/docs/processes.rst
new file mode 100644
index 00000000..19ec90fa
--- /dev/null
+++ b/docs/processes.rst
@@ -0,0 +1,4 @@
+processes
+=========
+
+.. autofunction:: satella.processes.call_and_return_stdout
diff --git a/satella/__init__.py b/satella/__init__.py
index 61ad89d8..10939f77 100644
--- a/satella/__init__.py
+++ b/satella/__init__.py
@@ -1 +1 @@
-__version__ = '2.7.16_a3'
+__version__ = '2.7.16_a4'
diff --git a/satella/exceptions.py b/satella/exceptions.py
index a31e1536..098c54ac 100644
--- a/satella/exceptions.py
+++ b/satella/exceptions.py
@@ -4,7 +4,8 @@ import typing as tp
 __all__ = ['BaseSatellaError', 'ResourceLockingError', 'ResourceNotLocked', 'ResourceLocked',
            'ConfigurationValidationError', 'ConfigurationError', 'ConfigurationSchemaError',
            'PreconditionError', 'MetricAlreadyExists', 'BaseSatellaException', 'CustomException',
-           'CodedCustomException', 'CodedCustomExceptionMetaclass', 'WouldWaitMore', 'LockIsHeld']
+           'CodedCustomException', 'CodedCustomExceptionMetaclass', 'WouldWaitMore', 'LockIsHeld',
+           'ProcessFailed']
 
 
 class CustomException(Exception):
@@ -156,3 +157,10 @@ class LockIsHeld(ResourceLocked):
 
     def __init__(self, pid):
         self.pid = pid
+
+
+class ProcessFailed(BaseSatellaError):
+    """A process finished with other result code that it was requested"""
+
+    def __init__(self, rc: int):
+        self.rc = rc
diff --git a/satella/processes.py b/satella/processes.py
new file mode 100644
index 00000000..cb8da055
--- /dev/null
+++ b/satella/processes.py
@@ -0,0 +1,29 @@
+import subprocess
+import typing as tp
+
+from .exceptions import ProcessFailed
+
+
+def call_and_return_stdout(args: tp.Union[str, tp.List[str]],
+                           expected_return_code: int = 0, **kwargs) -> tp.Union[bytes, str]:
+    """
+    Call a process and return it's stdout.
+
+    :param args: arguments to run the program with. If passed a string, it will be split on space.
+    :param expected_return_code: an expected return code of this process. 0 is the default. If process
+        returns anything else, ProcessFailed will be raise
+    :param ProcessFailed: process' result code was different from the requested
+    """
+    if isinstance(args, str):
+        args = args.split(' ')
+
+    kwargs['capture_output'] = True
+
+    proc = subprocess.run(args, **kwargs)
+
+    if proc.returncode != expected_return_code:
+        raise ProcessFailed(proc.returncode)
+    else:
+        return proc.stdout
+
+
diff --git a/tests/test_processes.py b/tests/test_processes.py
new file mode 100644
index 00000000..2a1e2510
--- /dev/null
+++ b/tests/test_processes.py
@@ -0,0 +1,10 @@
+import unittest
+import sys
+from satella.processes import call_and_return_stdout
+
+
+class TestProcesses(unittest.TestCase):
+    @unittest.skipIf('win' in sys.platform, 'Running on Windows')
+    def test_return_stdout(self):
+        output = call_and_return_stdout('cat /proc/meminfo', shell=True, encoding='utf8')
+        self.assertIn('MemTotal', output)
-- 
GitLab