Skip to content

Commit

Permalink
Merge pull request #7780 from bluetech/final
Browse files Browse the repository at this point in the history
Mark some public and to-be-public classes as `@final`
  • Loading branch information
bluetech committed Sep 23, 2020
2 parents 050c2df + a99ca87 commit 5cfd7c0
Show file tree
Hide file tree
Showing 23 changed files with 81 additions and 1 deletion.
3 changes: 3 additions & 0 deletions changelog/7780.improvement.rst
@@ -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.
2 changes: 2 additions & 0 deletions src/_pytest/_code/code.py
Expand Up @@ -38,6 +38,7 @@
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
Expand Down Expand Up @@ -414,6 +415,7 @@ def recursionindex(self) -> Optional[int]:
_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."""
Expand Down
2 changes: 2 additions & 0 deletions src/_pytest/_io/terminalwriter.py
Expand Up @@ -7,6 +7,7 @@
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.
Expand Down Expand Up @@ -36,6 +37,7 @@ def should_do_markup(file: TextIO) -> bool:
)


@final
class TerminalWriter:
_esctable = dict(
black=30,
Expand Down
2 changes: 2 additions & 0 deletions src/_pytest/cacheprovider.py
Expand Up @@ -21,6 +21,7 @@
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
Expand Down Expand Up @@ -50,6 +51,7 @@
"""


@final
@attr.s
class Cache:
_cachedir = attr.ib(type=Path, repr=False)
Expand Down
2 changes: 2 additions & 0 deletions src/_pytest/capture.py
Expand Up @@ -17,6 +17,7 @@
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
Expand Down Expand Up @@ -498,6 +499,7 @@ def writeorg(self, data):
# 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`."""
Expand Down
16 changes: 15 additions & 1 deletion src/_pytest/compat.py
Expand Up @@ -19,7 +19,6 @@

import attr

from _pytest._io.saferepr import saferepr
from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -357,6 +358,19 @@ def overload(f): # noqa: F811
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:
Expand Down
5 changes: 5 additions & 0 deletions src/_pytest/config/__init__.py
Expand Up @@ -43,6 +43,7 @@
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
Expand Down Expand Up @@ -76,6 +77,7 @@
hookspec = HookspecMarker("pytest")


@final
class ExitCode(enum.IntEnum):
"""Encodes the valid exit codes by pytest.
Expand Down Expand Up @@ -322,6 +324,7 @@ def _prepareconfig(
raise


@final
class PytestPluginManager(PluginManager):
"""A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with
additional pytest-specific functionality:
Expand Down Expand Up @@ -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.
Expand All @@ -825,6 +829,7 @@ class Config:
invocation.
"""

@final
@attr.s(frozen=True)
class InvocationParams:
"""Holds parameters passed during :func:`pytest.main`.
Expand Down
2 changes: 2 additions & 0 deletions src/_pytest/config/argparsing.py
Expand Up @@ -16,6 +16,7 @@
import py

import _pytest._io
from _pytest.compat import final
from _pytest.compat import TYPE_CHECKING
from _pytest.config.exceptions import UsageError

Expand All @@ -26,6 +27,7 @@
FILE_OR_DIR = "file_or_dir"


@final
class Parser:
"""Parser for command line arguments and ini-file values.
Expand Down
4 changes: 4 additions & 0 deletions src/_pytest/config/exceptions.py
@@ -1,3 +1,7 @@
from _pytest.compat import final


@final
class UsageError(Exception):
"""Error in pytest usage or invocation."""

Expand Down
5 changes: 5 additions & 0 deletions src/_pytest/fixtures.py
Expand Up @@ -32,6 +32,7 @@
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
Expand Down Expand Up @@ -730,6 +731,7 @@ def __repr__(self) -> str:
return "<FixtureRequest for %r>" % (self.node)


@final
class SubRequest(FixtureRequest):
"""A sub request for handling getting a fixture from a test function/fixture."""

Expand Down Expand Up @@ -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)."""

