def test_remove_keeps_dir_if_not_deleteable(tmp_dir, manager, poetry, config, mocker): # Ensure we empty rather than delete folder if its is an active mount point. # See https://github.com/python-poetry/poetry/pull/2064 config.merge({"virtualenvs": {"path": str(tmp_dir)}}) venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) venv_path = Path(tmp_dir) / "{}-py3.6".format(venv_name) venv_path.mkdir() folder1_path = venv_path / "folder1" folder1_path.mkdir() file1_path = folder1_path / "file1" file1_path.touch(exist_ok=False) file2_path = venv_path / "file2" file2_path.touch(exist_ok=False) mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) original_rmtree = shutil.rmtree def err_on_rm_venv_only(path, *args, **kwargs): if path == str(venv_path): raise OSError(16, "Test error") # ERRNO 16: Device or resource busy else: original_rmtree(path) m = mocker.patch("shutil.rmtree", side_effect=err_on_rm_venv_only) venv = manager.remove("{}-py3.6".format(venv_name)) m.assert_any_call(str(venv_path)) assert venv_path == venv.path assert venv_path.exists() assert not folder1_path.exists() assert not file1_path.exists() assert not file2_path.exists() m.side_effect = original_rmtree # Avoid teardown using `err_on_rm_venv_only`
def test_activate_does_not_recreate_when_switching_minor( tmp_dir: str, manager: EnvManager, poetry: "Poetry", config: "Config", mocker: "MockerFixture", ): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": "3.7", "patch": "3.7.0"} envs_file.write(doc) os.mkdir(os.path.join(tmp_dir, f"{venv_name}-py3.7")) os.mkdir(os.path.join(tmp_dir, f"{venv_name}-py3.6")) config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) mocker.patch( "subprocess.Popen.communicate", side_effect=[("/prefix", None), ("/prefix", None), ("/prefix", None)], ) build_venv_m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv) remove_venv_m = mocker.patch("poetry.utils.env.EnvManager.remove_venv", side_effect=EnvManager.remove_venv) env = manager.activate("python3.6", NullIO()) build_venv_m.assert_not_called() remove_venv_m.assert_not_called() assert envs_file.exists() envs = envs_file.read() assert envs[venv_name]["minor"] == "3.6" assert envs[venv_name]["patch"] == "3.6.6" assert env.path == Path(tmp_dir) / f"{venv_name}-py3.6" assert env.base == Path("/prefix") assert (Path(tmp_dir) / f"{venv_name}-py3.6").exists()
def test_difference() -> None: v = Version.parse("1.2.3") assert v.difference(v).is_empty() assert v.difference(Version.parse("0.8.0")) == v assert v.difference( VersionRange(Version.parse("1.1.4"), Version.parse("1.2.4"))).is_empty() assert (v.difference( VersionRange(Version.parse("1.4.0"), Version.parse("3.0.0"))) == v)
def all_classifiers(self) -> List[str]: from poetry.core.semver.version import Version # noqa classifiers = copy.copy(self.classifiers) # Automatically set python classifiers if self.python_versions == "*": python_constraint = parse_constraint("~2.7 || ^3.4") else: python_constraint = self.python_constraint python_classifier_prefix = "Programming Language :: Python" python_classifiers = [] # we sort python versions by sorting an int tuple of (major, minor) version # to ensure we sort 3.10 after 3.9 for version in sorted(self.AVAILABLE_PYTHONS, key=lambda x: tuple(map(int, x.split(".")))): if len(version) == 1: constraint = parse_constraint(version + ".*") else: constraint = Version.parse(version) if python_constraint.allows_any(constraint): classifier = "{} :: {}".format(python_classifier_prefix, version) if classifier not in python_classifiers: python_classifiers.append(classifier) # Automatically set license classifiers if self.license: classifiers.append(self.license.classifier) # Sort classifiers and insert python classifiers at the right location. We do # it like this so that 3.10 is sorted after 3.9. sorted_classifiers = [] python_classifiers_inserted = False for classifier in sorted(set(classifiers)): if (not python_classifiers_inserted and classifier > python_classifier_prefix): sorted_classifiers.extend(python_classifiers) python_classifiers_inserted = True sorted_classifiers.append(classifier) if not python_classifiers_inserted: sorted_classifiers.extend(python_classifiers) return sorted_classifiers
def read_setup_cfg( self, filepath: Union[str, Path] ) -> Dict[str, Union[List, Dict]]: parser = ConfigParser() parser.read(str(filepath)) name = None version = None if parser.has_option("metadata", "name"): name = parser.get("metadata", "name") if parser.has_option("metadata", "version"): version = Version.parse(parser.get("metadata", "version")).text install_requires = [] extras_require = {} python_requires = None if parser.has_section("options"): if parser.has_option("options", "install_requires"): for dep in parser.get("options", "install_requires").split("\n"): dep = dep.strip() if not dep: continue install_requires.append(dep) if parser.has_option("options", "python_requires"): python_requires = parser.get("options", "python_requires") if parser.has_section("options.extras_require"): for group in parser.options("options.extras_require"): extras_require[group] = [] deps = parser.get("options.extras_require", group) for dep in deps.split("\n"): dep = dep.strip() if not dep: continue extras_require[group].append(dep) return { "name": name, "version": version, "install_requires": install_requires, "extras_require": extras_require, "python_requires": python_requires, }
def test_activate_activates_different_virtualenv_with_envs_file( tmp_dir: str, manager: EnvManager, poetry: "Poetry", config: "Config", mocker: "MockerFixture", ): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": "3.7", "patch": "3.7.1"} envs_file.write(doc) os.mkdir(os.path.join(tmp_dir, f"{venv_name}-py3.7")) config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) mocker.patch( "subprocess.Popen.communicate", side_effect=[("/prefix", None), ("/prefix", None), ("/prefix", None)], ) m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv) env = manager.activate("python3.6", NullIO()) m.assert_called_with( Path(tmp_dir) / f"{venv_name}-py3.6", executable="/usr/bin/python3.6", flags={"always-copy": False, "system-site-packages": False}, with_pip=True, with_setuptools=True, with_wheel=True, ) assert envs_file.exists() envs = envs_file.read() assert envs[venv_name]["minor"] == "3.6" assert envs[venv_name]["patch"] == "3.6.6" assert env.path == Path(tmp_dir) / f"{venv_name}-py3.6" assert env.base == Path("/prefix")
def test_remove_by_name(tmp_dir, manager, poetry, config, mocker): config.merge({"virtualenvs": {"path": str(tmp_dir)}}) venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) (Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir() mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) venv = manager.remove("{}-py3.6".format(venv_name)) assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)) == venv.path assert not (Path(tmp_dir) / "{}-py3.6".format(venv_name)).exists()
def _get_release_info(self, name: str, version: str) -> dict: page = self._get("/{}/".format( canonicalize_name(name).replace(".", "-"))) if page is None: raise PackageNotFound('No package named "{}"'.format(name)) data = PackageInfo( name=name, version=version, summary="", platform=None, requires_dist=[], requires_python=None, files=[], cache_version=str(self.CACHE_VERSION), ) links = list(page.links_for_version(Version.parse(version))) if not links: raise PackageNotFound( 'No valid distribution links found for package: "{}" version: "{}"' .format(name, version)) urls = defaultdict(list) files = [] for link in links: if link.is_wheel: urls["bdist_wheel"].append(link.url) elif link.filename.endswith( (".tar.gz", ".zip", ".bz2", ".xz", ".Z", ".tar")): urls["sdist"].append(link.url) h = link.hash if h: h = link.hash_name + ":" + link.hash files.append({"file": link.filename, "hash": h}) data.files = files info = self._get_info_from_urls(urls) data.summary = info.summary data.requires_dist = info.requires_dist data.requires_python = info.requires_python return data.asdict()
def _transform_version(self, version: str, pretty_version: str) -> str: try: parsed = Version.parse(version) parts = [parsed.major, parsed.minor, parsed.patch] except ValueError: return pretty_version parts = parts[:parsed.precision] # check to see if we have a semver-looking version if len(parts) < 3: version = pretty_version else: version = ".".join(str(p) for p in parts) if parsed.is_unstable(): version += "-{}".format(parsed.pre.to_string()) return "^{}".format(version)
def link_version(self, link: Link) -> Optional[Version]: m = wheel_file_re.match(link.filename) if m: version = m.group("ver") else: info, ext = link.splitext() match = self.VERSION_REGEX.match(info) if not match: return None version = match.group(2) try: version = Version.parse(version) except ValueError: return None return version
def test_intersect(): v = Version.parse("1.2.3") assert v.intersect(v) == v assert v.intersect(Version.parse("1.1.4")).is_empty() assert ( v.intersect(VersionRange(Version.parse("1.1.4"), Version.parse("1.2.4"))) == v ) assert ( Version.parse("1.1.4") .intersect(VersionRange(v, Version.parse("1.2.4"))) .is_empty() )
def test_deactivate_activated( tmp_dir: str, manager: EnvManager, poetry: Poetry, config: Config, mocker: MockerFixture, ): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) version = Version.from_parts(*sys.version_info[:3]) other_version = Version.parse( "3.4") if version.major == 2 else version.next_minor() (Path(tmp_dir) / f"{venv_name}-py{version.major}.{version.minor}").mkdir() (Path(tmp_dir) / f"{venv_name}-py{other_version.major}.{other_version.minor}").mkdir() envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") doc = tomlkit.document() doc[venv_name] = { "minor": f"{other_version.major}.{other_version.minor}", "patch": other_version.text, } envs_file.write(doc) config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(), ) manager.deactivate(NullIO()) env = manager.get() assert env.path == Path( tmp_dir) / f"{venv_name}-py{version.major}.{version.minor}" assert Path("/prefix") envs = envs_file.read() assert len(envs) == 0
def test_remove_by_python_version( mocker: MockerFixture, tester: CommandTester, venvs_in_cache_dirs: list[str], venv_name: str, venv_cache: Path, ): check_output = mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) tester.execute("3.6") assert check_output.called assert not (venv_cache / f"{venv_name}-py3.6").exists() expected = f"Deleted virtualenv: {venv_cache / venv_name}-py3.6\n" assert tester.io.fetch_output() == expected
def _get_release_info(self, name: str, version: str) -> dict[str, Any]: page = self._get_page(f"/{canonicalize_name(name).replace('.', '-')}/") if page is None: raise PackageNotFound(f'No package named "{name}"') links = list(page.links_for_version(name, Version.parse(version))) return self._links_to_data( links, PackageInfo( name=name, version=version, summary="", platform=None, requires_dist=[], requires_python=None, files=[], cache_version=str(self.CACHE_VERSION), ), )
def test_locker_should_neither_emit_warnings_nor_raise_error_for_lower_compatible_versions( locker, caplog): current_version = Version.parse(Locker._VERSION) older_version = ".".join( [str(current_version.major), str(current_version.minor - 1)]) content = """\ [metadata] content-hash = "c3d07fca33fba542ef2b2a4d75bf5b48d892d21a830e2ad9c952ba5123a52f77" lock-version = "{version}" python-versions = "~2.7 || ^3.4" [metadata.files] """.format(version=older_version) caplog.set_level(logging.WARNING, logger="poetry.packages.locker") locker.lock.write(tomlkit.parse(content)) _ = locker.lock_data assert 0 == len(caplog.records)
def test_create_venv_uses_patch_version_to_detect_compatibility( manager: EnvManager, poetry: Poetry, config: Config, mocker: MockerFixture, config_virtualenvs_path: Path, ): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] version = Version.from_parts(*sys.version_info[:3]) poetry.package.python_versions = "^" + ".".join( str(c) for c in sys.version_info[:3]) venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) mocker.patch("sys.version_info", (version.major, version.minor, version.patch + 1)) check_output = mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.9")), ) m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "") manager.create_venv(NullIO()) assert not check_output.called m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py{version.major}.{version.minor}", executable=None, flags={ "always-copy": False, "system-site-packages": False, "no-pip": False, "no-setuptools": False, }, prompt=f"simple-project-py{version.major}.{version.minor}", )
def test_remove_by_name( tmp_dir: str, manager: EnvManager, poetry: "Poetry", config: "Config", mocker: "MockerFixture", ): config.merge({"virtualenvs": {"path": str(tmp_dir)}}) venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) (Path(tmp_dir) / f"{venv_name}-py3.7").mkdir() (Path(tmp_dir) / f"{venv_name}-py3.6").mkdir() mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) venv = manager.remove(f"{venv_name}-py3.6") assert (Path(tmp_dir) / f"{venv_name}-py3.6") == venv.path assert not (Path(tmp_dir) / f"{venv_name}-py3.6").exists()
def __init__(self, version_info=(3, 7, 0), python_implementation="CPython", platform="darwin", os_name="posix", is_venv=False, pip_version="19.1", sys_path=None, marker_env=None, supported_tags=None, **kwargs): super(MockEnv, self).__init__(**kwargs) self._version_info = version_info self._python_implementation = python_implementation self._platform = platform self._os_name = os_name self._is_venv = is_venv self._pip_version = Version.parse(pip_version) self._sys_path = sys_path self._mock_marker_env = marker_env self._supported_tags = supported_tags
def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable( manager: EnvManager, poetry: "Poetry", config: "Config", mocker: "MockerFixture", config_virtualenvs_path: Path, ): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] version = Version.from_parts(*sys.version_info[:3]) poetry.package.python_versions = f"~{version.major}.{version.minor-1}.0" venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) check_output = mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper( Version.parse(f"{version.major}.{version.minor - 1}.0")), ) m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "") manager.create_venv( NullIO(), executable=f"python{version.major}.{version.minor - 1}") assert check_output.called m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py{version.major}.{version.minor - 1}", executable=f"python{version.major}.{version.minor - 1}", flags={ "always-copy": False, "system-site-packages": False }, with_pip=True, with_setuptools=True, with_wheel=True, )
def test_self_update_can_update_from_recommended_installation( tester: CommandTester, repo: TestRepository, installed: TestRepository, ): new_version = Version.parse(__version__).next_minor().text old_poetry = Package("poetry", __version__) old_poetry.add_dependency(Factory.create_dependency("cleo", "^0.8.2")) new_poetry = Package("poetry", new_version) new_poetry.add_dependency(Factory.create_dependency("cleo", "^1.0.0")) installed.add_package(old_poetry) installed.add_package(Package("cleo", "0.8.2")) repo.add_package(new_poetry) repo.add_package(Package("cleo", "1.0.0")) tester.execute() expected_output = f"""\ Updating Poetry version ... Using version ^{new_version} for poetry Updating dependencies Resolving dependencies... Writing lock file Package operations: 0 installs, 2 updates, 0 removals • Updating cleo (0.8.2 -> 1.0.0) • Updating poetry ({__version__} -> {new_version}) """ assert tester.io.fetch_output() == expected_output
def test_add_constraint_with_source_old_installer( app: "PoetryTestApplication", poetry: "Poetry", installer: "NoopInstaller", old_tester: "CommandTester", ): repo = LegacyRepository(name="my-index", url="https://my-index.fake") repo.add_package(get_package("cachy", "0.2.0")) repo._cache.store("matches").put("cachy:0.2.0", [Version.parse("0.2.0")], 5) poetry.pool.add_repository(repo) old_tester.execute("cachy=0.2.0 --source my-index") expected = """\ Updating dependencies Resolving dependencies... Writing lock file Package operations: 1 install, 0 updates, 0 removals - Installing cachy (0.2.0) """ assert expected == old_tester.io.fetch_output() assert len(installer.installs) == 1 content = app.poetry.file.read()["tool"]["poetry"] assert "cachy" in content["dependencies"] assert content["dependencies"]["cachy"] == { "version": "0.2.0", "source": "my-index", }
def test_create_venv_fails_if_current_python_version_is_not_supported( manager: EnvManager, poetry: Poetry): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] manager.create_venv(NullIO()) current_version = Version.parse(".".join( str(c) for c in sys.version_info[:3])) next_version = ".".join( str(c) for c in (current_version.major, current_version.minor + 1, 0)) package_version = "~" + next_version poetry.package.python_versions = package_version with pytest.raises(InvalidCurrentPythonVersionError) as e: manager.create_venv(NullIO()) expected_message = ( f"Current Python version ({current_version}) is not allowed by the project" f' ({package_version}).\nPlease change python executable via the "env use"' " command.") assert expected_message == str(e.value)
def __init__( self, version_info: Tuple[int, int, int] = (3, 7, 0), python_implementation: str = "CPython", platform: str = "darwin", os_name: str = "posix", is_venv: bool = False, pip_version: str = "19.1", sys_path: Optional[List[str]] = None, marker_env: Dict[str, Any] = None, supported_tags: List[Tag] = None, **kwargs: Any, ): super(MockEnv, self).__init__(**kwargs) self._version_info = version_info self._python_implementation = python_implementation self._platform = platform self._os_name = os_name self._is_venv = is_venv self._pip_version = Version.parse(pip_version) self._sys_path = sys_path self._mock_marker_env = marker_env self._supported_tags = supported_tags
def __init__( self, version_info=(3, 7, 0), # type: Tuple[int, int, int] python_implementation="CPython", # type: str platform="darwin", # type: str os_name="posix", # type: str is_venv=False, # type: bool pip_version="19.1", # type: str sys_path=None, # type: Optional[List[str]] marker_env=None, # type: Dict[str, Any] supported_tags=None, # type: List[Tag] **kwargs, # type: Any ): super(MockEnv, self).__init__(**kwargs) self._version_info = version_info self._python_implementation = python_implementation self._platform = platform self._os_name = os_name self._is_venv = is_venv self._pip_version = Version.parse(pip_version) self._sys_path = sys_path self._mock_marker_env = marker_env self._supported_tags = supported_tags
def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ones_first( manager, poetry, config, mocker, config_virtualenvs_path): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] poetry.package.python_versions = "^3.6" venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) mocker.patch("sys.version_info", (2, 7, 16)) mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.7.5")), ) m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: "") manager.create_venv(NullIO()) m.assert_called_with( config_virtualenvs_path / "{}-py3.7".format(venv_name), executable="python3", flags={"always-copy": False}, )
def test_remove_also_deactivates(tmp_dir, manager, poetry, config, mocker): config.merge({"virtualenvs": {"path": str(tmp_dir)}}) venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) (Path(tmp_dir) / "{}-py3.7".format(venv_name)).mkdir() (Path(tmp_dir) / "{}-py3.6".format(venv_name)).mkdir() mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": "3.6", "patch": "3.6.6"} envs_file.write(doc) venv = manager.remove("python3.6") assert (Path(tmp_dir) / "{}-py3.6".format(venv_name)) == venv.path assert not (Path(tmp_dir) / "{}-py3.6".format(venv_name)).exists() envs = envs_file.read() assert venv_name not in envs
def test_remove_by_python_version( tmp_dir: str, manager: EnvManager, poetry: Poetry, config: Config, mocker: MockerFixture, ): config.merge({"virtualenvs": {"path": str(tmp_dir)}}) venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) (Path(tmp_dir) / f"{venv_name}-py3.7").mkdir() (Path(tmp_dir) / f"{venv_name}-py3.6").mkdir() mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) venv = manager.remove("3.6") expected_venv_path = Path(tmp_dir) / f"{venv_name}-py3.6" assert venv.path == expected_venv_path assert not expected_venv_path.exists()
from __future__ import annotations from pathlib import Path from typing import TYPE_CHECKING from typing import Any from typing import Callable from poetry.core.semver.version import Version if TYPE_CHECKING: from poetry.core.version.pep440.version import PEP440Version VERSION_3_7_1 = Version.parse("3.7.1") def build_venv(path: Path | str, **_: Any) -> None: Path(path).mkdir(parents=True, exist_ok=True) def check_output_wrapper( version: PEP440Version = VERSION_3_7_1, ) -> Callable[[str, Any, Any], str]: def check_output(cmd: str, *_: Any, **__: Any) -> str: if "sys.version_info[:3]" in cmd: return version.text elif "sys.version_info[:2]" in cmd: return f"{version.major}.{version.minor}" elif '-c "import sys; print(sys.executable)"' in cmd: return f"/usr/bin/{cmd.split()[0]}" else:
def get_python_constraint_from_marker( marker: BaseMarker, ) -> VersionConstraint: from poetry.core.semver.empty_constraint import EmptyConstraint from poetry.core.semver.helpers import parse_constraint from poetry.core.semver.version import Version from poetry.core.semver.version_range import VersionRange python_marker = marker.only("python_version", "python_full_version") if python_marker.is_any(): return VersionRange() if python_marker.is_empty(): return EmptyConstraint() markers = convert_markers(marker) if contains_group_without_marker(markers, "python_version"): # groups are in disjunctive normal form (DNF), # an empty group means that python_version does not appear in this group, # which means that python_version is arbitrary for this group return VersionRange() ors = [] for or_ in markers["python_version"]: ands = [] for op, version in or_: # Expand python version if op == "==": if "*" not in version: version = "~" + version op = "" elif op == "!=": if "*" not in version: version += ".*" elif op in ("<=", ">"): parsed_version = Version.parse(version) if parsed_version.precision == 1: if op == "<=": op = "<" version = parsed_version.next_major().text elif op == ">": op = ">=" version = parsed_version.next_major().text elif parsed_version.precision == 2: if op == "<=": op = "<" version = parsed_version.next_minor().text elif op == ">": op = ">=" version = parsed_version.next_minor().text elif op in ("in", "not in"): versions = [] for v in re.split("[ ,]+", version): split = v.split(".") if len(split) in [1, 2]: split.append("*") op_ = "" if op == "in" else "!=" else: op_ = "==" if op == "in" else "!=" versions.append(op_ + ".".join(split)) glue = " || " if op == "in" else ", " if versions: ands.append(glue.join(versions)) continue ands.append(f"{op}{version}") ors.append(" ".join(ands)) return parse_constraint(" || ".join(ors))
def test_links_for_version( version_string: str, filenames: Iterable[str], link_source: LinkSource ) -> None: version = Version.parse(version_string) expected = {Link(f"{link_source.url}/{name}") for name in filenames} assert set(link_source.links_for_version("demo", version)) == expected