Repository: jazzband/pip-tools
Pull request #709 (2019-01-15 01:03:04)

Use 'dist: xenial' in Travis to simplify configuration
Allows using Python 3.7 without sudo declarations.

Travis officially added support for Xenial on 2018-11-08.

https://blog.travis-ci.com/2018-11-08-xenial-release

Include tests for PyPy3 as well and document all supported versions/environments as trove classifiers.


Patch:
diff --git a/.travis.yml b/.travis.yml
index 66402c7..6f80e34 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,4 @@
+dist: xenial
 sudo: false
 language: python
 python:
@@ -5,7 +6,9 @@ python:
   - "3.4"
   - "3.5"
   - "3.6"
-  - "pypy"
+  - "3.7"
+  - "pypy2.7-6.0"
+  - "pypy3.5-6.0"
 
 env:
   - PIP=8.1.1
@@ -13,8 +16,8 @@ env:
   - PIP=9.0.3
   - PIP=10.0.1
   - PIP=18.0
-  - PIP=master
   - PIP=latest
+  - PIP=master
 
 cache: pip
 install:
@@ -23,37 +26,10 @@ script:
   - tox
 
 jobs:
-  include:
-    # Python 3.7 in Travis: https://github.com/travis-ci/travis-ci/issues/9815
-    - python: 3.7
-      dist: xenial
-      sudo: true
+  exclude:
+    - python: "pypy3.5-6.0"
       env: PIP=8.1.1
-    - python: 3.7
-      dist: xenial
-      sudo: true
-      env: PIP=9.0.1
-    - python: 3.7
-      dist: xenial
-      sudo: true
-      env: PIP=9.0.3
-    - python: 3.7
-      dist: xenial
-      sudo: true
-      env: PIP=10.0.1
-    - python: 3.7
-      dist: xenial
-      sudo: true
-      env: PIP=18.0
-    - python: 3.7
-      dist: xenial
-      sudo: true
-      env: PIP=latest
-    - python: 3.7
-      dist: xenial
-      sudo: true
-      env: PIP=master
-
+  include:
     - stage: flake8
       python: 2.7
       env: TOXENV=flake8
@@ -75,6 +51,5 @@ jobs:
         on:
           tags: true
           repo: jazzband/pip-tools
-
   allow_failures:
     - env: PIP=master
diff --git a/piptools/scripts/compile.py b/piptools/scripts/compile.py
index 5eba0fe..cd6fe93 100755
--- a/piptools/scripts/compile.py
+++ b/piptools/scripts/compile.py
@@ -241,15 +241,15 @@ def cli(verbose, dry_run, pre, rebuild, find_links, index_url, extra_index_url,
                           default_index_url=repository.DEFAULT_INDEX_URL,
                           index_urls=repository.finder.index_urls,
                           trusted_hosts=pip_options.trusted_hosts,
-                          format_control=repository.finder.format_control)
+                          format_control=repository.finder.format_control,
+                          allow_unsafe=allow_unsafe)
     writer.write(results=results,
                  unsafe_requirements=resolver.unsafe_constraints,
                  reverse_dependencies=reverse_dependencies,
                  primary_packages={key_from_req(ireq.req) for ireq in constraints if not ireq.constraint},
                  markers={key_from_req(ireq.req): ireq.markers
                           for ireq in constraints if ireq.markers},
-                 hashes=hashes,
-                 allow_unsafe=allow_unsafe)
+                 hashes=hashes)
 
     if dry_run:
         log.warning('Dry-run, so nothing updated.')
diff --git a/piptools/writer.py b/piptools/writer.py
index d57a602..68c0c3b 100644
--- a/piptools/writer.py
+++ b/piptools/writer.py
@@ -11,7 +11,8 @@ from .utils import comment, dedup, format_requirement, key_from_req, UNSAFE_PACK
 class OutputWriter(object):
     def __init__(self, src_files, dst_file, dry_run, emit_header, emit_index,
                  emit_trusted_host, annotate, generate_hashes,
-                 default_index_url, index_urls, trusted_hosts, format_control):
+                 default_index_url, index_urls, trusted_hosts, format_control,
+                 allow_unsafe):
         self.src_files = src_files
         self.dst_file = dst_file
         self.dry_run = dry_run
@@ -24,6 +25,7 @@ class OutputWriter(object):
         self.index_urls = index_urls
         self.trusted_hosts = trusted_hosts
         self.format_control = format_control
+        self.allow_unsafe = allow_unsafe
 
     def _sort_key(self, ireq):
         return (not ireq.editable, str(ireq.req).lower())
@@ -47,6 +49,8 @@ class OutputWriter(object):
                     params += ['--no-annotate']
                 if self.generate_hashes:
                     params += ["--generate-hashes"]
+                if self.allow_unsafe:
+                    params += ["--allow-unsafe"]
                 params += ['--output-file', self.dst_file]
                 params += self.src_files
                 yield comment('#    pip-compile {}'.format(' '.join(params)))
