예제 #1
0
파일: installer.py 프로젝트: blueyed/poetry
    def _filter_operations(self,
                           ops,
                           repo
                           ):  # type: (List[Operation], Repository) -> None
        extra_packages = [p.name for p in
                          self._get_extra_packages(repo)]
        for op in ops:
            if isinstance(op, Update):
                package = op.target_package
            else:
                package = op.package

            if op.job_type == 'uninstall':
                continue

            parser = VersionParser()
            python = '.'.join([str(i) for i in self._venv.version_info[:3]])
            if 'python' in package.requirements:
                python_constraint = parser.parse_constraints(
                    package.requirements['python']
                )
                if not python_constraint.matches(Constraint('=', python)):
                    # Incompatible python versions
                    op.skip('Not needed for the current python version')
                    continue

            if not package.python_constraint.matches(Constraint('=', python)):
                op.skip('Not needed for the current python version')
                continue

            if 'platform' in package.requirements:
                platform_constraint = GenericConstraint.parse(
                    package.requirements['platform']
                )
                if not platform_constraint.matches(
                        GenericConstraint('=', sys.platform)
                ):
                    # Incompatible systems
                    op.skip('Not needed for the current platform')
                    continue

            if self._update:
                extras = {}
                for extra, deps in self._package.extras.items():
                    extras[extra] = [dep.name for dep in deps]
            else:
                extras = {}
                for extra, deps in self._locker.lock_data.get('extras', {}).items():
                    extras[extra] = [dep.lower() for dep in deps]

            # If a package is optional and not requested
            # in any extra we skip it
            if package.optional:
                if package.name not in extra_packages:
                    op.skip('Not required')

            # If the package is a dev package and dev packages
            # are not requests, we skip it
            if package.category == 'dev' and not self.is_dev_mode():
                op.skip('Dev dependencies not requested')
예제 #2
0
    def _filter_operations(
            self, ops, repo):  # type: (List[Operation], Repository) -> None
        extra_packages = [p.name for p in self._get_extra_packages(repo)]
        for op in ops:
            if isinstance(op, Update):
                package = op.target_package
            else:
                package = op.package

            if op.job_type == "uninstall":
                continue

            if package.name in self._develop and package.source_type == "directory":
                package.develop = True
                if op.skipped:
                    op.unskip()

            python = Version.parse(".".join(
                [str(i) for i in self._venv.version_info[:3]]))
            if "python" in package.requirements:
                python_constraint = parse_constraint(
                    package.requirements["python"])
                if not python_constraint.allows(python):
                    # Incompatible python versions
                    op.skip("Not needed for the current python version")
                    continue

            if not package.python_constraint.allows(python):
                op.skip("Not needed for the current python version")
                continue

            if "platform" in package.requirements:
                platform_constraint = GenericConstraint.parse(
                    package.requirements["platform"])
                if not platform_constraint.matches(
                        GenericConstraint("=", sys.platform)):
                    # Incompatible systems
                    op.skip("Not needed for the current platform")
                    continue

            if self._update:
                extras = {}
                for extra, deps in self._package.extras.items():
                    extras[extra] = [dep.name for dep in deps]
            else:
                extras = {}
                for extra, deps in self._locker.lock_data.get("extras",
                                                              {}).items():
                    extras[extra] = [dep.lower() for dep in deps]

            # If a package is optional and not requested
            # in any extra we skip it
            if package.optional:
                if package.name not in extra_packages:
                    op.skip("Not required")

            # If the package is a dev package and dev packages
            # are not requests, we skip it
            if package.category == "dev" and not self.is_dev_mode():
                op.skip("Dev dependencies not requested")
