Skip to content

Commit

Permalink
Merge pull request #4241 from Avasam/StrPath-TypeAlias
Browse files Browse the repository at this point in the history
Standardize and centralize `StrPath` TypeAlias
  • Loading branch information
jaraco committed Mar 3, 2024
2 parents 8c45d6e + 2d8ab86 commit 80da90c
Show file tree
Hide file tree
Showing 12 changed files with 85 additions and 83 deletions.
1 change: 1 addition & 0 deletions newsfragments/4241.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improvements to `Path`-related type annotations when it could be ``str | PathLike`` -- by :user:`Avasam`
4 changes: 0 additions & 4 deletions setuptools/_normalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,9 @@
"""

import re
from pathlib import Path
from typing import Union

from .extern import packaging

_Path = Union[str, Path]

# https://packaging.python.org/en/latest/specifications/core-metadata/#name
_VALID_NAME = re.compile(r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.I)
_UNSAFE_NAME_CHARS = re.compile(r"[^A-Z0-9._-]+", re.I)
Expand Down
9 changes: 6 additions & 3 deletions setuptools/_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
import sys
from typing import Union

_Path = Union[str, os.PathLike]
if sys.version_info >= (3, 9):
StrPath = Union[str, os.PathLike[str]] # Same as _typeshed.StrPath
else:
StrPath = Union[str, os.PathLike]


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


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


def normpath(filename: _Path) -> str:
def normpath(filename: StrPath) -> str:
"""Normalize a file/dir name for comparison purposes."""
# See pkg_resources.normalize_path for notes about cygwin
file = os.path.abspath(filename) if sys.platform == 'cygwin' else filename
Expand Down
25 changes: 14 additions & 11 deletions setuptools/command/editable_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
Protocol,
Tuple,
TypeVar,
Union,
)

from .. import (
Expand All @@ -43,6 +42,7 @@
errors,
namespaces,
)
from .._path import StrPath
from ..discovery import find_package_path
from ..dist import Distribution
from ..warnings import (
Expand All @@ -55,8 +55,7 @@
if TYPE_CHECKING:
from wheel.wheelfile import WheelFile # noqa

_Path = Union[str, Path]
_P = TypeVar("_P", bound=_Path)
_P = TypeVar("_P", bound=StrPath)
_logger = logging.getLogger(__name__)


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

def _configure_build(
self, name: str, unpacked_wheel: _Path, build_lib: _Path, tmp_dir: _Path
self, name: str, unpacked_wheel: StrPath, build_lib: StrPath, tmp_dir: StrPath
):
"""Configure commands to behave in the following ways:
Expand Down Expand Up @@ -256,7 +255,11 @@ def _collect_build_outputs(self) -> Tuple[List[str], Dict[str, str]]:
return files, mapping

def _run_build_commands(
self, dist_name: str, unpacked_wheel: _Path, build_lib: _Path, tmp_dir: _Path
self,
dist_name: str,
unpacked_wheel: StrPath,
build_lib: StrPath,
tmp_dir: StrPath,
) -> Tuple[List[str], Dict[str, str]]:
self._configure_build(dist_name, unpacked_wheel, build_lib, tmp_dir)
self._run_build_subcommands()
Expand Down Expand Up @@ -354,7 +357,7 @@ def _select_strategy(
self,
name: str,
tag: str,
build_lib: _Path,
build_lib: StrPath,
) -> "EditableStrategy":
"""Decides which strategy to use to implement an editable installation."""
build_name = f"__editable__.{name}-{tag}"
Expand Down Expand Up @@ -424,8 +427,8 @@ def __init__(
self,
dist: Distribution,
name: str,
auxiliary_dir: _Path,
build_lib: _Path,
auxiliary_dir: StrPath,
build_lib: StrPath,
):
self.auxiliary_dir = Path(auxiliary_dir)
self.build_lib = Path(build_lib).resolve()
Expand Down Expand Up @@ -567,7 +570,7 @@ def _can_symlink_files(base_dir: Path) -> bool:


