diff --git a/CHANGES/130.feature.rst b/CHANGES/130.feature.rst new file mode 100644 index 0000000..3cee944 --- /dev/null +++ b/CHANGES/130.feature.rst @@ -0,0 +1 @@ +Made propcache available for use with Cython by using cimport -- by :user:`Vizonex`. diff --git a/MANIFEST.in b/MANIFEST.in index 14ab34f..6d74591 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -19,3 +19,5 @@ exclude src/propcache/*.html exclude src/propcache/*.so exclude src/propcache/*.pyd prune docs/_build +include src/propcache/__init__.pxd +include src/propcache/_helpers_c.pxd diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 9ae57bc..00a9af9 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -18,6 +18,7 @@ booleans bools changelog changelogs +cimport config de decodable diff --git a/src/propcache/__init__.pxd b/src/propcache/__init__.pxd new file mode 100644 index 0000000..8084fd7 --- /dev/null +++ b/src/propcache/__init__.pxd @@ -0,0 +1 @@ +from ._helpers_c cimport cached_property, under_cached_property diff --git a/src/propcache/_helpers_c.pxd b/src/propcache/_helpers_c.pxd new file mode 100644 index 0000000..5c35396 --- /dev/null +++ b/src/propcache/_helpers_c.pxd @@ -0,0 +1,19 @@ +cdef class under_cached_property: + # Use as a class method decorator. It operates almost exactly like + # the Python `@property` decorator, but it puts the result of the + # method it decorates into the instance dict after the first call, + # effectively replacing the function it decorates with an instance + # variable. It is, in Python parlance, a data descriptor. + + cdef readonly object wrapped + cdef object name + +cdef class cached_property: + # Use as a class method decorator. It operates almost exactly like + # the Python `@property` decorator, but it puts the result of the + # method it decorates into the instance dict after the first call, + # effectively replacing the function it decorates with an instance + # variable. It is, in Python parlance, a data descriptor. + + cdef readonly object func + cdef object name diff --git a/src/propcache/_helpers_c.pyx b/src/propcache/_helpers_c.pyx index 9e9e558..0519c2a 100644 --- a/src/propcache/_helpers_c.pyx +++ b/src/propcache/_helpers_c.pyx @@ -28,9 +28,6 @@ cdef class under_cached_property: """ - cdef readonly object wrapped - cdef object name - def __init__(self, object wrapped): self.wrapped = wrapped self.name = wrapped.__name__ @@ -65,9 +62,6 @@ cdef class cached_property: """ - cdef readonly object func - cdef object name - def __init__(self, func): self.func = func self.name = None diff --git a/src/propcache/_test_cythonapi.pyx b/src/propcache/_test_cythonapi.pyx new file mode 100644 index 0000000..d3b02b0 --- /dev/null +++ b/src/propcache/_test_cythonapi.pyx @@ -0,0 +1,48 @@ +# cython: language_level=3, freethreading_compatible=True + +from ._helpers_c cimport cached_property, under_cached_property + +# WARNING: Do not use under normal circumstances +# feel free to ignore this file if your not testing. + +cdef class TestUnderCachedProperty: + cdef: + public dict _cache + + def __init__(self) -> None: + self._cache = {} + + @under_cached_property + def prop(self) -> int: + return 1 + + @under_cached_property + def prop2(self) -> str: + return "foo" + +cdef class TestCachedProperty: + cdef: + dict __dict__ + + def __init__(self) -> None: + pass + + @cached_property + def prop(self) -> int: + return 1 + + @cached_property + def prop2(self) -> str: + return "foo" + + +cdef class TestUnderCachedPropertyAssignment: + cdef: + public dict _cache + + def __init__(self) -> None: + self._cache = {} + + @cached_property + def prop(self) -> int: + return 1 diff --git a/tests/test_cython_api.py b/tests/test_cython_api.py new file mode 100644 index 0000000..f6ebbb7 --- /dev/null +++ b/tests/test_cython_api.py @@ -0,0 +1,21 @@ +import pytest + +CYTHON_API = pytest.importorskip("propcache._test_cythonapi") + + +def test_under_cached_property() -> None: + uc_property = CYTHON_API.TestUnderCachedProperty() + assert uc_property.prop == 1 + assert uc_property.prop2 == "foo" + + +def test_cached_property() -> None: + c_property = CYTHON_API.TestCachedProperty() + assert c_property.prop == 1 + assert c_property.prop2 == "foo" + + +def test_under_cached_property_assignment() -> None: + a = CYTHON_API.TestUnderCachedPropertyAssignment() + with pytest.raises(AttributeError): + a.prop = 123