Skip to content

Commit 59d8f8a

Browse files
committed
_pytest._py.path: get mypy passing
1 parent ed4c18f commit 59d8f8a

File tree

1 file changed

+58
-24
lines changed

1 file changed

+58
-24
lines changed

src/_pytest/_py/path.py

+58-24
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,16 @@
2222
from stat import S_ISDIR
2323
from stat import S_ISLNK
2424
from stat import S_ISREG
25+
from typing import Any
26+
from typing import Callable
27+
from typing import overload
28+
from typing import TYPE_CHECKING
2529

2630
from . import error
2731

32+
if TYPE_CHECKING:
33+
from typing import Literal
34+
2835
# Moved from local.py.
2936
iswin32 = sys.platform == "win32" or (getattr(os, "_name", False) == "nt")
3037

@@ -96,7 +103,9 @@ def _evaluate(self, kw):
96103
return False
97104
return True
98105

99-
def _stat(self):
106+
_statcache: Stat
107+
108+
def _stat(self) -> Stat:
100109
try:
101110
return self._statcache
102111
except AttributeError:
@@ -129,7 +138,7 @@ def __init__(self, fil, rec, ignore, bf, sort):
129138
if isinstance(fil, str):
130139
fil = FNMatcher(fil)
131140
if isinstance(rec, str):
132-
self.rec = FNMatcher(rec)
141+
self.rec: Callable[[LocalPath], bool] = FNMatcher(rec)
133142
elif not hasattr(rec, "__call__") and rec:
134143
self.rec = lambda path: True
135144
else:
@@ -192,7 +201,17 @@ def map_as_list(func, iter):
192201

193202

194203
class Stat:
195-
def __getattr__(self, name):
204+
if TYPE_CHECKING:
205+
206+
@property
207+
def size(self) -> int:
208+
...
209+
210+
@property
211+
def mtime(self) -> float:
212+
...
213+
214+
def __getattr__(self, name: str) -> Any:
196215
return getattr(self._osstatresult, "st_" + name)
197216

198217
def __init__(self, path, osstatresult):
@@ -295,9 +314,10 @@ def chown(self, user, group, rec=0):
295314
error.checked_call(os.chown, str(x), uid, gid)
296315
error.checked_call(os.chown, str(self), uid, gid)
297316

298-
def readlink(self):
317+
def readlink(self) -> str:
299318
"""Return value of a symbolic link."""
300-
return error.checked_call(os.readlink, self.strpath)
319+
# https://github.com/python/mypy/issues/12278
320+
return error.checked_call(os.readlink, self.strpath) # type: ignore[arg-type,return-value]
301321

302322
def mklinkto(self, oldname):
303323
"""Posix style hard link to another name."""
@@ -659,32 +679,31 @@ def new(self, **kw):
659679
obj.strpath = normpath("%(dirname)s%(sep)s%(basename)s" % kw)
660680
return obj
661681

662-
def _getbyspec(self, spec):
682+
def _getbyspec(self, spec: str) -> list[str]:
663683
"""See new for what 'spec' can be."""
664684
res = []
665685
parts = self.strpath.split(self.sep)
666686

667687
args = filter(None, spec.split(","))
668-
append = res.append
669688
for name in args:
670689
if name == "drive":
671-
append(parts[0])
690+
res.append(parts[0])
672691
elif name == "dirname":
673-
append(self.sep.join(parts[:-1]))
692+
res.append(self.sep.join(parts[:-1]))
674693
else:
675694
basename = parts[-1]
676695
if name == "basename":
677-
append(basename)
696+
res.append(basename)
678697
else:
679698
i = basename.rfind(".")
680699
if i == -1:
681700
purebasename, ext = basename, ""
682701
else:
683702
purebasename, ext = basename[:i], basename[i:]
684703
if name == "purebasename":
685-
append(purebasename)
704+
res.append(purebasename)
686705
elif name == "ext":
687-
append(ext)
706+
res.append(ext)
688707
else:
689708
raise ValueError("invalid part specification %r" % name)
690709
return res
@@ -699,16 +718,16 @@ def dirpath(self, *args, **kwargs):
699718
return path
700719
return self.new(basename="").join(*args, **kwargs)
701720

