Skip to content

Commit 731b7ea

Browse files
committed
Standardize and centralize StrPath TypeAlias
1 parent 8c45d6e commit 731b7ea

13 files changed

+86
-85
lines changed

newsfragments/4241.misc.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improvements to `Path`-related type annotations when it could be ``str | PathLike`` -- by :user:`Avasam`

setuptools/_normalization.py

-4
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,9 @@
44
"""
55

66
import re
7-
from pathlib import Path
8-
from typing import Union
97

108
from .extern import packaging
119

12-
_Path = Union[str, Path]
13-
1410
# https://packaging.python.org/en/latest/specifications/core-metadata/#name
1511
_VALID_NAME = re.compile(r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.I)
1612
_UNSAFE_NAME_CHARS = re.compile(r"[^A-Z0-9._-]+", re.I)

setuptools/_path.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
import sys
33
from typing import Union
44

5-
_Path = Union[str, os.PathLike]
5+
if sys.version_info >= (3, 9):
6+
StrPath = Union[str, os.PathLike[str]] # Same as _typeshed.StrPath
7+
else:
8+
StrPath = Union[str, os.PathLike]
69

710

811
def ensure_directory(path):
@@ -11,7 +14,7 @@ def ensure_directory(path):
1114
os.makedirs(dirname, exist_ok=True)
1215

1316

14-
def same_path(p1: _Path, p2: _Path) -> bool:
17+
def same_path(p1: StrPath, p2: StrPath) -> bool:
1518
"""Differs from os.path.samefile because it does not require paths to exist.
1619
Purely string based (no comparison between i-nodes).
1720
>>> same_path("a/b", "./a/b")
@@ -30,7 +33,7 @@ def same_path(p1: _Path, p2: _Path) -> bool:
3033
return normpath(p1) == normpath(p2)
3134

3235

33-
def normpath(filename: _Path) -> str:
36+
def normpath(filename: StrPath) -> str:
3437
"""Normalize a file/dir name for comparison purposes."""
3538
# See pkg_resources.normalize_path for notes about cygwin
3639
file = os.path.abspath(filename) if sys.platform == 'cygwin' else filename

setuptools/command/editable_wheel.py

+14-11
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
Protocol,
3434
Tuple,
3535
TypeVar,
36-
Union,
3736
)
3837

