Methods and Nested Functions
Classmethods
Put @cache_results below @classmethod:
class Report:
@classmethod
@cache_results(timeout=600)
def totals(cls, year: int) -> dict: ...
cls is part of the key, identified by its qualified name, so a subclass’s
results are cached apart from its parent’s. Pass the class to invalidate(),
because it receives the arguments of the undecorated function:
Report.totals.invalidate(Report, 2026)
Static methods and plain functions work as you would expect. Put
@cache_results below @staticmethod too.
Instance methods are refused
Decorating a method whose first parameter is self raises TypeError when
the class is defined:
class Book(models.Model):
@cache_results() # TypeError
def summary(self) -> dict: ...
self would be part of the key, and an instance can’t be part of a safe key.
Cache a function of the values that matter instead, and call it from the
method:
@cache_results()
def book_summary(book_id: int) -> dict: ...
class Book(models.Model):
def summary(self) -> dict:
return book_summary(self.pk)
Functions defined inside an inherited classmethod
A function’s qualified name records where it is defined, not who calls it. A
cached function defined inside a classmethod of a base class has the same
qualified name for every subclass, so without help, Book 5 and Author 5
would share one entry. Give it the class name as a parameter, so that it is
part of the key:
class CachedModel(models.Model):
class Meta:
abstract = True
@classmethod
def get_cached(cls, pk: int):
@cache_results()
def _get(pk: int, class_name: str = cls.__name__):
return cls.objects.get(pk=pk)
return _get(pk)
The nested function is created again on every call, which is fine: its key
depends on its module, qualified name and arguments, not on the function
object. For the same reason, _get.invalidate(pk) called on a new copy of
the nested function clears the entry.