GithubHelp home page GithubHelp logo

Comments (12)

rkrell avatar rkrell commented on May 23, 2024 1

This is great, thanks a lot.

from cashews.

AIGeneratedUsername avatar AIGeneratedUsername commented on May 23, 2024

My temporary workaround is a custom decorator:

def ...  # decorator's name does not matter now

    def wrapper(func):  # type: ignore[no-untyped-def]
        @wraps(func)
        async def wrap(*args, **kwargs):  # type: ignore[no-untyped-def]
            key_template = get_cache_key_template(func, prefix=namespace, exclude_parameters={"self"} if noself else ())
            func_params = set(get_func_params(func))
            for k, v in kwargs.items():
                if k not in func_params:
                    key_template += f":{k}:{v}"
            cache_decorator = simple.cache(cache, ttl=ttl, prefix=namespace, key=key_template)
            if prevent_concurrent_calls:
                locked_decorator = locked(cache, ttl=ttl)
                return await locked_decorator(cache_decorator(func))(*args, **kwargs)
            else:
                return await cache_decorator(func)(*args, **kwargs)

        return wrap

    return wrapper

I append manually passed args, kwargs if they are not in the function's signature.

UPD:
I've found that I forgot to append args here. I will fix.

from cashews.

Krukov avatar Krukov commented on May 23, 2024

Thanks - yep I know about this bug because that was a feature )).

For my point of view a function with *args, **kwargs is some kind of bad design, a weak, not clear signature. But it can't be a reason to make this bug happened

from cashews.

AIGeneratedUsername avatar AIGeneratedUsername commented on May 23, 2024

If a user did not pass a custom key template, what is the purpose of your beautiful keys like param_name:param_value? Only readability in logs (if someone will look at them)? I think that if a user do not pass a key template, then the key can be built the same as "aiocache" and disccache.core.args_to_key do. In other words, just take all 'args' and 'kwargs' (including defaults that were not passed explicitly). Am I missing something?