@@ -82,7 +86,7 @@ class OutputWriter(object):
             yield ''
 
     def _iter_lines(self, results, unsafe_requirements, reverse_dependencies,
-                    primary_packages, markers, hashes, allow_unsafe=False):
+                    primary_packages, markers, hashes):
         for line in self.write_header():
             yield line
         for line in self.write_flags():
@@ -110,20 +114,20 @@ class OutputWriter(object):
                                                primary_packages,
                                                marker=markers.get(key_from_req(ireq.req)),
                                                hashes=hashes)
-                if not allow_unsafe:
+                if not self.allow_unsafe:
                     yield comment('# {}'.format(req))
                 else:
                     yield req
 
     def write(self, results, unsafe_requirements, reverse_dependencies,
-              primary_packages, markers, hashes, allow_unsafe=False):
+              primary_packages, markers, hashes):
         with ExitStack() as stack:
             f = None
             if not self.dry_run:
                 f = stack.enter_context(AtomicSaver(self.dst_file))
 
             for line in self._iter_lines(results, unsafe_requirements, reverse_dependencies,
-                                         primary_packages, markers, hashes, allow_unsafe=allow_unsafe):
+                                         primary_packages, markers, hashes):
                 log.info(line)
                 if f:
                     f.write(unstyle(line).encode('utf-8'))
diff --git a/setup.py b/setup.py
index bda72b3..ffe84b2 100644
--- a/setup.py
+++ b/setup.py
@@ -47,6 +47,9 @@ setup(
         'Programming Language :: Python :: 3.4',
         'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
+        'Programming Language :: Python :: 3.7',
+        'Programming Language :: Python :: Implementation :: CPython',
+        'Programming Language :: Python :: Implementation :: PyPy',
         'Topic :: System :: Systems Administration',
     ]
 )
diff --git a/tox.ini b/tox.ini
index a3a6706..c41a2bc 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
 [tox]
 envlist =
-    py{27,34,35,36,37,py}-pip{8.1.1,9.0.1,9.0.3,10.0.1,18.0,latest,master}
+    py{27,34,35,36,37,py,py3}-pip{8.1.1,9.0.1,9.0.3,10.0.1,18.0,latest,master}
     flake8
     readme
 skip_missing_interpreters = True


Test patch:
diff --git a/tests/test_writer.py b/tests/test_writer.py
index 50b0025..b3b3773 100644
--- a/tests/test_writer.py
+++ b/tests/test_writer.py
@@ -1,4 +1,4 @@
-from pytest import fixture
+from pytest import fixture, mark
 
 from piptools._compat import FormatControl
 from piptools.utils import comment
@@ -14,7 +14,8 @@ def writer():
                         generate_hashes=False,
                         default_index_url=None, index_urls=[],
                         trusted_hosts=[],
-                        format_control=FormatControl(set(), set()))
+                        format_control=FormatControl(set(), set()),
+                        allow_unsafe=False)
 
 
 def test_format_requirement_annotation_editable(from_editable, writer):
@@ -82,20 +83,38 @@ def test_format_requirement_environment_marker(from_line, writer):
             'test ; python_version == "2.7" and platform_python_implementation == "CPython"')
 
 
-def test_iter_lines__unsafe_dependencies(from_line, writer):
+@mark.parametrize(('allow_unsafe',), [(True,), (False,)])
+def test_iter_lines__unsafe_dependencies(from_line, allow_unsafe):
+
+    writer = OutputWriter(
+        src_files=["src_file", "src_file2"], dst_file="dst_file",
+        dry_run=True,
+        emit_header=True, emit_index=True, emit_trusted_host=True,
+        annotate=True,
+        generate_hashes=False,
+        default_index_url=None, index_urls=[],
+        trusted_hosts=[],
+        format_control=FormatControl(set(), set()),
+        allow_unsafe=allow_unsafe,
+    )
+
     ireq = [from_line('test==1.2')]
     unsafe_req = [from_line('setuptools')]
     reverse_dependencies = {'test': ['xyz']}
 
-    lines = writer._iter_lines(ireq,
-                               unsafe_req,
-                               reverse_dependencies,
-                               ['test'],
-                               {},
-                               None)
-    str_lines = []
-    for line in lines:
-        str_lines.append(line)
+    str_lines = list(writer._iter_lines(
+        ireq,
+        unsafe_req,
+        reverse_dependencies,
+        ['test'],
+        {},
+        None,
+    ))
     assert comment('# The following packages are considered to be unsafe in a requirements file:') in str_lines
-    assert comment('# setuptools') in str_lines
+    if allow_unsafe:
+        assert comment('#    pip-compile --allow-unsafe --output-file dst_file src_file src_file2') in str_lines
+        assert 'setuptools' in str_lines
+    else:
+        assert comment('#    pip-compile --output-file dst_file src_file src_file2') in str_lines
+        assert comment('# setuptools') in str_lines
     assert 'test==1.2' in str_lines