Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions debug_toolbar/panels/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
]


def _monkey_patch_method(cache, name):
def _monkey_patch_method(cache, name, alias):
original_method = getattr(cache, name)

@functools.wraps(original_method)
Expand All @@ -39,15 +39,15 @@ def wrapper(*args, **kwargs):
if panel is None:
return original_method(*args, **kwargs)
else:
return panel._record_call(cache, name, original_method, args, kwargs)
return panel._record_call(cache, alias, name, original_method, args, kwargs)

setattr(cache, name, wrapper)


def _monkey_patch_cache(cache):
def _monkey_patch_cache(cache, alias):
if not hasattr(cache, "_djdt_patched"):
for name in WRAPPED_CACHE_METHODS:
_monkey_patch_method(cache, name)
_monkey_patch_method(cache, name, alias)
cache._djdt_patched = True


Expand Down Expand Up @@ -95,7 +95,7 @@ def wrapper(self, alias):
cache = original_method(self, alias)
panel = cls.current_instance()
if panel is not None:
_monkey_patch_cache(cache)
_monkey_patch_cache(cache, alias)
cache._djdt_panel = panel
return cache

Expand Down Expand Up @@ -138,7 +138,7 @@ def _store_call_info(
}
)

def _record_call(self, cache, name, original_method, args, kwargs):
def _record_call(self, cache, alias, name, original_method, args, kwargs):
# Some cache backends implement certain cache methods in terms of other cache
# methods (e.g. get_or_set() in terms of get() and add()). In order to only
# record the calls made directly by the user code, set the cache's _djdt_panel
Expand All @@ -161,7 +161,7 @@ def _record_call(self, cache, name, original_method, args, kwargs):
kwargs=kwargs,
trace=get_stack_trace(skip=2),
template_info=get_template_info(),
backend=cache,
backend=alias,
)
return value

Expand Down Expand Up @@ -194,9 +194,10 @@ def enable_instrumentation(self):
# requests. The monkey patch of CacheHander.create_connection() installed in
# the .ready() method will ensure that any new cache connections that get opened
# during this request will also be monkey patched.
for cache in caches.all(initialized_only=True):
_monkey_patch_cache(cache)
cache._djdt_panel = self
for alias in caches:
if hasattr(caches._connections, alias):
Copy link
Member

@tim-schilling tim-schilling Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like it's going to be hard for us to remember that this is effectively the old version of initialized_only=True. Do you have any ideas on making this more maintainable? [Specifically if Django changes its implementation upstream.]

The only idea I have is to use a cache._djdt_panel.record = functools.partial(cache._djdt_panel.record, alias=alias)

Though it makes me wonder if we need the panel on the cache at all. Seems like we could just store a _djdt_record function on the cache, where then the partial gets much cleaner.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though it makes me wonder if we need the panel on the cache at all. Seems like we could just store a _djdt_record function on the cache, where then the partial gets much cleaner.

We do not need the panel on the cache in the current code, it's just used a monkeypatch marker.

The only idea I have is to use a cache._djdt_panel.record = functools.partial(cache._djdt_panel.record, alias=alias)

I thought about something similar, but it needed more changes to the panel inner workings so I went with the most straightforward implementation to validate interest first. I could try a similar approach, storing a _djdt_record function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I came back to rework this but if I understand correctly there is no way to get the alias for the given cache without initializing a connection to the backend as a side effect. Checking for the caches._connections looks like the only way to work around this when monkeypatching existing connections.

Copy link
Member

@tim-schilling tim-schilling Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright thank you @federicobond. We need to document this somehow in the code. I'm between separating it out as it's own function or just commenting the loop here:

def initialized_caches():
    """
    Return the initialized caches and aliases.

    This does the same as`caches.all(initialized_only=True)`, but keeps
    the alias with each cache instance.
    """
    for alias in caches:
        if hasattr(caches._connections, alias):
            yield caches[alias], alias

So the rest can be

for cache, alias in initialized_caches():
    _monkey_patch_cache(caches[alias], alias)
    cache._djdt_panel = self

_monkey_patch_cache(caches[alias], alias)
caches[alias]._djdt_panel = self
# Mark this panel instance as the current one for the active thread/async task
# context. This will be used by the CacheHander.create_connection() monkey
# patch.
Expand Down
2 changes: 2 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ Pending
conflicts
* Added CSS for resetting the height of elements too to avoid problems with
global CSS of a website where the toolbar is used.
* Show the cache backend alias instead of the cache instance for each call in
the cache panel.

5.1.0 (2025-03-20)
------------------
Expand Down
4 changes: 4 additions & 0 deletions tests/panels/test_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,7 @@ def test_generate_server_timing(self):
}

self.assertEqual(self.panel.get_server_timing_stats(), expected_data)

def test_backend_alias_is_recorded(self):
cache.cache.get("foo")
self.assertEqual(self.panel.calls[0]["backend"], "default")
Loading