diff --git a/src/pip/_internal/commands/freeze.py b/src/pip/_internal/commands/freeze.py index fd9d88a8b01..eb7f29b3e1f 100644 --- a/src/pip/_internal/commands/freeze.py +++ b/src/pip/_internal/commands/freeze.py @@ -81,6 +81,12 @@ def add_options(self) -> None: action="store_true", help="Exclude editable package from output.", ) + self.cmd_opts.add_option( + "--exclude-dependencies", + dest="exclude_dependencies", + action="store_true", + help="Exclude dependency packages from output.", + ) self.cmd_opts.add_option(cmdoptions.list_exclude()) self.parser.insert_option_group(0, self.cmd_opts) @@ -103,6 +109,7 @@ def run(self, options: Values, args: List[str]) -> int: isolated=options.isolated_mode, skip=skip, exclude_editable=options.exclude_editable, + exclude_dependencies=options.exclude_dependencies, ): sys.stdout.write(line + "\n") return SUCCESS diff --git a/src/pip/_internal/commands/list.py b/src/pip/_internal/commands/list.py index e551dda9a96..0bbe26c0402 100644 --- a/src/pip/_internal/commands/list.py +++ b/src/pip/_internal/commands/list.py @@ -129,6 +129,12 @@ def add_options(self) -> None: help="Include editable package from output.", default=True, ) + self.cmd_opts.add_option( + "--exclude-dependencies", + dest="exclude_dependencies", + action="store_true", + help="Exclude dependency packages from output.", + ) self.cmd_opts.add_option(cmdoptions.list_exclude()) index_opts = cmdoptions.make_option_group(cmdoptions.index_group, self.parser) @@ -176,6 +182,7 @@ def run(self, options: Values, args: List[str]) -> int: user_only=options.user, editables_only=options.editable, include_editables=options.include_editable, + exclude_dependencies=options.exclude_dependencies, skip=skip, ) ] diff --git a/src/pip/_internal/metadata/base.py b/src/pip/_internal/metadata/base.py index 92491244108..cba7dab7c3d 100644 --- a/src/pip/_internal/metadata/base.py +++ b/src/pip/_internal/metadata/base.py @@ -649,6 +649,7 @@ def iter_installed_distributions( include_editables: bool = True, editables_only: bool = False, user_only: bool = False, + exclude_dependencies: bool = False, ) -> Iterator[BaseDistribution]: """Return a list of installed distributions. @@ -665,8 +666,20 @@ def iter_installed_distributions( :param editables_only: If True, only report editables. :param user_only: If True, only report installations in the user site directory. + :param exclude_dependencies: If True, dont't report distributions + that are dependencies of other installed distributions. """ - it = self.iter_all_distributions() + if exclude_dependencies: + dists = list(self.iter_all_distributions()) + it = iter(dists) + dep_keys = { + canonicalize_name(dep.name) + for dist in dists + for dep in (dist.iter_dependencies() or ()) + } + it = (d for d in it if d.canonical_name not in dep_keys) + else: + it = self.iter_all_distributions() if local_only: it = (d for d in it if d.local) if not include_editables: diff --git a/src/pip/_internal/operations/freeze.py b/src/pip/_internal/operations/freeze.py index 35445684514..a0e5a572ad8 100644 --- a/src/pip/_internal/operations/freeze.py +++ b/src/pip/_internal/operations/freeze.py @@ -30,6 +30,7 @@ def freeze( paths: Optional[List[str]] = None, isolated: bool = False, exclude_editable: bool = False, + exclude_dependencies: bool = False, skip: Container[str] = (), ) -> Generator[str, None, None]: installations: Dict[str, FrozenRequirement] = {} @@ -38,6 +39,7 @@ def freeze( local_only=local_only, skip=(), user_only=user_only, + exclude_dependencies=exclude_dependencies, ) for dist in dists: req = FrozenRequirement.from_dist(dist)