Skip to content

Commit

Permalink
Added support for optional encoding in Popen (fixes #53)
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisTimperley committed Apr 10, 2020
1 parent 6a51963 commit 1d6a352
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 6 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ v0.3.1 (????-??-??)

* updated `TimeoutExpired` and `CalledProcessError` exceptions to inherit
from their counterparts in the Python standard library
* added `encoding` parameter to `Popen` to allow users to specify which
encoding, if any, should be used to decode the output stream from a
process.


v0.3.0 (2020-03-13)
Expand Down
20 changes: 15 additions & 5 deletions src/dockerblade/popen.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
__all__ = ('Popen',)

from typing import Optional, Iterator, Dict, Any
from typing import Any, Dict, Iterator, Optional, Union
import time
import signal
import subprocess
Expand Down Expand Up @@ -38,7 +38,7 @@ class should be generated by :meth:`ShellProxy.popen` rather than via
Attributes
----------
stream: Iterator[str]
stream: Union[Iterator[str], Iterator[bytes]]
An output stream for this process.
args: str
The argument string that was used to generate this process.
Expand All @@ -52,6 +52,9 @@ class should be generated by :meth:`ShellProxy.popen` rather than via
has terminated.
retcode: int, optional
The return code produced by this process, if known.
encoding: str, optional
The encoding, if any, used by this process. If :code:`None`, the
output stream of this process will be treated as a bytestream.
"""
args: str = attr.ib()
cwd: str = attr.ib()
Expand All @@ -62,14 +65,21 @@ class should be generated by :meth:`ShellProxy.popen` rather than via
_pid: Optional[int] = attr.ib(init=False, default=None, repr=False)
_pid_host: Optional[int] = attr.ib(init=False, default=None)
_returncode: Optional[int] = attr.ib(init=False, default=None)
encoding: Optional[str] = attr.ib(default='utf-8')

def _inspect(self) -> Dict[str, Any]:
return self._docker_api.exec_inspect(self._exec_id)

@property
def stream(self) -> Iterator[str]:
for line_bytes in self._stream:
yield line_bytes.decode('utf-8')
def stream(self) -> Union[Iterator[bytes], Iterator[str]]:
def decoded_stream():
for line_bytes in self._stream:
yield line_bytes.decode(self.encoding)

if self.encoding is None:
yield from self._stream
else:
yield from decoded_stream()

@property
def host_pid(self) -> Optional[int]:
Expand Down
5 changes: 4 additions & 1 deletion src/dockerblade/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ def popen(self,
args: str,
*,
cwd: str = '/',
encoding: Optional[str] = 'utf-8',
stdout: bool = True,
stderr: bool = False,
time_limit: Optional[int] = None,
Expand All @@ -307,11 +308,13 @@ def popen(self,
stdout=stdout,
stderr=stderr)
exec_id = exec_response['Id']
exec_stream = docker_api.exec_start(exec_id, stream=True)
exec_stream = docker_api.exec_start(exec_id,
stream=True)
logger.debug(f'started Exec [{exec_id}] for Popen')
return Popen(args=args,
cwd=cwd,
container=self.container,
docker_api=docker_api,
exec_id=exec_id,
encoding=encoding,
stream=exec_stream)
5 changes: 5 additions & 0 deletions test/test_shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,8 @@ def test_popen(alpine_310):
p.wait(1)
p.kill()
assert p.wait(1.5) != 0

expected = 'hello world'
p = shell.popen(f"echo '{expected}'", encoding=None)
actual = ''.join([b.decode('utf-8') for b in p.stream]).strip()
assert actual == expected

0 comments on commit 1d6a352

Please sign in to comment.