예제 #3
0
파일: solver.py 프로젝트: shawegit/poetry
    def _get_tags_for_package(self, package, graph, depth=0):
        categories = ["dev"]
        optionals = [True]
        python_versions = []
        platforms = []
        _depths = [0]

        children = graph["children"]
        found = False
        for child in children:
            if child["name"] == package.name:
                category = child["category"]
                optional = child["optional"]
                python_version = child["python_version"]
                platform = child["platform"]
                _depths.append(depth)
            else:
                (
                    category,
                    optional,
                    python_version,
                    platform,
                    _depth,
                ) = self._get_tags_for_package(package, child, depth=depth + 1)

                _depths.append(_depth)

            categories.append(category)
            optionals.append(optional)
            if python_version is not None:
                python_versions.append(python_version)

            if platform is not None:
                platforms.append(platform)

        if "main" in categories:
            category = "main"
        else:
            category = "dev"

        optional = all(optionals)

        if not python_versions:
            python_version = None
        else:
            # Find the least restrictive constraint
            python_version = python_versions[0]
            for constraint in python_versions[1:]:
                previous = parse_constraint(python_version)
                current = parse_constraint(constraint)

                if python_version == "*":
                    continue
                elif constraint == "*":
                    python_version = constraint
                elif current.allows_all(previous):
                    python_version = constraint

        if not platforms:
            platform = None
        else:
            platform = platforms[0]
            for constraint in platforms[1:]:
                previous = GenericConstraint.parse(platform)
                current = GenericConstraint.parse(constraint)

                if platform == "*":
                    continue
                elif constraint == "*":
                    platform = constraint
                elif current.matches(previous):
                    platform = constraint

        depth = max(*(_depths + [0]))

        return category, optional, python_version, platform, depth
예제 #4
0
파일: solver.py 프로젝트: shawegit/poetry
    def _build_graph(
        self, package, packages, previous=None, previous_dep=None, dep=None
    ):
        if not previous:
            category = "dev"
            optional = True
            python_version = "*"
            platform = "*"
        else:
            category = dep.category
            optional = dep.is_optional() and not dep.is_activated()
            python_version = str(
                parse_constraint(previous["python_version"]).intersect(
                    previous_dep.python_constraint
                )
            )
            platform = str(
                previous_dep.platform
                if GenericConstraint.parse(previous["platform"]).matches(
                    previous_dep.platform_constraint
                )
                and previous_dep.platform != "*"
                else previous["platform"]
            )

        graph = {
            "name": package.name,
            "category": category,
            "optional": optional,
            "python_version": python_version,
            "platform": platform,
            "children": [],
        }

        if previous_dep and previous_dep is not dep and previous_dep.name == dep.name:
            return graph

        for dependency in package.all_requires:
            if dependency.is_optional():
                if not package.is_root() and (
                    not previous_dep or not previous_dep.extras
                ):
                    continue

                is_activated = False
                for group, extras in package.extras.items():
                    if dep:
                        extras = previous_dep.extras
                    elif package.is_root():
                        extras = package.extras
                    else:
                        extras = []

                    if group in extras:
                        is_activated = True
                        break

                if not is_activated:
                    continue

            for pkg in packages:
                if pkg.name == dependency.name:
                    # If there is already a child with this name
                    # we merge the requirements
                    existing = None
                    for child in graph["children"]:
                        if (
                            child["name"] == pkg.name
                            and child["category"] == dependency.category
                        ):
                            existing = child
                            continue

                    child_graph = self._build_graph(
                        pkg, packages, graph, dependency, dep or dependency
                    )

                    if existing:
                        existing["python_version"] = str(
                            parse_constraint(existing["python_version"]).union(
                                parse_constraint(child_graph["python_version"])
                            )
                        )
                        continue

                    graph["children"].append(child_graph)

        return graph