def _simple_layout(
packages: Iterable[str], package_dir: Dict[str, str], project_dir: Path
packages: Iterable[str], package_dir: Dict[str, str], project_dir: StrPath
) -> bool:
"""Return ``True`` if:
- all packages are contained by the same parent directory, **and**
Expand Down Expand Up @@ -649,7 +652,7 @@ def _find_top_level_modules(dist: Distribution) -> Iterator[str]:
def _find_package_roots(
packages: Iterable[str],
package_dir: Mapping[str, str],
src_root: _Path,
src_root: StrPath,
) -> Dict[str, str]:
pkg_roots: Dict[str, str] = {
pkg: _absolute_root(find_package_path(pkg, package_dir, src_root))
Expand All @@ -659,7 +662,7 @@ def _find_package_roots(
return _remove_nested(pkg_roots)


def _absolute_root(path: _Path) -> str:
def _absolute_root(path: StrPath) -> str:
"""Works for packages and top-level modules"""
path_ = Path(path)
parent = path_.parent
Expand Down
19 changes: 9 additions & 10 deletions setuptools/config/_apply_pyprojecttoml.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
Union,
cast,
)

from .._path import StrPath
from ..errors import RemovedConfigError
from ..warnings import SetuptoolsWarning

Expand All @@ -38,15 +38,14 @@
from setuptools.dist import Distribution # noqa

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

_logger = logging.getLogger(__name__)


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

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


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


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


def _long_description(dist: "Distribution", val: _DictOrStr, root_dir: _Path):
def _long_description(dist: "Distribution", val: _DictOrStr, root_dir: StrPath):
from setuptools.config import expand

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


def _license(dist: "Distribution", val: dict, root_dir: _Path):
def _license(dist: "Distribution", val: dict, root_dir: StrPath):
from setuptools.config import expand

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


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


def _copy_command_options(pyproject: dict, dist: "Distribution", filename: _Path):
def _copy_command_options(pyproject: dict, dist: "Distribution", filename: StrPath):
tool_table = pyproject.get("tool", {})
cmdclass = tool_table.get("setuptools", {}).get("cmdclass", {})
valid_options = _valid_command_options(cmdclass)
Expand Down
31 changes: 15 additions & 16 deletions setuptools/config/expand.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@

from distutils.errors import DistutilsOptionError

from .._path import same_path as _same_path
from .._path import same_path as _same_path, StrPath
from ..warnings import SetuptoolsWarning

if TYPE_CHECKING:
Expand All @@ -55,7 +55,6 @@
from distutils.dist import DistributionMetadata # noqa

chain_iter = chain.from_iterable
_Path = Union[str, os.PathLike]
_K = TypeVar("_K")
_V = TypeVar("_V", covariant=True)

Expand Down Expand Up @@ -88,7 +87,7 @@ def __getattr__(self, attr):


def glob_relative(
patterns: Iterable[str], root_dir: Optional[_Path] = None
patterns: Iterable[str], root_dir: Optional[StrPath] = None
) -> List[str]:
"""Expand the list of glob patterns, but preserving relative paths.
Expand Down Expand Up @@ -120,7 +119,7 @@ def glob_relative(
return expanded_values


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


def _filter_existing_files(filepaths: Iterable[_Path]) -> Iterator[_Path]:
def _filter_existing_files(filepaths: Iterable[StrPath]) -> Iterator[StrPath]:
for path in filepaths:
if os.path.isfile(path):
yield path
else:
SetuptoolsWarning.emit(f"File {path!r} cannot be found")


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


def _assert_local(filepath: _Path, root_dir: str):
def _assert_local(filepath: StrPath, root_dir: str):
if Path(os.path.abspath(root_dir)) not in Path(os.path.abspath(filepath)).parents:
msg = f"Cannot access {filepath!r} (or anything outside {root_dir!r})"
raise DistutilsOptionError(msg)
Expand All @@ -162,7 +161,7 @@ def _assert_local(filepath: _Path, root_dir: str):
def read_attr(
attr_desc: str,
package_dir: Optional[Mapping[str, str]] = None,
root_dir: Optional[_Path] = None,
root_dir: Optional[StrPath] = None,
):
"""Reads the value of an attribute from a module.
Expand Down Expand Up @@ -197,7 +196,7 @@ def read_attr(
return getattr(module, attr_name)


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

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


def _find_module(
module_name: str, package_dir: Optional[Mapping[str, str]], root_dir: _Path
) -> Tuple[_Path, Optional[str], str]:
module_name: str, package_dir: Optional[Mapping[str, str]], root_dir: StrPath
) -> Tuple[StrPath, Optional[str], str]:
"""Given a module (that could normally be imported by ``module_name``
after the build is complete), find the path to the parent directory where
it is contained and the canonical name that could be used to import it
Expand Down Expand Up @@ -254,7 +253,7 @@ def _find_module(
def resolve_class(
qualified_class_name: str,
package_dir: Optional[Mapping[str, str]] = None,
root_dir: Optional[_Path] = None,
root_dir: Optional[StrPath] = None,
) -> Callable:
"""Given a qualified class name, return the associated class object"""
root_dir = root_dir or os.getcwd()
Expand All @@ -270,7 +269,7 @@ def resolve_class(
def cmdclass(
values: Dict[str, str],
package_dir: Optional[Mapping[str, str]] = None,
root_dir: Optional[_Path] = None,
root_dir: Optional[StrPath] = None,
) -> Dict[str, Callable]:
"""Given a dictionary mapping command names to strings for qualified class
names, apply :func:`resolve_class` to the dict values.
Expand All @@ -282,7 +281,7 @@ def find_packages(
*,
namespaces=True,
fill_package_dir: Optional[Dict[str, str]] = None,
root_dir: Optional[_Path] = None,
root_dir: Optional[StrPath] = None,
**kwargs,
) -> List[str]:
"""Works similarly to :func:`setuptools.find_packages`, but with all
Expand Down Expand Up @@ -331,7 +330,7 @@ def find_packages(
return packages


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

Expand Down Expand Up @@ -361,7 +360,7 @@ def canonic_package_data(package_data: dict) -> dict:


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

0 comments on commit 80da90c

Please sign in to comment.