Skip to content

Commit

Permalink
typing
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco committed Oct 7, 2024
1 parent 1b06049 commit 317aee9
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 29 deletions.
32 changes: 30 additions & 2 deletions src/propcache/_helpers_py.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
"""Various helper functions."""

import sys
from functools import cached_property
from typing import Any, Callable, Dict, Generic, Optional, Protocol, Type, TypeVar
from typing import (
Any,
Callable,
Dict,
Generic,
Optional,
Protocol,
Type,
TypeVar,
Union,
overload,
)

__all__ = ("under_cached_property", "cached_property")


if sys.version_info >= (3, 11):
from typing import Self
else:
Self = Any

_T = TypeVar("_T")


Expand All @@ -27,7 +45,17 @@ def __init__(self, wrapped: Callable[..., _T]) -> None:
self.__doc__ = wrapped.__doc__
self.name = wrapped.__name__

def __get__(self, inst: _TSelf[_T], owner: Optional[Type[Any]] = None) -> _T:
@overload
def __get__(self, inst: None, owner: Optional[Type[Any]] = None) -> Self: ...

Check notice

Code scanning / CodeQL

Statement has no effect Note

This statement has no effect.

@overload
def __get__(self, inst: _TSelf[_T], owner: Optional[Type[Any]] = None) -> _T: ...

Check notice

Code scanning / CodeQL

Statement has no effect Note

This statement has no effect.

def __get__(
self, inst: Optional[_TSelf[_T]], owner: Optional[Type[Any]] = None
) -> Union[_T, Self]:
if inst is None:
return self
try:
try:
return inst._cache[self.name]
Expand Down
39 changes: 28 additions & 11 deletions tests/test_cached_property.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
from operator import not_
from types import ModuleType
from typing import Type

import pytest

from propcache.api import cached_property

CPType = Type[cached_property]


def test_cached_property(propcache_module: ModuleType) -> None:
cp: CPType = propcache_module.cached_property

class A:
def __init__(self) -> None:
"""Init."""

@propcache_module.cached_property
@cp
def prop(self) -> int:
return 1

Expand All @@ -18,12 +25,14 @@ def prop(self) -> int:


def test_cached_property_class(propcache_module: ModuleType) -> None:
cp: CPType = propcache_module.cached_property

class A:
def __init__(self) -> None:
"""Init."""
# self._cache not set because its never accessed in this test

@propcache_module.cached_property
@cp
def prop(self) -> None:
"""Docstring."""

Expand All @@ -32,14 +41,16 @@ def prop(self) -> None:


def test_cached_property_without_cache(propcache_module: ModuleType) -> None:
cp: CPType = propcache_module.cached_property

class A:

__slots__ = ()

def __init__(self) -> None:
pass

@propcache_module.cached_property
@cp
def prop(self) -> None:
"""Mock property."""

Expand All @@ -50,14 +61,16 @@ def prop(self) -> None:


def test_cached_property_check_without_cache(propcache_module: ModuleType) -> None:
cp: CPType = propcache_module.cached_property

class A:

__slots__ = ()

def __init__(self) -> None:
"""Init."""

@propcache_module.cached_property
@cp
def prop(self) -> None:
"""Mock property."""

Expand All @@ -67,40 +80,43 @@ def prop(self) -> None:


def test_cached_property_caching(propcache_module: ModuleType) -> None:
cp: CPType = propcache_module.cached_property

class A:
def __init__(self) -> None:
"""Init."""

@propcache_module.cached_property
@cp
def prop(self) -> int:
"""Docstring."""
return 1

a = A()
assert 1 == a.prop
assert a.prop == 1


def test_cached_property_class_docstring(propcache_module: ModuleType) -> None:
cp: CPType = propcache_module.cached_property

class A:
def __init__(self) -> None:
"""Init."""

@propcache_module.cached_property
@cp
def prop(self) -> None:
"""Docstring."""

assert isinstance(A.prop, propcache_module.cached_property)
assert isinstance(A.prop, cached_property)
assert "Docstring." == A.prop.__doc__


def test_set_name(propcache_module: ModuleType) -> None:
"""Test that the __set_name__ method is called and checked."""
cp: CPType = propcache_module.cached_property

