Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pip/_internal/metadata/importlib/_compat.py: 65%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

40 statements  

1from __future__ import annotations 

2 

3import importlib.metadata 

4import os 

5from typing import Any, Protocol, cast 

6 

7from pip._vendor.packaging.utils import NormalizedName, canonicalize_name 

8 

9 

10class BadMetadata(ValueError): 

11 def __init__(self, dist: importlib.metadata.Distribution, *, reason: str) -> None: 

12 self.dist = dist 

13 self.reason = reason 

14 

15 def __str__(self) -> str: 

16 return f"Bad metadata in {self.dist} ({self.reason})" 

17 

18 

19class BasePath(Protocol): 

20 """A protocol that various path objects conform. 

21 

22 This exists because importlib.metadata uses both ``pathlib.Path`` and 

23 ``zipfile.Path``, and we need a common base for type hints (Union does not 

24 work well since ``zipfile.Path`` is too new for our linter setup). 

25 

26 This does not mean to be exhaustive, but only contains things that present 

27 in both classes *that we need*. 

28 """ 

29 

30 @property 

31 def name(self) -> str: 

32 raise NotImplementedError() 

33 

34 @property 

35 def parent(self) -> BasePath: 

36 raise NotImplementedError() 

37 

38 

39def get_info_location(d: importlib.metadata.Distribution) -> BasePath | None: 

40 """Find the path to the distribution's metadata directory. 

41 

42 HACK: This relies on importlib.metadata's private ``_path`` attribute. Not 

43 all distributions exist on disk, so importlib.metadata is correct to not 

44 expose the attribute as public. But pip's code base is old and not as clean, 

45 so we do this to avoid having to rewrite too many things. Hopefully we can 

46 eliminate this some day. 

47 """ 

48 return getattr(d, "_path", None) 

49 

50 

51def parse_name_and_version_from_info_directory( 

52 dist: importlib.metadata.Distribution, 

53) -> tuple[str | None, str | None]: 

54 """Get a name and version from the metadata directory name. 

55 

56 This is much faster than reading distribution metadata. 

57 """ 

58 info_location = get_info_location(dist) 

59 if info_location is None: 

60 return None, None 

61 

62 stem, suffix = os.path.splitext(info_location.name) 

63 if suffix == ".dist-info": 

64 name, sep, version = stem.partition("-") 

65 if sep: 

66 return name, version 

67 

68 if suffix == ".egg-info": 

69 name = stem.split("-", 1)[0] 

70 return name, None 

71 

72 return None, None 

73 

74 

75def get_dist_canonical_name(dist: importlib.metadata.Distribution) -> NormalizedName: 

76 """Get the distribution's normalized name. 

77 

78 The ``name`` attribute is only available in Python 3.10 or later. We are 

79 targeting exactly that, but Mypy does not know this. 

80 """ 

81 if name := parse_name_and_version_from_info_directory(dist)[0]: 

82 return canonicalize_name(name) 

83 

84 name = cast(Any, dist).name 

85 if not isinstance(name, str): 

86 raise BadMetadata(dist, reason="invalid metadata entry 'name'") 

87 return canonicalize_name(name)