Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
poetry-core / tests / packages / test_dependency.py
Size: Mime:
from __future__ import annotations

import pytest

from packaging.utils import canonicalize_name

from poetry.core.constraints.version.exceptions import ParseConstraintError
from poetry.core.packages.dependency import Dependency
from poetry.core.version.markers import parse_marker


@pytest.mark.parametrize(
    "constraint,result",
    [
        ("^1.0", False),
        ("^1.0.dev0", True),
        ("^1.0.0", False),
        ("^1.0.0.dev0", True),
        ("^1.0.0.alpha0", True),
        ("^1.0.0.alpha0+local", True),
        ("^1.0.0.rc0+local", True),
        ("^1.0.0-1", False),
    ],
)
def test_allows_prerelease(constraint: str, result: bool) -> None:
    assert Dependency("A", constraint).allows_prereleases() == result


def test_to_pep_508() -> None:
    dependency = Dependency("Django", "^1.23")

    result = dependency.to_pep_508()
    assert result == "Django (>=1.23,<2.0)"

    dependency = Dependency("Django", "^1.23")
    dependency.python_versions = "~2.7 || ^3.6"

    result = dependency.to_pep_508()
    assert (
        result
        == "Django (>=1.23,<2.0); "
        'python_version >= "2.7" and python_version < "2.8" '
        'or python_version >= "3.6" and python_version < "4.0"'
    )


def test_to_pep_508_wilcard() -> None:
    dependency = Dependency("Django", "*")

    result = dependency.to_pep_508()
    assert result == "Django"


def test_to_pep_508_in_extras() -> None:
    dependency = Dependency("Django", "^1.23")
    dependency.in_extras.append(canonicalize_name("foo"))

    result = dependency.to_pep_508()
    assert result == 'Django (>=1.23,<2.0); extra == "foo"'

    result = dependency.to_pep_508(with_extras=False)
    assert result == "Django (>=1.23,<2.0)"

    dependency.in_extras.append(canonicalize_name("bar"))

    result = dependency.to_pep_508()
    assert result == 'Django (>=1.23,<2.0); extra == "foo" or extra == "bar"'

    dependency.python_versions = "~2.7 || ^3.6"

    result = dependency.to_pep_508()
    assert (
        result
        == "Django (>=1.23,<2.0); "
        "("
        'python_version >= "2.7" and python_version < "2.8" '
        'or python_version >= "3.6" and python_version < "4.0"'
        ") "
        'and (extra == "foo" or extra == "bar")'
    )

    result = dependency.to_pep_508(with_extras=False)
    assert (
        result
        == "Django (>=1.23,<2.0); "
        'python_version >= "2.7" and python_version < "2.8" '
        'or python_version >= "3.6" and python_version < "4.0"'
    )


def test_to_pep_508_in_extras_parsed() -> None:
    dependency = Dependency.create_from_pep_508(
        'foo[baz,bar] (>=1.23,<2.0) ; extra == "baz"'
    )

    result = dependency.to_pep_508()
    assert result == 'foo[bar,baz] (>=1.23,<2.0); extra == "baz"'

    result = dependency.to_pep_508(with_extras=False)
    assert result == "foo[bar,baz] (>=1.23,<2.0)"


@pytest.mark.parametrize(
    ("exclusion", "expected"),
    [
        ("!=1.2.3", "!=1.2.3"),
        ("!=1.2.*", "!=1.2.*"),
        ("<2.0 || >=2.1", "!=2.0.*"),
    ],
)
def test_to_pep_508_with_excluded_versions(exclusion: str, expected: str) -> None:
    dependency = Dependency("foo", exclusion)

    assert dependency.to_pep_508() == f"foo ({expected})"


@pytest.mark.parametrize(
    "python_versions, marker",
    [
        (">=3.5,<3.5.4", 'python_version >= "3.5" and python_full_version < "3.5.4"'),
        (">=3.5.4,<3.6", 'python_full_version >= "3.5.4" and python_version < "3.6"'),
        ("<3.5.4", 'python_full_version < "3.5.4"'),
        (">=3.5.4", 'python_full_version >= "3.5.4"'),
        ("== 3.5.4", 'python_full_version == "3.5.4"'),
    ],
)
def test_to_pep_508_with_patch_python_version(
    python_versions: str, marker: str
) -> None:
    dependency = Dependency("Django", "^1.23")
    dependency.python_versions = python_versions

    expected = f"Django (>=1.23,<2.0); {marker}"

    assert dependency.to_pep_508() == expected
    assert str(dependency.marker) == marker


