Type annotate misc functions

This commit is contained in:
Ran Benita 2020-05-01 14:40:16 +03:00
parent d95132178c
commit e68a26199c
8 changed files with 73 additions and 44 deletions

View File

@ -8,6 +8,7 @@ import json
import os
from typing import Dict
from typing import Generator
from typing import Iterable
from typing import List
from typing import Optional
from typing import Set
@ -27,10 +28,12 @@ from _pytest.compat import order_preserving_dict
from _pytest.config import Config
from _pytest.config import ExitCode
from _pytest.config.argparsing import Parser
from _pytest.fixtures import FixtureRequest
from _pytest.main import Session
from _pytest.python import Module
from _pytest.reports import TestReport
README_CONTENT = """\
# pytest cache directory #
@ -52,8 +55,8 @@ Signature: 8a477f597d28d172789f06886806bc55
@attr.s
class Cache:
_cachedir = attr.ib(repr=False)
_config = attr.ib(repr=False)
_cachedir = attr.ib(type=Path, repr=False)
_config = attr.ib(type=Config, repr=False)
# sub-directory under cache-dir for directories created by "makedir"
_CACHE_PREFIX_DIRS = "d"
@ -62,14 +65,14 @@ class Cache:
_CACHE_PREFIX_VALUES = "v"
@classmethod
def for_config(cls, config):
def for_config(cls, config: Config) -> "Cache":
cachedir = cls.cache_dir_from_config(config)
if config.getoption("cacheclear") and cachedir.is_dir():
cls.clear_cache(cachedir)
return cls(cachedir, config)
@classmethod
def clear_cache(cls, cachedir: Path):
def clear_cache(cls, cachedir: Path) -> None:
"""Clears the sub-directories used to hold cached directories and values."""
for prefix in (cls._CACHE_PREFIX_DIRS, cls._CACHE_PREFIX_VALUES):
d = cachedir / prefix
@ -77,10 +80,10 @@ class Cache:
rm_rf(d)
@staticmethod
def cache_dir_from_config(config):
def cache_dir_from_config(config: Config):
return resolve_from_str(config.getini("cache_dir"), config.rootdir)
def warn(self, fmt, **args):
def warn(self, fmt: str, **args: object) -> None:
import warnings
from _pytest.warning_types import PytestCacheWarning
@ -90,7 +93,7 @@ class Cache:
stacklevel=3,
)
def makedir(self, name):
def makedir(self, name: str) -> py.path.local:
""" return a directory path object with the given name. If the
directory does not yet exist, it will be created. You can use it
to manage files likes e. g. store/retrieve database
@ -100,14 +103,14 @@ class Cache:
Make sure the name contains your plugin or application
identifiers to prevent clashes with other cache users.
"""
name = Path(name)
if len(name.parts) > 1:
path = Path(name)
if len(path.parts) > 1:
raise ValueError("name is not allowed to contain path separators")
res = self._cachedir.joinpath(self._CACHE_PREFIX_DIRS, name)
res = self._cachedir.joinpath(self._CACHE_PREFIX_DIRS, path)
res.mkdir(exist_ok=True, parents=True)
return py.path.local(res)
def _getvaluepath(self, key):
def _getvaluepath(self, key: str) -> Path:
return self._cachedir.joinpath(self._CACHE_PREFIX_VALUES, Path(key))
def get(self, key, default):
@ -128,7 +131,7 @@ class Cache:
except (ValueError, OSError):
return default
def set(self, key, value):
def set(self, key, value) -> None:
""" save value for the given key.
:param key: must be a ``/`` separated value. Usually the first
@ -158,7 +161,7 @@ class Cache:
with f:
f.write(data)
def _ensure_supporting_files(self):
def _ensure_supporting_files(self) -> None:
"""Create supporting files in the cache dir that are not really part of the cache."""
readme_path = self._cachedir / "README.md"
readme_path.write_text(README_CONTENT)
@ -172,12 +175,12 @@ class Cache:
class LFPluginCollWrapper:
def __init__(self, lfplugin: "LFPlugin"):
def __init__(self, lfplugin: "LFPlugin") -> None:
self.lfplugin = lfplugin
self._collected_at_least_one_failure = False
@pytest.hookimpl(hookwrapper=True)
def pytest_make_collect_report(self, collector) -> Generator:
def pytest_make_collect_report(self, collector: nodes.Collector) -> Generator:
if isinstance(collector, Session):
out = yield
res = out.get_result() # type: CollectReport
@ -220,11 +223,13 @@ class LFPluginCollWrapper:
class LFPluginCollSkipfiles:
def __init__(self, lfplugin: "LFPlugin"):
def __init__(self, lfplugin: "LFPlugin") -> None:
self.lfplugin = lfplugin
@pytest.hookimpl
def pytest_make_collect_report(self, collector) -> Optional[CollectReport]:
def pytest_make_collect_report(
self, collector: nodes.Collector
) -> Optional[CollectReport]:
if isinstance(collector, Module):
if Path(str(collector.fspath)) not in self.lfplugin._last_failed_paths:
self.lfplugin._skipped_files += 1
@ -262,9 +267,10 @@ class LFPlugin:
result = {rootpath / nodeid.split("::")[0] for nodeid in self.lastfailed}
return {x for x in result if x.exists()}
def pytest_report_collectionfinish(self):
def pytest_report_collectionfinish(self) -> Optional[str]:
if self.active and self.config.getoption("verbose") >= 0:
return "run-last-failure: %s" % self._report_status
return None
def pytest_runtest_logreport(self, report: TestReport) -> None:
if (report.when == "call" and report.passed) or report.skipped:
@ -347,9 +353,10 @@ class LFPlugin:
class NFPlugin:
""" Plugin which implements the --nf (run new-first) option """
def __init__(self, config):
def __init__(self, config: Config) -> None:
self.config = config
self.active = config.option.newfirst
assert config.cache is not None
self.cached_nodeids = set(config.cache.get("cache/nodeids", []))
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
@ -374,7 +381,7 @@ class NFPlugin:
else:
self.cached_nodeids.update(item.nodeid for item in items)
def _get_increasing_order(self, items):
def _get_increasing_order(self, items: Iterable[nodes.Item]) -> List[nodes.Item]:
return sorted(items, key=lambda item: item.fspath.mtime(), reverse=True)
def pytest_sessionfinish(self) -> None:
@ -384,6 +391,8 @@ class NFPlugin:
if config.getoption("collectonly"):
return
assert config.cache is not None
config.cache.set("cache/nodeids", sorted(self.cached_nodeids))
@ -462,7 +471,7 @@ def pytest_configure(config: Config) -> None:
@pytest.fixture
def cache(request):
def cache(request: FixtureRequest) -> Cache:
"""
Return a cache object that can persist state between testing sessions.
@ -474,12 +483,14 @@ def cache(request):
Values can be any object handled by the json stdlib module.
"""
assert request.config.cache is not None
return request.config.cache
def pytest_report_header(config):
def pytest_report_header(config: Config) -> Optional[str]:
"""Display cachedir with --cache-show and if non-default."""
if config.option.verbose > 0 or config.getini("cache_dir") != ".pytest_cache":
assert config.cache is not None
cachedir = config.cache._cachedir
# TODO: evaluate generating upward relative paths
# starting with .., ../.. if sensible
@ -489,11 +500,14 @@ def pytest_report_header(config):
except ValueError:
displaypath = cachedir
return "cachedir: {}".format(displaypath)
return None
def cacheshow(config, session):
def cacheshow(config: Config, session: Session) -> int:
from pprint import pformat
assert config.cache is not None
tw = TerminalWriter()
tw.line("cachedir: " + str(config.cache._cachedir))
if not config.cache._cachedir.is_dir():

