req_dependency_group.py 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. from collections.abc import Iterable, Iterator
  2. from typing import Any
  3. from pip._vendor.dependency_groups import DependencyGroupResolver
  4. from pip._internal.exceptions import InstallationError
  5. from pip._internal.utils.compat import tomllib
  6. def parse_dependency_groups(groups: list[tuple[str, str]]) -> list[str]:
  7. """
  8. Parse dependency groups data as provided via the CLI, in a `[path:]group` syntax.
  9. Raises InstallationErrors if anything goes wrong.
  10. """
  11. resolvers = _build_resolvers(path for (path, _) in groups)
  12. return list(_resolve_all_groups(resolvers, groups))
  13. def _resolve_all_groups(
  14. resolvers: dict[str, DependencyGroupResolver], groups: list[tuple[str, str]]
  15. ) -> Iterator[str]:
  16. """
  17. Run all resolution, converting any error from `DependencyGroupResolver` into
  18. an InstallationError.
  19. """
  20. for path, groupname in groups:
  21. resolver = resolvers[path]
  22. try:
  23. yield from (str(req) for req in resolver.resolve(groupname))
  24. except (ValueError, TypeError, LookupError) as e:
  25. raise InstallationError(
  26. f"[dependency-groups] resolution failed for '{groupname}' "
  27. f"from '{path}': {e}"
  28. ) from e
  29. def _build_resolvers(paths: Iterable[str]) -> dict[str, Any]:
  30. resolvers = {}
  31. for path in paths:
  32. if path in resolvers:
  33. continue
  34. pyproject = _load_pyproject(path)
  35. if "dependency-groups" not in pyproject:
  36. raise InstallationError(
  37. f"[dependency-groups] table was missing from '{path}'. "
  38. "Cannot resolve '--group' option."
  39. )
  40. raw_dependency_groups = pyproject["dependency-groups"]
  41. if not isinstance(raw_dependency_groups, dict):
  42. raise InstallationError(
  43. f"[dependency-groups] table was malformed in {path}. "
  44. "Cannot resolve '--group' option."
  45. )
  46. resolvers[path] = DependencyGroupResolver(raw_dependency_groups)
  47. return resolvers
  48. def _load_pyproject(path: str) -> dict[str, Any]:
  49. """
  50. This helper loads a pyproject.toml as TOML.
  51. It raises an InstallationError if the operation fails.
  52. """
  53. try:
  54. with open(path, "rb") as fp:
  55. return tomllib.load(fp)
  56. except FileNotFoundError:
  57. raise InstallationError(f"{path} not found. Cannot resolve '--group' option.")
  58. except tomllib.TOMLDecodeError as e:
  59. raise InstallationError(f"Error parsing {path}: {e}") from e
  60. except OSError as e:
  61. raise InstallationError(f"Error reading {path}: {e}") from e