Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for @property for once_per_instance. #22

Merged
merged 1 commit into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion once/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

def _is_method(func: collections.abc.Callable):
"""Determine if a function is a method on a class."""
if isinstance(func, (classmethod, staticmethod)):
if isinstance(func, (classmethod, staticmethod, property)):
return True
sig = inspect.signature(func)
return "self" in sig.parameters
Expand Down Expand Up @@ -309,6 +309,8 @@ def __get__(self, obj, cls) -> collections.abc.Callable:
class once_per_instance: # pylint: disable=invalid-name
"""A version of once for class methods which runs once per instance."""

is_property: bool

@classmethod
def with_options(cls, per_thread: bool = False, retry_exceptions=False):
return lambda func: cls(func, per_thread=per_thread, retry_exceptions=retry_exceptions)
Expand Down Expand Up @@ -339,6 +341,11 @@ def once_factory(self) -> _ONCE_FACTORY_TYPE:
def _inspect_function(self, func: collections.abc.Callable):
if isinstance(func, (classmethod, staticmethod)):
raise SyntaxError("Must use @once.once_per_class on classmethod and staticmethod")
if isinstance(func, property):
func = func.fget
self.is_property = True
else:
self.is_property = False
if not _is_method(func):
raise SyntaxError(
"Attempting to use @once.once_per_instance method-only decorator "
Expand All @@ -357,4 +364,6 @@ def __get__(self, obj, cls) -> collections.abc.Callable:
bound_func, self.once_factory(), self.fn_type, self.retry_exceptions
)
self.callables[obj] = callable
if self.is_property:
return callable()
aebrahim marked this conversation as resolved.
Show resolved Hide resolved
return callable
19 changes: 19 additions & 0 deletions once_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,25 @@ def execute(i):
self.assertEqual(min(results), 1)
self.assertEqual(max(results), math.ceil(_N_WORKERS / 4))

def test_once_per_instance_property(self):
counter = Counter()

class _CallOnceClass:
@once.once_per_instance
@property
def value(self):
nonlocal counter
return counter.get_incremented()

a = _CallOnceClass()
b = _CallOnceClass()
self.assertEqual(a.value, 1)
self.assertEqual(b.value, 2)
self.assertEqual(a.value, 1)
self.assertEqual(b.value, 2)
self.assertEqual(_CallOnceClass().value, 3)
self.assertEqual(_CallOnceClass().value, 4)

def test_once_per_class_classmethod(self):
counter = Counter()

Expand Down
Loading