View File

@ -57,6 +57,7 @@ if TYPE_CHECKING:
from _pytest import nodes
from _pytest.main import Session
from _pytest.python import Metafunc
from _pytest.python import CallSpec2
_Scope = Literal["session", "package", "module", "class", "function"]
@ -217,10 +218,11 @@ def get_parametrized_fixture_keys(item, scopenum):
the specified scope. """
assert scopenum < scopenum_function # function
try:
cs = item.callspec
callspec = item.callspec # type: ignore[attr-defined] # noqa: F821
except AttributeError:
pass
else:
cs = callspec # type: CallSpec2
# cs.indices.items() is random order of argnames. Need to
# sort this so that different calls to
# get_parametrized_fixture_keys will be deterministic.
@ -434,9 +436,9 @@ class FixtureRequest:
return fixturedefs[index]
@property
def config(self):
def config(self) -> Config:
""" the pytest config object associated with this request. """
return self._pyfuncitem.config
return self._pyfuncitem.config # type: ignore[no-any-return] # noqa: F723
@scopeproperty()
def function(self):
@ -1464,7 +1466,7 @@ class FixtureManager:
else:
continue # will raise FixtureLookupError at setup time
def pytest_collection_modifyitems(self, items):
def pytest_collection_modifyitems(self, items: "List[nodes.Item]") -> None:
# separate parametrized setups
items[:] = reorder_items(items)

View File

@ -223,7 +223,9 @@ def pytest_collection(session: "Session") -> Optional[Any]:
"""
def pytest_collection_modifyitems(session: "Session", config: "Config", items):
def pytest_collection_modifyitems(
session: "Session", config: "Config", items: List["Item"]
) -> None:
""" called after collection has been performed, may filter or re-order
the items in-place.

View File

@ -333,7 +333,7 @@ def pytest_ignore_collect(
return None
def pytest_collection_modifyitems(items, config: Config) -> None:
def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> None:
deselect_prefixes = tuple(config.getoption("deselect") or [])
if not deselect_prefixes:
return
@ -487,18 +487,18 @@ class Session(nodes.FSCollector):
@overload
def _perform_collect(
self, args: Optional[Sequence[str]], genitems: "Literal[True]"
) -> Sequence[nodes.Item]:
) -> List[nodes.Item]:
raise NotImplementedError()
@overload # noqa: F811
def _perform_collect( # noqa: F811
self, args: Optional[Sequence[str]], genitems: bool
) -> Sequence[Union[nodes.Item, nodes.Collector]]:
) -> Union[List[Union[nodes.Item]], List[Union[nodes.Item, nodes.Collector]]]:
raise NotImplementedError()
def _perform_collect( # noqa: F811
self, args: Optional[Sequence[str]], genitems: bool
) -> Sequence[Union[nodes.Item, nodes.Collector]]:
) -> Union[List[Union[nodes.Item]], List[Union[nodes.Item, nodes.Collector]]]:
if args is None:
args = self.config.args
self.trace("perform_collect", self, args)

View File

@ -2,6 +2,7 @@
import typing
import warnings
from typing import AbstractSet
from typing import List
from typing import Optional
from typing import Union
@ -173,7 +174,7 @@ class KeywordMatcher:
return False
def deselect_by_keyword(items, config: Config) -> None:
def deselect_by_keyword(items: "List[Item]", config: Config) -> None:
keywordexpr = config.option.keyword.lstrip()
if not keywordexpr:
return
@ -229,7 +230,7 @@ class MarkMatcher:
return name in self.own_mark_names
def deselect_by_mark(items, config: Config) -> None:
def deselect_by_mark(items: "List[Item]", config: Config) -> None:
matchexpr = config.option.markexpr
if not matchexpr:
return
@ -254,7 +255,7 @@ def deselect_by_mark(items, config: Config) -> None:
items[:] = remaining
def pytest_collection_modifyitems(items, config: Config) -> None:
def pytest_collection_modifyitems(items: "List[Item]", config: Config) -> None:
deselect_by_keyword(items, config)
deselect_by_mark(items, config)

View File

@ -348,7 +348,7 @@ def make_numbered_dir_with_cleanup(
raise e
def resolve_from_str(input, root):
def resolve_from_str(input: str, root):
assert not isinstance(input, Path), "would break on py2"
root = Path(root)
input = expanduser(input)

View File

@ -647,8 +647,8 @@ class Testdir:
for basename, value in items:
p = self.tmpdir.join(basename).new(ext=ext)
p.dirpath().ensure_dir()
source = Source(value)
source = "\n".join(to_text(line) for line in source.lines)
source_ = Source(value)
source = "\n".join(to_text(line) for line in source_.lines)
p.write(source.strip().encode(encoding), "wb")
if ret is None:
ret = p
@ -839,7 +839,7 @@ class Testdir:
config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK)
return res
def genitems(self, colitems):
def genitems(self, colitems: List[Union[Item, Collector]]) -> List[Item]:
"""Generate all test items from a collection node.
This recurses into the collection node and returns a list of all the
@ -847,7 +847,7 @@ class Testdir:
"""
session = colitems[0].session
result = []
result = [] # type: List[Item]
for colitem in colitems:
result.extend(session.genitems(colitem))
return result

View File

@ -1,4 +1,8 @@
from typing import List
from typing import Optional
import pytest
from _pytest import nodes
from _pytest.config import Config
from _pytest.config.argparsing import Parser
from _pytest.main import Session
@ -28,20 +32,23 @@ def pytest_configure(config: Config) -> None:
class StepwisePlugin:
def __init__(self, config):
def __init__(self, config: Config) -> None:
self.config = config
self.active = config.getvalue("stepwise")
self.session = None
self.session = None # type: Optional[Session]
self.report_status = ""
if self.active:
assert config.cache is not None
self.lastfailed = config.cache.get("cache/stepwise", None)
self.skip = config.getvalue("stepwise_skip")
def pytest_sessionstart(self, session: Session) -> None:
self.session = session
def pytest_collection_modifyitems(self, session, config, items):
def pytest_collection_modifyitems(
self, session: Session, config: Config, items: List[nodes.Item]
) -> None:
if not self.active:
return
if not self.lastfailed:
@ -89,6 +96,7 @@ class StepwisePlugin:
else:
# Mark test as the last failing and interrupt the test session.
self.lastfailed = report.nodeid
assert self.session is not None
self.session.shouldstop = (
"Test failed, continuing from this test next run."
)
@ -100,11 +108,13 @@ class StepwisePlugin:
if report.nodeid == self.lastfailed:
self.lastfailed = None
def pytest_report_collectionfinish(self):
def pytest_report_collectionfinish(self) -> Optional[str]:
if self.active and self.config.getoption("verbose") >= 0 and self.report_status:
return "stepwise: %s" % self.report_status
return None
def pytest_sessionfinish(self, session: Session) -> None:
assert self.config.cache is not None
if self.active:
self.config.cache.set("cache/stepwise", self.lastfailed)
else: