1717import warnings
1818from collections import OrderedDict
1919from collections .abc import Callable
20+ from collections .abc import Iterable
2021from typing import Any
2122from typing import cast
2223from typing import Concatenate
2627from typing import TypeAlias
2728from typing import TypeVar
2829
29- from blinker import Namespace
3030from cachelib .serializers import BaseSerializer
3131from flask import current_app
3232from flask import Flask
3636from flask import url_for
3737from werkzeug .utils import import_string
3838
39- from flask_caching .backends .base import BaseCache
40- from flask_caching .backends .simplecache import SimpleCache
41- from flask_caching .utils import function_namespace
42- from flask_caching .utils import get_arg_default
43- from flask_caching .utils import get_arg_names
44- from flask_caching .utils import get_id
45- from flask_caching .utils import join_generator
46- from flask_caching .utils import make_template_fragment_key as make_template_fragment_key
47- from flask_caching .utils import wants_args
39+ from .backends .base import BaseCache
40+ from .backends .simplecache import SimpleCache
41+ from .signals import cache_memoize_hit as cache_memoize_hit
42+ from .signals import cache_memoize_miss as cache_memoize_miss
43+ from .signals import cache_view_hit as cache_view_hit
44+ from .signals import cache_view_miss as cache_view_miss
45+ from .utils import _QueryArgs
46+ from .utils import function_namespace
47+ from .utils import get_arg_default
48+ from .utils import get_arg_names
49+ from .utils import get_id
50+ from .utils import join_generator
51+ from .utils import make_template_fragment_key as make_template_fragment_key
52+ from .utils import query_args_as_pairs
53+ from .utils import wants_args
4854
4955logger = logging .getLogger (__name__ )
5056
51- _signals = Namespace ()
5257
5358# The initial version timeout of a memoize version key. Will be overwritten on the first
5459# write with the memoize value timeout
6368 hashlib .md5 ,
6469]
6570
71+
6672P = ParamSpec ("P" )
6773# The parameters left over after ``__get__`` binds the instance.
6874P2 = ParamSpec ("P2" )
7379T_co = TypeVar ("T_co" , covariant = True )
7480T_contra = TypeVar ("T_contra" , contravariant = True )
7581
76- cache_view_hit = _signals .signal ("cache-view-hit" )
77- cache_view_miss = _signals .signal ("cache-view-miss" )
78- cache_memoize_hit = _signals .signal ("cache-memoize-hit" )
79- cache_memoize_miss = _signals .signal ("cache-memoize-miss" )
80-
8182
8283class _BoundCachedFunction (Protocol [T_contra , P , T_co ]):
8384 """The type of a :meth:`Cache.cached` method accessed on an instance."""
@@ -160,6 +161,10 @@ def __get__(self, instance: Any, owner: type | None = None, /) -> Any: ...
160161 "_MemoizedFunction[..., Any] | _BoundMemoizedFunction[Any, ..., Any]"
161162)
162163
164+ _AnyCachedFunction : TypeAlias = (
165+ "_CachedFunction[..., Any] | _BoundCachedFunction[Any, ..., Any]"
166+ )
167+
163168
164169class CachedResponse (Response ):
165170 """
@@ -463,6 +468,20 @@ def get_list():
463468
464469 readable and writable
465470
471+ Outside of a request context, pass ``path`` to build the
472+ key for a given ``request.path`` and, for
473+ ``query_string=True``, ``query_args`` to build the key for
474+ a given query string::
475+
476+ key = view.make_cache_key(
477+ path="/works", query_args="limit=15&mock=true"
478+ )
479+ cache.delete(key)
480+
481+ ``query_args`` accepts a query string, a mapping or an
482+ iterable of ``(key, value)`` pairs. See
483+ :meth:`delete_cached` for the shorthand.
484+
466485 :param timeout: Default None. If set to an integer, will cache for that
467486 amount of time. Unit of time is in seconds.
468487
@@ -636,9 +655,20 @@ def default_make_cache_key(*args: Any, **kwargs: Any) -> str:
636655 kwargs [arg_name ] = arg
637656
638657 use_request = kwargs .pop ("use_request" , False )
639- return _make_cache_key (args , kwargs , use_request = use_request )
658+ path = kwargs .pop ("path" , None )
659+ query_args = kwargs .pop ("query_args" , None )
660+ return _make_cache_key (
661+ args ,
662+ kwargs ,
663+ use_request = use_request ,
664+ path = path ,
665+ query_args = query_args ,
666+ )
640667
641- def _make_cache_key_query_string () -> str :
668+ def _make_cache_key_query_string (
669+ path : str | None = None ,
670+ query_args : _QueryArgs | None = None ,
671+ ) -> str :
642672 """Create consistent keys for query string arguments.
643673
644674 Produces the same cache key regardless of argument order, e.g.,
@@ -657,9 +687,12 @@ def _make_cache_key_query_string() -> str:
657687 # are the same, regardless of the order in which they are
658688 # provided.
659689
660- args_as_sorted_tuple = tuple (
661- sorted (pair for pair in request .args .items (multi = True ))
662- )
690+ if query_args is None :
691+ pairs : Iterable [tuple [str , str ]] = request .args .items (multi = True )
692+ else :
693+ pairs = query_args_as_pairs (query_args )
694+
695+ args_as_sorted_tuple = tuple (sorted (pair for pair in pairs ))
663696 # ... now hash the sorted (key, value) tuple so it can be
664697 # used as a key for cache. Turn them into bytes so that the
665698 # hash function will accept them
@@ -678,26 +711,37 @@ def _make_cache_key_query_string() -> str:
678711 if callable (key_prefix ):
679712 cache_key = key_prefix ()
680713 elif "%s" in key_prefix :
681- cache_key = key_prefix % request .path
714+ cache_key = key_prefix % ( request .path if path is None else path )
682715 else :
683716 cache_key = key_prefix
684717
685718 return cache_key + cache_hash
686719
687720 def _make_cache_key (
688- args : tuple [Any , ...], kwargs : dict [str , Any ], use_request : bool
721+ args : tuple [Any , ...],
722+ kwargs : dict [str , Any ],
723+ use_request : bool ,
724+ path : str | None = None ,
725+ query_args : _QueryArgs | None = None ,
689726 ) -> str :
690727 if query_string :
691- return _make_cache_key_query_string ()
728+ return _make_cache_key_query_string (path , query_args )
692729 else :
693730 cache_key : str
694731 if callable (key_prefix ):
695732 cache_key = key_prefix ()
696733 elif "%s" in key_prefix :
697- if use_request :
734+ if path is not None :
735+ cache_key = key_prefix % path
736+ elif use_request :
698737 cache_key = key_prefix % request .path
699738 else :
700- cache_key = key_prefix % url_for (f .__name__ , ** kwargs )
739+ # Outside of a request context ``url_for``
740+ # defaults to an external URL, which would not
741+ # match the key the request stored.
742+ cache_key = key_prefix % url_for (
743+ f .__name__ , _external = False , ** kwargs
744+ )
701745 else :
702746 cache_key = key_prefix
703747
@@ -720,6 +764,39 @@ def _make_cache_key(
720764
721765 return decorator
722766
767+ def delete_cached (
768+ self ,
769+ f : _AnyCachedFunction ,
770+ path : str | None = None ,
771+ query_args : _QueryArgs | None = None ,
772+ ** kwargs : Any ,
773+ ) -> bool :
774+ """Delete the cached value of a :meth:`cached` decorated function.
775+
776+ Example::
777+
778+ @app.route("/works")
779+ @cache.cached(query_string=True)
780+ def view_works():
781+ return do_search(request.args)
782+
783+ cache.delete_cached(view_works, "/works", {"limit": 15})
784+
785+ If you are calling this outside of a request context pass ``path`` and
786+ when the function was decorated with ``query_string=True`` you also
787+ have to pass ``query_args``.
788+
789+ :param f: The decorated function whose cached value to delete.
790+ :param path: The ``request.path`` the value was cached for.
791+ :param query_args: The query string the value was cached for, as a
792+ query string, a mapping or an iterable of
793+ ``(key, value)`` pairs. Only used when the function
794+ was decorated with ``query_string=True``.
795+ :param kwargs: The view arguments, passed to ``url_for()`` to build
796+ the path when ``path`` is not given.
797+ """
798+ return self .delete (f .make_cache_key (path = path , query_args = query_args , ** kwargs ))
799+
723800 def _memvname (self , funcname : str ) -> str :
724801 return funcname + "_memver"
725802
0 commit comments