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

Cache functions returning None #452

Closed
argaen opened this issue Mar 26, 2019 · 8 comments
Closed

Cache functions returning None #452

argaen opened this issue Mar 26, 2019 · 8 comments

Comments

@argaen
Copy link
Member

argaen commented Mar 26, 2019

Right now if you decorate a function that doesn't return anything, it will not work and will execute always, this is not intuitive

Is there a use case for this? could someone want to cache a function that doesn't return anything? happy to hear thoughts from people.

@millilux
Copy link

millilux commented Jun 5, 2019

Hi there,

One use case where this could be useful is caching responses from a GraphQL server. We have a Sanic app that calls out to a GraphQL server. When a particular object can't be found, the GraphQL server returns a null, translating to None in Python land.
We want to cache this result for a short duration, as we know the object will eventually become available. Currently we have some wrapper logic to handle caching a None, but it'd be nice to have the option to toggle this behaviour.

@argaen
Copy link
Member Author

argaen commented Jun 7, 2019

Yep, I also thought on cases where calling a function triggers an expensive calculation and you don't want to trigger it if the endpoint is called after 5s or something like that. But not sure how common is that. But yes makes sense.

you think this wrapper logic is something that could be reused?

@paulo-raca
Copy link

+1, same issue here.

I think we could use a guard value internally (Cache.MISSING = object()), and add a default argument on the public methods

@Dreamsorcerer
Copy link
Member

In #652 we are looking to add a function to customise the behaviour needed to skip the cache.

So, we should proceed with caching all results, then the old behaviour can be reproduced with skip_cache_func=lambda r: r is None.

@Dreamsorcerer Dreamsorcerer added this to the 1.0 milestone Jan 20, 2023
@antont
Copy link

antont commented Jun 9, 2023

Dogpile caching lib has a NO_VALUE thing for this purpose, to allow caching None as a return value.

get(key, expiration_time=None, ignore_expiration=False)
Return a value from the cache, based on the given key.

If the value is not present, the method returns the token NO_VALUE. NO_VALUE evaluates to False, but is separate from None to distinguish between a cached value of None.

https://dogpilecache.sqlalchemy.org/en/latest/api.html#dogpile.cache.region.CacheRegion.get

@Dreamsorcerer
Copy link
Member

Dreamsorcerer commented Jun 9, 2023

We've added the skip_cache_func, so this works in future. Having some other sentinel value is less Pythonic and not going to work with decorators (we have to return the value the function returns...), which is probably the main use case for this library.

@kkom
Copy link

kkom commented Jun 1, 2024

Hey @Dreamsorcerer – I think that the caching of functions returning None still doesn't work (despite adding skip_cache_func in #652).

E.g. see this example which uses aiocache = "0.12.2":

import asyncio
from aiocache import cached

@cached()
async def cached_returns_int() -> int:
    print("executing cached_returns_int")
    return 1

@cached()
async def cached_returns_none() -> None:
    print("executing cached_returns_none")
    return

async def test_caching() -> None:
    for _ in range(5):
        await cached_returns_int()

    for _ in range(5):
        await cached_returns_none()

asyncio.run(test_caching())

This prints:

executing cached_returns_int
executing cached_returns_none
executing cached_returns_none
executing cached_returns_none
executing cached_returns_none
executing cached_returns_none

I believe that's because receiving a cached value of None is still conflated with "the value is missing from cache" and results in running L118, i.e. executing the underlying function:

if cache_read:
value = await self.get_from_cache(key)
if value is not None:
return value
result = await f(*args, **kwargs)

async def get_from_cache(self, key):
try:
return await self.cache.get(key)
except Exception:
logger.exception("Couldn't retrieve %s, unexpected error", key)
return None

@Dreamsorcerer
Copy link
Member

Hmm, yeah. If you can create a test in a PR that'd be really helpful.

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

No branches or pull requests

6 participants