Skip to content

Commit 440d7a6

Browse files
committed
feat: implement caching for get_object in ContentLibraryData
1 parent ac98f5c commit 440d7a6

1 file changed

Lines changed: 51 additions & 3 deletions

File tree

openedx_authz/api/data.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import Any, ClassVar, Literal, Type
99

1010
from attrs import define
11+
from django.core.cache import cache
1112
from opaque_keys import InvalidKeyError
1213
from opaque_keys.edx.locator import LibraryLocatorV2
1314

@@ -361,6 +362,8 @@ class ContentLibraryData(ScopeData):
361362
"""
362363

363364
NAMESPACE: ClassVar[str] = "lib"
365+
CACHE_TIMEOUT: ClassVar[int] = 5
366+
CACHE_KEY_PREFIX: ClassVar[str] = "authz:content_library"
364367

365368
@property
366369
def library_id(self) -> str:
@@ -393,22 +396,67 @@ def get_object(self) -> ContentLibrary | None:
393396
"""Retrieve the ContentLibrary instance associated with this scope.
394397
395398
This method converts the library_id to a LibraryLocatorV2 key and queries the
396-
database to fetch the corresponding ContentLibrary object.
399+
database to fetch the corresponding ContentLibrary object. Results are cached
400+
using Django's cache framework with a configurable timeout.
397401
398402
Returns:
399403
ContentLibrary | None: The ContentLibrary instance if found in the database,
400404
or None if the library does not exist.
401405
402406
Examples:
403407
>>> library_scope = ContentLibraryData(external_key='lib:DemoX:CSPROB')
404-
>>> library_obj = library_scope.get_object() # Returns a ContentLibrary instance
408+
>>> library_obj = library_scope.get_object() # First call: queries DB
409+
>>> library_obj = library_scope.get_object() # Cached for 5 seconds
410+
>>> # Even with a new instance:
411+
>>> library_scope2 = ContentLibraryData(external_key='lib:DemoX:CSPROB')
412+
>>> library_obj2 = library_scope2.get_object() # Uses cache
413+
414+
Note:
415+
- Uses Django cache with timeout (default: 5 seconds)
416+
- Cache key format: 'authz:content_library:<library_id>'
417+
- To clear cache: ContentLibraryData.clear_cache(library_id)
418+
- Cache timeout can be configured via CACHE_TIMEOUT class variable
405419
"""
420+
cache_key = f"{self.CACHE_KEY_PREFIX}:{self.library_id}"
421+
422+
cached_library = cache.get(cache_key)
423+
if cached_library is not None:
424+
return cached_library
425+
406426
try:
407427
library_key = LibraryLocatorV2.from_string(self.library_id)
408-
return ContentLibrary.objects.get_by_key(library_key=library_key)
428+
library_obj = ContentLibrary.objects.get_by_key(library_key=library_key)
429+
cache.set(cache_key, library_obj, self.CACHE_TIMEOUT)
430+
return library_obj
409431
except ContentLibrary.DoesNotExist:
432+
cache.set(cache_key, None, self.CACHE_TIMEOUT)
410433
return None
411434

435+
@classmethod
436+
def clear_cache(cls, library_id: str | None = None) -> None:
437+
"""Clear the ContentLibrary object cache.
438+
439+
Args:
440+
library_id: Specific library ID to clear from cache. If None, clears all
441+
library caches (requires cache backend that supports pattern deletion).
442+
443+
Examples:
444+
>>> # Clear specific library
445+
>>> ContentLibraryData.clear_cache('lib:DemoX:CSPROB')
446+
>>> # Clear all libraries (if supported by cache backend)
447+
>>> ContentLibraryData.clear_cache()
448+
"""
449+
if library_id:
450+
cache_key = f"{cls.CACHE_KEY_PREFIX}:{library_id}"
451+
cache.delete(cache_key)
452+
else:
453+
# Clear all libraries
454+
try:
455+
cache.delete_pattern(f"{cls.CACHE_KEY_PREFIX}:*")
456+
except AttributeError:
457+
# Fallback: cache backend doesn't support pattern deletion
458+
pass
459+
412460
def __str__(self):
413461
"""Human readable string representation of the content library."""
414462
return self.library_id

0 commit comments

Comments
 (0)