From f660f7a21162de876a04f8fce5f9266f5f7e3abf Mon Sep 17 00:00:00 2001
From: wolfgang-aura <169568318+wolfgang-aura@users.noreply.github.com>
Date: Tue, 8 Sep 2026 22:40:23 +0800
Subject: [PATCH 1/2] Add dependency sources to poetry show
---
docs/cli.md | 1 +
src/poetry/console/commands/show.py | 70 +++++++++++-
tests/console/commands/test_show.py | 165 ++++++++++++++++++++++++++++
3 files changed, 230 insertions(+), 6 deletions(-)
diff --git a/docs/cli.md b/docs/cli.md
index 5abc0a31ddb..54049fec6ee 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -1171,6 +1171,7 @@ required by
* `--all (-a)`: Show all packages (even those not compatible with current system).
* `--top-level (-T)`: Only show explicitly defined packages.
* `--no-truncate`: Do not truncate the output based on the terminal width.
+* `--source`: Show the source of each package (`PyPI`, a configured source name, or a direct source URL).
* `--format (-f)`: Specify the output format (`json` or `text`). Default is `text`. `json` cannot be combined with the `--tree` option.
{{% note %}}
diff --git a/src/poetry/console/commands/show.py b/src/poetry/console/commands/show.py
index 328201f5689..6c4ffd37042 100644
--- a/src/poetry/console/commands/show.py
+++ b/src/poetry/console/commands/show.py
@@ -40,6 +40,16 @@ def reverse_deps(pkg: Package, repo: Repository) -> dict[str, str]:
return required_by
+def get_package_source(package: Package) -> str:
+ if package.source_type is None:
+ return "PyPI"
+
+ if package.source_type == "legacy":
+ return package.source_reference or package.source_url or "PyPI"
+
+ return package.source_url or package.source_reference or package.source_type
+
+
class OutputFormats(str, Enum):
JSON = "json"
TEXT = "text"
@@ -79,6 +89,7 @@ class ShowCommand(GroupCommand, EnvCommand):
None,
"Do not truncate the output based on the terminal width.",
),
+ option("source", None, "Show the source of each package."),
option(
"format",
"f",
@@ -143,6 +154,12 @@ def handle(self) -> int:
)
return 1
+ if self.option("source") and self.option("tree"):
+ self.line_error(
+ "Error: --source cannot be used with --tree."
+ )
+ return 1
+
if self.option("outdated"):
self.io.input.set_option("latest", True)
@@ -221,6 +238,8 @@ def _display_single_package_information(
"version": pkg.pretty_version,
"description": pkg.description,
}
+ if self.option("source"):
+ package_info["source"] = get_package_source(pkg)
if pkg.requires:
package_info["dependencies"] = {
dependency.pretty_name: dependency.pretty_constraint
@@ -238,6 +257,10 @@ def _display_single_package_information(
["version>", f" : {pkg.pretty_version}"],
["description>", f" : {pkg.description}"],
]
+ if self.option("source"):
+ rows.insert(
+ 2, ["source>", f" : {get_package_source(pkg)}"]
+ )
self.table(rows=rows, style="compact").render()
@@ -289,12 +312,14 @@ def _display_packages_information(
show_all = self.option("all")
show_top_level = self.option("top-level")
show_why = self.option("why")
+ show_source = self.option("source")
width = (
sys.maxsize
if self.option("no-truncate")
else shutil.get_terminal_size().columns
)
- name_length = version_length = latest_length = required_by_length = 0
+ name_length = version_length = latest_length = source_length = 0
+ required_by_length = 0
latest_packages = {}
latest_statuses = {}
installed_repo = InstalledRepository.load(self.env)
@@ -329,7 +354,9 @@ def _display_packages_information(
version_length = max(
version_length,
len(
- get_package_version_display_string(
+ locked.pretty_version
+ if show_source and locked.is_direct_origin()
+ else get_package_version_display_string(
locked, root=self.poetry.file.path.parent
)
),
@@ -349,12 +376,19 @@ def _display_packages_information(
required_by_length,
len(" from " + ",".join(required_by.keys())),
)
+
+ if show_source:
+ source_length = max(
+ source_length, len(get_package_source(locked))
+ )
else:
name_length = max(name_length, current_length)
version_length = max(
version_length,
len(
- get_package_version_display_string(
+ locked.pretty_version
+ if show_source and locked.is_direct_origin()
+ else get_package_version_display_string(
locked, root=self.poetry.file.path.parent
)
),
@@ -366,6 +400,9 @@ def _display_packages_information(
required_by_length, len(" from " + ",".join(required_by.keys()))
)
+ if show_source:
+ source_length = max(source_length, len(get_package_source(locked)))
+
if self.option("format") == OutputFormats.JSON:
packages = []
@@ -398,6 +435,9 @@ def _display_packages_information(
latest, root=self.poetry.file.path.parent
)
+ if show_source:
+ package["source"] = get_package_source(locked)
+
if show_why:
required_by = reverse_deps(locked, locked_repository)
if required_by:
@@ -414,8 +454,16 @@ def _display_packages_information(
write_version = name_length + version_length + 3 <= width
write_latest = name_length + version_length + latest_length + 3 <= width
+ source_end_column = (
+ name_length + version_length + latest_length + source_length
+ )
+ write_source = show_source and (source_end_column + 3) <= width
why_end_column = (
- name_length + version_length + latest_length + required_by_length
+ name_length
+ + version_length
+ + latest_length
+ + (source_length if write_source else 0)
+ + required_by_length
)
write_why = show_why and (why_end_column + 3) <= width
write_description = (why_end_column + 24) <= width
@@ -456,8 +504,12 @@ def _display_packages_information(
f"{name:{name_length - len(install_marker)}}{install_marker}>"
)
if write_version:
- version = get_package_version_display_string(
- locked, root=self.poetry.file.path.parent
+ version = (
+ locked.pretty_version
+ if show_source and locked.is_direct_origin()
+ else get_package_version_display_string(
+ locked, root=self.poetry.file.path.parent
+ )
)
line += f" {version:{version_length}}"
if show_latest:
@@ -476,6 +528,9 @@ def _display_packages_information(
)
line += f" {version:{latest_length}}>"
+ if write_source:
+ line += f" {get_package_source(locked):{source_length}}"
+
if write_why:
required_by = reverse_deps(locked, locked_repository)
if required_by:
@@ -494,6 +549,9 @@ def _display_packages_information(
if show_latest:
remaining -= latest_length
+ if write_source:
+ remaining -= source_length + 1
+
if len(locked.description) > remaining:
description = description[: remaining - 3] + "..."
diff --git a/tests/console/commands/test_show.py b/tests/console/commands/test_show.py
index 8687bbb5158..7e01e01ffc3 100644
--- a/tests/console/commands/test_show.py
+++ b/tests/console/commands/test_show.py
@@ -144,6 +144,171 @@ def test_show_basic_with_installed_packages(
assert tester.io.fetch_output() == expected
+@output_format_parametrize
+def test_show_with_source(
+ output_format: str,
+ tester: CommandTester,
+ poetry: Poetry,
+ installed: Repository,
+) -> None:
+ poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0"))
+ poetry.package.add_dependency(
+ Factory.create_dependency(
+ "internal-package",
+ {"version": "^1.0.0", "source": "internal"},
+ )
+ )
+
+ cachy_010 = get_package("cachy", "0.1.0")
+ cachy_010.description = "Cachy package"
+ internal_package_100 = get_package("internal-package", "1.0.0")
+ internal_package_100.description = "Internal package"
+
+ installed.add_package(cachy_010)
+ installed.add_package(internal_package_100)
+
+ assert isinstance(poetry.locker, DummyLocker)
+ poetry.locker.mock_lock_data(
+ {
+ "package": [
+ {
+ "name": "cachy",
+ "version": "0.1.0",
+ "description": "Cachy package",
+ "optional": False,
+ "platform": "*",
+ "python-versions": "*",
+ "checksum": [],
+ },
+ {
+ "name": "internal-package",
+ "version": "1.0.0",
+ "description": "Internal package",
+ "optional": False,
+ "platform": "*",
+ "python-versions": "*",
+ "checksum": [],
+ "source": {
+ "type": "legacy",
+ "url": "https://example.com/simple/",
+ "reference": "internal",
+ },
+ },
+ ],
+ "metadata": {
+ "python-versions": "*",
+ "platform": "*",
+ "content-hash": "123456789",
+ "files": {"cachy": [], "internal-package": []},
+ },
+ }
+ )
+
+ tester.execute(f"--source {output_format}")
+
+ expected: str | list[dict[str, str]] = ""
+ if "json" in output_format:
+ expected = [
+ {
+ "name": "cachy",
+ "installed_status": "installed",
+ "version": "0.1.0",
+ "source": "PyPI",
+ "description": "Cachy package",
+ },
+ {
+ "name": "internal-package",
+ "installed_status": "installed",
+ "version": "1.0.0",
+ "source": "internal",
+ "description": "Internal package",
+ },
+ ]
+ assert json.loads(tester.io.fetch_output()) == expected
+ else:
+ expected = """\
+cachy 0.1.0 PyPI Cachy package
+internal-package 1.0.0 internal Internal package
+"""
+ assert tester.io.fetch_output() == expected
+
+
+def test_show_with_source_long_direct_url_keeps_descriptions(
+ tester: CommandTester,
+ poetry: Poetry,
+ installed: Repository,
+) -> None:
+ direct_url = "https://example.com/aaaaaaaaaaa.whl"
+ poetry.package.add_dependency(
+ Factory.create_dependency("regular-package", "^1.0.0")
+ )
+ poetry.package.add_dependency(
+ Factory.create_dependency("direct-package", {"url": direct_url})
+ )
+
+ regular_package = get_package("regular-package", "1.0.0")
+ regular_package.description = "Regular package"
+ direct_package = get_package("direct-package", "1.0.0")
+ direct_package.description = "Direct package"
+ direct_package._source_type = "url"
+ direct_package._source_url = direct_url
+
+ installed.add_package(regular_package)
+ installed.add_package(direct_package)
+
+ assert isinstance(poetry.locker, DummyLocker)
+ poetry.locker.mock_lock_data(
+ {
+ "package": [
+ {
+ "name": "regular-package",
+ "version": "1.0.0",
+ "description": "Regular package",
+ "optional": False,
+ "platform": "*",
+ "python-versions": "*",
+ "checksum": [],
+ },
+ {
+ "name": "direct-package",
+ "version": "1.0.0",
+ "description": "Direct package",
+ "optional": False,
+ "platform": "*",
+ "python-versions": "*",
+ "checksum": [],
+ "source": {
+ "type": "url",
+ "url": direct_url,
+ },
+ },
+ ],
+ "metadata": {
+ "python-versions": "*",
+ "platform": "*",
+ "content-hash": "123456789",
+ "files": {"regular-package": [], "direct-package": []},
+ },
+ }
+ )
+
+ tester.execute("--source --no-truncate")
+
+ source_width = len(direct_url)
+ expected = (
+ f"{'regular-package':<15} {'1.0.0':<5} {'PyPI':<{source_width}} Regular package\n"
+ f"{'direct-package':<15} {'1.0.0':<5} {direct_url:<{source_width}} Direct package\n"
+ )
+ assert tester.io.fetch_output() == expected
+
+
+def test_show_with_source_rejects_tree(tester: CommandTester) -> None:
+ tester.execute("--source --tree")
+
+ assert tester.status_code == 1
+ assert tester.io.fetch_error() == "Error: --source cannot be used with --tree.\n"
+
+
def _configure_project_with_groups(poetry: Poetry, installed: Repository) -> None:
poetry.package.add_dependency(Factory.create_dependency("cachy", "^0.1.0"))
From a0a31b1e41772baa80bc93c1ecd91b864934cc78 Mon Sep 17 00:00:00 2001
From: wolfgang-aura <169568318+wolfgang-aura@users.noreply.github.com>
Date: Wed, 9 Sep 2026 02:24:51 +0800
Subject: [PATCH 2/2] style: apply ruff formatting
---
src/poetry/console/commands/show.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/poetry/console/commands/show.py b/src/poetry/console/commands/show.py
index 6c4ffd37042..86f2788574b 100644
--- a/src/poetry/console/commands/show.py
+++ b/src/poetry/console/commands/show.py
@@ -454,9 +454,7 @@ def _display_packages_information(
write_version = name_length + version_length + 3 <= width
write_latest = name_length + version_length + latest_length + 3 <= width
- source_end_column = (
- name_length + version_length + latest_length + source_length
- )
+ source_end_column = name_length + version_length + latest_length + source_length
write_source = show_source and (source_end_column + 3) <= width
why_end_column = (
name_length