diff --git a/.travis.yml b/.travis.yml index 84ba3607..67b1259c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,8 +21,10 @@ matrix: env: TOXENV=py35-django18 - python: "3.6" env: TOXENV=py36-django18 DEPLOY=1 - - python: "3.6" - env: TOXENV=py36-cov-travis + - python: "3.7" + env: TOXENV=py37-django18 + - python: "3.7" + env: TOXENV=py37-cov-travis deploy: # Tagging a new kobo automatically releases to PyPI. diff --git a/kobo/shortcuts.py b/kobo/shortcuts.py index d1d13efc..8b331ddd 100644 --- a/kobo/shortcuts.py +++ b/kobo/shortcuts.py @@ -284,6 +284,14 @@ def run(cmd, show_cmd=False, stdout=False, logfile=None, can_fail=False, workdir cmd = " ".join((shlex_quote(i) for i in cmd)) universal_newlines = kwargs.get('universal_newlines', False) + # new args added in py3 for Popen + text = kwargs.get('text', False) + encoding = kwargs.get('encoding', None) + errors = kwargs.get('errors', None) + + # any of these args is passed, text mode will be enabled. + is_text_mode = any([universal_newlines, text, encoding, errors]) + encoding = encoding or 'utf-8' log = None if logfile: @@ -292,7 +300,7 @@ def run(cmd, show_cmd=False, stdout=False, logfile=None, can_fail=False, workdir # it will be overwritten. Otherwise the command output will just be # appended to the existing file. mode = 'a' if not show_cmd and os.path.exists(logfile) else 'w' - if not universal_newlines: + if not is_text_mode: mode += 'b' log = open(logfile, mode) @@ -306,9 +314,9 @@ def run(cmd, show_cmd=False, stdout=False, logfile=None, can_fail=False, workdir if stdout: print(command, end='') if logfile: - if six.PY3 and not universal_newlines: + if six.PY3 and not is_text_mode: # Log file opened as binary, encode the command - command = bytes(command, encoding='utf-8') + command = bytes(command, encoding=encoding) log.write(command) stdin = None @@ -328,8 +336,8 @@ def run(self): stdin_thread.daemon = True stdin_thread.start() - output = "" if universal_newlines else b"" - sentinel = "" if universal_newlines else b"" + output = "" if is_text_mode else b"" + sentinel = "" if is_text_mode else b"" while True: if buffer_size == -1: lines = proc.stdout.readline() @@ -346,8 +354,8 @@ def run(self): if lines == sentinel: break if stdout: - if not universal_newlines: - sys.stdout.write(lines.decode('utf-8')) + if not is_text_mode: + sys.stdout.write(lines.decode(encoding)) else: sys.stdout.write(lines) if logfile: diff --git a/tests/test_shortcuts.py b/tests/test_shortcuts.py index e0df71bf..70e82b14 100755 --- a/tests/test_shortcuts.py +++ b/tests/test_shortcuts.py @@ -4,8 +4,10 @@ import mock import unittest2 as unittest +import pytest import os +import sys import shutil import tempfile from six.moves import StringIO @@ -176,6 +178,24 @@ def test_run(self): # passes in bash run("echo foo | tee >(md5sum -b) >/dev/null", executable="/bin/bash") + @pytest.mark.xfail(sys.version_info < (3, 7), reason="python3.7 api changes") + def test_run_in_text_mode(self): + """test run with kwargs 'text', 'encoding' or/and 'errors' set. These kwargs are + added to Popen from python3.6(encoding, errors) and python3.7(text). Running test + with python version older than 3.7 is expected to fail + """ + ret, out = run("echo hello", text=True) + self.assertEqual(ret, 0) + self.assertEqual(out, "hello\n") + + ret, out = run("echo hello", encoding="utf-8") + self.assertEqual(ret, 0) + self.assertEqual(out, "hello\n") + + ret, out = run("echo hello", errors="strict") + self.assertEqual(ret, 0) + self.assertEqual(out, "hello\n") + @mock.patch('sys.stdout', new_callable=StringIO) def test_run_univ_nl_show_cmd_logfile_stdout(self, mock_out): logfile = os.path.join(self.tmp_dir, 'output.log') diff --git a/tox.ini b/tox.ini index 30f519c5..0e710409 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = {py27,py34,py35,py36}-django18 +envlist = {py27,py34,py35,py36,py37}-django18 skip_missing_interpreters = True [testenv] @@ -10,11 +10,12 @@ basepython = py34: python3.4 py35: python3.5 py36: python3.6 + py37: python3.7 deps = -rtest-requirements.txt django18: -cconstraints-django18.txt -[testenv:py36-cov-travis] +[testenv:py37-cov-travis] passenv = TRAVIS TRAVIS_* deps= -rtest-requirements.txt