예제 #5
0
파일: solver.py 프로젝트: blueyed/poetry
    def _get_tags_for_vertex(self, vertex, requested):
        category = 'dev'
        optional = True
        python_version = None
        platform = None

        if not vertex.incoming_edges:
            # Original dependency
            for req in requested:
                if vertex.payload.name == req.name:
                    category = req.category
                    optional = req.is_optional()

                    python_version = str(req.python_constraint)

                    platform = str(req.platform_constraint)

                    break

            return category, optional, python_version, platform

        parser = VersionParser()
        python_versions = []
        platforms = []
        for edge in vertex.incoming_edges:
            python_version = None
            platform = None
            for req in edge.origin.payload.requires:
                if req.name == vertex.payload.name:
                    python_version = req.python_versions
                    platform = req.platform

                    break

            (top_category,
             top_optional,
             top_python_version,
             top_platform) = self._get_tags_for_vertex(
                edge.origin, requested
            )

            if top_category == 'main':
                category = top_category

            optional = optional and top_optional

            # Take the most restrictive constraints
            if top_python_version is not None:
                if python_version is not None:
                    previous = parser.parse_constraints(python_version)
                    current = parser.parse_constraints(top_python_version)

                    if top_python_version != '*' and previous.matches(current):
                        python_versions.append(top_python_version)
                    else:
                        python_versions.append(python_version)
                else:
                    python_versions.append(top_python_version)
            elif python_version is not None:
                python_versions.append(python_version)

            if top_platform is not None:
                if platform is not None:
                    previous = GenericConstraint.parse(platform)
                    current = GenericConstraint.parse(top_platform)

                    if top_platform != '*' and previous.matches(current):
                        platforms.append(top_platform)
                    else:
                        platforms.append(platform)
                else:
                    platforms.append(top_platform)
            elif platform is not None:
                platforms.append(platform)

        if not python_versions:
            python_version = None
        else:
            # Find the least restrictive constraint
            python_version = python_versions[0]
            previous = parser.parse_constraints(python_version)
            for constraint in python_versions[1:]:
                current = parser.parse_constraints(constraint)

                if python_version == '*':
                    continue
                elif constraint == '*':
                    python_version = constraint
                elif current.matches(previous):
                    python_version = constraint

        if not platforms:
            platform = None
        else:
            platform = platforms[0]
            previous = GenericConstraint.parse(platform)
            for constraint in platforms[1:]:
                current = GenericConstraint.parse(constraint)

                if platform == '*':
                    continue
                elif constraint == '*':
                    platform = constraint
                elif current.matches(previous):
                    platform = constraint

        return category, optional, python_version, platform
예제 #6
0
    def _get_tags_for_package(self, package, graph):
        categories = ["dev"]
        optionals = [True]
        python_versions = []
        platforms = []

        children = graph["children"]
        for child in children:
            if child["name"] == package.name:
                category = child["category"]
                optional = child["optional"]
                python_version = child["python_version"]
                platform = child["platform"]
            else:
                (
                    category,
                    optional,
                    python_version,
                    platform,
                ) = self._get_tags_for_package(package, child)

            categories.append(category)
            optionals.append(optional)
            if python_version is not None:
                python_versions.append(python_version)

            if platform is not None:
                platforms.append(platform)

        if "main" in categories:
            category = "main"
        else:
            category = "dev"

        optional = all(optionals)

        if not python_versions:
            python_version = None
        else:
            # Find the least restrictive constraint
            python_version = python_versions[0]
            for constraint in python_versions[1:]:
                previous = parse_constraint(python_version)
                current = parse_constraint(constraint)

                if python_version == "*":
                    continue
                elif constraint == "*":
                    python_version = constraint
                elif current.allows_all(previous):
                    python_version = constraint

        if not platforms:
            platform = None
        else:
            platform = platforms[0]
            for constraint in platforms[1:]:
                previous = GenericConstraint.parse(platform)
                current = GenericConstraint.parse(constraint)

                if platform == "*":
                    continue
                elif constraint == "*":
                    platform = constraint
                elif current.matches(previous):
                    platform = constraint

        return category, optional, python_version, platform