702-
def join(self, *args, **kwargs):
721+
def join(self, *args: os.PathLike[str], abs: bool = False) -> LocalPath:
703722
"""Return a new path by appending all 'args' as path
704723
components. if abs=1 is used restart from root if any
705724
of the args is an absolute path.
706725
"""
707726
sep = self.sep
708727
strargs = [os.fspath(arg) for arg in args]
709728
strpath = self.strpath
710-
if kwargs.get("abs"):
711-
newargs = []
729+
if abs:
730+
newargs: list[str] = []
712731
for arg in reversed(strargs):
713732
if isabs(arg):
714733
strpath = arg
@@ -801,11 +820,11 @@ def listdir(self, fil=None, sort=None):
801820
self._sortlist(res, sort)
802821
return res
803822

804-
def size(self):
823+
def size(self) -> int:
805824
"""Return size of the underlying file object"""
806825
return self.stat().size
807826

808-
def mtime(self):
827+
def mtime(self) -> float:
809828
"""Return last modification time of the path."""
810829
return self.stat().mtime
811830

@@ -936,7 +955,15 @@ def ensure(self, *args, **kwargs):
936955
p.open("w").close()
937956
return p
938957

939-
def stat(self, raising=True):
958+
@overload
959+
def stat(self, raising: Literal[True] = ...) -> Stat:
960+
...
961+
962+
@overload
963+
def stat(self, raising: Literal[False]) -> Stat | None:
964+
...
965+
966+
def stat(self, raising: bool = True) -> Stat | None:
940967
"""Return an os.stat() tuple."""
941968
if raising:
942969
return Stat(self, error.checked_call(os.stat, self.strpath))
@@ -947,7 +974,7 @@ def stat(self, raising=True):
947974
except Exception:
948975
return None
949976

950-
def lstat(self):
977+
def lstat(self) -> Stat:
951978
"""Return an os.lstat() tuple."""
952979
return Stat(self, error.checked_call(os.lstat, self.strpath))
953980

@@ -1067,7 +1094,7 @@ def pyimport(self, modname=None, ensuresyspath=True):
10671094
if modname is None:
10681095
modname = self.purebasename
10691096
spec = importlib.util.spec_from_file_location(modname, str(self))
1070-
if spec is None:
1097+
if spec is None or spec.loader is None:
10711098
raise ImportError(
10721099
f"Can't find module {modname} at location {str(self)}"
10731100
)
@@ -1095,6 +1122,7 @@ def pyimport(self, modname=None, ensuresyspath=True):
10951122
return mod # we don't check anything as we might
10961123
# be in a namespace package ... too icky to check
10971124
modfile = mod.__file__
1125+
assert modfile is not None
10981126
if modfile[-4:] in (".pyc", ".pyo"):
10991127
modfile = modfile[:-1]
11001128
elif modfile.endswith("$py.class"):
@@ -1129,16 +1157,22 @@ def pyimport(self, modname=None, ensuresyspath=True):
11291157
raise
11301158
return mod
11311159

1132-
def sysexec(self, *argv, **popen_opts):
1160+
def sysexec(self, *argv: os.PathLike[str], **popen_opts: Any) -> str:
11331161
"""Return stdout text from executing a system child process,
11341162
where the 'self' path points to executable.
11351163
The process is directly invoked and not through a system shell.
11361164
"""
11371165
from subprocess import Popen, PIPE
11381166

1139-
argv = map_as_list(str, argv)
1140-
popen_opts["stdout"] = popen_opts["stderr"] = PIPE
1141-
proc = Popen([str(self)] + argv, **popen_opts)
1167+
popen_opts.pop("stdout", None)
1168+
popen_opts.pop("stderr", None)
1169+
proc = Popen(
1170+
[str(self)] + [str(arg) for arg in argv],
1171+
**popen_opts,
1172+
stdout=PIPE,
1173+
stderr=PIPE,
1174+
)
1175+
stdout: str | bytes
11421176
stdout, stderr = proc.communicate()
11431177
ret = proc.wait()
11441178
if isinstance(stdout, bytes):

0 commit comments

Comments
 (0)