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

Eviction callback #31

Merged
merged 15 commits into from
Sep 19, 2023
Merged

Eviction callback #31

merged 15 commits into from
Sep 19, 2023

Conversation

uncle-lv
Copy link
Contributor

An optional callback when the cache entry was deleted.

@uncle-lv
Copy link
Contributor Author

uncle-lv commented Sep 10, 2023

Usage

set on_delete callback.

cache = Cache(on_delete=on_delete)

on_delete is a callable object.

Callable[[key: Hashable, value: Any, cause: EvictedCause],  None]

First parameter is the key of cache entry has been deleted.

Second is the value.

Third is the cause of cache entry eviction.

EvictedCause is an enum.

class EvictedCause(Enum):
    EXPLICIT = auto()
    REPLACED = auto()
    EXPIRED = auto()
    SIZE = auto()
  • EXPLICIT: indicates that the cache entry was deleted explicitly.

  • REPLACED: indicates that the entry was replaced with a new value.

  • EXPIRED: indicates that the cache entry was removed because it expired.

  • SIZE: indicates that the cache entry was removed because cache has reached the maximum size limit.

Trigger

The table shows methods will trigger on_delete.

cause trigger
EXPLICIT The cache entry was removed explicitly by delete() or delete_many()
REPLACED The cache entry was replaced by set() or set_many()
EXPIRED Implicit. When the cache entry was removed because it expired
SIZE Implicit. The cache entry was removed because cache has reached the maximum size limit

clear() never triggers on_delete.

@coveralls
Copy link

coveralls commented Sep 10, 2023

Coverage Status

coverage: 100.0%. remained the same when pulling db9d136 on uncle-lv:eviction-callback into 11ba7fd on dgilland:master.

@uncle-lv
Copy link
Contributor Author

uncle-lv commented Sep 10, 2023

This pr is for #30 .

@uncle-lv
Copy link
Contributor Author

This pr includes the commits of last pr. I tried to solve it, but failed. So does anyone know the right way to do it?

@uncle-lv
Copy link
Contributor Author

Sorry, I was not able to solve this problem finally. So I used force push to make commits clean.

Copy link
Owner

@dgilland dgilland left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall! A few minor tweaks and some discussion around which eviction cause to use when popitem() called.

@@ -23,6 +24,23 @@
UNSET = object()


class EvictedCause(Enum):
"""
An enum to represent the cause for the evication of a cache entry.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
An enum to represent the cause for the evication of a cache entry.
An enum to represent the cause for the eviction of a cache entry.

@@ -23,6 +24,23 @@
UNSET = object()


class EvictedCause(Enum):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class EvictedCause(Enum):
class EvictionCause(Enum):

@@ -513,7 +537,7 @@ def _popitem(self):
raise KeyError("popitem(): cache is empty")

value = self._cache[key]
self._delete(key)
self._delete(key, EvictedCause.SIZE)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if _popitem() might need the eviction cause as an argument since it could potentially be called for different reasons. It's either called from evict() which would be SIZE but when called from popitem() it could be considered EXPLICIT since it's similar to a direct call to delete() except the key isn't being supplied. Seems like SIZE might not fit so well if the caller is the one making the popitem() call (so really the cache might not be full, it's just being intentionally pruned based on the cache policy).

Just brainstorming here, but another option might be to have POLICY replace SIZE which would encompass deleting the key because either the cache was full or because the key was removed via popitem(). But maybe wanting to differentiate between cache-full and an explicit call to remove a key would be preferable.

Thoughts?

@uncle-lv
Copy link
Contributor Author

I will add an EvictionCause argument for _popitem().
It will be committed soon.

@uncle-lv
Copy link
Contributor Author

uncle-lv commented Sep 18, 2023

After careful consideration, I don't agree popitem() should be considered EXPLICIT, because the user don't know which entry will be deleted explicitly. But I have no idea about it now. Maybe a new EvictionCause value called POPITEM? Any suggestions?

@dgilland
Copy link
Owner

If going the route of something like EvictionCause.POPITEM, then could potentially align with the method names where the eviction is happening.

E.g.

  • EXPLICIT -> DELETE
  • REPLACE -> SET
  • SIZE -> FULL
  • EXPIRED (leave as-is)
  • POPITEM (new)

Might be a little clearer that way, although, it is a little more implementation specific (but maybe that's fine).

@uncle-lv
Copy link
Contributor Author

Good idea! I'll do it.

@uncle-lv
Copy link
Contributor Author

uncle-lv commented Sep 19, 2023

Updated usage.

Usage

set on_delete callback.

cache = Cache(on_delete=on_delete)

on_delete is a callable object.

Callable[[key: Hashable, value: Any, cause: RemovalCause],  None]

First parameter is the key of cache entry has been deleted.

Second is the value.

Third is the cause of cache entry removal.

RemovalCause is an enum.

class RemovalCause(Enum):
    DELETE = auto()
    SET = auto()
    EXPIRED = auto()
    FULL = auto()
    POPITEM = auto()
  • DELETE: indicates that the cache entry was deleted by delete() or delete_many() explicitly.
  • SET: indicates that the cache entry was replaced with a new value by set() or set_many().
  • EXPIRED: indicates that the cache entry was removed because it expired.
  • FULL: indicates that the cache entry was removed because cache has been full (reached the maximum size limit).
  • POPITEM: indicates that the cache entry was deleted by popitem().

Trigger

The table shows methods will trigger on_delete.

cause trigger
DELETE The cache entry was removed explicitly by delete() or delete_many()
SET The cache entry was replaced by set() or set_many()
POPITEM The cache entry was deleted by popitem()
EXPIRED Implicit. When the cache entry was removed because it expired
FULL Implicit. The cache entry was removed because cache has reached the maximum size limit

clear() never triggers on_delete.

Copy link
Owner

@dgilland dgilland left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! Thanks for taking the time to add this! 👍

@dgilland dgilland merged commit 51c88ba into dgilland:master Sep 19, 2023
@dgilland dgilland mentioned this pull request Oct 31, 2023
@arossert
Copy link

arossert commented Nov 1, 2023

When are you planing to make an official release with this change?

@dgilland
Copy link
Owner

dgilland commented Nov 4, 2023

@arossert

Released in v0.15.0: https://pypi.org/project/cacheout/0.15.0/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants