Add a @cached_property implementation
This is a useful utility to abstract the caching property idiom. It is in compat.py since eventually it will be replaced by functools.cached_property. Fixes #6131.
This commit is contained in:
parent
710e3c40e0
commit
42a46ea786
|
@ -10,7 +10,11 @@ import sys
|
|||
from contextlib import contextmanager
|
||||
from inspect import Parameter
|
||||
from inspect import signature
|
||||
from typing import Callable
|
||||
from typing import Generic
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import TypeVar
|
||||
|
||||
import attr
|
||||
import py
|
||||
|
@ -20,6 +24,13 @@ from _pytest._io.saferepr import saferepr
|
|||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import TEST_OUTCOME
|
||||
|
||||
if False: # TYPE_CHECKING
|
||||
from typing import Type # noqa: F401 (used in type string)
|
||||
|
||||
|
||||
_T = TypeVar("_T")
|
||||
_S = TypeVar("_S")
|
||||
|
||||
|
||||
NOTSET = object()
|
||||
|
||||
|
@ -374,3 +385,33 @@ if getattr(attr, "__version_info__", ()) >= (19, 2):
|
|||
ATTRS_EQ_FIELD = "eq"
|
||||
else:
|
||||
ATTRS_EQ_FIELD = "cmp"
|
||||
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
# TODO: Remove type ignore on next mypy update.
|
||||
# https://github.com/python/typeshed/commit/add0b5e930a1db16560fde45a3b710eefc625709
|
||||
from functools import cached_property # type: ignore
|
||||
else:
|
||||
|
||||
class cached_property(Generic[_S, _T]):
|
||||
__slots__ = ("func", "__doc__")
|
||||
|
||||
def __init__(self, func: Callable[[_S], _T]) -> None:
|
||||
self.func = func
|
||||
self.__doc__ = func.__doc__
|
||||
|
||||
@overload
|
||||
def __get__(
|
||||
self, instance: None, owner: Optional["Type[_S]"] = ...
|
||||
) -> "cached_property[_S, _T]":
|
||||
raise NotImplementedError()
|
||||
|
||||
@overload # noqa: F811
|
||||
def __get__(self, instance: _S, owner: Optional["Type[_S]"] = ...) -> _T:
|
||||
raise NotImplementedError()
|
||||
|
||||
def __get__(self, instance, owner=None): # noqa: F811
|
||||
if instance is None:
|
||||
return self
|
||||
value = instance.__dict__[self.func.__name__] = self.func(instance)
|
||||
return value
|
||||
|
|
|
@ -15,6 +15,7 @@ import _pytest._code
|
|||
from _pytest._code.code import ExceptionChainRepr
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest._code.code import ReprExceptionInfo
|
||||
from _pytest.compat import cached_property
|
||||
from _pytest.compat import getfslineno
|
||||
from _pytest.fixtures import FixtureDef
|
||||
from _pytest.fixtures import FixtureLookupError
|
||||
|
@ -448,17 +449,9 @@ class Item(Node):
|
|||
def reportinfo(self) -> Tuple[str, Optional[int], str]:
|
||||
return self.fspath, None, ""
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def location(self) -> Tuple[str, Optional[int], str]:
|
||||
try:
|
||||
return self._location
|
||||
except AttributeError:
|
||||
location = self.reportinfo()
|
||||
fspath = self.session._node_location_to_relpath(location[0])
|
||||
assert type(location[2]) is str
|
||||
self._location = (
|
||||
fspath,
|
||||
location[1],
|
||||
location[2],
|
||||
) # type: Tuple[str, Optional[int], str]
|
||||
return self._location
|
||||
location = self.reportinfo()
|
||||
fspath = self.session._node_location_to_relpath(location[0])
|
||||
assert type(location[2]) is str
|
||||
return (fspath, location[1], location[2])
|
||||
|
|
|
@ -4,6 +4,7 @@ from functools import wraps
|
|||
|
||||
import pytest
|
||||
from _pytest.compat import _PytestWrapper
|
||||
from _pytest.compat import cached_property
|
||||
from _pytest.compat import get_real_func
|
||||
from _pytest.compat import is_generator
|
||||
from _pytest.compat import safe_getattr
|
||||
|
@ -178,3 +179,23 @@ def test_safe_isclass():
|
|||
assert False, "Should be ignored"
|
||||
|
||||
assert safe_isclass(CrappyClass()) is False
|
||||
|
||||
|
||||
def test_cached_property() -> None:
|
||||
ncalls = 0
|
||||
|
||||
class Class:
|
||||
@cached_property
|
||||
def prop(self) -> int:
|
||||
nonlocal ncalls
|
||||
ncalls += 1
|
||||
return ncalls
|
||||
|
||||
c1 = Class()
|
||||
assert ncalls == 0
|
||||
assert c1.prop == 1
|
||||
assert c1.prop == 1
|
||||
c2 = Class()
|
||||
assert ncalls == 1
|
||||
assert c2.prop == 2
|
||||
assert c1.prop == 1
|
||||
|
|
Loading…
Reference in New Issue