Comments (12)
This is great, thanks a lot.
from cashews.
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.
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.
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.
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:
- 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")
- 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.
Thanks for the reply. Yes, raising an error makes sense. A few thoughts:
- The exception must be custom, e.g.
CacheError
(notValueError
,RuntimeError
, etc.). This will prevent someone from suppressing the exception by mistake. - 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:
- There is already
cashews.key.get_func_params
function where 'args' and 'kwargs' are checked. We can raise the error in that function. - The decorator check whether a custom
key
argument was passed by a user. If it was passed, then suppress the error.
from cashews.
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.
@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 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 acceptlocations
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.
@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 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 thatlocations
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.
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)
- Invalid register tag with optional arguments HOT 2
- Not support redis unix socket HOT 2
- Error when delete tags the cache before caching itself HOT 2
- TTL type annotation
- Key template doesnt work correctly with class methods HOT 2
- Get keys number
- Allow cache exceptions aka. "white exceptions" HOT 6
- Current implementation of `thunder_protection` may cause "Future exception was never retrieved" warning HOT 1
- Add More contrib modules
- Pass cache result into ttl function HOT 5
- Timeout and Cancelled errors HOT 6
- Utilizing Functions as Key Generators HOT 3
- [Migration] [User experience] An exception must be raised if `cache.setup()` was not called HOT 2
- [removed by author]
- A wrong exception is raised when `prefix` from the setup does not match a prefix in a passed `key` HOT 2
- Is the `cache-control` header set? HOT 9
- 'coroutine' object has no attribute 'execute_command' - Client Side Caching HOT 4
- Does cashews need handle `IndexError` in `handle_message` in "redis/asyncio/client.py"? HOT 3
- AttributeError: 'Redis' object has no attribute '_client' HOT 4
- "No connection available" errors with redis-py 5.0.1 HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from cashews.