diff --git a/docs/cli.md b/docs/cli.md
index e57b1548e2e..ada0a8764ac 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -510,6 +510,7 @@ required by
* `--latest (-l)`: Show the latest version.
* `--outdated (-o)`: Show the latest version but only for packages that are outdated.
* `--all (-a)`: Show all packages (even those not compatible with current system).
+* `--top-level (-T)`: Only show explicitly defined packages.
{{% note %}}
When `--only` is specified, `--with` and `--without` options are ignored.
diff --git a/src/poetry/console/commands/show.py b/src/poetry/console/commands/show.py
index a97c0dc34bc..1b8b03e718b 100644
--- a/src/poetry/console/commands/show.py
+++ b/src/poetry/console/commands/show.py
@@ -64,6 +64,7 @@ class ShowCommand(GroupCommand, EnvCommand):
"a",
"Show all packages (even those not compatible with current system).",
),
+ option("top-level", "T", "Show only top-level dependencies."),
]
help = """The show command displays detailed information about a package, or
@@ -77,6 +78,20 @@ def handle(self) -> int:
if self.option("tree"):
self.init_styles(self.io)
+ if self.option("top-level"):
+ if self.option("tree"):
+ self.line_error(
+ "Error: Cannot use --tree and --top-level at the same"
+ " time."
+ )
+ return 1
+ if package is not None:
+ self.line_error(
+ "Error: Cannot use --top-level when displaying a single"
+ " package."
+ )
+ return 1
+
if self.option("why"):
if self.option("tree") and package is None:
self.line_error(
@@ -215,6 +230,7 @@ def _display_packages_information(
show_latest = self.option("latest")
show_all = self.option("all")
+ show_top_level = self.option("top-level")
width = shutil.get_terminal_size().columns
name_length = version_length = latest_length = required_by_length = 0
latest_packages = {}
@@ -296,10 +312,18 @@ def _display_packages_information(
write_why = self.option("why") and (why_end_column + 3) <= width
write_description = (why_end_column + 24) <= width
+ requires = root.all_requires
+
for locked in locked_packages:
color = "cyan"
name = locked.pretty_name
install_marker = ""
+
+ if show_top_level and not any(
+ locked.is_same_package_as(r) for r in requires
+ ):
+ continue
+
if locked not in required_locked_packages:
if not show_all:
continue
diff --git a/tests/console/commands/test_show.py b/tests/console/commands/test_show.py
index c7d15f7ce6c..821e5b87c85 100644
--- a/tests/console/commands/test_show.py
+++ b/tests/console/commands/test_show.py
@@ -2082,6 +2082,125 @@ def test_url_dependency_is_not_outdated_by_repository_package(
assert tester.io.fetch_output() == ""
+def test_show_top_level(
+ tester: CommandTester, poetry: Poetry, installed: Repository
+) -> None:
+ poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.2.0"))
+
+ cachy2 = get_package("cachy", "0.2.0")
+ cachy2.add_dependency(Factory.create_dependency("msgpack-python", ">=0.5 <0.6"))
+
+ installed.add_package(cachy2)
+
+ poetry.locker.mock_lock_data(
+ {
+ "package": [
+ {
+ "name": "cachy",
+ "version": "0.2.0",
+ "description": "",
+ "category": "main",
+ "optional": False,
+ "platform": "*",
+ "python-versions": "*",
+ "checksum": [],
+ "dependencies": {"msgpack-python": ">=0.5 <0.6"},
+ },
+ {
+ "name": "msgpack-python",
+ "version": "0.5.1",
+ "description": "",
+ "category": "main",
+ "optional": False,
+ "platform": "*",
+ "python-versions": "*",
+ "checksum": [],
+ },
+ ],
+ "metadata": {
+ "python-versions": "*",
+ "platform": "*",
+ "content-hash": "123456789",
+ "files": {"cachy": [], "msgpack-python": []},
+ },
+ }
+ )
+
+ tester.execute("--top-level")
+
+ expected = """cachy 0.2.0 \n"""
+
+ assert tester.io.fetch_output() == expected
+
+
+def test_show_top_level_with_explicitly_defined_depenancy(
+ tester: CommandTester, poetry: Poetry, installed: Repository
+) -> None:
+ poetry.package.add_dependency(Factory.create_dependency("a", "^0.1.0"))
+ poetry.package.add_dependency(Factory.create_dependency("b", "^0.2.0"))
+
+ a = get_package("a", "0.1.0")
+ a.add_dependency(Factory.create_dependency("b", "0.2.0"))
+ b = get_package("b", "0.2.0")
+
+ installed.add_package(a)
+ installed.add_package(b)
+
+ poetry.locker.mock_lock_data(
+ {
+ "package": [
+ {
+ "name": "a",
+ "version": "0.1.0",
+ "description": "",
+ "category": "main",
+ "optional": False,
+ "platform": "*",
+ "python-versions": "*",
+ "checksum": [],
+ "dependencies": {"b": "0.2.0"},
+ },
+ {
+ "name": "b",
+ "version": "0.2.0",
+ "description": "",
+ "category": "main",
+ "optional": False,
+ "platform": "*",
+ "python-versions": "*",
+ "checksum": [],
+ },
+ ],
+ "metadata": {
+ "python-versions": "*",
+ "platform": "*",
+ "content-hash": "123456789",
+ "files": {"a": [], "b": []},
+ },
+ }
+ )
+
+ tester.execute("--top-level")
+
+ expected = """a 0.1.0 \nb 0.2.0 \n"""
+
+ assert tester.io.fetch_output() == expected
+
+
+def test_show_error_top_level_with_tree(tester: CommandTester) -> None:
+ expected = "Error: Cannot use --tree and --top-level at the same time.\n"
+ tester.execute("--top-level --tree")
+ assert tester.io.fetch_error() == expected
+ assert tester.status_code == 1
+
+
+def test_show_error_top_level_with_single_package(tester: CommandTester) -> None:
+ expected = "Error: Cannot use --top-level when displaying a single package.\n"
+ tester.execute("--top-level some_package_name")
+ assert tester.io.fetch_error() == expected
+ assert tester.status_code == 1
+
+
@pytest.mark.parametrize(
("project_directory", "required_fixtures"),
[