-
Notifications
You must be signed in to change notification settings - Fork 255
/
Copy pathhelpers.py
124 lines (95 loc) · 3.18 KB
/
helpers.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
from __future__ import annotations
import shutil
import stat
import sys
import tempfile
import time
import unicodedata
from contextlib import contextmanager
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from packaging.utils import canonicalize_name
if TYPE_CHECKING:
from collections.abc import Iterator
def combine_unicode(string: str) -> str:
return unicodedata.normalize("NFC", string)
def module_name(name: str) -> str:
return canonicalize_name(name).replace("-", "_")
@contextmanager
def temporary_directory(*args: Any, **kwargs: Any) -> Iterator[Path]:
if sys.version_info >= (3, 10):
# mypy reports an error if ignore_cleanup_errors is
# specified literally in the call
kwargs["ignore_cleanup_errors"] = True
with tempfile.TemporaryDirectory(*args, **kwargs) as name:
yield Path(name)
else:
name = tempfile.mkdtemp(*args, **kwargs)
yield Path(name)
robust_rmtree(name)
def parse_requires(requires: str) -> list[str]:
lines = requires.split("\n")
requires_dist = []
current_marker = None
for line in lines:
line = line.strip()
if not line:
continue
if line.startswith("["):
# extras or conditional dependencies
marker = line.lstrip("[").rstrip("]")
if ":" not in marker:
extra, marker = marker, ""
else:
extra, marker = marker.split(":")
if extra:
if marker:
marker = f'{marker} and extra == "{extra}"'
else:
marker = f'extra == "{extra}"'
if marker:
current_marker = marker
continue
if current_marker:
line = f"{line} ; {current_marker}"
requires_dist.append(line)
return requires_dist
def _on_rm_error(func: Any, path: str | Path, exc_info: Any) -> None:
path = Path(path)
if not path.exists():
return
path.chmod(stat.S_IWRITE)
func(path)
def robust_rmtree(path: str | Path, max_timeout: float = 1) -> None:
"""
Robustly tries to delete paths.
Retries several times if an OSError occurs.
If the final attempt fails, the Exception is propagated
to the caller.
"""
path = Path(path) # make sure this is a Path object, not str
timeout = 0.001
while timeout < max_timeout:
try:
# both os.unlink and shutil.rmtree can throw exceptions on Windows
# if the files are in use when called
if path.is_symlink():
path.unlink()
else:
shutil.rmtree(path)
return # Only hits this on success
except OSError:
# Increase the timeout and try again
time.sleep(timeout)
timeout *= 2
# Final attempt, pass any Exceptions up to caller.
shutil.rmtree(path, onerror=_on_rm_error)
def readme_content_type(path: str | Path) -> str:
suffix = Path(path).suffix
if suffix == ".rst":
return "text/x-rst"
elif suffix in (".md", ".markdown"):
return "text/markdown"
else:
return "text/plain"