def test_to_pep_508_tilde() -> None:
    dependency = Dependency("foo", "~1.2.3")

    assert dependency.to_pep_508() == "foo (>=1.2.3,<1.3.0)"

    dependency = Dependency("foo", "~1.2")

    assert dependency.to_pep_508() == "foo (>=1.2,<1.3)"

    dependency = Dependency("foo", "~0.2.3")

    assert dependency.to_pep_508() == "foo (>=0.2.3,<0.3.0)"

    dependency = Dependency("foo", "~0.2")

    assert dependency.to_pep_508() == "foo (>=0.2,<0.3)"


def test_to_pep_508_caret() -> None:
    dependency = Dependency("foo", "^1.2.3")

    assert dependency.to_pep_508() == "foo (>=1.2.3,<2.0.0)"

    dependency = Dependency("foo", "^1.2")

    assert dependency.to_pep_508() == "foo (>=1.2,<2.0)"

    dependency = Dependency("foo", "^0.2.3")

    assert dependency.to_pep_508() == "foo (>=0.2.3,<0.3.0)"

    dependency = Dependency("foo", "^0.2")

    assert dependency.to_pep_508() == "foo (>=0.2,<0.3)"


def test_to_pep_508_combination() -> None:
    dependency = Dependency("foo", "^1.2,!=1.3.5")

    assert dependency.to_pep_508() == "foo (>=1.2,<2.0,!=1.3.5)"

    dependency = Dependency("foo", "~1.2,!=1.2.5")

    assert dependency.to_pep_508() == "foo (>=1.2,<1.3,!=1.2.5)"


def test_complete_name() -> None:
    assert Dependency("foo", ">=1.2.3").complete_name == "foo"
    assert (
        Dependency("foo", ">=1.2.3", extras=["baz", "bar"]).complete_name
        == "foo[bar,baz]"
    )


@pytest.mark.parametrize(
    "name,constraint,extras,expected",
    [
        ("A", ">2.7,<3.0", None, "A (>2.7,<3.0)"),
        ("A", ">2.7,<3.0", ["x"], "A[x] (>2.7,<3.0)"),
        ("A", ">=1.6.5,<1.8.0 || >1.8.0,<3.1.0", None, "A (>=1.6.5,!=1.8.0,<3.1.0)"),
        (
            "A",
            ">=1.6.5,<1.8.0 || >1.8.0,<3.1.0",
            ["x"],
            "A[x] (>=1.6.5,!=1.8.0,<3.1.0)",
        ),
        # test single version range exclusions
        ("A", ">=1.8,!=2.0.*", None, "A (>=1.8,!=2.0.*)"),
        ("A", "!=0.0.*", None, "A (!=0.0.*)"),
        ("A", "!=0.1.*", None, "A (!=0.1.*)"),
        ("A", "!=0.*", None, "A (>=1.0.0)"),
        ("A", ">=1.8,!=2.*", None, "A (>=1.8,!=2.*)"),
        ("A", ">=1.8,!=2.*.*", None, "A (>=1.8,!=2.*)"),
        ("A", ">=1.8,<2.0 || >=2.1.0", None, "A (>=1.8,!=2.0.*)"),
        ("A", ">=1.8,<2.0.0 || >=3.0.0", None, "A (>=1.8,!=2.*)"),
        ("A", ">=1.8,<2.0 || >=3", None, "A (>=1.8,!=2.*)"),
        ("A", ">=1.8,<2 || >=2.1.0", None, "A (>=1.8,!=2.0.*)"),
        ("A", ">=1.8,<2 || >=2.1", None, "A (>=1.8,!=2.0.*)"),
        ("A", ">=1.8,!=2.0.*,!=3.0.*", None, "A (>=1.8,!=2.0.*,!=3.0.*)"),
        ("A", ">=1.8.0.0,<2.0.0.0 || >=2.0.1.0", None, "A (>=1.8.0.0,!=2.0.0.*)"),
        ("A", ">=1.8.0.0,<2 || >=2.0.1.0", None, "A (>=1.8.0.0,!=2.0.0.*)"),
        # we verify that the range exclusion logic is not too eager
        ("A", ">=1.8,<2.0 || >=2.2.0", None, "A (>=1.8,<2.0 || >=2.2.0)"),
        ("A", ">=1.8,<2.0 || >=2.1.5", None, "A (>=1.8,<2.0 || >=2.1.5)"),
        ("A", ">=1.8.0.0,<2 || >=2.0.1.5", None, "A (>=1.8.0.0,<2 || >=2.0.1.5)"),
        # non-semver version test is ignored due to existing bug in wildcard
        # constraint parsing that ignores non-semver versions
        # TODO: re-enable for verification once fixed
        # ("A", ">=1.8.0.0,!=2.0.0.*", None, "A (>=1.8.0.0,!=2.0.0.*)"),  # noqa: E800
    ],
)
def test_dependency_string_representation(
    name: str, constraint: str, extras: list[str] | None, expected: str
) -> None:
    dependency = Dependency(name=name, constraint=constraint, extras=extras)
    assert str(dependency) == expected


def test_set_constraint_sets_pretty_constraint() -> None:
    dependency = Dependency("A", "^1.0")
    assert dependency.pretty_constraint == "^1.0"
    dependency.constraint = "^2.0"  # type: ignore[assignment]
    assert dependency.pretty_constraint == "^2.0"


def test_set_bogus_constraint_raises_exception() -> None:
    dependency = Dependency("A", "^1.0")
    with pytest.raises(ParseConstraintError):
        dependency.constraint = "^=4.5"  # type: ignore[assignment]


def test_with_constraint() -> None:
    dependency = Dependency(
        "foo",
        "^1.2.3",
        optional=True,
        groups=["dev"],
        allows_prereleases=True,
        extras=["bar", "baz"],
    )
    dependency.marker = parse_marker(
        'python_version >= "3.6" and python_version < "4.0"'
    )
    dependency.transitive_marker = parse_marker(
        'python_version >= "3.7" and python_version < "4.0"'
    )
    dependency.python_versions = "^3.6"
    dependency.transitive_python_versions = "^3.7"

    new = dependency.with_constraint("^1.2.6")

    assert new.name == dependency.name
    assert str(new.constraint) == ">=1.2.6,<2.0.0"
    assert new.is_optional()
    assert new.groups == frozenset(["dev"])
    assert new.allows_prereleases()
    assert set(new.extras) == {"bar", "baz"}
    assert new.marker == dependency.marker
    assert new.transitive_marker == dependency.transitive_marker
    assert new.python_constraint == dependency.python_constraint
    assert new.transitive_python_constraint == dependency.transitive_python_constraint


@pytest.mark.parametrize(
    "marker, expected",
    [
        ('python_version >= "3.6" and python_version < "4.0"', ">=3.6,<4.0"),
        ('sys_platform == "linux"', "*"),
        ('python_version >= "3.9" or sys_platform == "linux"', "*"),
        ('python_version >= "3.9" and sys_platform == "linux"', ">=3.9"),
    ],
)
def test_marker_properly_sets_python_constraint(marker: str, expected: str) -> None:
    dependency = Dependency("foo", "^1.2.3")
    dependency.marker = marker  # type: ignore[assignment]
    assert str(dependency.python_constraint) == expected


def test_dependency_markers_are_the_same_as_markers() -> None:
    dependency = Dependency.create_from_pep_508('foo ; extra=="bar"')
    marker = parse_marker('extra=="bar"')

    assert dependency.marker == marker


def test_marker_properly_unsets_python_constraint() -> None:
    dependency = Dependency("foo", "^1.2.3")

    dependency.marker = 'python_version >= "3.6"'  # type: ignore[assignment]
    assert str(dependency.python_constraint) == ">=3.6"

    dependency.marker = "*"  # type: ignore[assignment]
    assert str(dependency.python_constraint) == "*"


def test_create_from_pep_508_url_with_activated_extras() -> None:
    dependency = Dependency.create_from_pep_508("name [fred,bar] @ http://foo.com")
    assert dependency.extras == {"fred", "bar"}


@pytest.mark.parametrize(
    "dependency1, dependency2, expected",
    [
        (Dependency("a", "1.0"), Dependency("a", "1.0"), True),
        (Dependency("a", "1.0"), Dependency("a", "1.0.1"), False),
        (Dependency("a", "1.0"), Dependency("a1", "1.0"), False),
        (Dependency("a", "1.0"), Dependency("a", "1.0", source_type="file"), False),
        # constraint is implicitly given for direct origin dependencies,
        # but might not be set
        (
            Dependency("a", "1.0", source_type="file"),
            Dependency("a", "*", source_type="file"),
            True,
        ),
        # constraint is not implicit for non direct origin dependencies
        (Dependency("a", "1.0"), Dependency("a", "*"), False),
        (
            Dependency("a", "1.0", source_type="legacy"),
            Dependency("a", "*", source_type="legacy"),
            False,
        ),
    ],
)
def test_eq(dependency1: Dependency, dependency2: Dependency, expected: bool) -> None:
    assert (dependency1 == dependency2) is expected
    assert (dependency2 == dependency1) is expected


@pytest.mark.parametrize(
    "attr_name, value",
    [
        ("constraint", "2.0"),
        ("python_versions", "<3.8"),
        ("transitive_python_versions", "<3.8"),
        ("marker", "sys_platform == 'linux'"),
        ("transitive_marker", "sys_platform == 'linux'"),
    ],
)
def test_mutable_attributes_not_in_hash(attr_name: str, value: str) -> None:
    dependency = Dependency("foo", "^1.2.3")
    ref_hash = hash(dependency)

    ref_value = getattr(dependency, attr_name)
    setattr(dependency, attr_name, value)
    assert value != ref_value
    assert hash(dependency) == ref_hash