-
-
Notifications
You must be signed in to change notification settings - Fork 525
fix: add GraphQL cache invalidation for programs and modules #3387
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
arkid15r
merged 13 commits into
OWASP:main
from
HarshitVerma109:fix/mentorship-cache-invalidation
Jan 24, 2026
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
6f70def
fix: add GraphQL cache invalidation for programs and modules
HarshitVerma109 f6b983f
fix:address CodeRabbit review comments
HarshitVerma109 ab049fc
fix: move cache invalidation to extensions.py
HarshitVerma109 c9d3544
refactor cache invalidation
rudransh-shrivastava 19b18ae
update tests
rudransh-shrivastava cdb7867
fix: resolve cache race conditions
HarshitVerma109 e05ffff
test: add tests for cache invalidation
HarshitVerma109 49ec160
Update code
arkid15r 8964ba6
Merge branch 'main' into pr/HarshitVerma109/3387
arkid15r dde7343
Clean up code
arkid15r 5f765ff
Address CR's comment
arkid15r 23b7f3d
Update code
arkid15r 83bb75e
Merge branch 'main' into fix/mentorship-cache-invalidation
arkid15r File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Strawberry extensions.""" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| """Strawberry cache extension.""" | ||
|
|
||
| import hashlib | ||
| import json | ||
| from functools import lru_cache | ||
|
|
||
| from django.conf import settings | ||
| from django.core.cache import cache | ||
| from django.core.serializers.json import DjangoJSONEncoder | ||
| from strawberry.extensions import SchemaExtension | ||
| from strawberry.permission import PermissionExtension | ||
| from strawberry.schema import Schema | ||
| from strawberry.utils.str_converters import to_camel_case | ||
|
|
||
|
|
||
| @lru_cache(maxsize=1) | ||
| def get_protected_fields(schema: Schema) -> tuple[str, ...]: | ||
| """Get protected field names. | ||
|
|
||
| Args: | ||
| schema (Schema): The GraphQL schema. | ||
|
|
||
| Returns: | ||
| tuple[str, ...]: Tuple of protected field names in camelCase. | ||
|
|
||
| """ | ||
| return tuple( | ||
| to_camel_case(field.name) | ||
| for field in getattr( | ||
| getattr(schema.schema_converter.type_map.get("Query"), "definition", None), | ||
| "fields", | ||
| (), | ||
| ) | ||
| if any(isinstance(ext, PermissionExtension) for ext in field.extensions) | ||
| ) | ||
|
|
||
|
|
||
| def generate_key(field_name: str, field_args: dict) -> str: | ||
| """Generate a unique cache key for a query. | ||
|
|
||
| Args: | ||
| field_name (str): The GraphQL field name. | ||
| field_args (dict): The field's arguments. | ||
|
|
||
| Returns: | ||
| str: The unique cache key. | ||
|
|
||
| """ | ||
| key = f"{field_name}:{json.dumps(field_args, cls=DjangoJSONEncoder, sort_keys=True)}" | ||
| return f"{settings.GRAPHQL_RESOLVER_CACHE_PREFIX}-{hashlib.sha256(key.encode()).hexdigest()}" | ||
|
|
||
|
|
||
| def invalidate_cache(field_name: str, field_args: dict) -> bool: | ||
| """Invalidate a specific GraphQL query from the resolver cache. | ||
|
|
||
| Args: | ||
| field_name: The GraphQL field name (e.g., 'getProgram'). | ||
| field_args: The field's arguments as a dict (e.g., {'programKey': 'my-program'}). | ||
|
|
||
| Returns: | ||
| True if cache was invalidated, False if key didn't exist. | ||
|
|
||
| """ | ||
| return cache.delete(generate_key(field_name, field_args)) | ||
|
|
||
|
|
||
| def invalidate_program_cache(program_key: str) -> None: | ||
| """Invalidate all GraphQL caches related to a program. | ||
|
|
||
| Args: | ||
| program_key: The program's key identifier. | ||
|
|
||
| """ | ||
| invalidate_cache("getProgram", {"programKey": program_key}) | ||
| invalidate_cache("getProgramModules", {"programKey": program_key}) | ||
|
|
||
|
|
||
| def invalidate_module_cache(module_key: str, program_key: str) -> None: | ||
| """Invalidate all GraphQL caches related to a module. | ||
|
|
||
| Args: | ||
| module_key: The module's key identifier. | ||
| program_key: The program's key identifier. | ||
|
|
||
| """ | ||
| invalidate_cache("getModule", {"moduleKey": module_key, "programKey": program_key}) | ||
| invalidate_program_cache(program_key) | ||
|
|
||
|
|
||
| class CacheExtension(SchemaExtension): | ||
| """Cache extension.""" | ||
|
|
||
| def resolve(self, _next, root, info, *args, **kwargs): | ||
| """Wrap the resolver to provide caching.""" | ||
| if ( | ||
| info.field_name.startswith("__") | ||
| or info.parent_type.name != "Query" | ||
| or info.field_name in get_protected_fields(self.execution_context.schema) | ||
| ): | ||
| return _next(root, info, *args, **kwargs) | ||
|
|
||
| return cache.get_or_set( | ||
| generate_key(info.field_name, kwargs), | ||
| lambda: _next(root, info, *args, **kwargs), | ||
| settings.GRAPHQL_RESOLVER_CACHE_TIME_SECONDS, | ||
| ) | ||
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.