115 lines
3.2 KiB
Python
115 lines
3.2 KiB
Python
import inspect
|
|
import string
|
|
from typing import Callable
|
|
from typing import List
|
|
from typing import Optional
|
|
|
|
TEMPLATE_FRAGMENT_KEY_TEMPLATE = "_template_fragment_cache_%s%s"
|
|
# Used to remove control characters and whitespace from cache keys.
|
|
valid_chars = set(string.ascii_letters + string.digits + "_.")
|
|
del_chars = "".join(c for c in map(chr, range(256)) if c not in valid_chars)
|
|
null_control = ({k: None for k in del_chars},)
|
|
|
|
|
|
def wants_args(f: Callable) -> bool:
|
|
"""Check if the function wants any arguments"""
|
|
arg_spec = inspect.getfullargspec(f)
|
|
return bool(arg_spec.args or arg_spec.varargs or arg_spec.varkw)
|
|
|
|
|
|
def get_function_parameters(f: Callable) -> List:
|
|
"""Get function parameters
|
|
:param f
|
|
:return: Parameter list of function
|
|
"""
|
|
return list(inspect.signature(f).parameters.values())
|
|
|
|
|
|
def get_arg_names(f: Callable) -> List[str]:
|
|
"""Return arguments of function
|
|
:param f:
|
|
:return: String list of arguments
|
|
"""
|
|
return [
|
|
parameter.name
|
|
for parameter in get_function_parameters(f)
|
|
if parameter.kind == parameter.POSITIONAL_OR_KEYWORD
|
|
]
|
|
|
|
|
|
def get_arg_default(f: Callable, position: int):
|
|
arg = get_function_parameters(f)[position]
|
|
arg_def = arg.default
|
|
return arg_def if arg_def != inspect.Parameter.empty else None
|
|
|
|
|
|
def get_id(obj):
|
|
return getattr(obj, "__caching_id__", repr)(obj)
|
|
|
|
|
|
def function_namespace(f, args=None):
|
|
"""Attempts to returns unique namespace for function"""
|
|
m_args = get_arg_names(f)
|
|
|
|
instance_token = None
|
|
|
|
instance_self = getattr(f, "__self__", None)
|
|
|
|
if instance_self and not inspect.isclass(instance_self):
|
|
instance_token = get_id(f.__self__)
|
|
elif m_args and m_args[0] == "self" and args:
|
|
instance_token = get_id(args[0])
|
|
|
|
module = f.__module__
|
|
|
|
if m_args and m_args[0] == "cls" and not inspect.isclass(args[0]):
|
|
raise ValueError(
|
|
"When using `delete_memoized` on a "
|
|
"`@classmethod` you must provide the "
|
|
"class as the first argument"
|
|
)
|
|
|
|
if hasattr(f, "__qualname__"):
|
|
name = f.__qualname__
|
|
else:
|
|
klass = getattr(f, "__self__", None)
|
|
|
|
if klass and not inspect.isclass(klass):
|
|
klass = klass.__class__
|
|
|
|
if not klass:
|
|
klass = getattr(f, "im_class", None)
|
|
|
|
if not klass:
|
|
if m_args and args:
|
|
if m_args[0] == "self":
|
|
klass = args[0].__class__
|
|
elif m_args[0] == "cls":
|
|
klass = args[0]
|
|
|
|
if klass:
|
|
name = klass.__name__ + "." + f.__name__
|
|
else:
|
|
name = f.__name__
|
|
|
|
ns = ".".join((module, name)).translate(*null_control)
|
|
|
|
ins = (
|
|
".".join((module, name, instance_token)).translate(*null_control)
|
|
if instance_token
|
|
else None
|
|
)
|
|
|
|
return ns, ins
|
|
|
|
|
|
def make_template_fragment_key(
|
|
fragment_name: str, vary_on: Optional[List[str]] = None
|
|
) -> str:
|
|
"""Make a cache key for a specific fragment name."""
|
|
if vary_on:
|
|
fragment_name = "%s_" % fragment_name
|
|
else:
|
|
vary_on = []
|
|
return TEMPLATE_FRAGMENT_KEY_TEMPLATE % (fragment_name, "_".join(vary_on))
|