예제 #7
0
    def _build_graph(self,
                     package,
                     packages,
                     previous=None,
                     previous_dep=None,
                     dep=None):
        if not previous:
            category = "dev"
            optional = True
            python_version = "*"
            platform = "*"
        else:
            category = dep.category
            optional = dep.is_optional() and not dep.is_activated()
            python_version = str(
                parse_constraint(previous["python_version"]).intersect(
                    previous_dep.python_constraint))
            platform = str(previous_dep.platform if GenericConstraint.parse(
                previous["platform"]).matches(previous_dep.platform_constraint)
                           and previous_dep.platform != "*" else
                           previous["platform"])

        graph = {
            "name": package.name,
            "category": category,
            "optional": optional,
            "python_version": python_version,
            "platform": platform,
            "children": [],
        }

        if previous_dep and previous_dep is not dep and previous_dep.name == dep.name:
            return graph

        for dependency in package.all_requires:
            if dependency.is_optional():
                if not package.is_root() and (not previous_dep
                                              or not previous_dep.extras):
                    continue

                is_activated = False
                for group, extras in package.extras.items():
                    if dep:
                        extras = previous_dep.extras
                    elif package.is_root():
                        extras = package.extras
                    else:
                        extras = []

                    if group in extras:
                        is_activated = True
                        break

                if not is_activated:
                    continue

            for pkg in packages:
                if pkg.name == dependency.name:
                    # If there is already a child with this name
                    # we merge the requirements
                    existing = None
                    for child in graph["children"]:
                        if (child["name"] == pkg.name
                                and child["category"] == dependency.category):
                            existing = child
                            continue

                    child_graph = self._build_graph(pkg, packages, graph,
                                                    dependency, dep
                                                    or dependency)

                    if existing:
                        existing["python_version"] = str(
                            parse_constraint(existing["python_version"]).union(
                                parse_constraint(
                                    child_graph["python_version"])))
                        continue

                    graph["children"].append(child_graph)

        return graph
