Merge pull request #10 from hjwp/master

A first cut of working processes and working | based chains
This commit is contained in:
2014-11-11 17:27:22 -05:00
14 changed files with 3179 additions and 89 deletions
+2
View File
@@ -0,0 +1,2 @@
*.pyc
__pycache__
+9 -9
View File
@@ -13,7 +13,6 @@ Ideas
- Process monitoring
- programatically compose a chain of streams.
- process call timeouts
- >>> uptime.stdout >> cowsay.stdin
Usage
-----
@@ -23,7 +22,7 @@ Simple Usage::
>>> import procs
>>> c = procs.run('uptime')
>>> c.exit_code
>>> c.returncode
0
>>> c.ok
True
@@ -33,13 +32,14 @@ Simple Usage::
Advanced Usage::
>>> chain = procs.chain()
>>> uptime = chain.process('uptime')
>>> cowsay = chain.process('cowsay')
>>> chain.link(uptime.stdout, cowsay.stdin)
>>> chain.start(wait=True)
>>> chain.wait()
>>> ls = procs.Process('ls /usr/bin')
>>> grep = procs.Process('grep python')
>>> chain = ls | grep
>>> chain.run()
>>> print(chain.stdout)
python
python3
python3.4
>>> from procs import ProcessHandler
-80
View File
@@ -1,80 +0,0 @@
import os
import subprocess
class Process(object):
def __init__(self, command):
self.command = command
self.environ = {}
self.cwd = None
def set_command(self, command):
# process popen chain, etc
self.command = command
def set_environ(self, environ, clean=False):
self.environ = dict() if clean else dict(os.environ)
self.environ.update(environ)
def set_cwd(self, cwd):
# expand, discover, etc.
self.cwd = cwd
def start(self):
pass
@property
def stdin(self):
return 'stdin'
@property
def stdout(self):
return 'stdout'
@property
def stderr(self):
return 'stderr'
class Chain(object):
def __init__(self):
self.processes = []
def process(self, command):
p = Process(command)
self.processes.append(p)
return p
def link(self, x, y):
print 'Linking {} to {}'.format(x, y)
def start(self, wait=False):
for process in self.processes:
process.start()
self.wait()
def wait(self):
# wait, somehow
pass
def chain():
return Chain()
def process(command, env=None, clean_env=False, cwd=None, wait=False):
p = Process(command)
if wait:
p.start()
p.wait()
def run(command, env=None, cwd=None, clean_environ=False):
pass
+6
View File
@@ -0,0 +1,6 @@
from .process import Process
def run(command):
process = Process(command)
process.run()
return process
+32
View File
@@ -0,0 +1,32 @@
from __future__ import print_function
import os
class Chain(object):
def __init__(self, processes):
self.processes = processes
def run(self):
for proc, next_proc in zip(self.processes, self.processes[1:]):
read, write = os.pipe()
proc.set_stdout(write)
next_proc.set_stdin(read)
for proc in self.processes:
proc.start()
for proc in self.processes:
proc.wait()
if proc._stdout is not None:
os.close(proc._stdout)
@property
def returncode(self):
return self.processes[-1].returncode
@property
def stdout(self):
return self.processes[-1].stdout
+70
View File
@@ -0,0 +1,70 @@
from __future__ import print_function
import subprocess
from .chain import Chain
class Process(object):
def __init__(self, command):
self.command = command
self._stdin = None
self._stdout = None
self._stdout_text = None
self._returncode = None
def set_stdin(self, stdin):
self._stdin = stdin
def set_stdout(self, stdout):
self._stdout = stdout
@property
def stdin(self):
return 'stdin'
@property
def stdout(self):
if self._stdout_text is not None:
return self._stdout_text
@property
def returncode(self):
if self._returncode is not None:
return self._returncode
@property
def ok(self):
if self._returncode is not None:
return self.returncode is 0
@property
def subprocess(self):
if self._subprocess is not None:
return self._subprocess
def start(self):
self._subprocess = subprocess.Popen(
args=self.command,
shell=True,
stdin=self._stdin if self._stdin else subprocess.PIPE,
stdout=self._stdout if self._stdout else subprocess.PIPE,
)
def wait(self):
self._returncode = self._subprocess.wait()
if self._subprocess.stdout is not None:
self._stdout_text = self._subprocess.stdout.read().decode()
def run(self):
self.start()
self.wait()
def __or__(self, other):
return Chain([self, other])
def __repr__(self):
return '<Process: {command}>'.format(command=self.command)
Executable
+2986
View File
File diff suppressed because it is too large Load Diff
View File
+18
View File
@@ -0,0 +1,18 @@
import os
from procs import Process
TEST_DIR = os.path.abspath(os.path.join(
os.path.dirname(os.path.abspath(__file__)), 'test_data'
))
def test_chained_procs():
ls = Process('ls {test_dir}'.format(test_dir=TEST_DIR))
grep = Process('grep 2')
chain = ls | grep
chain.run()
assert chain.returncode == 0
assert chain.stdout.strip() == 'file2'
View File
View File
View File
+46
View File
@@ -0,0 +1,46 @@
import os
from procs import Process
TEST_DIR = os.path.abspath(os.path.join(
os.path.dirname(os.path.abspath(__file__)), 'test_data'
))
def tests_repr():
p = Process('foo')
assert repr(p) == '<Process: foo>'
def test_single_proc():
ls = Process('ls {test_dir}'.format(test_dir=TEST_DIR))
ls.run()
assert ls.returncode == 0
assert ls.stdout == u'file1\nfile2\nfile3\n'
def test_empty_output():
cat = Process('cat /dev/null')
cat.run()
assert cat.stdout == u''
def test_returncode():
assert not os.path.exists('/bin/nosuchcommand')
p = Process('/bin/nosuchcommand')
p.run()
assert p.returncode == 127
def test_ok_if_returncode_0():
p = Process('ls')
p.run()
assert p.ok is True
def test_not_ok_if_returncode_not_0():
assert not os.path.exists('/bin/nosuchcommand')
p = Process('/bin/nosuchcommand')
p.run()
assert p.ok is False
+10
View File
@@ -0,0 +1,10 @@
import procs
def test_run_is_same_as_Process_run():
process = procs.Process('ls')
process.run()
p = procs.run('ls')
assert p.returncode == process.returncode
assert p.stdout == process.stdout