From a88bc0de73eb815fc64215b691efbae42af874a9 Mon Sep 17 00:00:00 2001 From: Donald Stufft Date: Mon, 16 Mar 2015 08:34:52 -0400 Subject: [PATCH] Send log messages >= logging.WARNING to stderr --- CHANGES.txt | 2 ++ pip/basecommand.py | 18 ++++++++++++++++-- pip/commands/freeze.py | 2 +- pip/utils/logging.py | 9 +++++++++ tests/functional/test_install.py | 15 +++++++++------ tests/functional/test_install_cleanup.py | 11 ++++++++--- tests/functional/test_install_extras.py | 6 +++--- tests/functional/test_install_reqs.py | 1 + tests/functional/test_install_upgrade.py | 4 ++-- tests/functional/test_install_user.py | 6 +++--- tests/functional/test_install_vcs.py | 3 ++- tests/functional/test_install_wheel.py | 5 ++++- tests/functional/test_list.py | 4 +++- tests/functional/test_search.py | 2 +- tests/functional/test_show.py | 2 +- tests/functional/test_uninstall.py | 2 +- tests/functional/test_wheel.py | 2 +- 17 files changed, 67 insertions(+), 27 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index c5ec8e74c..1bcf4b96a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -41,6 +41,8 @@ * Support arch specific wheels that are not tied to a specific Python ABI. (:pull:`2561`) +* Output warnings and errors to stderr instead of stdout. (:pull:`2543`) + **6.0.8 (2015-02-04)** diff --git a/pip/basecommand.py b/pip/basecommand.py index e8f6bdd5b..eb276e087 100644 --- a/pip/basecommand.py +++ b/pip/basecommand.py @@ -38,7 +38,7 @@ class Command(object): name = None usage = None hidden = False - log_stream = "ext://sys.stdout" + log_streams = ("ext://sys.stdout", "ext://sys.stderr") def __init__(self, isolated=False): parser_kw = { @@ -124,6 +124,12 @@ class Command(object): logging_dictConfig({ "version": 1, "disable_existing_loggers": False, + "filters": { + "exclude_warnings": { + "()": "pip.utils.logging.MaxLevelFilter", + "level": logging.WARNING, + }, + }, "formatters": { "indent": { "()": IndentingFormatter, @@ -138,7 +144,14 @@ class Command(object): "console": { "level": level, "class": "pip.utils.logging.ColorizedStreamHandler", - "stream": self.log_stream, + "stream": self.log_streams[0], + "filters": ["exclude_warnings"], + "formatter": "indent", + }, + "console_errors": { + "level": "WARNING", + "class": "pip.utils.logging.ColorizedStreamHandler", + "stream": self.log_streams[1], "formatter": "indent", }, "debug_log": { @@ -162,6 +175,7 @@ class Command(object): "level": level, "handlers": list(filter(None, [ "console", + "console_errors", "debug_log" if write_debug_log else None, "user_log" if options.log else None, ])), diff --git a/pip/commands/freeze.py b/pip/commands/freeze.py index 82409620d..42261b295 100644 --- a/pip/commands/freeze.py +++ b/pip/commands/freeze.py @@ -16,7 +16,7 @@ class FreezeCommand(Command): usage = """ %prog [options]""" summary = 'Output installed packages in requirements format.' - log_stream = "ext://sys.stderr" + log_streams = ("ext://sys.stderr", "ext://sys.stderr") def __init__(self, *args, **kw): super(FreezeCommand, self).__init__(*args, **kw) diff --git a/pip/utils/logging.py b/pip/utils/logging.py index e2467d8ff..f5c1521a8 100644 --- a/pip/utils/logging.py +++ b/pip/utils/logging.py @@ -119,3 +119,12 @@ class BetterRotatingFileHandler(logging.handlers.RotatingFileHandler): os.makedirs(os.path.dirname(self.baseFilename)) return logging.handlers.RotatingFileHandler._open(self) + + +class MaxLevelFilter(logging.Filter): + + def __init__(self, level): + self.level = level + + def filter(self, record): + return record.levelno < self.level diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py index 2ccc6335a..2e9ecacc1 100644 --- a/tests/functional/test_install.py +++ b/tests/functional/test_install.py @@ -27,7 +27,7 @@ def test_without_setuptools(script, data): ) assert ( "setuptools must be installed to install from a source distribution" - in result.stdout + in result.stderr ) @@ -74,7 +74,7 @@ def test_editable_install(script): result = script.pip('install', '-e', 'INITools==0.2', expect_error=True) assert ( "INITools==0.2 should either be a path to a local project or a VCS url" - in result.stdout + in result.stderr ) assert not result.files_created assert not result.files_updated @@ -205,7 +205,7 @@ def test_bad_install_with_no_download(script): ) assert ( "perhaps --no-download was used without first running " - "an equivalent install with --no-install?" in result.stdout + "an equivalent install with --no-install?" in result.stderr ) @@ -310,7 +310,7 @@ def test_install_from_local_directory_with_no_setup_py(script, data): """ result = script.pip('install', data.root, expect_error=True) assert not result.files_created - assert "is not installable. File 'setup.py' not found." in result.stdout + assert "is not installable. File 'setup.py' not found." in result.stderr def test_editable_install_from_local_directory_with_no_setup_py(script, data): @@ -319,7 +319,7 @@ def test_editable_install_from_local_directory_with_no_setup_py(script, data): """ result = script.pip('install', '-e', data.root, expect_error=True) assert not result.files_created - assert "is not installable. File 'setup.py' not found." in result.stdout + assert "is not installable. File 'setup.py' not found." in result.stderr def test_install_as_egg(script, data): @@ -508,7 +508,9 @@ def test_install_package_with_target(script): ) # Test repeated call without --upgrade, no files should have changed - result = script.pip_install_local('-t', target_dir, "simple==1.0") + result = script.pip_install_local( + '-t', target_dir, "simple==1.0", expect_stderr=True, + ) assert not Path('scratch') / 'target' / 'simple' in result.files_updated # Test upgrade call, check that new version is installed @@ -658,6 +660,7 @@ def test_url_incorrect_case_file_index(script, data): """ result = script.pip( 'install', '--index-url', data.find_links3, "dinner", + expect_stderr=True, ) # only Upper-2.0.tar.gz should get installed. diff --git a/tests/functional/test_install_cleanup.py b/tests/functional/test_install_cleanup.py index fdc4d6494..41d9e7956 100644 --- a/tests/functional/test_install_cleanup.py +++ b/tests/functional/test_install_cleanup.py @@ -80,7 +80,9 @@ def test_no_install_and_download_should_not_leave_build_dir(script): script.scratch_path.join("downloaded_packages").mkdir() assert not os.path.exists(script.venv_path / 'build') result = script.pip( - 'install', '--no-install', 'INITools==0.2', '-d', 'downloaded_packages' + 'install', '--no-install', 'INITools==0.2', '-d', + 'downloaded_packages', + expect_stderr=True, ) assert ( Path('scratch') / 'downloaded_packages/build' @@ -118,7 +120,10 @@ def test_download_should_not_delete_existing_build_dir(script): """ script.venv_path.join("build").mkdir() script.venv_path.join("build", "somefile.txt").write("I am not empty!") - script.pip('install', '--no-install', 'INITools==0.2', '-d', '.') + script.pip( + 'install', '--no-install', 'INITools==0.2', '-d', '.', + expect_stderr=True, + ) with open(script.venv_path / 'build' / 'somefile.txt') as fp: content = fp.read() assert os.path.exists(script.venv_path / 'build'), ( @@ -175,5 +180,5 @@ def test_cleanup_prevented_upon_build_dir_exception(script, data): ) assert result.returncode == PREVIOUS_BUILD_DIR_ERROR - assert "pip can't proceed" in result.stdout, result.stdout + assert "pip can't proceed" in result.stderr assert exists(build_simple) diff --git a/tests/functional/test_install_extras.py b/tests/functional/test_install_extras.py index 1e7def3e9..2cbb50178 100644 --- a/tests/functional/test_install_extras.py +++ b/tests/functional/test_install_extras.py @@ -67,7 +67,7 @@ def test_nonexistent_extra_warns_user_no_wheel(script, data): ) assert ( "simple 3.0 does not provide the extra 'nonexistent'" - in result.stdout + in result.stderr ) @@ -85,7 +85,7 @@ def test_nonexistent_extra_warns_user_with_wheel(script, data): ) assert ( "simplewheel 2.0 does not provide the extra 'nonexistent'" - in result.stdout + in result.stderr ) @@ -102,4 +102,4 @@ def test_nonexistent_options_listed_in_order(script, data): " simplewheel 2.0 does not provide the extra 'nonexistent'\n" " simplewheel 2.0 does not provide the extra 'nope'" ) - assert msg in result.stdout + assert msg in result.stderr diff --git a/tests/functional/test_install_reqs.py b/tests/functional/test_install_reqs.py index f51bd2f4c..0fe2af414 100644 --- a/tests/functional/test_install_reqs.py +++ b/tests/functional/test_install_reqs.py @@ -137,6 +137,7 @@ def test_install_local_editable_with_extras(script, data): res = script.pip( 'install', '-e', to_install + '[bar]', '--process-dependency-links', expect_error=False, + expect_stderr=True, ) assert script.site_packages / 'easy-install.pth' in res.files_updated, ( str(res) diff --git a/tests/functional/test_install_upgrade.py b/tests/functional/test_install_upgrade.py index 5a413dc38..f28152152 100644 --- a/tests/functional/test_install_upgrade.py +++ b/tests/functional/test_install_upgrade.py @@ -293,8 +293,8 @@ def test_upgrade_vcs_req_with_dist_found(script): "743aad47656b27" ) ) - script.pip("install", req) - result = script.pip("install", "-U", req) + script.pip("install", req, expect_stderr=True) + result = script.pip("install", "-U", req, expect_stderr=True) assert "pypi.python.org" not in result.stdout, result.stdout diff --git a/tests/functional/test_install_user.py b/tests/functional/test_install_user.py index 82cd93304..049c7c8ee 100644 --- a/tests/functional/test_install_user.py +++ b/tests/functional/test_install_user.py @@ -104,7 +104,7 @@ class Tests_UserSite: ) assert ( "Can not perform a '--user' install. User site-packages are not " - "visible in this virtualenv." in result.stdout + "visible in this virtualenv." in result.stderr ) @pytest.mark.network @@ -288,5 +288,5 @@ class Tests_UserSite: assert ( "Will not install to the user site because it will lack sys.path " "precedence to %s in %s" % - ('INITools', dist_location) in result2.stdout - ), result2.stdout + ('INITools', dist_location) in result2.stderr + ) diff --git a/tests/functional/test_install_vcs.py b/tests/functional/test_install_vcs.py index 06ca8ffb1..0f0ea71d2 100644 --- a/tests/functional/test_install_vcs.py +++ b/tests/functional/test_install_vcs.py @@ -56,7 +56,8 @@ def test_git_with_sha1_revisions(script): script.pip( 'install', '-e', '%s@%s#egg=version_pkg' % - ('git+file://' + version_pkg_path.abspath.replace('\\', '/'), sha1) + ('git+file://' + version_pkg_path.abspath.replace('\\', '/'), sha1), + expect_stderr=True ) version = script.run('version_pkg') assert '0.1' in version.stdout, version.stdout diff --git a/tests/functional/test_install_wheel.py b/tests/functional/test_install_wheel.py index cad217774..7688eb416 100644 --- a/tests/functional/test_install_wheel.py +++ b/tests/functional/test_install_wheel.py @@ -17,7 +17,10 @@ def test_install_from_future_wheel_version(script, data): editable=False) package = data.packages.join("futurewheel-1.9-py2.py3-none-any.whl") - result = script.pip('install', package, '--no-index', expect_error=False) + result = script.pip( + 'install', package, '--no-index', expect_error=False, + expect_stderr=True, + ) result.assert_installed('futurewheel', without_egg_link=True, editable=False) diff --git a/tests/functional/test_list.py b/tests/functional/test_list.py index 13608ffeb..04c9ff802 100644 --- a/tests/functional/test_list.py +++ b/tests/functional/test_list.py @@ -55,7 +55,8 @@ def test_uptodate_flag(script, data): 'git+https://github.com/pypa/pip-test-package.git#egg=pip-test-package' ) result = script.pip( - 'list', '-f', data.find_links, '--no-index', '--uptodate' + 'list', '-f', data.find_links, '--no-index', '--uptodate', + expect_stderr=True, ) assert 'simple (1.0)' not in result.stdout # 3.0 is latest assert 'pip-test-package' not in result.stdout # editables excluded @@ -78,6 +79,7 @@ def test_outdated_flag(script, data): ) result = script.pip( 'list', '-f', data.find_links, '--no-index', '--outdated', + expect_stderr=True, ) assert 'simple (Current: 1.0 Latest: 3.0 [sdist])' in result.stdout assert 'simplewheel (Current: 1.0 Latest: 2.0 [wheel])' in result.stdout diff --git a/tests/functional/test_search.py b/tests/functional/test_search.py index 2559e1a61..bc6ddfd7a 100644 --- a/tests/functional/test_search.py +++ b/tests/functional/test_search.py @@ -129,7 +129,7 @@ def test_search_missing_argument(script): Test missing required argument for search """ result = script.pip('search', expect_error=True) - assert 'ERROR: Missing required argument (search query).' in result.stdout + assert 'ERROR: Missing required argument (search query).' in result.stderr @pytest.mark.network diff --git a/tests/functional/test_show.py b/tests/functional/test_show.py index 05cffde78..2fe5e2b92 100644 --- a/tests/functional/test_show.py +++ b/tests/functional/test_show.py @@ -67,7 +67,7 @@ def test_missing_argument(script): Test show command with no arguments. """ result = script.pip('show', expect_error=True) - assert 'ERROR: Please provide a package name or names.' in result.stdout + assert 'ERROR: Please provide a package name or names.' in result.stderr def test_find_package_not_found(): diff --git a/tests/functional/test_uninstall.py b/tests/functional/test_uninstall.py index 22c2d81fc..c7a7f16e8 100644 --- a/tests/functional/test_uninstall.py +++ b/tests/functional/test_uninstall.py @@ -47,7 +47,7 @@ def test_simple_uninstall_distutils(script): result = script.run('python', pkg_path / 'setup.py', 'install') result = script.pip('list') assert "distutils-install (0.1)" in result.stdout - script.pip('uninstall', 'distutils_install', '-y') + script.pip('uninstall', 'distutils_install', '-y', expect_stderr=True) result2 = script.pip('list') assert "distutils-install (0.1)" not in result2.stdout diff --git a/tests/functional/test_wheel.py b/tests/functional/test_wheel.py index 15a908f88..e7e0f7fab 100644 --- a/tests/functional/test_wheel.py +++ b/tests/functional/test_wheel.py @@ -17,7 +17,7 @@ def test_pip_wheel_fails_without_wheel(script, data): 'wheel', '--no-index', '-f', data.find_links, 'simple==3.0', expect_error=True, ) - assert "'pip wheel' requires the 'wheel' package" in result.stdout + assert "'pip wheel' requires the 'wheel' package" in result.stderr @pytest.mark.network