예제 #8
0
파일: show.py 프로젝트: shawegit/poetry
    def handle(self):
        from poetry.packages.constraints.generic_constraint import GenericConstraint
        from poetry.repositories.installed_repository import InstalledRepository
        from poetry.semver import Version
        from poetry.semver import parse_constraint

        package = self.argument("package")

        if self.option("tree"):
            self.init_styles()

        if self.option("outdated"):
            self.input.set_option("latest", True)

        locked_repo = self.poetry.locker.locked_repository(True)

        # Show tree view if requested
        if self.option("tree") and not package:
            requires = self.poetry.package.requires + self.poetry.package.dev_requires
            packages = locked_repo.packages
            for package in packages:
                for require in requires:
                    if package.name == require.name:
                        self.display_package_tree(package, locked_repo)
                        break

            return 0

        table = self.table(style="compact")
        table.get_style().set_vertical_border_char("")
        locked_packages = locked_repo.packages

        if package:
            pkg = None
            for locked in locked_packages:
                if package.lower() == locked.name:
                    pkg = locked
                    break

            if not pkg:
                raise ValueError("Package {} not found".format(package))

            if self.option("tree"):
                self.display_package_tree(pkg, locked_repo)

                return 0

            rows = [
                ["<info>name</>", " : <fg=cyan>{}</>".format(pkg.pretty_name)],
                ["<info>version</>", " : <comment>{}</>".format(pkg.pretty_version)],
                ["<info>description</>", " : {}".format(pkg.description)],
            ]

            table.add_rows(rows)
            table.render()

            if pkg.requires:
                self.line("")
                self.line("<info>dependencies</info>")
                for dependency in pkg.requires:
                    self.line(
                        " - {} <comment>{}</>".format(
                            dependency.pretty_name, dependency.pretty_constraint
                        )
                    )

            return 0

        show_latest = self.option("latest")
        show_all = self.option("all")
        terminal = self.get_application().terminal
        width = terminal.width
        name_length = version_length = latest_length = 0
        latest_packages = {}
        installed_repo = InstalledRepository.load(self.venv)
        skipped = []

        platform = sys.platform
        python = Version.parse(".".join([str(i) for i in self._venv.version_info[:3]]))

        # Computing widths
        for locked in locked_packages:
            python_constraint = parse_constraint(locked.requirements.get("python", "*"))
            platform_constraint = GenericConstraint.parse(
                locked.requirements.get("platform", "*")
            )
            if not python_constraint.allows(python) or not platform_constraint.matches(
                GenericConstraint("=", platform)
            ):
                skipped.append(locked)

                if not show_all:
                    continue

            current_length = len(locked.pretty_name)
            if not self.output.is_decorated():
                installed_status = self.get_installed_status(locked, installed_repo)

                if installed_status == "not-installed":
                    current_length += 4

            name_length = max(name_length, current_length)
            version_length = max(version_length, len(locked.full_pretty_version))
            if show_latest:
                latest = self.find_latest_package(locked)
                if not latest:
                    latest = locked

                latest_packages[locked.pretty_name] = latest
                latest_length = max(latest_length, len(latest.full_pretty_version))

        write_version = name_length + version_length + 3 <= width
        write_latest = name_length + version_length + latest_length + 3 <= width
        write_description = name_length + version_length + latest_length + 24 <= width

        for locked in locked_packages:
            color = "green"
            name = locked.pretty_name
            install_marker = ""
            if locked in skipped:
                if not show_all:
                    continue

                color = "black;options=bold"
            else:
                installed_status = self.get_installed_status(locked, installed_repo)
                if installed_status == "not-installed":
                    color = "red"

                    if not self.output.is_decorated():
                        # Non installed in non decorated mode
                        install_marker = " (!)"

            line = "<fg={}>{:{}}{}</>".format(
                color, name, name_length - len(install_marker), install_marker
            )
            if write_version:
                line += " <comment>{:{}}</comment>".format(
                    locked.full_pretty_version, version_length
                )
            if show_latest and write_latest:
                latest = latest_packages[locked.pretty_name]

                update_status = self.get_update_status(latest, locked)
                color = "green"
                if update_status == "semver-safe-update":
                    color = "red"
                elif update_status == "update-possible":
                    color = "yellow"

                line += " <fg={}>{:{}}</>".format(
                    color, latest.pretty_version, latest_length
                )
                if self.option("outdated") and update_status == "up-to-date":
                    continue

            if write_description:
                description = locked.description
                remaining = width - name_length - version_length - 4
                if show_latest:
                    remaining -= latest_length

                if len(locked.description) > remaining:
                    description = description[: remaining - 3] + "..."

                line += " " + description

            self.line(line)
