Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
6384f53
Add is_public field to caching
kathryn-dale Feb 12, 2026
c076561
Update tests
kathryn-dale Feb 12, 2026
dadfa62
Add non public cache bypass
kathryn-dale Feb 17, 2026
b41d7dd
Update testing
kathryn-dale Feb 17, 2026
b76ec72
Fix - rename duplicate handle_exceptions method
kathryn-dale Feb 17, 2026
c66a137
linting
kathryn-dale Feb 17, 2026
c156a21
linting
kathryn-dale Feb 17, 2026
b601d14
Minor test fix for consistency
kathryn-dale Feb 19, 2026
9f3bc57
Update cache-control for public api
kathryn-dale Mar 5, 2026
999d61c
Update caching
kathryn-dale Mar 6, 2026
8684ab6
update public api v2
kathryn-dale Mar 9, 2026
dde1e9e
TESTING ONLY: Remove data request restrictions for caching testing
kathryn-dale Mar 9, 2026
834cfda
Remove is_public query param check
kathryn-dale Mar 11, 2026
9a6155b
Update based on validation work
kathryn-dale Mar 11, 2026
3334ca0
linting
kathryn-dale Mar 11, 2026
4ea3e9c
Update validation
kathryn-dale Mar 11, 2026
35f78c9
Add django-cognito-jwt files
mattjreynolds Mar 9, 2026
525587a
Fix failing jwt tests
mattjreynolds Mar 9, 2026
5bf602d
Resolve linting issues with jwt
mattjreynolds Mar 9, 2026
d71b665
Make aud check optional for jwt
mattjreynolds Mar 9, 2026
48dcaa8
Use custom x-UHD-AUTH header for token, add requirements
mattjreynolds Mar 18, 2026
71567c2
Add cognito user manager, use env vars
mattjreynolds Mar 23, 2026
8607fe7
Resolve gap in test coverage
mattjreynolds Mar 23, 2026
cb19700
Resolve intermittent test failure triggered by random ordering
mattjreynolds Mar 23, 2026
95ac503
Ignore invalid sonar rule
mattjreynolds Mar 23, 2026
feb9651
Linting
mattjreynolds Mar 23, 2026
8b8bf22
Move Sonar ignore to correct line after linting broke it
mattjreynolds Mar 23, 2026
8de1b86
Ignore invalid sonar rule
mattjreynolds Mar 23, 2026
a229750
Add comment explaining constant
mattjreynolds Mar 24, 2026
affd9a9
Use jwt validation
kathryn-dale Mar 25, 2026
37f92e1
Update decorator
kathryn-dale Mar 25, 2026
a128bed
Update tests
kathryn-dale Mar 26, 2026
fea7423
Update linting errors
kathryn-dale Mar 26, 2026
78ac873
Update imports to match location change
kathryn-dale Mar 31, 2026
3e80b4f
remove old test files
kathryn-dale Mar 31, 2026
82f45ac
test updating sonar-project file
kathryn-dale Mar 31, 2026
e858469
attempt 2
kathryn-dale Mar 31, 2026
fcb0de9
super test
kathryn-dale Mar 31, 2026
749ef2b
Final test
kathryn-dale Mar 31, 2026
9429988
fully exclude tests?
kathryn-dale Mar 31, 2026
22a4bcd
specific file names might work?
kathryn-dale Mar 31, 2026
fc0cd53
Add clarity to decorator function
kathryn-dale Apr 1, 2026
96c26cf
Try new sonarcloud file
kathryn-dale Apr 1, 2026
fce4ab2
test it's running properly by forcing failure
kathryn-dale Apr 1, 2026
87640b6
Update sonarqube file
kathryn-dale Apr 1, 2026
25ce032
Remove testing change
kathryn-dale Apr 1, 2026
a5064f6
remove comment
kathryn-dale Apr 1, 2026
1e85485
Merge branch 'main' into task/CDD-3099-update-non-public-caching
sahmed06 Apr 7, 2026
d984a5b
Update decorator to reduce duplicate calls
kathryn-dale Apr 9, 2026
bdd3b57
Tweaks and updates
kathryn-dale Apr 16, 2026
eb5767c
Merge branch 'main' into task/CDD-3099-update-non-public-caching
sahmed06 Apr 16, 2026
606e8da
Update unit tests
kathryn-dale Apr 17, 2026
3465bd9
linting
kathryn-dale Apr 17, 2026
dbf0e79
Merge branch 'main' into task/CDD-3099-update-non-public-caching
kathryn-dale Apr 17, 2026
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
3 changes: 3 additions & 0 deletions sonar-project.properties → .sonarcloud.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ sonar.organization=ukhsa-internal