My main problem now is how to distinguish between positional arguments and arguments passed as *args without rewriting all your code from scratch. E.g. if there is a func(a, *args, **kwargs), then distinguish between func("spam", "eggs", foo=bar) and func("spam, foo=bar). For now, the mentioned libraries approaches look as the only way.

from cashews.

Krukov avatar Krukov commented on May 23, 2024

Sorry for a long response,
Yep This "beautiful" keys for logs and also for simpler maintain and troubleshooting some problems from storage side. e.g: you faced some problems with redis and what to understand what's going on. You can make redis-cli monitor to see cmds activity with keys. If your keys are easy to read, you can detect some anomalies based on keys patterns. But of course with this approach we will use more memory, because keys are bigger.

Regarding *args **kwargs - there are 2 problems with it:

  1. Ordering and call signature - with signatures like that you can't guarantee that you will build the same key, because it depends of how user call this function - he can pass arguments by kwargs in one place and in the other by args, or he can pass it in different order.
# user may expect that next 2 call should have 1 cache, but it will be a different  
await func("foo", "bar", "foo")
await func("foo", bar="bar", foo="foo") 

# sometimes user may expect the same key for a next calls 
await func("foo", "bar", "zar")
await func("foo", "zar" "bar")
  1. I hope that there is no doubt that *args **kwargs should be avoided in signature of functions especially if you want to cache it - it is unclear and leading to errors.

So based on it I think that cashews should raise an error, when user want to decorate functions with *args or **kwargs signature without definition of a key template.

from cashews.

AIGeneratedUsername avatar AIGeneratedUsername commented on May 23, 2024

Thanks for the reply. Yes, raising an error makes sense. A few thoughts:

  1. The exception must be custom, e.g. CacheError (not ValueError, RuntimeError, etc.). This will prevent someone from suppressing the exception by mistake.
  2. The error must be raised only if a user did not define a custom key template. For example, if the user passes a custom key="{foo:bar}", then he is safe from possible problems with his kwargs. In other words, the user explicitly uses specific parameter(s) as a key. No need to raise an error or show a warning.

Possible workflow:

  1. There is already cashews.key.get_func_params function where 'args' and 'kwargs' are checked. We can raise the error in that function.
  2. The decorator check whether a custom key argument was passed by a user. If it was passed, then suppress the error.

from cashews.

rkrell avatar rkrell commented on May 23, 2024

Now, after this change has appeared in 4.0.0, on the following definition:

    @cache(ttl=timedelta(hours=12))
    async def get(*locations):

I get this error:

Traceback (most recent call last):
  ...
  File "/home/myproject/location.py", line 17, in LocationQuery
    async def get(*barcodes) -> Optional[List[Location]]:
  File "/home/myproject/lib/python3.7/site-packages/cashews/wrapper.py", line 240, in _decorator
    decorator = decorator_fabric(self, **decor_kwargs)(func)
  File "/home/myproject/lib/python3.7/site-packages/cashews/decorators/cache/simple.py", line 32, in _decor
    _key_template = get_cache_key_template(func, key=key, prefix=prefix)
  File "/home/myproject/lib/python3.7/site-packages/cashews/key.py", line 106, in get_cache_key_template
    raise KeyTemplateRequired("Use key option for functions with *args **key")
cashews.key.KeyTemplateRequired: Use key option for functions with *args **key

So this is a breaking change for me.

How can this be avoided using the key parameter or however?

from cashews.

AIGeneratedUsername avatar AIGeneratedUsername commented on May 23, 2024

@rkrell While I prefer the 'aiocache' (deprecated) package key building design, Krukov wrote about the reasons why caching *args was not implemented in this package. So, in the current situation, the only workaround that I imagine is to refactor your code to accept locations as a single argument, e.g.:

from collections.abc import Collection

@cache(ttl=timedelta(hours=12))
async def get(locations: Collection):
    for location in locations:
         # do something...

or move out caching into a helper private function:

@cache(ttl=timedelta(hours=12))
async def _get(location: str):
    ...


async def get(*locations):
    for location in locations:
       await _get(location)

from cashews.

rkrell avatar rkrell commented on May 23, 2024

@rkrell While I prefer the 'aiocache' (deprecated) package key building design, Krukov wrote about the reasons why caching *args was not implemented in this package. So, in the current situation, the only workaround that I imagine is to refactor your code to accept locations as a single argument, e.g.:
...

I see, the Collections or list way as argument could be the way to go here.

Thank you both.

from cashews.

Krukov avatar Krukov commented on May 23, 2024

@rkrell yep sorry for breaking change, but seems it should be done because in your case the key was __func__.__module__.get, but I think that you expected that locations should be in the key.

May be I should rethink about key template. Can you tell me what you expect with next example:

@cache(ttl=timedelta(hours=12))
async def get(*locations):
      ...
      
await get("1", "2", "3") 
await get("1", "3", "2") 

For my point of view - it depends.
If a logic of this method depends on ordering of locations, in this case those two call should have different keys.
If a logic of this method independent of ordering => keys should be the same.
But the library doesn't know what you expect, that's why a key building should be done clear and obvious.

May be we can solve this problem with some flag in a decorator, to tell what we expect ( like ordering: bool)?

from cashews.

rkrell avatar rkrell commented on May 23, 2024

@rkrell yep sorry for breaking change, but seems it should be done because in your case the key was __func__.__module__.get, but I think that you expected that locations should be in the key.

Yes, exactly, in particular the array resulting from it should be the key.

May be I should rethink about key template. Can you tell me what you expect with next example:

@cache(ttl=timedelta(hours=12))
async def get(*locations):
      ...
      
await get("1", "2", "3") 
await get("1", "3", "2") 

I had calls like:

await get("A", "B", "C") 
await get("Z") 

In the function definition I just use a for loop:

for loc in locations:
    # process single loc
...

The advantage was the variable size of the argument list without the need of converting the args to a list while calling, like this:

await get(["A", "B", "C"]) 
await get(["Z"]) 

For my point of view - it depends. If a logic of this method depends on ordering of locations, in this case those two call should have different keys. If a logic of this method independent of ordering => keys should be the same. But the library doesn't know what you expect, that's why a key building should be done clear and obvious.

May be we can solve this problem with some flag in a decorator, to tell what we expect ( like ordering: bool)?

I would not do this. It should be the developer's responsibility to ensure an order of arguments for caching.
In worst case he gets multiple keys for different orders and caching is not optimally.
If I passed the arguments repeatedly always in the same order caching worked in 3.3.0. also with the *locations approach.

from cashews.

Krukov avatar Krukov commented on May 23, 2024

As I see , that is a very common thing to use *args **kwargs and developers expect that cache key will include these parameters.

So after 4.1.0 key will include disordered *args and ordered **kwargs

from cashews.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.