Mark some public and to-be-public classes as `@final`
This indicates at least for people using type checkers that these classes are not designed for inheritance and we make no stability guarantees regarding inheritance of them. Currently this doesn't show up in the docs. Sphinx does actually support `@final`, however it only works when imported directly from `typing`, while we import from `_pytest.compat`. In the future there might also be a `@sealed` decorator which would cover some more cases.
This commit is contained in:
parent
cdfdb3a25d
commit
a99ca879e7
|
@ -0,0 +1,3 @@
|
|||
Public classes which are not designed to be inherited from are now marked `@final <https://docs.python.org/3/library/typing.html#typing.final>`_.
|
||||
Code which inherits from these classes will trigger a type-checking (e.g. mypy) error, but will still work in runtime.
|
||||
Currently the ``final`` designation does not appear in the API Reference but hopefully will in the future.
|
|
@ -38,6 +38,7 @@ from _pytest._io import TerminalWriter
|
|||
from _pytest._io.saferepr import safeformat
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.compat import ATTRS_EQ_FIELD
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import get_real_func
|
||||
from _pytest.compat import overload
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
|
@ -414,6 +415,7 @@ co_equal = compile(
|
|||
_E = TypeVar("_E", bound=BaseException, covariant=True)
|
||||
|
||||
|
||||
@final
|
||||
@attr.s(repr=False)
|
||||
class ExceptionInfo(Generic[_E]):
|
||||
"""Wraps sys.exc_info() objects and offers help for navigating the traceback."""
|
||||
|
|
|
@ -7,6 +7,7 @@ from typing import Sequence
|
|||
from typing import TextIO
|
||||
|
||||
from .wcwidth import wcswidth
|
||||
from _pytest.compat import final
|
||||
|
||||
|
||||
# This code was initially copied from py 1.8.1, file _io/terminalwriter.py.
|
||||
|
@ -36,6 +37,7 @@ def should_do_markup(file: TextIO) -> bool:
|
|||
)
|
||||
|
||||
|
||||
@final
|
||||
class TerminalWriter:
|
||||
_esctable = dict(
|
||||
black=30,
|
||||
|
|
|
@ -21,6 +21,7 @@ from .pathlib import rm_rf
|
|||
from .reports import CollectReport
|
||||
from _pytest import nodes
|
||||
from _pytest._io import TerminalWriter
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import order_preserving_dict
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import ExitCode
|
||||
|
@ -50,6 +51,7 @@ Signature: 8a477f597d28d172789f06886806bc55
|
|||
"""
|
||||
|
||||
|
||||
@final
|
||||
@attr.s
|
||||
class Cache:
|
||||
_cachedir = attr.ib(type=Path, repr=False)
|
||||
|
|
|
@ -17,6 +17,7 @@ from typing import Tuple
|
|||
from typing import Union
|
||||
|
||||
import pytest
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.config import Config
|
||||
from _pytest.config.argparsing import Parser
|
||||
|
@ -498,6 +499,7 @@ class FDCapture(FDCaptureBinary):
|
|||
# pertinent parts of a namedtuple. If the mypy limitation is ever lifted, can
|
||||
# make it a namedtuple again.
|
||||
# [0]: https://github.com/python/mypy/issues/685
|
||||
@final
|
||||
@functools.total_ordering
|
||||
class CaptureResult(Generic[AnyStr]):
|
||||
"""The result of :method:`CaptureFixture.readouterr`."""
|
||||
|
|
|
@ -19,7 +19,6 @@ from typing import Union
|
|||
|
||||
import attr
|
||||
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import TEST_OUTCOME
|
||||
|
||||
|
@ -297,6 +296,8 @@ def get_real_func(obj):
|
|||
break
|
||||
obj = new_obj
|
||||
else:
|
||||
from _pytest._io.saferepr import saferepr
|
||||
|
||||
raise ValueError(
|
||||
("could not find real function of {start}\nstopped at {current}").format(
|
||||
start=saferepr(start_obj), current=saferepr(obj)
|
||||
|
@ -357,6 +358,19 @@ if sys.version_info < (3, 5, 2):
|
|||
return f
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
if sys.version_info >= (3, 8):
|
||||
from typing import final as final
|
||||
else:
|
||||
from typing_extensions import final as final
|
||||
elif sys.version_info >= (3, 8):
|
||||
from typing import final as final
|
||||
else:
|
||||
|
||||
def final(f): # noqa: F811
|
||||
return f
|
||||
|
||||
|
||||
if getattr(attr, "__version_info__", ()) >= (19, 2):
|
||||
ATTRS_EQ_FIELD = "eq"
|
||||
else:
|
||||
|
|
|
@ -43,6 +43,7 @@ from .findpaths import determine_setup
|
|||
from _pytest._code import ExceptionInfo
|
||||
from _pytest._code import filter_traceback
|
||||
from _pytest._io import TerminalWriter
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import importlib_metadata
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.outcomes import fail
|
||||
|
@ -76,6 +77,7 @@ hookimpl = HookimplMarker("pytest")
|
|||
hookspec = HookspecMarker("pytest")
|
||||
|
||||
|
||||
@final
|
||||
class ExitCode(enum.IntEnum):
|
||||
"""Encodes the valid exit codes by pytest.
|
||||
|
||||
|
@ -322,6 +324,7 @@ def _prepareconfig(
|
|||
raise
|
||||
|
||||
|
||||
@final
|
||||
class PytestPluginManager(PluginManager):
|
||||
"""A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with
|
||||
additional pytest-specific functionality:
|
||||
|
@ -815,6 +818,7 @@ def _args_converter(args: Iterable[str]) -> Tuple[str, ...]:
|
|||
return tuple(args)
|
||||
|
||||
|
||||
@final
|
||||
class Config:
|
||||
"""Access to configuration values, pluginmanager and plugin hooks.
|
||||
|
||||
|
@ -825,6 +829,7 @@ class Config:
|
|||
invocation.
|
||||
"""
|
||||
|
||||
@final
|
||||
@attr.s(frozen=True)
|
||||
class InvocationParams:
|
||||
"""Holds parameters passed during :func:`pytest.main`.
|
||||
|
|
|
@ -16,6 +16,7 @@ from typing import Union
|
|||
import py
|
||||
|
||||
import _pytest._io
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.config.exceptions import UsageError
|
||||
|
||||
|
@ -26,6 +27,7 @@ if TYPE_CHECKING:
|
|||
FILE_OR_DIR = "file_or_dir"
|
||||
|
||||
|
||||
@final
|
||||
class Parser:
|
||||
"""Parser for command line arguments and ini-file values.
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
from _pytest.compat import final
|
||||
|
||||
|
||||
@final
|
||||
class UsageError(Exception):
|
||||
"""Error in pytest usage or invocation."""
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ from _pytest._code.code import TerminalRepr
|
|||
from _pytest._io import TerminalWriter
|
||||
from _pytest.compat import _format_args
|
||||
from _pytest.compat import _PytestWrapper
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import get_real_func
|
||||
from _pytest.compat import get_real_method
|
||||
from _pytest.compat import getfuncargnames
|
||||
|
@ -730,6 +731,7 @@ class FixtureRequest:
|
|||
return "<FixtureRequest for %r>" % (self.node)
|
||||
|
||||
|
||||
@final
|
||||
class SubRequest(FixtureRequest):
|
||||
"""A sub request for handling getting a fixture from a test function/fixture."""
|
||||
|
||||
|
@ -796,6 +798,7 @@ def scope2index(scope: str, descr: str, where: Optional[str] = None) -> int:
|
|||
)
|
||||
|
||||
|
||||
@final
|
||||
class FixtureLookupError(LookupError):
|
||||
"""Could not return a requested fixture (missing or invalid)."""
|
||||
|
||||
|
@ -952,6 +955,7 @@ def _eval_scope_callable(
|
|||
return result
|
||||
|
||||
|
||||
@final
|
||||
class FixtureDef(Generic[_FixtureValue]):
|
||||
"""A container for a factory definition."""
|
||||
|
||||
|
@ -1161,6 +1165,7 @@ def wrap_function_to_error_out_if_called_directly(function, fixture_marker):
|
|||
return result
|
||||
|
||||
|
||||
@final
|
||||
@attr.s(frozen=True)
|
||||
class FixtureFunctionMarker:
|
||||
scope = attr.ib(type="Union[_Scope, Callable[[str, Config], _Scope]]")
|
||||
|
|
|
@ -19,6 +19,7 @@ import pytest
|
|||
from _pytest import nodes
|
||||
from _pytest._io import TerminalWriter
|
||||
from _pytest.capture import CaptureManager
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import nullcontext
|
||||
from _pytest.config import _strtobool
|
||||
from _pytest.config import Config
|
||||
|
@ -339,6 +340,7 @@ class LogCaptureHandler(logging.StreamHandler):
|
|||
raise
|
||||
|
||||
|
||||
@final
|
||||
class LogCaptureFixture:
|
||||
"""Provides access and control of log capturing."""
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import py
|
|||
|
||||
import _pytest._code
|
||||
from _pytest import nodes
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import overload
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.config import Config
|
||||
|
@ -435,6 +436,7 @@ class _bestrelpath_cache(Dict[Path, str]):
|
|||
return r
|
||||
|
||||
|
||||
@final
|
||||
class Session(nodes.FSCollector):
|
||||
Interrupted = Interrupted
|
||||
Failed = Failed
|
||||
|
|
|
@ -20,6 +20,7 @@ import attr
|
|||
|
||||
from .._code import getfslineno
|
||||
from ..compat import ascii_escaped
|
||||
from ..compat import final
|
||||
from ..compat import NOTSET
|
||||
from ..compat import NotSetType
|
||||
from ..compat import overload
|
||||
|
@ -199,6 +200,7 @@ class ParameterSet(
|
|||
return argnames, parameters
|
||||
|
||||
|
||||
@final
|
||||
@attr.s(frozen=True)
|
||||
class Mark:
|
||||
#: Name of the mark.
|
||||
|
@ -452,6 +454,7 @@ if TYPE_CHECKING:
|
|||
...
|
||||
|
||||
|
||||
@final
|
||||
class MarkGenerator:
|
||||
"""Factory for :class:`MarkDecorator` objects - exposed as
|
||||
a ``pytest.mark`` singleton instance.
|
||||
|
@ -525,6 +528,7 @@ MARK_GEN = MarkGenerator()
|
|||
|
||||
|
||||
# TODO(py36): inherit from typing.MutableMapping[str, Any].
|
||||
@final
|
||||
class NodeKeywords(collections.abc.MutableMapping): # type: ignore[type-arg]
|
||||
def __init__(self, node: "Node") -> None:
|
||||
self.node = node
|
||||
|
|
|
@ -14,6 +14,7 @@ from typing import TypeVar
|
|||
from typing import Union
|
||||
|
||||
import pytest
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import overload
|
||||
from _pytest.fixtures import fixture
|
||||
from _pytest.pathlib import Path
|
||||
|
@ -110,6 +111,7 @@ class Notset:
|
|||
notset = Notset()
|
||||
|
||||
|
||||
@final
|
||||
class MonkeyPatch:
|
||||
"""Object returned by the ``monkeypatch`` fixture keeping a record of
|
||||
setattr/item/env/syspath changes."""
|
||||
|
|
|
@ -28,6 +28,7 @@ import pytest
|
|||
from _pytest import timing
|
||||
from _pytest._code import Source
|
||||
from _pytest.capture import _get_multicapture
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import overload
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.config import _PluggyPlugin
|
||||
|
@ -597,6 +598,7 @@ class SysPathsSnapshot:
|
|||
sys.path[:], sys.meta_path[:] = self.__saved
|
||||
|
||||
|
||||
@final
|
||||
class Testdir:
|
||||
"""Temporary test directory with tools to test/run pytest itself.
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ from _pytest._code.code import TerminalRepr
|
|||
from _pytest._io import TerminalWriter
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.compat import ascii_escaped
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import get_default_arg_names
|
||||
from _pytest.compat import get_real_func
|
||||
from _pytest.compat import getimfunc
|
||||
|
@ -864,6 +865,7 @@ def hasnew(obj: object) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
@final
|
||||
class CallSpec2:
|
||||
def __init__(self, metafunc: "Metafunc") -> None:
|
||||
self.metafunc = metafunc
|
||||
|
@ -924,6 +926,7 @@ class CallSpec2:
|
|||
self.marks.extend(normalize_mark_list(marks))
|
||||
|
||||
|
||||
@final
|
||||
class Metafunc:
|
||||
"""Objects passed to the :func:`pytest_generate_tests <_pytest.hookspec.pytest_generate_tests>` hook.
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ from typing import TypeVar
|
|||
from typing import Union
|
||||
|
||||
import _pytest._code
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import overload
|
||||
from _pytest.compat import STRING_TYPES
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
|
@ -699,6 +700,7 @@ def raises( # noqa: F811
|
|||
raises.Exception = fail.Exception # type: ignore
|
||||
|
||||
|
||||
@final
|
||||
class RaisesContext(Generic[_E]):
|
||||
def __init__(
|
||||
self,
|
||||
|
|
|
@ -13,6 +13,7 @@ from typing import Tuple
|
|||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import overload
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.fixtures import fixture
|
||||
|
@ -228,6 +229,7 @@ class WarningsRecorder(warnings.catch_warnings):
|
|||
self._entered = False
|
||||
|
||||
|
||||
@final
|
||||
class WarningsChecker(WarningsRecorder):
|
||||
def __init__(
|
||||
self,
|
||||
|
|
|
@ -26,6 +26,7 @@ from _pytest._code.code import ReprLocals
|
|||
from _pytest._code.code import ReprTraceback
|
||||
from _pytest._code.code import TerminalRepr
|
||||
from _pytest._io import TerminalWriter
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.config import Config
|
||||
from _pytest.nodes import Collector
|
||||
|
@ -225,6 +226,7 @@ def _report_unserialization_failure(
|
|||
raise RuntimeError(stream.getvalue())
|
||||
|
||||
|
||||
@final
|
||||
class TestReport(BaseReport):
|
||||
"""Basic test report object (also used for setup and teardown calls if
|
||||
they fail)."""
|
||||
|
@ -333,6 +335,7 @@ class TestReport(BaseReport):
|
|||
)
|
||||
|
||||
|
||||
@final
|
||||
class CollectReport(BaseReport):
|
||||
"""Collection report object."""
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ from _pytest import timing
|
|||
from _pytest._code.code import ExceptionChainRepr
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest._code.code import TerminalRepr
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.config.argparsing import Parser
|
||||
from _pytest.nodes import Collector
|
||||
|
@ -259,6 +260,7 @@ def call_runtest_hook(
|
|||
TResult = TypeVar("TResult", covariant=True)
|
||||
|
||||
|
||||
@final
|
||||
@attr.s(repr=False)
|
||||
class CallInfo(Generic[TResult]):
|
||||
"""Result/Exception info a function invocation.
|
||||
|
|
|
@ -32,6 +32,7 @@ from _pytest import timing
|
|||
from _pytest._code import ExceptionInfo
|
||||
from _pytest._code.code import ExceptionRepr
|
||||
from _pytest._io.wcwidth import wcswidth
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import order_preserving_dict
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.config import _PluggyPlugin
|
||||
|
@ -309,6 +310,7 @@ class WarningReport:
|
|||
return None
|
||||
|
||||
|
||||
@final
|
||||
class TerminalReporter:
|
||||
def __init__(self, config: Config, file: Optional[TextIO] = None) -> None:
|
||||
import _pytest.config
|
||||
|
|
|
@ -13,11 +13,13 @@ from .pathlib import LOCK_TIMEOUT
|
|||
from .pathlib import make_numbered_dir
|
||||
from .pathlib import make_numbered_dir_with_cleanup
|
||||
from .pathlib import Path
|
||||
from _pytest.compat import final
|
||||
from _pytest.config import Config
|
||||
from _pytest.fixtures import FixtureRequest
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
|
||||
@final
|
||||
@attr.s
|
||||
class TempPathFactory:
|
||||
"""Factory for temporary directories under the common base temp directory.
|
||||
|
@ -103,6 +105,7 @@ class TempPathFactory:
|
|||
return t
|
||||
|
||||
|
||||
@final
|
||||
@attr.s
|
||||
class TempdirFactory:
|
||||
"""Backward comptibility wrapper that implements :class:``py.path.local``
|
||||
|
|
|
@ -4,6 +4,7 @@ from typing import TypeVar
|
|||
|
||||
import attr
|
||||
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -16,36 +17,42 @@ class PytestWarning(UserWarning):
|
|||
__module__ = "pytest"
|
||||
|
||||
|
||||
@final
|
||||
class PytestAssertRewriteWarning(PytestWarning):
|
||||
"""Warning emitted by the pytest assert rewrite module."""
|
||||
|
||||
__module__ = "pytest"
|
||||
|
||||
|
||||
@final
|
||||
class PytestCacheWarning(PytestWarning):
|
||||
"""Warning emitted by the cache plugin in various situations."""
|
||||
|
||||
__module__ = "pytest"
|
||||
|
||||
|
||||
@final
|
||||
class PytestConfigWarning(PytestWarning):
|
||||
"""Warning emitted for configuration issues."""
|
||||
|
||||
__module__ = "pytest"
|
||||
|
||||
|
||||
@final
|
||||
class PytestCollectionWarning(PytestWarning):
|
||||
"""Warning emitted when pytest is not able to collect a file or symbol in a module."""
|
||||
|
||||
__module__ = "pytest"
|
||||
|
||||
|
||||
@final
|
||||
class PytestDeprecationWarning(PytestWarning, DeprecationWarning):
|
||||
"""Warning class for features that will be removed in a future version."""
|
||||
|
||||
__module__ = "pytest"
|
||||
|
||||
|
||||
@final
|
||||
class PytestExperimentalApiWarning(PytestWarning, FutureWarning):
|
||||
"""Warning category used to denote experiments in pytest.
|
||||
|
||||
|
@ -64,6 +71,7 @@ class PytestExperimentalApiWarning(PytestWarning, FutureWarning):
|
|||
)
|
||||
|
||||
|
||||
@final
|
||||
class PytestUnhandledCoroutineWarning(PytestWarning):
|
||||
"""Warning emitted for an unhandled coroutine.
|
||||
|
||||
|
@ -75,6 +83,7 @@ class PytestUnhandledCoroutineWarning(PytestWarning):
|
|||
__module__ = "pytest"
|
||||
|
||||
|
||||
@final
|
||||
class PytestUnknownMarkWarning(PytestWarning):
|
||||
"""Warning emitted on use of unknown markers.
|
||||
|
||||
|
@ -87,6 +96,7 @@ class PytestUnknownMarkWarning(PytestWarning):
|
|||
_W = TypeVar("_W", bound=PytestWarning)
|
||||
|
||||
|
||||
@final
|
||||
@attr.s
|
||||
class UnformattedWarning(Generic[_W]):
|
||||
"""A warning meant to be formatted during runtime.
|
||||
|
|
Loading…
Reference in New Issue