def test_marker_str_conversion_skips_empty_and_any():
    union = MarkerUnion(
        parse_marker("<empty>"),
        parse_marker(
            'sys_platform == "darwin" or python_version <= "3.6" or os_name == "Windows"'
        ),
        parse_marker(""),
    )

    assert str(union) == (
        'sys_platform == "darwin" or python_version <= "3.6" or os_name == "Windows"'
    )
Exemple #2
0
    def complete_package(
            self, package):  # type: (DependencyPackage) -> DependencyPackage
        if package.is_root():
            package = package.clone()

        if not package.is_root() and package.source_type not in {
                "directory",
                "file",
                "url",
                "git",
        }:
            package = DependencyPackage(
                package.dependency,
                self._pool.package(
                    package.name,
                    package.version.text,
                    extras=package.requires_extras,
                    repository=package.dependency.source_name,
                ),
            )

        dependencies = [
            r for r in package.requires
            if self._package.python_constraint.allows_any(r.python_constraint)
        ]

        # Searching for duplicate dependencies
        #
        # If the duplicate dependencies have the same constraint,
        # the requirements will be merged.
        #
        # For instance:
        #   - enum34; python_version=="2.7"
        #   - enum34; python_version=="3.3"
        #
        # will become:
        #   - enum34; python_version=="2.7" or python_version=="3.3"
        #
        # If the duplicate dependencies have different constraints
        # we have to split the dependency graph.
        #
        # An example of this is:
        #   - pypiwin32 (220); sys_platform == "win32" and python_version >= "3.6"
        #   - pypiwin32 (219); sys_platform == "win32" and python_version < "3.6"
        duplicates = OrderedDict()
        for dep in dependencies:
            if dep.name not in duplicates:
                duplicates[dep.name] = []

            duplicates[dep.name].append(dep)

        dependencies = []
        for dep_name, deps in duplicates.items():
            if len(deps) == 1:
                dependencies.append(deps[0])
                continue

            self.debug("<debug>Duplicate dependencies for {}</debug>".format(
                dep_name))

            # Regrouping by constraint
            by_constraint = OrderedDict()
            for dep in deps:
                if dep.constraint not in by_constraint:
                    by_constraint[dep.constraint] = []

                by_constraint[dep.constraint].append(dep)

            # We merge by constraint
            for constraint, _deps in by_constraint.items():
                new_markers = []
                for dep in _deps:
                    marker = dep.marker.without_extras()
                    if marker.is_empty():
                        # No marker or only extras
                        continue

                    new_markers.append(marker)

                if not new_markers:
                    continue

                dep = _deps[0]
                dep.marker = dep.marker.union(MarkerUnion(*new_markers))
                by_constraint[constraint] = [dep]

                continue

            if len(by_constraint) == 1:
                self.debug("<debug>Merging requirements for {}</debug>".format(
                    str(deps[0])))
                dependencies.append(list(by_constraint.values())[0][0])
                continue

            # We leave dependencies as-is if they have the same
            # python/platform constraints.
            # That way the resolver will pickup the conflict
            # and display a proper error.
            _deps = [value[0] for value in by_constraint.values()]
            seen = set()
            for _dep in _deps:
                pep_508_dep = _dep.to_pep_508(False)
                if ";" not in pep_508_dep:
                    _requirements = ""
                else:
                    _requirements = pep_508_dep.split(";")[1].strip()

                if _requirements not in seen:
                    seen.add(_requirements)

            if len(_deps) != len(seen):
                for _dep in _deps:
                    dependencies.append(_dep)

                continue

            # At this point, we raise an exception that will
            # tell the solver to enter compatibility mode
            # which means it will resolve for subsets
            # Python constraints
            #
            # For instance, if our root package requires Python ~2.7 || ^3.6
            # And we have one dependency that requires Python <3.6
            # and the other Python >=3.6 than the solver will solve
            # dependencies for Python >=2.7,<2.8 || >=3.4,<3.6
            # and Python >=3.6,<4.0
            python_constraints = []
            for constraint, _deps in by_constraint.items():
                python_constraints.append(_deps[0].python_versions)

            _deps = [str(_dep[0]) for _dep in by_constraint.values()]
            self.debug(
                "<warning>Different requirements found for {}.</warning>".
                format(", ".join(_deps[:-1]) + " and " + _deps[-1]))
            raise CompatibilityError(*python_constraints)

        # Modifying dependencies as needed
        for dep in dependencies:
            if not package.dependency.python_constraint.is_any():
                dep.transitive_python_versions = str(
                    dep.python_constraint.intersect(
                        package.dependency.python_constraint))

            if (package.dependency.is_directory()
                    or package.dependency.is_file()) and (dep.is_directory()
                                                          or dep.is_file()):
                if dep.path.as_posix().startswith(package.source_url):
                    relative = (Path(package.source_url) /
                                dep.path).relative_to(package.source_url)
                else:
                    relative = Path(package.source_url) / dep.path

                # TODO: Improve the way we set the correct relative path for dependencies
                dep._path = relative

        package.requires = dependencies

        return package
def test_marker_union_not_all_empty():
    union = MarkerUnion(parse_marker("<empty>"), parse_marker("<empty>"),
                        parse_marker(""))

    assert not union.is_empty()
def test_marker_union_all_any():
    union = MarkerUnion(parse_marker(""), parse_marker(""))

    assert union.is_any()