예제 #9
0
    def handle(self):
        from poetry.packages.constraints.generic_constraint import GenericConstraint
        from poetry.repositories.installed_repository import InstalledRepository
        from poetry.semver import Version
        from poetry.semver import parse_constraint

        package = self.argument("package")

        if self.option("tree"):
            self.init_styles()

        if self.option("outdated"):
            self.input.set_option("latest", True)

        locked_repo = self.poetry.locker.locked_repository(True)

        # Show tree view if requested
        if self.option("tree") and not package:
            requires = self.poetry.package.requires + self.poetry.package.dev_requires
            packages = locked_repo.packages
            for package in packages:
                for require in requires:
                    if package.name == require.name:
                        self.display_package_tree(package, locked_repo)
                        break

            return 0

        table = self.table(style="compact")
        table.get_style().set_vertical_border_char("")
        locked_packages = locked_repo.packages

        if package:
            pkg = None
            for locked in locked_packages:
                if package.lower() == locked.name:
                    pkg = locked
                    break

            if not pkg:
                raise ValueError("Package {} not found".format(package))

            if self.option("tree"):
                self.display_package_tree(pkg, locked_repo)

                return 0

            rows = [
                ["<info>name</>", " : <fg=cyan>{}</>".format(pkg.pretty_name)],
                [
                    "<info>version</>",
                    " : <comment>{}</>".format(pkg.pretty_version)
                ],
                ["<info>description</>", " : {}".format(pkg.description)],
            ]

            table.add_rows(rows)
            table.render()

            if pkg.requires:
                self.line("")
                self.line("<info>dependencies</info>")
                for dependency in pkg.requires:
                    self.line(" - {} <comment>{}</>".format(
                        dependency.pretty_name, dependency.pretty_constraint))

            return 0

        show_latest = self.option("latest")
        show_all = self.option("all")
        terminal = self.get_application().terminal
        width = terminal.width
        name_length = version_length = latest_length = 0
        latest_packages = {}
        installed_repo = InstalledRepository.load(self.venv)
        skipped = []

        platform = sys.platform
        python = Version.parse(".".join(
            [str(i) for i in self._venv.version_info[:3]]))

        # Computing widths
        for locked in locked_packages:
            python_constraint = parse_constraint(
                locked.requirements.get("python", "*"))
            platform_constraint = GenericConstraint.parse(
                locked.requirements.get("platform", "*"))
            if not python_constraint.allows(
                    python) or not platform_constraint.matches(
                        GenericConstraint("=", platform)):
                skipped.append(locked)

                if not show_all:
                    continue

            current_length = len(locked.pretty_name)
            if not self.output.is_decorated():
                installed_status = self.get_installed_status(
                    locked, installed_repo)

                if installed_status == "not-installed":
                    current_length += 4

            name_length = max(name_length, current_length)
            version_length = max(version_length,
                                 len(locked.full_pretty_version))
            if show_latest:
                latest = self.find_latest_package(locked)
                if not latest:
                    latest = locked

                latest_packages[locked.pretty_name] = latest
                latest_length = max(latest_length,
                                    len(latest.full_pretty_version))

        write_version = name_length + version_length + 3 <= width
        write_latest = name_length + version_length + latest_length + 3 <= width
        write_description = name_length + version_length + latest_length + 24 <= width

        for locked in locked_packages:
            color = "green"
            name = locked.pretty_name
            install_marker = ""
            if locked in skipped:
                if not show_all:
                    continue

                color = "black;options=bold"
            else:
                installed_status = self.get_installed_status(
                    locked, installed_repo)
                if installed_status == "not-installed":
                    color = "red"

                    if not self.output.is_decorated():
                        # Non installed in non decorated mode
                        install_marker = " (!)"

            line = "<fg={}>{:{}}{}</>".format(
                color, name, name_length - len(install_marker), install_marker)
            if write_version:
                line += " <comment>{:{}}</comment>".format(
                    locked.full_pretty_version, version_length)
            if show_latest and write_latest:
                latest = latest_packages[locked.pretty_name]

                update_status = self.get_update_status(latest, locked)
                color = "green"
                if update_status == "semver-safe-update":
                    color = "red"
                elif update_status == "update-possible":
                    color = "yellow"

                line += " <fg={}>{:{}}</>".format(color, latest.pretty_version,
                                                  latest_length)
                if self.option("outdated") and update_status == "up-to-date":
                    continue

            if write_description:
                description = locked.description
                remaining = width - name_length - version_length - 4
                if show_latest:
                    remaining -= latest_length

                if len(locked.description) > remaining:
                    description = description[:remaining - 3] + "..."

                line += " " + description

            self.line(line)