3938
from .. import (
@@ -43,6 +42,7 @@
4342
errors,
4443
namespaces,
4544
)
45+
from .._path import StrPath
4646
from ..discovery import find_package_path
4747
from ..dist import Distribution
4848
from ..warnings import (
@@ -55,8 +55,7 @@
5555
if TYPE_CHECKING:
5656
from wheel.wheelfile import WheelFile # noqa
5757

58-
_Path = Union[str, Path]
59-
_P = TypeVar("_P", bound=_Path)
58+
_P = TypeVar("_P", bound=StrPath)
6059
_logger = logging.getLogger(__name__)
6160

6261

@@ -181,7 +180,7 @@ def _find_egg_info_dir(self) -> Optional[str]:
181180
return next(candidates, None)
182181

183182
def _configure_build(
184-
self, name: str, unpacked_wheel: _Path, build_lib: _Path, tmp_dir: _Path
183+
self, name: str, unpacked_wheel: StrPath, build_lib: StrPath, tmp_dir: StrPath
185184
):
186185
"""Configure commands to behave in the following ways:
187186
@@ -256,7 +255,11 @@ def _collect_build_outputs(self) -> Tuple[List[str], Dict[str, str]]:
256255
return files, mapping
257256

258257
def _run_build_commands(
259-
self, dist_name: str, unpacked_wheel: _Path, build_lib: _Path, tmp_dir: _Path
258+
self,
259+
dist_name: str,
260+
unpacked_wheel: StrPath,
261+
build_lib: StrPath,
262+
tmp_dir: StrPath,
260263
) -> Tuple[List[str], Dict[str, str]]:
261264
self._configure_build(dist_name, unpacked_wheel, build_lib, tmp_dir)
262265
self._run_build_subcommands()
@@ -354,7 +357,7 @@ def _select_strategy(
354357
self,
355358
name: str,
356359
tag: str,
357-
build_lib: _Path,
360+
build_lib: StrPath,
358361
) -> "EditableStrategy":
359362
"""Decides which strategy to use to implement an editable installation."""
360363
build_name = f"__editable__.{name}-{tag}"
@@ -424,8 +427,8 @@ def __init__(
424427
self,
425428
dist: Distribution,
426429
name: str,
427-
auxiliary_dir: _Path,
428-
build_lib: _Path,
430+
auxiliary_dir: StrPath,
431+
build_lib: StrPath,
429432
):
430433
self.auxiliary_dir = Path(auxiliary_dir)
431434
self.build_lib = Path(build_lib).resolve()
@@ -567,7 +570,7 @@ def _can_symlink_files(base_dir: Path) -> bool:
567570

568571

569572
def _simple_layout(
570-
packages: Iterable[str], package_dir: Dict[str, str], project_dir: Path
573+
packages: Iterable[str], package_dir: Dict[str, str], project_dir: StrPath
571574
) -> bool:
572575
"""Return ``True`` if:
573576
- all packages are contained by the same parent directory, **and**
@@ -649,7 +652,7 @@ def _find_top_level_modules(dist: Distribution) -> Iterator[str]:
649652
def _find_package_roots(
650653
packages: Iterable[str],
651654
package_dir: Mapping[str, str],
652-
src_root: _Path,
655+
src_root: StrPath,
653656
) -> Dict[str, str]:
654657
pkg_roots: Dict[str, str] = {
655658
pkg: _absolute_root(find_package_path(pkg, package_dir, src_root))
@@ -659,7 +662,7 @@ def _find_package_roots(
659662
return _remove_nested(pkg_roots)
660663

661664

662-
def _absolute_root(path: _Path) -> str:
665+
def _absolute_root(path: StrPath) -> str:
663666
"""Works for packages and top-level modules"""
664667
path_ = Path(path)
665668
parent = path_.parent

setuptools/config/_apply_pyprojecttoml.py

+9-10
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
Union,
3030
cast,
3131
)
32-
32+
from .._path import StrPath
3333
from ..errors import RemovedConfigError
3434
from ..warnings import SetuptoolsWarning
3535

@@ -38,15 +38,14 @@
3838
from setuptools.dist import Distribution # noqa
3939

4040
EMPTY: Mapping = MappingProxyType({}) # Immutable dict-like
41-
_Path = Union[os.PathLike, str]
4241
_DictOrStr = Union[dict, str]
43-
_CorrespFn = Callable[["Distribution", Any, _Path], None]
42+
_CorrespFn = Callable[["Distribution", Any, StrPath], None]
4443
_Correspondence = Union[str, _CorrespFn]
4544

4645
_logger = logging.getLogger(__name__)
4746

4847

49-
def apply(dist: "Distribution", config: dict, filename: _Path) -> "Distribution":
48+
def apply(dist: "Distribution", config: dict, filename: StrPath) -> "Distribution":
5049
"""Apply configuration dict read with :func:`read_configuration`"""
5150

5251
if not config:
@@ -68,7 +67,7 @@ def apply(dist: "Distribution", config: dict, filename: _Path) -> "Distribution"
6867
return dist
6968

7069

71-
def _apply_project_table(dist: "Distribution", config: dict, root_dir: _Path):
70+
def _apply_project_table(dist: "Distribution", config: dict, root_dir: StrPath):
7271
project_table = config.get("project", {}).copy()
7372
if not project_table:
7473
return # short-circuit
@@ -85,7 +84,7 @@ def _apply_project_table(dist: "Distribution", config: dict, root_dir: _Path):
8584
_set_config(dist, corresp, value)
8685

8786

88-
def _apply_tool_table(dist: "Distribution", config: dict, filename: _Path):
87+
def _apply_tool_table(dist: "Distribution", config: dict, filename: StrPath):
8988
tool_table = config.get("tool", {}).get("setuptools", {})
9089
if not tool_table:
9190
return # short-circuit
@@ -153,7 +152,7 @@ def _guess_content_type(file: str) -> Optional[str]:
153152
raise ValueError(f"Undefined content type for {file}, {msg}")
154153

155154

156-
def _long_description(dist: "Distribution", val: _DictOrStr, root_dir: _Path):
155+
def _long_description(dist: "Distribution", val: _DictOrStr, root_dir: StrPath):
157156
from setuptools.config import expand
158157

159158
if isinstance(val, str):
@@ -174,7 +173,7 @@ def _long_description(dist: "Distribution", val: _DictOrStr, root_dir: _Path):
174173
dist._referenced_files.add(cast(str, file))
175174

176175

177-
def _license(dist: "Distribution", val: dict, root_dir: _Path):
176+
def _license(dist: "Distribution", val: dict, root_dir: StrPath):
178177
from setuptools.config import expand
179178

180179
if "file" in val:
@@ -184,7 +183,7 @@ def _license(dist: "Distribution", val: dict, root_dir: _Path):
184183
_set_config(dist, "license", val["text"])
185184

186185

187-
def _people(dist: "Distribution", val: List[dict], _root_dir: _Path, kind: str):
186+
def _people(dist: "Distribution", val: List[dict], _root_dir: StrPath, kind: str):
188187
field = []
189188
email_field = []
190189
for person in val:
@@ -244,7 +243,7 @@ def _unify_entry_points(project_table: dict):
244243
# intentional (for resetting configurations that are missing `dynamic`).
245244

246245

247-
def _copy_command_options(pyproject: dict, dist: "Distribution", filename: _Path):
246+
def _copy_command_options(pyproject: dict, dist: "Distribution", filename: StrPath):
248247
tool_table = pyproject.get("tool", {})
249248
cmdclass = tool_table.get("setuptools", {}).get("cmdclass", {})
250249
valid_options = _valid_command_options(cmdclass)

setuptools/config/expand.py

+15-16
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646

4747
from distutils.errors import DistutilsOptionError
4848

49-
from .._path import same_path as _same_path
49+
from .._path import same_path as _same_path, StrPath
5050
from ..warnings import SetuptoolsWarning
5151

5252
if TYPE_CHECKING:
@@ -55,7 +55,6 @@
5555
from distutils.dist import DistributionMetadata # noqa
5656

5757
chain_iter = chain.from_iterable
58-
_Path = Union[str, os.PathLike]
5958
_K = TypeVar("_K")
6059
_V = TypeVar("_V", covariant=True)
6160

@@ -88,7 +87,7 @@ def __getattr__(self, attr):
8887

8988

9089
def glob_relative(
91-
patterns: Iterable[str], root_dir: Optional[_Path] = None
90+
patterns: Iterable[str], root_dir: Optional[StrPath] = None
9291
) -> List[str]:
9392
"""Expand the list of glob patterns, but preserving relative paths.
9493
@@ -120,7 +119,7 @@ def glob_relative(
120119
return expanded_values
121120

122121

123-
def read_files(filepaths: Union[str, bytes, Iterable[_Path]], root_dir=None) -> str:
122+
def read_files(filepaths: Union[str, bytes, Iterable[StrPath]], root_dir=None) -> str:
124123
"""Return the content of the files concatenated using ``\n`` as str
125124
126125
This function is sandboxed and won't reach anything outside ``root_dir``
@@ -138,20 +137,20 @@ def read_files(filepaths: Union[str, bytes, Iterable[_Path]], root_dir=None) ->
138137
)
139138

140139

141-
def _filter_existing_files(filepaths: Iterable[_Path]) -> Iterator[_Path]:
140+
def _filter_existing_files(filepaths: Iterable[StrPath]) -> Iterator[StrPath]:
142141
for path in filepaths:
143142
if os.path.isfile(path):
144143
yield path
145144
else:
146145
SetuptoolsWarning.emit(f"File {path!r} cannot be found")
147146

148147

149-
def _read_file(filepath: Union[bytes, _Path]) -> str:
148+
def _read_file(filepath: Union[bytes, StrPath]) -> str:
150149
with open(filepath, encoding='utf-8') as f:
151150
return f.read()
152151

153152

154-
def _assert_local(filepath: _Path, root_dir: str):
153+
def _assert_local(filepath: StrPath, root_dir: str):
155154
if Path(os.path.abspath(root_dir)) not in Path(os.path.abspath(filepath)).parents:
156155
msg = f"Cannot access {filepath!r} (or anything outside {root_dir!r})"
157156
raise DistutilsOptionError(msg)
@@ -162,7 +161,7 @@ def _assert_local(filepath: _Path, root_dir: str):
162161
def read_attr(
163162
attr_desc: str,
164163
package_dir: Optional[Mapping[str, str]] = None,
165-
root_dir: Optional[_Path] = None,
164+
root_dir: Optional[StrPath] = None,
166165
):
167166
"""Reads the value of an attribute from a module.
168167
@@ -197,7 +196,7 @@ def read_attr(
197196
return getattr(module, attr_name)
198197

199198

200-
def _find_spec(module_name: str, module_path: Optional[_Path]) -> ModuleSpec:
199+
def _find_spec(module_name: str, module_path: Optional[StrPath]) -> ModuleSpec:
201200
spec = importlib.util.spec_from_file_location(module_name, module_path)
202201
spec = spec or importlib.util.find_spec(module_name)
203202

@@ -218,8 +217,8 @@ def _load_spec(spec: ModuleSpec, module_name: str) -> ModuleType:
218217

219218

220219
def _find_module(
221-
module_name: str, package_dir: Optional[Mapping[str, str]], root_dir: _Path
222-
) -> Tuple[_Path, Optional[str], str]:
220+
module_name: str, package_dir: Optional[Mapping[str, str]], root_dir: StrPath
221+
) -> Tuple[StrPath, Optional[str], str]:
223222
"""Given a module (that could normally be imported by ``module_name``
224223
after the build is complete), find the path to the parent directory where
225224
it is contained and the canonical name that could be used to import it
@@ -254,7 +253,7 @@ def _find_module(
254253
def resolve_class(
255254
qualified_class_name: str,
256255
package_dir: Optional[Mapping[str, str]] = None,
257-
root_dir: Optional[_Path] = None,
256+
root_dir: Optional[StrPath] = None,
258257
) -> Callable:
259258
"""Given a qualified class name, return the associated class object"""
260259
root_dir = root_dir or os.getcwd()
@@ -270,7 +269,7 @@ def resolve_class(
270269
def cmdclass(
271270
values: Dict[str, str],
272271
package_dir: Optional[Mapping[str, str]] = None,
273-
root_dir: Optional[_Path] = None,
272+
root_dir: Optional[StrPath] = None,
274273
) -> Dict[str, Callable]:
275274
"""Given a dictionary mapping command names to strings for qualified class
276275
names, apply :func:`resolve_class` to the dict values.
@@ -282,7 +281,7 @@ def find_packages(
282281
*,
283282
namespaces=True,
284283
fill_package_dir: Optional[Dict[str, str]] = None,
285-
root_dir: Optional[_Path] = None,
284+
root_dir: Optional[StrPath] = None,
286285
**kwargs,
287286
) -> List[str]:
288287
"""Works similarly to :func:`setuptools.find_packages`, but with all
@@ -331,7 +330,7 @@ def find_packages(
331330
return packages
332331

333332

334-
def _nest_path(parent: _Path, path: _Path) -> str:
333+
def _nest_path(parent: StrPath, path: StrPath) -> str:
335334
path = parent if path in {".", ""} else os.path.join(parent, path)
336335
return os.path.normpath(path)
337336

@@ -361,7 +360,7 @@ def canonic_package_data(package_data: dict) -> dict:
361360

362361

363362
def canonic_data_files(
364-
data_files: Union[list, dict], root_dir: Optional[_Path] = None
363+
data_files: Union[list, dict], root_dir: Optional[StrPath] = None
365364
) -> List[Tuple[str, List[str]]]:
366365
"""For compatibility with ``setup.py``, ``data_files`` should be a list
367366
of pairs instead of a dict.

0 commit comments

Comments
 (0)