Repository: jazzband/pip-tools (python)

Issue:
pip-compile quiet
Feature request:

Add -q/--quiet flag to pip-sync command.


Pull request:
[#627] Add option --quiet to pip-compile
With `--quiet`, the output file is not logged.
Fixes #627.

**Changelog-friendly one-liner**: Add option --quiet to pip-compile

##### Contributor checklist

- [x] Provided the tests for the changes
- [X] Requested (or received) a review from another contributor
- [X] Gave a clear one-line description in the PR (that the maintainers can add to CHANGELOG.md afterwards).

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b937cbd..e8d2ba4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,11 +1,3 @@
-# 3.3.2 (2019-01-26)
-
-Bug Fixes:
-- Fix `pip-sync` with a temporary requirement file on Windows
-([#723](https://github.com/jazzband/pip-tools/pull/723)). Thanks @atugushev
-- Fix `pip-sync` to prevent uninstall of stdlib and dev packages
-([#718](https://github.com/jazzband/pip-tools/pull/718)). Thanks @atugushev
-
 # 3.3.1 (2019-01-24)
 
 - Re-release of 3.3.0 after fixing the deployment pipeline
diff --git a/piptools/_compat/__init__.py b/piptools/_compat/__init__.py
index e4ac717..1fa3805 100644
--- a/piptools/_compat/__init__.py
+++ b/piptools/_compat/__init__.py
@@ -29,6 +29,4 @@ from .pip_compat import (
     PyPI,
     install_req_from_line,
     install_req_from_editable,
-    stdlib_pkgs,
-    DEV_PKGS,
 )
diff --git a/piptools/_compat/pip_compat.py b/piptools/_compat/pip_compat.py
index 82ccb8b..187c6ea 100644
--- a/piptools/_compat/pip_compat.py
+++ b/piptools/_compat/pip_compat.py
@@ -36,8 +36,6 @@ Command = do_import('cli.base_command', 'Command', old_path='basecommand')
 cmdoptions = do_import('cli.cmdoptions', old_path='cmdoptions')
 get_installed_distributions = do_import('utils.misc', 'get_installed_distributions', old_path='utils')
 PyPI = do_import('models.index', 'PyPI')
-stdlib_pkgs = do_import('utils.compat', 'stdlib_pkgs', old_path='compat')
-DEV_PKGS = do_import('commands.freeze', 'DEV_PKGS')
 
 # pip 18.1 has refactored InstallRequirement constructors use by pip-tools.
 if pkg_resources.parse_version(pip.__version__) < pkg_resources.parse_version('18.1'):
diff --git a/piptools/logging.py b/piptools/logging.py
index 98f0528..f0bd178 100644
--- a/piptools/logging.py
+++ b/piptools/logging.py
@@ -8,18 +8,19 @@ from . import click
 
 
 class LogContext(object):
-    def __init__(self, verbose=False):
-        self.verbose = verbose
+    def __init__(self, verbosity=0):
+        self.verbosity = verbosity
 
     def log(self, *args, **kwargs):
         click.secho(*args, **kwargs)
 
     def debug(self, *args, **kwargs):
-        if self.verbose:
+        if self.verbosity >= 1:
             self.log(*args, **kwargs)
 
     def info(self, *args, **kwargs):
-        self.log(*args, **kwargs)
+        if self.verbosity >= 0:
+            self.log(*args, **kwargs)
 
     def warning(self, *args, **kwargs):
         kwargs.setdefault('fg', 'yellow')
diff --git a/piptools/scripts/compile.py b/piptools/scripts/compile.py
index cd6fe93..87923c8 100755
--- a/piptools/scripts/compile.py
+++ b/piptools/scripts/compile.py
@@ -31,7 +31,8 @@ class PipCommand(Command):
 
 @click.command()
 @click.version_option()
-@click.option('-v', '--verbose', is_flag=True, help="Show more output")
+@click.option('-v', '--verbose', count=True, help="Show more output")
+@click.option('-q', '--quiet', count=True, help="Give less output")
 @click.option('-n', '--dry-run', is_flag=True, help="Only show what would happen, don't change anything")
 @click.option('-p', '--pre', is_flag=True, default=None, help="Allow resolving to prereleases (default is not)")
 @click.option('-r', '--rebuild', is_flag=True, help="Clear any caches upfront, rebuild from scratch")
@@ -65,12 +66,12 @@ class PipCommand(Command):
 @click.option('--max-rounds', default=10,
               help="Maximum number of rounds before resolving the requirements aborts.")
 @click.argument('src_files', nargs=-1, type=click.Path(exists=True, allow_dash=True))
-def cli(verbose, dry_run, pre, rebuild, find_links, index_url, extra_index_url,
+def cli(verbose, quiet, dry_run, pre, rebuild, find_links, index_url, extra_index_url,
         cert, client_cert, trusted_host, header, index, emit_trusted_host, annotate,
         upgrade, upgrade_packages, output_file, allow_unsafe, generate_hashes,
         src_files, max_rounds):
     """Compiles requirements.txt from requirements.in specs."""
-    log.verbose = verbose
+    log.verbosity = verbose - quiet
 
     if len(src_files) == 0:
         if os.path.exists(DEFAULT_REQUIREMENTS_FILE):
diff --git a/piptools/sync.py b/piptools/sync.py
index 614fa01..f4f1a23 100644
--- a/piptools/sync.py
+++ b/piptools/sync.py
@@ -4,7 +4,6 @@ import sys
 import tempfile
 from subprocess import check_call
 
-from piptools._compat import stdlib_pkgs, DEV_PKGS
 from . import click
 from .exceptions import IncompatibleRequirements, UnsupportedConstraint
 from .utils import flat_map, format_requirement, key_from_ireq, key_from_req, get_hashes_from_ireq
@@ -15,7 +14,9 @@ PACKAGES_TO_IGNORE = [
     'pip-tools',
     'pip-review',
     'pkg-resources',
-] + list(stdlib_pkgs) + list(DEV_PKGS)
+    'setuptools',
+    'wheel',
+]
 
 
 def dependency_tree(installed_keys, root_key):
@@ -163,13 +164,9 @@ def sync(to_install, to_uninstall, verbose=False, dry_run=False, pip_flags=None,
                 req_lines.append(format_requirement(ireq, hashes=ireq_hashes))
 
             # save requirement lines to a temporary file
-            tmp_req_file = tempfile.NamedTemporaryFile(mode='wt', delete=False)
-            tmp_req_file.write('\n'.join(req_lines))
-            tmp_req_file.close()
+            with tempfile.NamedTemporaryFile(mode='wt') as tmp_req_file:
+                tmp_req_file.write('\n'.join(req_lines))
+                tmp_req_file.flush()
 
-            try:
                 check_call([pip, 'install', '-r', tmp_req_file.name] + pip_flags + install_flags)
-            finally:
-                os.unlink(tmp_req_file.name)
-
     return 0


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 6319a93..3c4229d 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -357,6 +357,16 @@ def test_upgrade_packages_version_option(tmpdir):
         assert 'small-fake-b==0.2' in out.output
 
 
+def test_quiet_option(tmpdir):
+    runner = CliRunner()
+    with runner.isolated_filesystem():
+        with open('requirements', 'w'):
+            pass
+        out = runner.invoke(cli, ['--quiet', '-n', 'requirements'])
+        # Pinned requirements result has not been written to output
+        assert 'Dry-run, so nothing updated.' == out.output.strip()
+
+
 def test_generate_hashes_with_editable():
     small_fake_package_dir = os.path.join(
         os.path.split(__file__)[0], 'test_data', 'small_fake_package')
diff --git a/tests/test_sync.py b/tests/test_sync.py
index 32e820b..72b1e04 100644
--- a/tests/test_sync.py
+++ b/tests/test_sync.py
@@ -12,16 +12,10 @@ from piptools.sync import dependency_tree, diff, merge, sync
 
 
 @pytest.fixture
-def mocked_tmp_file():
+def mocked_tmp_req_file():
     with mock.patch.object(tempfile, 'NamedTemporaryFile') as m:
-        yield m.return_value
-
-
-@pytest.fixture
-def mocked_tmp_req_file(mocked_tmp_file):
-    with mock.patch('os.unlink'):
-        mocked_tmp_file.name = 'requirements.txt'
-        yield mocked_tmp_file
+        m.return_value.__enter__.return_value.name = 'requirements.txt'
+        yield m.return_value.__enter__.return_value
 
 
 @pytest.mark.parametrize(
@@ -117,8 +111,7 @@ def test_diff_should_uninstall(fake_dist):
 
 def test_diff_should_not_uninstall(fake_dist):
     ignored = ('pip==7.1.0', 'pip-tools==1.1.1', 'pip-review==1.1.1',
-               'pkg-resources==0.0.0', 'setuptools==34.0.0', 'wheel==0.29.0',
-               'python==3.0', 'distribute==0.1', 'wsgiref==0.1', 'argparse==0.1')
+               'pkg-resources==0.0.0', 'setuptools==34.0.0', 'wheel==0.29.0')
     installed = [fake_dist(pkg) for pkg in ignored]
     reqs = []
 
@@ -233,16 +226,6 @@ def test_sync_install_temporary_requirement_file(from_line, from_editable, mocke
         check_call.assert_called_once_with(['pip', 'install', '-r', mocked_tmp_req_file.name, '-q'])
 
 
-def test_temporary_requirement_file_deleted(from_line, from_editable, mocked_tmp_file):
-    with mock.patch('piptools.sync.check_call'):
-        to_install = {from_line('django==1.8')}
-
-        with mock.patch('os.unlink') as unlink:
-            sync(to_install, set())
-
-            unlink.assert_called_once_with(mocked_tmp_file.name)
-
-
 def test_sync_requirement_file(from_line, from_editable, mocked_tmp_req_file):
     with mock.patch('piptools.sync.check_call'):
         to_install = {