# Exclude other generated/auto-generated files
sonar.exclusions=**/migrations/**,**/__pycache__/**

# Exclude files from duplication check
Comment thread
kathryn-dale marked this conversation as resolved.
sonar.cpd.exclusions=tests/unit/public_api/views/test_base.py,tests/unit/public_api/v2/views/test_base.py
48 changes: 41 additions & 7 deletions caching/private_api/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ def is_caching_v2_enabled() -> bool:
return os.environ.get("CACHING_V2_ENABLED", "").lower() in {"true", "1"}
Comment thread
jeanpierrefouche-ukhsa marked this conversation as resolved.


def cache_response(*, timeout: int | None = None, is_reserved_namespace: bool = False):
def cache_response(
*,
timeout: int | None = None,
is_reserved_namespace: bool = False,
):
"""Decorator to wrap API views to use a previously cached response. Otherwise, calculate and save on the way out.

Notes:
Expand Down Expand Up @@ -49,17 +53,30 @@ def cache_response(*, timeout: int | None = None, is_reserved_namespace: bool =
def decorator(view_function):
@wraps(view_function)
def wrapped_view(*args, **kwargs) -> Response:

request = args[1]
is_public = not (_check_if_valid_non_public_request(request=request))

return _retrieve_response_from_cache_or_calculate(
view_function, timeout, is_reserved_namespace, *args, **kwargs
view_function,
timeout,
is_reserved_namespace,
is_public,
*args,
**kwargs,
)

return wrapped_view

return decorator


def _check_if_valid_non_public_request(request) -> bool:
return request.auth is not None


def _retrieve_response_from_cache_or_calculate(
view_function, timeout, is_reserved_namespace, *args, **kwargs
view_function, timeout, is_reserved_namespace, is_public, *args, **kwargs
) -> Response:
"""Gets the response from the cache, otherwise recalculates from the view

Expand All @@ -68,6 +85,10 @@ def _retrieve_response_from_cache_or_calculate(
then the response will always be recalculated from the server
and no caching will take place.

If data is not public (i.e. is_public is set to "false"), then the
response will always be recalculated from the server and no caching
will take place.

Args:
view_function: The view associated with the endpoint
timeout: The number of seconds after which the response is expired
Expand All @@ -83,9 +104,15 @@ def _retrieve_response_from_cache_or_calculate(

"""
request: Request = args[1]
if not is_public:
return _calculate_response_from_view(
view_function, *args, is_public=is_public, **kwargs
)

if is_caching_v2_enabled() and not is_reserved_namespace:
return _calculate_response_from_view(view_function, *args, **kwargs)
return _calculate_response_from_view(
view_function, *args, is_public=is_public, **kwargs
)

cache_management = kwargs.pop(
"cache_management",
Expand Down Expand Up @@ -114,7 +141,9 @@ def _retrieve_response_from_cache_or_calculate(
def _calculate_response_and_save_in_cache(
view_function, timeout, cache_management, cache_entry_key, *args, **kwargs
) -> Response:
response: Response = _calculate_response_from_view(view_function, *args, **kwargs)
response: Response = _calculate_response_from_view(
view_function, *args, is_public=True, **kwargs
Comment thread
kathryn-dale marked this conversation as resolved.
)
if timeout == 0:
return response

Expand All @@ -124,5 +153,10 @@ def _calculate_response_and_save_in_cache(
return response


def _calculate_response_from_view(view_function, *args, **kwargs) -> Response:
return view_function(*args, **kwargs)
def _calculate_response_from_view(
view_function, *args, is_public: bool = True, **kwargs
) -> Response:
response = view_function(*args, **kwargs)
if not (is_public):
response["Cache-Control"] = "private, no-cache"
return response
8 changes: 7 additions & 1 deletion public_api/version_02/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,10 @@ def get(self, request: Request, *args, **kwargs) -> Response:
)

serializer = self.get_serializer(timeseries_dto_slice, many=True)
return Response(serializer.data)
response = Response(data=serializer.data)

is_valid_non_public_request = request.auth is not None
if is_valid_non_public_request:
response["Cache-Control"] = "private, no-cache"
Comment thread
kathryn-dale marked this conversation as resolved.
Comment thread
kathryn-dale marked this conversation as resolved.

return response
9 changes: 8 additions & 1 deletion public_api/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def _build_request_serializer(

@extend_schema(tags=[PUBLIC_API_TAG])
def get(self, request: Request, *args, **kwargs) -> Response:

serializer: APITimeSeriesRequestSerializer = self._build_request_serializer(
request=request
)
Expand All @@ -39,4 +40,10 @@ def get(self, request: Request, *args, **kwargs) -> Response:
)

serializer = self.get_serializer(timeseries_dto_slice, many=True)
return Response(serializer.data)
response = Response(data=serializer.data)

is_valid_non_public_request = request.auth is not None
if is_valid_non_public_request:
response["Cache-Control"] = "private, no-cache"
Comment thread
kathryn-dale marked this conversation as resolved.

return response
Loading
Loading