Skip to content

Commit

Permalink
Reading and writing of files using execution contexts
Browse files Browse the repository at this point in the history
  • Loading branch information
xolox committed Mar 21, 2016
1 parent 01cecd1 commit 9b03dfd
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 5 deletions.
2 changes: 1 addition & 1 deletion executor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
unicode = str

# Semi-standard module versioning.
__version__ = '9.0.1'
__version__ = '9.1'

# Initialize a logger.
logger = logging.getLogger(__name__)
Expand Down
43 changes: 40 additions & 3 deletions executor/contexts.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Programmer friendly subprocess wrapper.
#
# Author: Peter Odding <peter@peterodding.com>
# Last Change: October 5, 2015
# Last Change: March 22, 2016
# URL: https://executor.readthedocs.org

r"""
Expand Down Expand Up @@ -65,7 +65,7 @@ def details_about_system(context):
import socket

# Modules included in our package.
from executor import DEFAULT_SHELL, ExternalCommand
from executor import DEFAULT_SHELL, ExternalCommand, quote
from executor.ssh.client import RemoteCommand, SSH_PROGRAM_NAME

# Initialize a logger.
Expand All @@ -78,7 +78,9 @@ class AbstractContext(object):
Abstract base class for shared logic of all context classes.
The most useful methods of this class are :func:`execute()`,
:func:`capture()`, :func:`cleanup()` and :func:`start_interactive_shell()`.
:func:`test()`, :func:`capture()`, :func:`cleanup()`,
:func:`start_interactive_shell()`, :func:`read_file()` and
:func:`write_file()`.
"""

def __init__(self, **options):
Expand Down Expand Up @@ -264,6 +266,41 @@ def cpu_count(self):
"""The number of CPUs in the system (an integer)."""
raise NotImplementedError()

def read_file(self, filename):
"""
Read the contents of a file.
:param filename: The pathname of the file to read (a string).
:returns: The contents of the file (a byte string).
This method uses cat_ to read the contents of files so that options
like :attr:`~.ExternalCommand.sudo` are respected (regardless of
whether we're dealing with a :class:`LocalContext` or
:class:`RemoteContext`).
.. _cat: http://linux.die.net/man/1/cat
"""
return self.execute('cat', filename, capture=True).stdout

def write_file(self, filename, contents):
"""
Change the contents of a file.
:param filename: The pathname of the file to write (a string).
:param contents: The contents to write to the file (a byte string).
This method uses a combination of cat_ and `output redirection`_ to
change the contents of files so that options like
:attr:`~.ExternalCommand.sudo` are respected (regardless of whether
we're dealing with a :class:`LocalContext` or :class:`RemoteContext`).
Due to the use of cat_ this method will create files that don't exist
yet, assuming the directory containing the file already exists and the
context provides permission to write to the directory.
.. _output redirection: https://en.wikipedia.org/wiki/Redirection_(computing)
"""
return self.execute('cat > %s' % quote(filename), shell=True, input=contents)

def __enter__(self):
"""Initialize a new "undo stack" (refer to :func:`cleanup()`)."""
self.undo_stack.append([])
Expand Down
13 changes: 12 additions & 1 deletion executor/tests.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Automated tests for the `executor' module.
#
# Author: Peter Odding <peter@peterodding.com>
# Last Change: February 20, 2016
# Last Change: March 21, 2016
# URL: https://executor.readthedocs.org

"""
Expand Down Expand Up @@ -745,6 +745,17 @@ def check_context(self, context):
assert not os.path.isfile(random_file)
# Test context.capture().
assert context.capture('hostname') == socket.gethostname()
# Test context.read_file() and context.write_file() and make sure they
# are binary safe (i.e. they should be usable for non-text files).
random_file = os.path.join(tempfile.gettempdir(), uuid.uuid4().hex)
assert not os.path.exists(random_file)
expected_contents = bytes(random.randint(0, 255) for i in range(25))
context.write_file(random_file, expected_contents)
# Make sure the file was indeed created.
assert os.path.exists(random_file)
# Make sure the contents are correct.
actual_contents = context.read_file(random_file)
assert actual_contents == expected_contents

def test_cli_usage(self):
"""Make sure the command line interface properly presents its usage message."""
Expand Down

0 comments on commit 9b03dfd

Please sign in to comment.