_pytest._py.path: get mypy passing

This commit is contained in:
Anthony Sottile 2022-10-19 19:26:44 -04:00
parent ed4c18f686
commit 59d8f8a223
1 changed files with 58 additions and 24 deletions

View File

@ -22,9 +22,16 @@ from os.path import normpath
from stat import S_ISDIR from stat import S_ISDIR
from stat import S_ISLNK from stat import S_ISLNK
from stat import S_ISREG from stat import S_ISREG
from typing import Any
from typing import Callable
from typing import overload
from typing import TYPE_CHECKING
from . import error from . import error
if TYPE_CHECKING:
from typing import Literal
# Moved from local.py. # Moved from local.py.
iswin32 = sys.platform == "win32" or (getattr(os, "_name", False) == "nt") iswin32 = sys.platform == "win32" or (getattr(os, "_name", False) == "nt")
@ -96,7 +103,9 @@ class Checkers:
return False return False
return True return True
def _stat(self): _statcache: Stat
def _stat(self) -> Stat:
try: try:
return self._statcache return self._statcache
except AttributeError: except AttributeError:
@ -129,7 +138,7 @@ class Visitor:
if isinstance(fil, str): if isinstance(fil, str):
fil = FNMatcher(fil) fil = FNMatcher(fil)
if isinstance(rec, str): if isinstance(rec, str):
self.rec = FNMatcher(rec) self.rec: Callable[[LocalPath], bool] = FNMatcher(rec)
elif not hasattr(rec, "__call__") and rec: elif not hasattr(rec, "__call__") and rec:
self.rec = lambda path: True self.rec = lambda path: True
else: else:
@ -192,7 +201,17 @@ def map_as_list(func, iter):
class Stat: class Stat:
def __getattr__(self, name): if TYPE_CHECKING:
@property
def size(self) -> int:
...
@property
def mtime(self) -> float:
...
def __getattr__(self, name: str) -> Any:
return getattr(self._osstatresult, "st_" + name) return getattr(self._osstatresult, "st_" + name)
def __init__(self, path, osstatresult): def __init__(self, path, osstatresult):
@ -295,9 +314,10 @@ class LocalPath:
error.checked_call(os.chown, str(x), uid, gid) error.checked_call(os.chown, str(x), uid, gid)
error.checked_call(os.chown, str(self), uid, gid) error.checked_call(os.chown, str(self), uid, gid)
def readlink(self): def readlink(self) -> str:
"""Return value of a symbolic link.""" """Return value of a symbolic link."""
return error.checked_call(os.readlink, self.strpath) # https://github.com/python/mypy/issues/12278
return error.checked_call(os.readlink, self.strpath) # type: ignore[arg-type,return-value]
def mklinkto(self, oldname): def mklinkto(self, oldname):
"""Posix style hard link to another name.""" """Posix style hard link to another name."""
@ -659,22 +679,21 @@ class LocalPath:
obj.strpath = normpath("%(dirname)s%(sep)s%(basename)s" % kw) obj.strpath = normpath("%(dirname)s%(sep)s%(basename)s" % kw)
return obj return obj
def _getbyspec(self, spec): def _getbyspec(self, spec: str) -> list[str]:
"""See new for what 'spec' can be.""" """See new for what 'spec' can be."""
res = [] res = []
parts = self.strpath.split(self.sep) parts = self.strpath.split(self.sep)
args = filter(None, spec.split(",")) args = filter(None, spec.split(","))
append = res.append
for name in args: for name in args:
if name == "drive": if name == "drive":
append(parts[0]) res.append(parts[0])
elif name == "dirname": elif name == "dirname":
append(self.sep.join(parts[:-1])) res.append(self.sep.join(parts[:-1]))
else: else:
basename = parts[-1] basename = parts[-1]
if name == "basename": if name == "basename":
append(basename) res.append(basename)
else: else:
i = basename.rfind(".") i = basename.rfind(".")
if i == -1: if i == -1:
@ -682,9 +701,9 @@ class LocalPath:
else: else:
purebasename, ext = basename[:i], basename[i:] purebasename, ext = basename[:i], basename[i:]
if name == "purebasename": if name == "purebasename":
append(purebasename) res.append(purebasename)
elif name == "ext": elif name == "ext":
append(ext) res.append(ext)
else: else:
raise ValueError("invalid part specification %r" % name) raise ValueError("invalid part specification %r" % name)
return res return res
@ -699,7 +718,7 @@ class LocalPath:
return path return path
return self.new(basename="").join(*args, **kwargs) return self.new(basename="").join(*args, **kwargs)
def join(self, *args, **kwargs): def join(self, *args: os.PathLike[str], abs: bool = False) -> LocalPath:
"""Return a new path by appending all 'args' as path """Return a new path by appending all 'args' as path
components. if abs=1 is used restart from root if any components. if abs=1 is used restart from root if any
of the args is an absolute path. of the args is an absolute path.
@ -707,8 +726,8 @@ class LocalPath:
sep = self.sep sep = self.sep
strargs = [os.fspath(arg) for arg in args] strargs = [os.fspath(arg) for arg in args]
strpath = self.strpath strpath = self.strpath
if kwargs.get("abs"): if abs:
newargs = [] newargs: list[str] = []
for arg in reversed(strargs): for arg in reversed(strargs):
if isabs(arg): if isabs(arg):
strpath = arg strpath = arg
@ -801,11 +820,11 @@ class LocalPath:
self._sortlist(res, sort) self._sortlist(res, sort)
return res return res
def size(self): def size(self) -> int:
"""Return size of the underlying file object""" """Return size of the underlying file object"""
return self.stat().size return self.stat().size
def mtime(self): def mtime(self) -> float:
"""Return last modification time of the path.""" """Return last modification time of the path."""
return self.stat().mtime return self.stat().mtime
@ -936,7 +955,15 @@ class LocalPath:
p.open("w").close() p.open("w").close()
return p return p
def stat(self, raising=True): @overload
def stat(self, raising: Literal[True] = ...) -> Stat:
...
@overload
def stat(self, raising: Literal[False]) -> Stat | None:
...
def stat(self, raising: bool = True) -> Stat | None:
"""Return an os.stat() tuple.""" """Return an os.stat() tuple."""
if raising: if raising:
return Stat(self, error.checked_call(os.stat, self.strpath)) return Stat(self, error.checked_call(os.stat, self.strpath))
@ -947,7 +974,7 @@ class LocalPath:
except Exception: except Exception:
return None return None
def lstat(self): def lstat(self) -> Stat:
"""Return an os.lstat() tuple.""" """Return an os.lstat() tuple."""
return Stat(self, error.checked_call(os.lstat, self.strpath)) return Stat(self, error.checked_call(os.lstat, self.strpath))
@ -1067,7 +1094,7 @@ class LocalPath:
if modname is None: if modname is None:
modname = self.purebasename modname = self.purebasename
spec = importlib.util.spec_from_file_location(modname, str(self)) spec = importlib.util.spec_from_file_location(modname, str(self))
if spec is None: if spec is None or spec.loader is None:
raise ImportError( raise ImportError(
f"Can't find module {modname} at location {str(self)}" f"Can't find module {modname} at location {str(self)}"
) )
@ -1095,6 +1122,7 @@ class LocalPath:
return mod # we don't check anything as we might return mod # we don't check anything as we might
# be in a namespace package ... too icky to check # be in a namespace package ... too icky to check
modfile = mod.__file__ modfile = mod.__file__
assert modfile is not None
if modfile[-4:] in (".pyc", ".pyo"): if modfile[-4:] in (".pyc", ".pyo"):
modfile = modfile[:-1] modfile = modfile[:-1]
elif modfile.endswith("$py.class"): elif modfile.endswith("$py.class"):
@ -1129,16 +1157,22 @@ class LocalPath:
raise raise
return mod return mod
def sysexec(self, *argv, **popen_opts): def sysexec(self, *argv: os.PathLike[str], **popen_opts: Any) -> str:
"""Return stdout text from executing a system child process, """Return stdout text from executing a system child process,
where the 'self' path points to executable. where the 'self' path points to executable.
The process is directly invoked and not through a system shell. The process is directly invoked and not through a system shell.
""" """
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
argv = map_as_list(str, argv) popen_opts.pop("stdout", None)
popen_opts["stdout"] = popen_opts["stderr"] = PIPE popen_opts.pop("stderr", None)
proc = Popen([str(self)] + argv, **popen_opts) proc = Popen(
[str(self)] + [str(arg) for arg in argv],
**popen_opts,
stdout=PIPE,
stderr=PIPE,
)
stdout: str | bytes
stdout, stderr = proc.communicate() stdout, stderr = proc.communicate()
ret = proc.wait() ret = proc.wait()
if isinstance(stdout, bytes): if isinstance(stdout, bytes):