예제 #10
0
    def _filter_operations(
        self, ops, repo
    ):  # type: (List[Operation], Repository) -> None
        extra_packages = [p.name for p in self._get_extra_packages(repo)]
        for op in ops:
            if isinstance(op, Update):
                package = op.target_package
            else:
                package = op.package

            if op.job_type == "uninstall":
                continue

            if package.name in self._develop and package.source_type == "directory":
                package.develop = True
                if op.skipped:
                    op.unskip()

            python = Version.parse(
                ".".join([str(i) for i in self._venv.version_info[:3]])
            )
            if "python" in package.requirements:
                python_constraint = parse_constraint(package.requirements["python"])
                if not python_constraint.allows(python):
                    # Incompatible python versions
                    op.skip("Not needed for the current python version")
                    continue

            if not package.python_constraint.allows(python):
                op.skip("Not needed for the current python version")
                continue

            if "platform" in package.requirements:
                platform_constraint = GenericConstraint.parse(
                    package.requirements["platform"]
                )
                if not platform_constraint.matches(
                    GenericConstraint("=", sys.platform)
                ):
                    # Incompatible systems
                    op.skip("Not needed for the current platform")
                    continue

            if self._update:
                extras = {}
                for extra, deps in self._package.extras.items():
                    extras[extra] = [dep.name for dep in deps]
            else:
                extras = {}
                for extra, deps in self._locker.lock_data.get("extras", {}).items():
                    extras[extra] = [dep.lower() for dep in deps]

            # If a package is optional and not requested
            # in any extra we skip it
            if package.optional:
                if package.name not in extra_packages:
                    op.skip("Not required")

            # If the package is a dev package and dev packages
            # are not requests, we skip it
            if package.category == "dev" and not self.is_dev_mode():
                op.skip("Dev dependencies not requested")
예제 #11
0
    def _get_tags_from_graph(self, graph, packages):
        category = graph['category']
        optional = graph['optional']
        python_version = graph['python_version']
        platform = graph['platform']

        if not graph['parents']:
            # Root dependency
            return category, optional, python_version, platform

        python_versions = []
        platforms = []

        for parent_name, parent_graph in graph['parents'].items():
            dep_python_version = graph['dependencies'][parent_name][
                'python_version']
            dep_platform = graph['dependencies'][parent_name]['platform']

            for pkg in packages:
                if pkg.name == parent_name:
                    (top_category, top_optional, top_python_version,
                     top_platform) = self._get_tags_from_graph(
                         parent_graph, packages)

                    if category is None or category != 'main':
                        category = top_category

                    optional = optional and top_optional

                    # Take the most restrictive constraints
                    if top_python_version is not None:
                        if dep_python_version is not None:
                            previous = parse_constraint(dep_python_version)
                            current = parse_constraint(top_python_version)

                            if previous.allows_all(current):
                                python_versions.append(top_python_version)
                            else:
                                python_versions.append(dep_python_version)
                        else:
                            python_versions.append(top_python_version)
                    elif dep_python_version is not None:
                        python_versions.append(dep_python_version)

                    if top_platform is not None:
                        if dep_platform is not None:
                            previous = GenericConstraint.parse(dep_platform)
                            current = GenericConstraint.parse(top_platform)

                            if top_platform != '*' and previous.matches(
                                    current):
                                platforms.append(top_platform)
                            else:
                                platforms.append(dep_platform)
                        else:
                            platforms.append(top_platform)
                    elif dep_platform is not None:
                        platforms.append(dep_platform)

                    break

        if not python_versions:
            python_version = None
        else:
            # Find the least restrictive constraint
            python_version = python_versions[0]
            previous = parse_constraint(python_version)
            for constraint in python_versions[1:]:
                current = parse_constraint(constraint)

                if python_version == '*':
                    continue
                elif constraint == '*':
                    python_version = constraint
                elif current.allows_all(previous):
                    python_version = constraint

        if not platforms:
            platform = None
        else:
            platform = platforms[0]
            previous = GenericConstraint.parse(platform)
            for constraint in platforms[1:]:
                current = GenericConstraint.parse(constraint)

                if platform == '*':
                    continue
                elif constraint == '*':
                    platform = constraint
                elif current.matches(previous):
                    platform = constraint

        return category, optional, python_version, platform