-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathutil.py
138 lines (116 loc) · 4.91 KB
/
util.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import base64
import hashlib
from keyword import iskeyword
import re
from typing import List, Optional, Tuple
from packaging.utils import canonicalize_name, canonicalize_version
from .errors import WheelValidationError
# <https://discuss.python.org/t/identifying-parsing-binary-extension-filenames/>
MODULE_EXT_RGX = re.compile(r"(?<=.)\.(?:py|pyd|so|[-A-Za-z0-9_]+\.(?:pyd|so))\Z")
DIST_INFO_DIR_RGX = re.compile(
r"[A-Za-z0-9](?:[A-Za-z0-9._]*[A-Za-z0-9])?-[A-Za-z0-9_.!+]+\.dist-info"
)
DATA_DIR_RGX = re.compile(
r"[A-Za-z0-9](?:[A-Za-z0-9._]*[A-Za-z0-9])?-[A-Za-z0-9_.!+]+\.data"
)
def comma_split(s: str) -> List[str]:
"""
Split apart a string on commas, discarding leading & trailing whitespace
from all parts and discarding empty parts
"""
return [k for k in map(str.strip, s.split(",")) if k]
def bytes_signature(b: bytes) -> Tuple[int, str]:
return (
len(b),
"sha256=" + urlsafe_b64encode_nopad(hashlib.sha256(b).digest()),
)
def urlsafe_b64encode_nopad(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b"=").decode("us-ascii")
def pymodule_basename(filename: str) -> Optional[str]:
"""
If ``filename`` (a filename without any directory components) has a file
extension indicating it is a Python module (either source or binary
extension), return the part before the extension; otherwise, return `None`.
"""
m = MODULE_EXT_RGX.search(filename)
if m is not None:
return filename[: m.start()]
else:
return None
def is_dist_info_dir(name: str) -> bool:
return DIST_INFO_DIR_RGX.fullmatch(name) is not None
def is_data_dir(name: str) -> bool:
return DATA_DIR_RGX.fullmatch(name) is not None
def validate_path(path: str) -> None:
if path.startswith("/"):
raise WheelValidationError(f"Absolute path in RECORD: {path!r}")
elif path == "":
raise WheelValidationError("Empty path in RECORD")
elif "//" in path:
raise WheelValidationError(f"Non-normalized path in RECORD: {path!r}")
parts = path.split("/")
if "." in parts or ".." in parts:
raise WheelValidationError(f"Non-normalized path in RECORD: {path!r}")
def is_stubs_dir(name: str) -> bool:
if not name.endswith("-stubs"):
return False
basename = name[:-6]
return basename.isidentifier() and not iskeyword(basename)
def find_wheel_dirs(
namelist: List[str], project: str, version: str
) -> Tuple[str, Optional[str]]:
"""
Given a list ``namelist`` of files in a wheel for a project ``project`` and
version ``version``, find & return the name of the wheel's ``.dist-info``
directory and (if it has one) its ``.data`` directory.
:raises WheelValidationError: if there is no unique ``.dist-info``
directory in the input
:raises WheelValidationError: if the name & version of the ``.dist-info``
directory are not normalization-equivalent to ``project`` & ``version``
:raises WheelValidationError: if there is more than one ``.data`` directory
in the input
:raises WheelValidationError: if the name & version of the ``.data``
directory are not normalization-equivalent to ``project`` & ``version``
"""
canon_project = canonicalize_name(project)
canon_version = canonicalize_version(version.replace("_", "-"))
dist_info_dirs = set()
data_dirs = set()
for n in namelist:
basename = n.rstrip("/").split("/")[0]
if is_dist_info_dir(basename):
dist_info_dirs.add(basename)
if is_data_dir(basename):
data_dirs.add(basename)
if len(dist_info_dirs) > 1:
raise WheelValidationError("Wheel contains multiple .dist-info directories")
elif len(dist_info_dirs) == 1:
dist_info_dir = next(iter(dist_info_dirs))
diname, _, diversion = dist_info_dir[: -len(".dist-info")].partition("-")
if (
canonicalize_name(diname) != canon_project
or canonicalize_version(diversion.replace("_", "-")) != canon_version
):
raise WheelValidationError(
f"Project & version of wheel's .dist-info directory do not"
f" match wheel name: {dist_info_dir!r}"
)
else:
raise WheelValidationError("No .dist-info directory in wheel")
data_dir: Optional[str]
if len(data_dirs) > 1:
raise WheelValidationError("Wheel contains multiple .data directories")
elif len(data_dirs) == 1:
data_dir = next(iter(data_dirs))
daname, _, daversion = data_dir[: -len(".data")].partition("-")
if (
canonicalize_name(daname) != canon_project
or canonicalize_version(daversion.replace("_", "-")) != canon_version
):
raise WheelValidationError(
f"Project & version of wheel's .data directory do not match"
f" wheel name: {data_dir!r}"
)
else:
data_dir = None
return (dist_info_dir, data_dir)