class A:

@propcache_module.cached_property
@cp
def prop(self) -> None:
"""Docstring."""

Expand All @@ -113,12 +129,13 @@ def prop(self) -> None:

def test_get_without_set_name(propcache_module: ModuleType) -> None:
"""Test that get without __set_name__ fails."""
cp = propcache_module.cached_property(not_)
cp: CPType = propcache_module.cached_property
cp_instance = cp(not_)

class A:
"""A class."""

A.cp = cp # type: ignore[attr-defined]
A.cp_instance = cp_instance # type: ignore[attr-defined]
match = r"Cannot use cached_property instance "
with pytest.raises(TypeError, match=match):
_ = A().cp # type: ignore[attr-defined]
51 changes: 35 additions & 16 deletions tests/test_under_cached_property.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
from types import ModuleType
from typing import Any, Dict
from typing import Any, Dict, Type

import pytest

from propcache.api import under_cached_property

UCPType = Type[under_cached_property]


def test_under_cached_property(propcache_module: ModuleType) -> None:
ucp: UCPType = propcache_module.under_cached_property

class A:
def __init__(self) -> None:
self._cache: Dict[str, Any] = {}
self._cache: Dict[str, int] = {}

@propcache_module.under_cached_property
@ucp
def prop(self) -> int:
return 1

Expand All @@ -18,25 +24,28 @@ def prop(self) -> int:


def test_under_cached_property_class(propcache_module: ModuleType) -> None:
ucp: UCPType = propcache_module.under_cached_property

class A:
def __init__(self) -> None:
"""Init."""
# self._cache not set because its never accessed in this test

@propcache_module.under_cached_property
@ucp
def prop(self) -> None:
"""Docstring."""

assert isinstance(A.prop, propcache_module.under_cached_property)
assert isinstance(A.prop, ucp)
assert A.prop.__doc__ == "Docstring."


def test_under_cached_property_assignment(propcache_module: ModuleType) -> None:
ucp: UCPType = propcache_module.under_cached_property

class A:
def __init__(self) -> None:
self._cache: Dict[str, Any] = {}

@propcache_module.under_cached_property
@ucp
def prop(self) -> None:
"""Mock property."""

Expand All @@ -47,11 +56,14 @@ def prop(self) -> None:


def test_under_cached_property_without_cache(propcache_module: ModuleType) -> None:
ucp: UCPType = propcache_module.under_cached_property

class A:
def __init__(self) -> None:
"""Init."""
self._cache: Dict[str, int] = {}

@propcache_module.under_cached_property
@ucp
def prop(self) -> None:
"""Mock property."""

Expand All @@ -64,42 +76,49 @@ def prop(self) -> None:
def test_under_cached_property_check_without_cache(
propcache_module: ModuleType,
) -> None:
ucp: UCPType = propcache_module.under_cached_property

class A:
def __init__(self) -> None:
"""Init."""
# Note that self._cache is intentionally missing
# here to verify AttributeError

@propcache_module.under_cached_property
@ucp
def prop(self) -> None:
"""Mock property."""

a = A()
with pytest.raises(AttributeError):
assert a.prop == 1
_ = a.prop # type: ignore[call-overload]


def test_under_cached_property_caching(propcache_module: ModuleType) -> None:
ucp: UCPType = propcache_module.under_cached_property

class A:
def __init__(self) -> None:
self._cache: Dict[str, Any] = {}
self._cache: Dict[str, int] = {}

@propcache_module.under_cached_property
@ucp
def prop(self) -> int:
"""Docstring."""
return 1

a = A()
assert 1 == a.prop
assert a.prop == 1


def test_under_cached_property_class_docstring(propcache_module: ModuleType) -> None:
ucp: UCPType = propcache_module.under_cached_property

class A:
def __init__(self) -> None:
"""Init."""

@propcache_module.under_cached_property
def prop(self) -> None:
@ucp
def prop(self) -> Any:
"""Docstring."""

assert isinstance(A.prop, propcache_module.under_cached_property)
assert isinstance(A.prop, ucp)
assert "Docstring." == A.prop.__doc__

0 comments on commit 317aee9

Please sign in to comment.