def test_unpinned_hash_checking(self, data): """Make sure prepare_files() raises an error when a requirement is not version-pinned in hash-checking mode. """ reqset = RequirementSet() # Test that there must be exactly 1 specifier: reqset.add_requirement(get_processed_req_from_line( 'simple --hash=sha256:a90427ae31f5d1d0d7ec06ee97d9fcf2d0fc9a786985' '250c1c83fd68df5911dd', lineno=1, )) # Test that the operator must be ==: reqset.add_requirement(get_processed_req_from_line( 'simple2>1.0 --hash=sha256:3ad45e1e9aa48b4462af0' '123f6a7e44a9115db1ef945d4d92c123dfe21815a06', lineno=2, )) finder = PackageFinder([data.find_links], [], session=PipSession()) resolver = self._basic_resolver(finder) assert_raises_regexp( HashErrors, # Make sure all failing requirements are listed: r'versions pinned with ==. These do not:\n' r' simple .* \(from -r file \(line 1\)\)\n' r' simple2>1.0 .* \(from -r file \(line 2\)\)', resolver.resolve, reqset)
def test_unsupported_hashes(self, data): """VCS and dir links should raise errors when --require-hashes is on. In addition, complaints about the type of requirement (VCS or dir) should trump the presence or absence of a hash. """ reqset = RequirementSet(require_hashes=True) reqset.add_requirement(get_processed_req_from_line( 'git+git://github.com/pypa/pip-test-package --hash=sha256:123', lineno=1, )) dir_path = data.packages.join('FSPkg') reqset.add_requirement(get_processed_req_from_line( 'file://%s' % (dir_path,), lineno=2, )) finder = PackageFinder([data.find_links], [], session=PipSession()) resolver = self._basic_resolver(finder) sep = os.path.sep if sep == '\\': sep = '\\\\' # This needs to be escaped for the regex assert_raises_regexp( HashErrors, r"Can't verify hashes for these requirements because we don't " r"have a way to hash version control repositories:\n" r" git\+git://github\.com/pypa/pip-test-package \(from -r file " r"\(line 1\)\)\n" r"Can't verify hashes for these file:// requirements because they " r"point to directories:\n" r" file://.*{sep}data{sep}packages{sep}FSPkg " r"\(from -r file \(line 2\)\)".format(sep=sep), resolver.resolve, reqset)
def test_unsupported_wheel_local_file_requirement_raises(self, data): reqset = RequirementSet() req = install_req_from_line( data.packages.join('simple.dist-0.1-py1-none-invalid.whl'), ) assert req.link is not None assert req.link.is_wheel assert req.link.scheme == "file" with pytest.raises(InstallationError): reqset.add_requirement(req)
def test_unsupported_wheel_link_requirement_raises(self): reqset = RequirementSet() req = install_req_from_line( 'https://whatever.com/peppercorn-0.4-py2.py3-bogus-any.whl', ) assert req.link is not None assert req.link.is_wheel assert req.link.scheme == "https" with pytest.raises(InstallationError): reqset.add_requirement(req)
def test_environment_marker_extras(self, data): """ Test that the environment marker extras are used with non-wheel installs. """ reqset = RequirementSet() req = InstallRequirement.from_editable( data.packages.join("LocalEnvironMarker")) reqset.add_requirement(req) finder = PackageFinder([data.find_links], [], session=PipSession()) resolver = self._basic_resolver(finder) resolver.resolve(reqset) # This is hacky but does test both case in py2 and py3 if sys.version_info[:2] in ((2, 7), (3, 4)): assert reqset.has_requirement('simple') else: assert not reqset.has_requirement('simple')
def test_missing_hash_with_require_hashes(self, data): """Setting --require-hashes explicitly should raise errors if hashes are missing. """ reqset = RequirementSet(require_hashes=True) reqset.add_requirement( list(process_line('simple==1.0', 'file', 1))[0]) finder = PackageFinder([data.find_links], [], session=PipSession()) resolver = self._basic_resolver(finder) assert_raises_regexp( HashErrors, r'Hashes are required in --require-hashes mode, but they are ' r'missing .*\n' r' simple==1.0 --hash=sha256:393043e672415891885c9a2a0929b1af95' r'fb866d6ca016b42d2e6ce53619b653$', resolver.resolve, reqset)
def test_no_reuse_existing_build_dir(self, data): """Test prepare_files raise exception with previous build dir""" build_dir = os.path.join(self.tempdir, 'build', 'simple') os.makedirs(build_dir) open(os.path.join(build_dir, "setup.py"), 'w') reqset = RequirementSet() req = InstallRequirement.from_line('simple') reqset.add_requirement(req) finder = PackageFinder([data.find_links], [], session=PipSession()) resolver = self._basic_resolver(finder) assert_raises_regexp( PreviousBuildDirError, r"pip can't proceed with [\s\S]*%s[\s\S]*%s" % (req, build_dir.replace('\\', '\\\\')), resolver.resolve, reqset, )
def test_unhashed_deps_on_require_hashes(self, data): """Make sure unhashed, unpinned, or otherwise unrepeatable dependencies get complained about when --require-hashes is on.""" reqset = RequirementSet() finder = PackageFinder([data.find_links], [], session=PipSession()) resolver = self._basic_resolver(finder) reqset.add_requirement(next(process_line( 'TopoRequires2==0.0.1 ' # requires TopoRequires '--hash=sha256:eaf9a01242c9f2f42cf2bd82a6a848cd' 'e3591d14f7896bdbefcf48543720c970', 'file', 1))) assert_raises_regexp( HashErrors, r'In --require-hashes mode, all requirements must have their ' r'versions pinned.*\n' r' TopoRequires from .*$', resolver.resolve, reqset)
def test_environment_marker_extras(self, data): """ Test that the environment marker extras are used with non-wheel installs. """ reqset = RequirementSet() req = install_req_from_editable( data.packages.joinpath("LocalEnvironMarker")) req.user_supplied = True reqset.add_requirement(req) finder = make_test_finder(find_links=[data.find_links]) with self._basic_resolver(finder) as resolver: reqset = resolver.resolve(reqset.all_requirements, True) # This is hacky but does test both case in py2 and py3 if sys.version_info[:2] == (2, 7): assert reqset.has_requirement('simple') else: assert not reqset.has_requirement('simple')
def test_environment_marker_extras(self, data): """ Test that the environment marker extras are used with non-wheel installs. """ reqset = RequirementSet() req = InstallRequirement.from_editable( data.packages.join("LocalEnvironMarker")) req.is_direct = True reqset.add_requirement(req) finder = PackageFinder([data.find_links], [], session=PipSession()) resolver = self._basic_resolver(finder) resolver.resolve(reqset) # This is hacky but does test both case in py2 and py3 if sys.version_info[:2] in ((2, 7), (3, 4)): assert reqset.has_requirement('simple') else: assert not reqset.has_requirement('simple')
def test_unhashed_deps_on_require_hashes(self, data): """Make sure unhashed, unpinned, or otherwise unrepeatable dependencies get complained about when --require-hashes is on.""" reqset = RequirementSet() finder = PackageFinder([data.find_links], [], session=PipSession()) resolver = self._basic_resolver(finder) reqset.add_requirement( next( process_line( 'TopoRequires2==0.0.1 ' # requires TopoRequires '--hash=sha256:eaf9a01242c9f2f42cf2bd82a6a848cd' 'e3591d14f7896bdbefcf48543720c970', 'file', 1))) assert_raises_regexp( HashErrors, r'In --require-hashes mode, all requirements must have their ' r'versions pinned.*\n' r' TopoRequires from .*$', resolver.resolve, reqset)
def test_unsupported_hashes(self, data: TestData) -> None: """VCS and dir links should raise errors when --require-hashes is on. In addition, complaints about the type of requirement (VCS or dir) should trump the presence or absence of a hash. """ reqset = RequirementSet() reqset.add_requirement( get_processed_req_from_line( "git+git://github.com/pypa/pip-test-package --hash=sha256:123", lineno=1, ) ) dir_path = data.packages.joinpath("FSPkg") reqset.add_requirement( get_processed_req_from_line( f"file://{dir_path}", lineno=2, ) ) finder = make_test_finder(find_links=[data.find_links]) sep = os.path.sep if sep == "\\": sep = "\\\\" # This needs to be escaped for the regex with self._basic_resolver(finder, require_hashes=True) as resolver: with pytest.raises( HashErrors, match=( r"Can't verify hashes for these requirements because we don't " r"have a way to hash version control repositories:\n" r" git\+git://github\.com/pypa/pip-test-package \(from -r " r"file \(line 1\)\)\n" r"Can't verify hashes for these file:// requirements because " r"they point to directories:\n" r" file://.*{sep}data{sep}packages{sep}FSPkg " r"\(from -r file \(line 2\)\)".format(sep=sep) ), ): resolver.resolve(reqset.all_requirements, True)
def test_hash_mismatch(self, data): """A hash mismatch should raise an error.""" file_url = path_to_url( (data.packages / 'simple-1.0.tar.gz').abspath) reqset = RequirementSet(require_hashes=True) reqset.add_requirement(get_processed_req_from_line( '%s --hash=sha256:badbad' % file_url, lineno=1, )) finder = PackageFinder([data.find_links], [], session=PipSession()) resolver = self._basic_resolver(finder) assert_raises_regexp( HashErrors, r'THESE PACKAGES DO NOT MATCH THE HASHES.*\n' r' file:///.*/data/packages/simple-1\.0\.tar\.gz .*:\n' r' Expected sha256 badbad\n' r' Got 393043e672415891885c9a2a0929b1af95fb866d' r'6ca016b42d2e6ce53619b653$', resolver.resolve, reqset)
def resolve_reqs( self, download_dir: Optional[str], ireq: InstallRequirement, wheel_cache: WheelCache, ) -> Set[InstallationCandidate]: with get_requirement_tracker() as req_tracker, TempDirectory( kind="resolver") as temp_dir, indent_log(): preparer_kwargs = { "temp_build_dir": temp_dir, "options": self.options, "req_tracker": req_tracker, "session": self.session, "finder": self.finder, "use_user_site": False, "download_dir": download_dir, } preparer = self.command.make_requirement_preparer( **preparer_kwargs) reqset = RequirementSet() ireq.user_supplied = True reqset.add_requirement(ireq) resolver = self.command.make_resolver( preparer=preparer, finder=self.finder, options=self.options, wheel_cache=wheel_cache, use_user_site=False, ignore_installed=True, ignore_requires_python=False, force_reinstall=False, upgrade_strategy="to-satisfy-only", ) results = resolver._resolve_one(reqset, ireq) if not ireq.prepared: # If still not prepared, e.g. a constraint, do enough to assign # the ireq a name: resolver._get_dist_for(ireq) return set(results)
def resolve_reqs(self, download_dir, ireq, wheel_cache): with get_requirement_tracker() as req_tracker, TempDirectory( kind="resolver") as temp_dir, indent_log(): preparer = self.command.make_requirement_preparer( temp_build_dir=temp_dir, options=self.options, req_tracker=req_tracker, session=self.session, finder=self.finder, use_user_site=False, download_dir=download_dir, wheel_download_dir=self._wheel_download_dir, ) reqset = RequirementSet() if PIP_VERSION[:2] <= (20, 1): ireq.is_direct = True else: ireq.user_supplied = True reqset.add_requirement(ireq) resolver = self.command.make_resolver( preparer=preparer, finder=self.finder, options=self.options, wheel_cache=wheel_cache, use_user_site=False, ignore_installed=True, ignore_requires_python=False, force_reinstall=False, upgrade_strategy="to-satisfy-only", ) results = resolver._resolve_one(reqset, ireq) if not ireq.prepared: # If still not prepared, e.g. a constraint, do enough to assign # the ireq a name: resolver._get_abstract_dist_for(ireq) if PIP_VERSION[:2] <= (20, 0): reqset.cleanup_files() return set(results)
def test_missing_hash_with_require_hashes(self, data: TestData) -> None: """Setting --require-hashes explicitly should raise errors if hashes are missing. """ reqset = RequirementSet() reqset.add_requirement( get_processed_req_from_line("simple==1.0", lineno=1)) finder = make_test_finder(find_links=[data.find_links]) with self._basic_resolver(finder, require_hashes=True) as resolver: with pytest.raises( HashErrors, match= (r"Hashes are required in --require-hashes mode, but they are " r"missing .*\n" r" simple==1.0 --hash=sha256:393043e672415891885c9a2a0929b1" r"af95fb866d6ca016b42d2e6ce53619b653$"), ): resolver.resolve(reqset.all_requirements, True)
def test_hashed_deps_on_require_hashes(self, data): """Make sure hashed dependencies get installed when --require-hashes is on. (We actually just check that no "not all dependencies are hashed!" error gets raised while preparing; there is no reason to expect installation to then fail, as the code paths are the same as ever.) """ reqset = RequirementSet() reqset.add_requirement(next(process_line( 'TopoRequires2==0.0.1 ' # requires TopoRequires '--hash=sha256:eaf9a01242c9f2f42cf2bd82a6a848cd' 'e3591d14f7896bdbefcf48543720c970', 'file', 1))) reqset.add_requirement(next(process_line( 'TopoRequires==0.0.1 ' '--hash=sha256:d6dd1e22e60df512fdcf3640ced3039b3b02a56ab2cee81ebcb' '3d0a6d4e8bfa6', 'file', 2)))
def test_no_reuse_existing_build_dir(self, data): """Test prepare_files raise exception with previous build dir""" build_dir = os.path.join(self.tempdir, 'build', 'simple') os.makedirs(build_dir) with open(os.path.join(build_dir, "setup.py"), 'w'): pass reqset = RequirementSet() req = install_req_from_line('simple') req.is_direct = True reqset.add_requirement(req) finder = make_test_finder(find_links=[data.find_links]) with self._basic_resolver(finder) as resolver: assert_raises_regexp( PreviousBuildDirError, r"pip can't proceed with [\s\S]*%s[\s\S]*%s" % (req, build_dir.replace('\\', '\\\\')), resolver.resolve, reqset, )
def test_missing_hash_with_require_hashes(self, data): """Setting --require-hashes explicitly should raise errors if hashes are missing. """ reqset = RequirementSet(require_hashes=True) reqset.add_requirement(get_processed_req_from_line( 'simple==1.0', lineno=1 )) finder = PackageFinder([data.find_links], [], session=PipSession()) resolver = self._basic_resolver(finder) assert_raises_regexp( HashErrors, r'Hashes are required in --require-hashes mode, but they are ' r'missing .*\n' r' simple==1.0 --hash=sha256:393043e672415891885c9a2a0929b1af95' r'fb866d6ca016b42d2e6ce53619b653$', resolver.resolve, reqset )
def test_unhashed_deps_on_require_hashes(self, data): """Make sure unhashed, unpinned, or otherwise unrepeatable dependencies get complained about when --require-hashes is on.""" reqset = RequirementSet() finder = make_test_finder(find_links=[data.find_links]) reqset.add_requirement( get_processed_req_from_line( 'TopoRequires2==0.0.1 ' # requires TopoRequires '--hash=sha256:eaf9a01242c9f2f42cf2bd82a6a848cd' 'e3591d14f7896bdbefcf48543720c970', lineno=1)) with self._basic_resolver(finder, require_hashes=True) as resolver: assert_raises_regexp( HashErrors, r'In --require-hashes mode, all requirements must have their ' r'versions pinned.*\n' r' TopoRequires from .*$', resolver.resolve, reqset, )
def test_hash_mismatch(self, data): """A hash mismatch should raise an error.""" file_url = path_to_url( (data.packages / 'simple-1.0.tar.gz').resolve()) reqset = RequirementSet() reqset.add_requirement(get_processed_req_from_line( '{file_url} --hash=sha256:badbad'.format(**locals()), lineno=1, )) finder = make_test_finder(find_links=[data.find_links]) with self._basic_resolver(finder, require_hashes=True) as resolver: assert_raises_regexp( HashErrors, r'THESE PACKAGES DO NOT MATCH THE HASHES.*\n' r' file:///.*/data/packages/simple-1\.0\.tar\.gz .*:\n' r' Expected sha256 badbad\n' r' Got 393043e672415891885c9a2a0929b1af95fb' r'866d6ca016b42d2e6ce53619b653$', resolver.resolve, reqset.all_requirements, True, )
def test_hash_mismatch(self, data: TestData) -> None: """A hash mismatch should raise an error.""" file_url = path_to_url((data.packages / "simple-1.0.tar.gz").resolve()) reqset = RequirementSet() reqset.add_requirement( get_processed_req_from_line( f"{file_url} --hash=sha256:badbad", lineno=1, )) finder = make_test_finder(find_links=[data.find_links]) with self._basic_resolver(finder, require_hashes=True) as resolver: with pytest.raises( HashErrors, match= (r"THESE PACKAGES DO NOT MATCH THE HASHES.*\n" r" file:///.*/data/packages/simple-1\.0\.tar\.gz .*:\n" r" Expected sha256 badbad\n" r" Got 393043e672415891885c9a2a0929b1af95fb" r"866d6ca016b42d2e6ce53619b653$"), ): resolver.resolve(reqset.all_requirements, True)
def test_missing_hash_with_require_hashes(self, data): """Setting --require-hashes explicitly should raise errors if hashes are missing. """ reqset = RequirementSet() reqset.add_requirement( get_processed_req_from_line('simple==1.0', lineno=1)) finder = make_test_finder(find_links=[data.find_links]) with self._basic_resolver(finder, require_hashes=True) as resolver: assert_raises_regexp( HashErrors, r'Hashes are required in --require-hashes mode, but they are ' r'missing .*\n' r' simple==1.0 --hash=sha256:393043e672415891885c9a2a0929b1' r'af95fb866d6ca016b42d2e6ce53619b653$', resolver.resolve, reqset.all_requirements, True, )
def test_no_reuse_existing_build_dir(self, data: TestData) -> None: """Test prepare_files raise exception with previous build dir""" build_dir = os.path.join(self.tempdir, "build", "simple") os.makedirs(build_dir) with open(os.path.join(build_dir, "setup.py"), "w"): pass reqset = RequirementSet() req = install_req_from_line("simple") req.user_supplied = True reqset.add_requirement(req) finder = make_test_finder(find_links=[data.find_links]) with self._basic_resolver(finder) as resolver: with pytest.raises( PreviousBuildDirError, match= (r"pip can't proceed with [\s\S]*{req}[\s\S]*{build_dir_esc}". format(build_dir_esc=build_dir.replace("\\", "\\\\"), req=req)), ): resolver.resolve(reqset.all_requirements, True)
def test_unsupported_hashes(self, data): """VCS and dir links should raise errors when --require-hashes is on. In addition, complaints about the type of requirement (VCS or dir) should trump the presence or absence of a hash. """ reqset = RequirementSet(require_hashes=True) reqset.add_requirement( get_processed_req_from_line( 'git+git://github.com/pypa/pip-test-package --hash=sha256:123', lineno=1, )) dir_path = data.packages.join('FSPkg') reqset.add_requirement( get_processed_req_from_line( 'file://%s' % (dir_path, ), lineno=2, )) finder = PackageFinder.create( [data.find_links], [], session=PipSession(), ) resolver = self._basic_resolver(finder) sep = os.path.sep if sep == '\\': sep = '\\\\' # This needs to be escaped for the regex assert_raises_regexp( HashErrors, r"Can't verify hashes for these requirements because we don't " r"have a way to hash version control repositories:\n" r" git\+git://github\.com/pypa/pip-test-package \(from -r file " r"\(line 1\)\)\n" r"Can't verify hashes for these file:// requirements because they " r"point to directories:\n" r" file://.*{sep}data{sep}packages{sep}FSPkg " r"\(from -r file \(line 2\)\)".format(sep=sep), resolver.resolve, reqset)
def test_hashed_deps_on_require_hashes(self): """Make sure hashed dependencies get installed when --require-hashes is on. (We actually just check that no "not all dependencies are hashed!" error gets raised while preparing; there is no reason to expect installation to then fail, as the code paths are the same as ever.) """ reqset = RequirementSet() reqset.add_requirement( get_processed_req_from_line( 'TopoRequires2==0.0.1 ' # requires TopoRequires '--hash=sha256:eaf9a01242c9f2f42cf2bd82a6a848cd' 'e3591d14f7896bdbefcf48543720c970', lineno=1)) reqset.add_requirement( get_processed_req_from_line( 'TopoRequires==0.0.1 ' '--hash=sha256:d6dd1e22e60df512fdcf3640ced3039b3b02a56ab2cee81ebcb' '3d0a6d4e8bfa6', lineno=2))
def test_unhashed_deps_on_require_hashes(self, data: TestData) -> None: """Make sure unhashed, unpinned, or otherwise unrepeatable dependencies get complained about when --require-hashes is on.""" reqset = RequirementSet() finder = make_test_finder(find_links=[data.find_links]) reqset.add_requirement( get_processed_req_from_line( "TopoRequires2==0.0.1 " # requires TopoRequires "--hash=sha256:eaf9a01242c9f2f42cf2bd82a6a848cd" "e3591d14f7896bdbefcf48543720c970", lineno=1, )) with self._basic_resolver(finder, require_hashes=True) as resolver: with pytest.raises( HashErrors, match= (r"In --require-hashes mode, all requirements must have their " r"versions pinned.*\n" r" TopoRequires from .*$"), ): resolver.resolve(reqset.all_requirements, True)
def test_missing_hash_checking(self): """Make sure prepare_files() raises an error when a requirement has no hash in implicit hash-checking mode. """ reqset = RequirementSet() # No flags here. This tests that detection of later flags nonetheless # requires earlier packages to have hashes: reqset.add_requirement( get_processed_req_from_line('blessings==1.0', lineno=1)) # This flag activates --require-hashes mode: reqset.add_requirement( get_processed_req_from_line( 'tracefront==0.1 --hash=sha256:somehash', lineno=2, )) # This hash should be accepted because it came from the reqs file, not # from the internet: reqset.add_requirement( get_processed_req_from_line( 'https://files.pythonhosted.org/packages/source/m/more-itertools/' 'more-itertools-1.0.tar.gz#md5=b21850c3cfa7efbb70fd662ab5413bdd', lineno=3, )) # The error text should list this as a URL and not `peep==3.1.1`: reqset.add_requirement( get_processed_req_from_line( 'https://files.pythonhosted.org/' 'packages/source/p/peep/peep-3.1.1.tar.gz', lineno=4, )) finder = PackageFinder( [], ['https://pypi.org/simple/'], session=PipSession(), ) resolver = self._basic_resolver(finder) assert_raises_regexp( HashErrors, r'Hashes are required in --require-hashes mode, but they are ' r'missing .*\n' r' https://files\.pythonhosted\.org/packages/source/p/peep/peep' r'-3\.1\.1\.tar\.gz --hash=sha256:[0-9a-f]+\n' r' blessings==1.0 --hash=sha256:[0-9a-f]+\n' r'THESE PACKAGES DO NOT MATCH THE HASHES.*\n' r' tracefront==0.1 .*:\n' r' Expected sha256 somehash\n' r' Got [0-9a-f]+$', resolver.resolve, reqset)
def test_missing_hash_checking(self): """Make sure prepare_files() raises an error when a requirement has no hash in implicit hash-checking mode. """ reqset = RequirementSet() # No flags here. This tests that detection of later flags nonetheless # requires earlier packages to have hashes: reqset.add_requirement(get_processed_req_from_line( 'blessings==1.0', lineno=1 )) # This flag activates --require-hashes mode: reqset.add_requirement(get_processed_req_from_line( 'tracefront==0.1 --hash=sha256:somehash', lineno=2, )) # This hash should be accepted because it came from the reqs file, not # from the internet: reqset.add_requirement(get_processed_req_from_line( 'https://files.pythonhosted.org/packages/source/m/more-itertools/' 'more-itertools-1.0.tar.gz#md5=b21850c3cfa7efbb70fd662ab5413bdd', lineno=3, )) # The error text should list this as a URL and not `peep==3.1.1`: reqset.add_requirement(get_processed_req_from_line( 'https://files.pythonhosted.org/' 'packages/source/p/peep/peep-3.1.1.tar.gz', lineno=4, )) finder = PackageFinder( [], ['https://pypi.org/simple/'], session=PipSession(), ) resolver = self._basic_resolver(finder) assert_raises_regexp( HashErrors, r'Hashes are required in --require-hashes mode, but they are ' r'missing .*\n' r' https://files\.pythonhosted\.org/packages/source/p/peep/peep' r'-3\.1\.1\.tar\.gz --hash=sha256:[0-9a-f]+\n' r' blessings==1.0 --hash=sha256:[0-9a-f]+\n' r'THESE PACKAGES DO NOT MATCH THE HASHES.*\n' r' tracefront==0.1 .*:\n' r' Expected sha256 somehash\n' r' Got [0-9a-f]+$', resolver.resolve, reqset )
def test_exclusive_environment_markers(): """Make sure RequirementSet accepts several excluding env markers""" eq26 = InstallRequirement.from_line( "Django>=1.6.10,<1.7 ; python_version == '2.6'") ne26 = InstallRequirement.from_line( "Django>=1.6.10,<1.8 ; python_version != '2.6'") req_set = RequirementSet('', '', '') req_set.add_requirement(eq26) req_set.add_requirement(ne26) assert req_set.has_requirement('Django')