Expand Down Expand Up @@ -952,6 +955,7 @@ def _eval_scope_callable(
return result


@final
class FixtureDef(Generic[_FixtureValue]):
"""A container for a factory definition."""

Expand Down Expand Up @@ -1161,6 +1165,7 @@ def result(*args, **kwargs):
return result


@final
@attr.s(frozen=True)
class FixtureFunctionMarker:
scope = attr.ib(type="Union[_Scope, Callable[[str, Config], _Scope]]")
Expand Down
2 changes: 2 additions & 0 deletions src/_pytest/logging.py
Expand Up @@ -19,6 +19,7 @@
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
Expand Down Expand Up @@ -339,6 +340,7 @@ def handleError(self, record: logging.LogRecord) -> None:
raise


@final
class LogCaptureFixture:
"""Provides access and control of log capturing."""

Expand Down
2 changes: 2 additions & 0 deletions src/_pytest/main.py
Expand Up @@ -21,6 +21,7 @@

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
Expand Down Expand Up @@ -435,6 +436,7 @@ def __missing__(self, path: Path) -> str:
return r


@final
class Session(nodes.FSCollector):
Interrupted = Interrupted
Failed = Failed
Expand Down
4 changes: 4 additions & 0 deletions src/_pytest/mark/structures.py
Expand Up @@ -20,6 +20,7 @@

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
Expand Down Expand Up @@ -199,6 +200,7 @@ def _for_parametrize(
return argnames, parameters


@final
@attr.s(frozen=True)
class Mark:
#: Name of the mark.
Expand Down Expand Up @@ -452,6 +454,7 @@ def __call__( # type: ignore[override]
...


@final
class MarkGenerator:
"""Factory for :class:`MarkDecorator` objects - exposed as
a ``pytest.mark`` singleton instance.
Expand Down Expand Up @@ -525,6 +528,7 @@ def __getattr__(self, name: str) -> MarkDecorator:


# 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
Expand Down
2 changes: 2 additions & 0 deletions src/_pytest/monkeypatch.py
Expand Up @@ -14,6 +14,7 @@
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
Expand Down Expand Up @@ -110,6 +111,7 @@ def __repr__(self) -> str:
notset = Notset()


@final
class MonkeyPatch:
"""Object returned by the ``monkeypatch`` fixture keeping a record of
setattr/item/env/syspath changes."""
Expand Down
2 changes: 2 additions & 0 deletions src/_pytest/pytester.py
Expand Up @@ -28,6 +28,7 @@
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
Expand Down Expand Up @@ -597,6 +598,7 @@ def restore(self) -> None:
sys.path[:], sys.meta_path[:] = self.__saved


@final
class Testdir:
"""Temporary test directory with tools to test/run pytest itself.
Expand Down
3 changes: 3 additions & 0 deletions src/_pytest/python.py
Expand Up @@ -37,6 +37,7 @@
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
Expand Down Expand Up @@ -864,6 +865,7 @@ def hasnew(obj: object) -> bool:
return False


@final
class CallSpec2:
def __init__(self, metafunc: "Metafunc") -> None:
self.metafunc = metafunc
Expand Down Expand Up @@ -924,6 +926,7 @@ def setmulti2(
self.marks.extend(normalize_mark_list(marks))


@final
class Metafunc:
"""Objects passed to the :func:`pytest_generate_tests <_pytest.hookspec.pytest_generate_tests>` hook.
Expand Down
2 changes: 2 additions & 0 deletions src/_pytest/python_api.py
Expand Up @@ -17,6 +17,7 @@
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
Expand Down Expand Up @@ -699,6 +700,7 @@ def raises( # noqa: F811
raises.Exception = fail.Exception # type: ignore


@final
class RaisesContext(Generic[_E]):
def __init__(
self,
Expand Down
2 changes: 2 additions & 0 deletions src/_pytest/recwarn.py
Expand Up @@ -13,6 +13,7 @@
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
Expand Down Expand Up @@ -228,6 +229,7 @@ def __exit__(
self._entered = False


@final
class WarningsChecker(WarningsRecorder):
def __init__(
self,
Expand Down
3 changes: 3 additions & 0 deletions src/_pytest/reports.py
Expand Up @@ -26,6 +26,7 @@
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
Expand Down Expand Up @@ -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)."""
Expand Down Expand Up @@ -333,6 +335,7 @@ def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport":
)


@final
class CollectReport(BaseReport):
"""Collection report object."""

Expand Down

0 comments on commit 5cfd7c0

Please sign in to comment.