diff --git a/news/2974.bugfix.md b/news/2974.bugfix.md new file mode 100644 index 0000000000..e73b7035d2 --- /dev/null +++ b/news/2974.bugfix.md @@ -0,0 +1 @@ +Fix crash when pdm is used with `importlib-metadata` version 8.0. \ No newline at end of file diff --git a/src/pdm/cli/commands/list.py b/src/pdm/cli/commands/list.py index f83ec49b3a..501cd8b23b 100644 --- a/src/pdm/cli/commands/list.py +++ b/src/pdm/cli/commands/list.py @@ -322,19 +322,19 @@ class Listable: def __init__(self, dist: im.Distribution, groups: set[str]): self.dist = dist - self.name: str | None = dist.metadata["Name"] + self.name = dist.metadata.get("Name") self.groups = "|".join(groups) - self.version: str | None = dist.metadata["Version"] + self.version = dist.metadata.get("Version") self.version = None if self.version == "UNKNOWN" else self.version - self.homepage: str | None = dist.metadata["Home-Page"] + self.homepage = dist.metadata.get("Home-Page") self.homepage = None if self.homepage == "UNKNOWN" else self.homepage # If the License metadata field is empty or UNKNOWN then try to # find the license in the Trove classifiers. There may be more than one # so generate a pipe separated list (to avoid complexity with CSV export). - self.licenses: str | None = dist.metadata["License"] + self.licenses = dist.metadata.get("License") self.licenses = None if self.licenses == "UNKNOWN" else self.licenses # Sometimes package metadata contains the full license text. diff --git a/src/pdm/cli/commands/self_cmd.py b/src/pdm/cli/commands/self_cmd.py index c6ed6e071b..fe249b3bb8 100644 --- a/src/pdm/cli/commands/self_cmd.py +++ b/src/pdm/cli/commands/self_cmd.py @@ -26,7 +26,7 @@ def list_distributions(plugin_only: bool = False) -> list[Distribution]: for dist in working_set.values(): if not plugin_only or any(ep.group in ("pdm", "pdm.plugin") for ep in dist.entry_points): result.append(dist) - return sorted(result, key=lambda d: d.metadata["Name"] or "UNKNOWN") + return sorted(result, key=lambda d: d.metadata.get("Name", "UNKNOWN")) def run_pip(project: Project, args: list[str]) -> subprocess.CompletedProcess[str]: @@ -96,12 +96,11 @@ def handle(self, project: Project, options: argparse.Namespace) -> None: echo("Installed packages:", err=True) rows = [] for dist in distributions: - metadata = dist.metadata rows.append( ( - f"[success]{metadata['Name']}[/]", - f"[warning]{metadata['Version']}[/]", - metadata["Summary"] or "", + f"[success]{dist.metadata.get('Name')}[/]", + f"[warning]{dist.metadata.get('Version')}[/]", + dist.metadata.get("Summary", ""), ), ) project.core.ui.display_columns(rows) diff --git a/src/pdm/installers/manager.py b/src/pdm/installers/manager.py index 2ad07e54d4..4a2061c458 100644 --- a/src/pdm/installers/manager.py +++ b/src/pdm/installers/manager.py @@ -45,7 +45,7 @@ def get_paths_to_remove(self, dist: Distribution) -> BaseRemovePaths: def uninstall(self, dist: Distribution) -> None: """Perform the uninstallation for a given distribution""" remove_path = self.get_paths_to_remove(dist) - dist_name = dist.metadata["Name"] + dist_name = dist.metadata.get("Name") termui.logger.info("Removing distribution %s", dist_name) try: remove_path.remove() @@ -58,7 +58,7 @@ def uninstall(self, dist: Distribution) -> None: def overwrite(self, dist: Distribution, candidate: Candidate) -> None: """An in-place update to overwrite the distribution with a new candidate""" paths_to_remove = self.get_paths_to_remove(dist) - termui.logger.info("Overwriting distribution %s", dist.metadata["Name"]) + termui.logger.info("Overwriting distribution %s", dist.metadata.get("Name")) installed = self.install(candidate) installed_paths = self.get_paths_to_remove(installed) # Remove the paths that are in the new distribution diff --git a/src/pdm/installers/uninstallers.py b/src/pdm/installers/uninstallers.py index 13377ac98b..3cb9fa0211 100644 --- a/src/pdm/installers/uninstallers.py +++ b/src/pdm/installers/uninstallers.py @@ -159,10 +159,11 @@ def from_dist(cls: type[_T], dist: Distribution, environment: BaseEnvironment) - dist_location = os.path.dirname(meta_location) if is_egg_link(dist): # pragma: no cover egg_link_path = cast("Path | None", getattr(dist, "link_file", None)) + dist_name = dist.metadata.get("Name") if not egg_link_path: termui.logger.warn( "No egg link is found for editable distribution %s, do nothing.", - dist.metadata["Name"], + dist_name, ) else: with egg_link_path.open("rb") as f: @@ -170,7 +171,7 @@ def from_dist(cls: type[_T], dist: Distribution, environment: BaseEnvironment) - if link_pointer != dist_location: raise UninstallError( f"The link pointer in {egg_link_path} doesn't match " - f"the location of {dist.metadata['Name']}(at {dist_location}" + f"the location of {dist_name} (at {dist_location}" ) instance.add_path(str(egg_link_path)) instance.add_pth(link_pointer) diff --git a/src/pdm/models/candidates.py b/src/pdm/models/candidates.py index c012da3db1..578adb00ae 100644 --- a/src/pdm/models/candidates.py +++ b/src/pdm/models/candidates.py @@ -611,11 +611,11 @@ def metadata(self) -> im.Distribution: if self._metadata is None: result = self.prepare_metadata() if not self.candidate.name: - self.req.name = self.candidate.name = cast(str, result.metadata["Name"]) + self.req.name = self.candidate.name = cast(str, result.metadata.get("Name")) if not self.candidate.version: self.candidate.version = result.version if not self.candidate.requires_python: - self.candidate.requires_python = cast(str, result.metadata["Requires-Python"] or "") + self.candidate.requires_python = result.metadata.get("Requires-Python", "") self._metadata = result return self._metadata diff --git a/src/pdm/models/project_info.py b/src/pdm/models/project_info.py index 8c4bc82487..cea936c576 100644 --- a/src/pdm/models/project_info.py +++ b/src/pdm/models/project_info.py @@ -42,8 +42,8 @@ def from_distribution(cls, data: Distribution) -> ProjectInfo: project_urls = {} return cls( - name=metadata["Name"], - version=metadata["Version"], + name=metadata.get("Name", ""), + version=metadata.get("Version", ""), summary=metadata.get("Summary", ""), author=metadata.get("Author", ""), email=metadata.get("Author-email", ""), diff --git a/src/pdm/models/repositories.py b/src/pdm/models/repositories.py index ebab162eae..483d1c5497 100644 --- a/src/pdm/models/repositories.py +++ b/src/pdm/models/repositories.py @@ -269,7 +269,7 @@ def _get_dependencies_from_metadata(self, candidate: Candidate) -> CandidateInfo prepared = candidate.prepare(self.environment) deps = prepared.get_dependencies_from_metadata() requires_python = candidate.requires_python - summary = prepared.metadata.metadata["Summary"] + summary = prepared.metadata.metadata.get("Summary", "") return deps, requires_python, summary def _get_dependency_from_local_package(self, candidate: Candidate) -> CandidateInfo: diff --git a/src/pdm/models/requirements.py b/src/pdm/models/requirements.py index 37c644f803..dcf51d366b 100644 --- a/src/pdm/models/requirements.py +++ b/src/pdm/models/requirements.py @@ -152,7 +152,7 @@ def from_dist(cls, dist: Distribution) -> Requirement: if direct_url_json is not None: direct_url = json.loads(direct_url_json) data = { - "name": dist.metadata["Name"], + "name": dist.metadata.get("Name"), "url": direct_url.get("url"), "editable": direct_url.get("dir_info", {}).get("editable"), "subdirectory": direct_url.get("subdirectory"), diff --git a/src/pdm/models/specifiers.py b/src/pdm/models/specifiers.py index c4af376ba2..5fc9d38929 100644 --- a/src/pdm/models/specifiers.py +++ b/src/pdm/models/specifiers.py @@ -32,7 +32,7 @@ def _read_max_versions() -> dict[Version, int]: @lru_cache -def get_specifier(version_str: str) -> SpecifierSet: +def get_specifier(version_str: str | None) -> SpecifierSet: if not version_str or version_str == "*": return SpecifierSet() return SpecifierSet(fix_legacy_specifier(version_str)) diff --git a/src/pdm/models/working_set.py b/src/pdm/models/working_set.py index 4d535613f8..78aea3531c 100644 --- a/src/pdm/models/working_set.py +++ b/src/pdm/models/working_set.py @@ -69,12 +69,12 @@ def __init__(self, paths: list[str] | None = None, shared_paths: list[str] | Non self._dist_map = { normalize_name(dist.metadata["Name"]): dist for dist in distributions(path=list(dict.fromkeys(paths))) - if dist.metadata["Name"] + if dist.metadata.get("Name") } self._shared_map = { normalize_name(dist.metadata["Name"]): dist for dist in distributions(path=list(dict.fromkeys(shared_paths))) - if dist.metadata["Name"] + if dist.metadata.get("Name") } self._iter_map = ChainMap(self._dist_map, self._shared_map) diff --git a/src/pdm/utils.py b/src/pdm/utils.py index 212e167771..4813884d60 100644 --- a/src/pdm/utils.py +++ b/src/pdm/utils.py @@ -441,7 +441,7 @@ def is_pip_compatible_with_python(python_version: Version | str) -> bool: from pdm.models.specifiers import get_specifier pip = importlib_metadata.distribution("pip") - requires_python = get_specifier(pip.metadata["Requires-Python"]) + requires_python = get_specifier(pip.metadata.get("Requires-Python")) return requires_python.contains(python_version, True)