djresttoolkit 0.16.1__py3-none-any.whl → 0.17.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- README.md +90 -13
- {djresttoolkit-0.16.1.dist-info → djresttoolkit-0.17.0.dist-info}/METADATA +91 -14
- {djresttoolkit-0.16.1.dist-info → djresttoolkit-0.17.0.dist-info}/RECORD +12 -7
- src/djresttoolkit/views/mixins/__init__.py +10 -0
- src/djresttoolkit/views/mixins/_cache_action_mixin.py +44 -0
- src/djresttoolkit/views/mixins/_cache_invalidate_mixin.py +38 -0
- src/djresttoolkit/views/mixins/_cache_key_mixin.py +33 -0
- src/djresttoolkit/views/mixins/_cache_list_retrieve_mixin.py +57 -0
- src/djresttoolkit/views/mixins/_cache_ops_mixin.py +47 -0
- {djresttoolkit-0.16.1.dist-info → djresttoolkit-0.17.0.dist-info}/WHEEL +0 -0
- {djresttoolkit-0.16.1.dist-info → djresttoolkit-0.17.0.dist-info}/entry_points.txt +0 -0
- {djresttoolkit-0.16.1.dist-info → djresttoolkit-0.17.0.dist-info}/licenses/LICENSE +0 -0
README.md
CHANGED
@@ -54,6 +54,9 @@ djresttoolkit is a collection of utilities and helpers for Django and Django RES
|
|
54
54
|
- **PaginatedDataBuilder**
|
55
55
|
Builder that combines `PageNumberPagination` + serializers to return standardized paginated responses with `"page"` + `"results"`.
|
56
56
|
|
57
|
+
- **Caching Mixins**
|
58
|
+
This module provides a set of DRF mixins to handle caching for `list`, `retrieve`, and `custom actions` with automatic invalidation on create, update, and destroy.
|
59
|
+
|
57
60
|
## 📦 Installation
|
58
61
|
|
59
62
|
- **By using uv:**
|
@@ -254,7 +257,7 @@ print(settings.database_url)
|
|
254
257
|
#### Features
|
255
258
|
|
256
259
|
- Prioritizes `.env` variables over YAML.
|
257
|
-
- Supports nested keys: `DATABASE__HOST
|
260
|
+
- Supports nested keys: `DATABASE__HOST`:- `settings.database.host`.
|
258
261
|
- Designed to be subclassed for project-specific settings.
|
259
262
|
|
260
263
|
### 4. EmailSender — API Reference
|
@@ -870,17 +873,17 @@ from djresttoolkit.pagination import PageNumberPagination
|
|
870
873
|
- Clients can control items per page using `?page-size=`.
|
871
874
|
- Structured pagination metadata:
|
872
875
|
|
873
|
-
- `current
|
874
|
-
- `total
|
875
|
-
- `size
|
876
|
-
- `total_items
|
877
|
-
- `next
|
878
|
-
- `previous
|
876
|
+
- `current`:- current page number
|
877
|
+
- `total`:- total number of pages
|
878
|
+
- `size`:- number of items per page
|
879
|
+
- `total_items`:- total number of items across all pages
|
880
|
+
- `next`:- next page URL
|
881
|
+
- `previous`:- previous page URL
|
879
882
|
- Standardized API response format.
|
880
883
|
|
881
884
|
### Attributes of Page Number Pagination
|
882
885
|
|
883
|
-
- `page_size_query_param: str
|
886
|
+
- `page_size_query_param: str`:- Query parameter name (`"page-size"`).
|
884
887
|
|
885
888
|
### Page Number Pagination Methods
|
886
889
|
|
@@ -925,8 +928,8 @@ from djresttoolkit.pagination import PaginatedDataBuilder
|
|
925
928
|
- Integrates with **DRF serializers**.
|
926
929
|
- Handles **invalid pages** gracefully by raising `NotFound`.
|
927
930
|
- Returns both:
|
928
|
-
- `"page"
|
929
|
-
- `"results"
|
931
|
+
- `"page"`:- pagination metadata
|
932
|
+
- `"results"`:- serialized data.
|
930
933
|
- Provides **structured pagination response format**.
|
931
934
|
|
932
935
|
---
|
@@ -941,9 +944,9 @@ builder = PaginatedDataBuilder(
|
|
941
944
|
)
|
942
945
|
```
|
943
946
|
|
944
|
-
- `request: Request
|
945
|
-
- `serializer_class: type[BaseSerializer]
|
946
|
-
- `queryset: QuerySet
|
947
|
+
- `request: Request`:- DRF request object.
|
948
|
+
- `serializer_class: type[BaseSerializer]`:- DRF serializer class for the model.
|
949
|
+
- `queryset: QuerySet`:- Django queryset to paginate.
|
947
950
|
|
948
951
|
### Paginated Data Builder Methods
|
949
952
|
|
@@ -973,6 +976,80 @@ builder = PaginatedDataBuilder(
|
|
973
976
|
}
|
974
977
|
```
|
975
978
|
|
979
|
+
### 16. Caching Mixins — API Reference
|
980
|
+
|
981
|
+
This module provides a set of DRF mixins to handle **caching for list, retrieve, and custom actions** with automatic invalidation on create, update, and destroy.
|
982
|
+
|
983
|
+
#### 1️ `CacheKeyMixin`
|
984
|
+
|
985
|
+
- **Purpose**: Generate unique cache keys for DRF viewset actions.
|
986
|
+
- **Attributes**:
|
987
|
+
- `cache_timeout: int = 300`:- default cache duration in seconds.
|
988
|
+
- **Methods**:
|
989
|
+
- `get_cache_timeout()`:- returns the cache timeout.
|
990
|
+
- `get_cache_key(action_type, pk=None, action_name=None)`:- returns a cache key string based on action type:**
|
991
|
+
- `list` or `custom-list`:- hash of query parameters.
|
992
|
+
- `retrieve` or `custom-detail`:- uses primary key (`pk`).
|
993
|
+
|
994
|
+
#### 2️ `CacheOpsMixin`
|
995
|
+
|
996
|
+
- **Purpose**: Get, set, and invalidate cache.
|
997
|
+
- **Methods**:
|
998
|
+
- `get_or_set_cache(cache_key, data_fn, timeout=None)`:- fetch from cache or compute and set.
|
999
|
+
- `invalidate_cache(pk=None, custom_actions=None)`:- delete cached items:
|
1000
|
+
- Deletes retrieve/detail caches for a `pk`.
|
1001
|
+
- Deletes list caches (supports `delete_pattern` if available).
|
1002
|
+
|
1003
|
+
#### 3️ `CacheActionMixin`
|
1004
|
+
|
1005
|
+
- **Purpose**: Decorator for caching custom DRF `@action` methods.
|
1006
|
+
- **Methods**:
|
1007
|
+
- `cache_action(detail=False, action_name=None)`:- returns a decorator that caches action results automatically.
|
1008
|
+
- Works for:
|
1009
|
+
- `detail=False`:- custom-list action cache.
|
1010
|
+
- `detail=True`:- custom-detail action cache.
|
1011
|
+
|
1012
|
+
#### 4️ `CacheListRetrieveMixin`
|
1013
|
+
|
1014
|
+
- **Purpose**: Caches DRF `list()` and `retrieve()` responses.
|
1015
|
+
- **Methods**:
|
1016
|
+
- `list(request, *args, **kwargs)`:- caches list responses.
|
1017
|
+
- `retrieve(request, *args, **kwargs)`:- caches detail responses.
|
1018
|
+
- `_get_list_data(request)`:- internal method to fetch paginated list data.
|
1019
|
+
- `_get_detail_data()`:- internal method to fetch a single object.
|
1020
|
+
|
1021
|
+
#### 5️ `CacheInvalidateMixin`
|
1022
|
+
|
1023
|
+
- **Purpose**: Automatically invalidates caches on write operations.
|
1024
|
+
- **Methods**:
|
1025
|
+
- `create(request, *args, **kwargs)`:- invalidates list caches.
|
1026
|
+
- `update(request, *args, **kwargs)`:- invalidates detail caches for `pk`.
|
1027
|
+
- `destroy(request, *args, **kwargs)`:- invalidates detail caches for `pk`.
|
1028
|
+
|
1029
|
+
#### Example of Caching Mixins
|
1030
|
+
|
1031
|
+
```python
|
1032
|
+
from rest_framework.viewsets import ModelViewSet
|
1033
|
+
from myapp.models import Book
|
1034
|
+
from myapp.serializers import BookSerializer
|
1035
|
+
from djresttoolkit.cache_mixins import CacheInvalidateMixin, CacheActionMixin
|
1036
|
+
|
1037
|
+
class BookViewSet(CacheInvalidateMixin, ModelViewSet):
|
1038
|
+
queryset = Book.objects.all()
|
1039
|
+
serializer_class = BookSerializer
|
1040
|
+
basename = "book"
|
1041
|
+
|
1042
|
+
@CacheActionMixin.cache_action(detail=False)
|
1043
|
+
def popular(self, request):
|
1044
|
+
data = Book.objects.filter(is_popular=True)
|
1045
|
+
serializer = self.get_serializer(data, many=True)
|
1046
|
+
return Response(serializer.data)
|
1047
|
+
```
|
1048
|
+
|
1049
|
+
- Automatically caches `list`, `retrieve`, and `popular` actions.
|
1050
|
+
- Invalidates caches when books are created, updated, or deleted.
|
1051
|
+
- Supports custom cache keys per action.
|
1052
|
+
|
976
1053
|
## 🛠️ Planned Features
|
977
1054
|
|
978
1055
|
- Add more utils
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: djresttoolkit
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.17.0
|
4
4
|
Summary: A collection of Django and DRF utilities to simplify API development.
|
5
5
|
Project-URL: Homepage, https://github.com/shaileshpandit141/djresttoolkit
|
6
6
|
Project-URL: Documentation, https://shaileshpandit141.github.io/djresttoolkit
|
@@ -112,6 +112,9 @@ djresttoolkit is a collection of utilities and helpers for Django and Django RES
|
|
112
112
|
- **PaginatedDataBuilder**
|
113
113
|
Builder that combines `PageNumberPagination` + serializers to return standardized paginated responses with `"page"` + `"results"`.
|
114
114
|
|
115
|
+
- **Caching Mixins**
|
116
|
+
This module provides a set of DRF mixins to handle caching for `list`, `retrieve`, and `custom actions` with automatic invalidation on create, update, and destroy.
|
117
|
+
|
115
118
|
## 📦 Installation
|
116
119
|
|
117
120
|
- **By using uv:**
|
@@ -312,7 +315,7 @@ print(settings.database_url)
|
|
312
315
|
#### Features
|
313
316
|
|
314
317
|
- Prioritizes `.env` variables over YAML.
|
315
|
-
- Supports nested keys: `DATABASE__HOST
|
318
|
+
- Supports nested keys: `DATABASE__HOST`:- `settings.database.host`.
|
316
319
|
- Designed to be subclassed for project-specific settings.
|
317
320
|
|
318
321
|
### 4. EmailSender — API Reference
|
@@ -928,17 +931,17 @@ from djresttoolkit.pagination import PageNumberPagination
|
|
928
931
|
- Clients can control items per page using `?page-size=`.
|
929
932
|
- Structured pagination metadata:
|
930
933
|
|
931
|
-
- `current
|
932
|
-
- `total
|
933
|
-
- `size
|
934
|
-
- `total_items
|
935
|
-
- `next
|
936
|
-
- `previous
|
934
|
+
- `current`:- current page number
|
935
|
+
- `total`:- total number of pages
|
936
|
+
- `size`:- number of items per page
|
937
|
+
- `total_items`:- total number of items across all pages
|
938
|
+
- `next`:- next page URL
|
939
|
+
- `previous`:- previous page URL
|
937
940
|
- Standardized API response format.
|
938
941
|
|
939
942
|
### Attributes of Page Number Pagination
|
940
943
|
|
941
|
-
- `page_size_query_param: str
|
944
|
+
- `page_size_query_param: str`:- Query parameter name (`"page-size"`).
|
942
945
|
|
943
946
|
### Page Number Pagination Methods
|
944
947
|
|
@@ -983,8 +986,8 @@ from djresttoolkit.pagination import PaginatedDataBuilder
|
|
983
986
|
- Integrates with **DRF serializers**.
|
984
987
|
- Handles **invalid pages** gracefully by raising `NotFound`.
|
985
988
|
- Returns both:
|
986
|
-
- `"page"
|
987
|
-
- `"results"
|
989
|
+
- `"page"`:- pagination metadata
|
990
|
+
- `"results"`:- serialized data.
|
988
991
|
- Provides **structured pagination response format**.
|
989
992
|
|
990
993
|
---
|
@@ -999,9 +1002,9 @@ builder = PaginatedDataBuilder(
|
|
999
1002
|
)
|
1000
1003
|
```
|
1001
1004
|
|
1002
|
-
- `request: Request
|
1003
|
-
- `serializer_class: type[BaseSerializer]
|
1004
|
-
- `queryset: QuerySet
|
1005
|
+
- `request: Request`:- DRF request object.
|
1006
|
+
- `serializer_class: type[BaseSerializer]`:- DRF serializer class for the model.
|
1007
|
+
- `queryset: QuerySet`:- Django queryset to paginate.
|
1005
1008
|
|
1006
1009
|
### Paginated Data Builder Methods
|
1007
1010
|
|
@@ -1031,6 +1034,80 @@ builder = PaginatedDataBuilder(
|
|
1031
1034
|
}
|
1032
1035
|
```
|
1033
1036
|
|
1037
|
+
### 16. Caching Mixins — API Reference
|
1038
|
+
|
1039
|
+
This module provides a set of DRF mixins to handle **caching for list, retrieve, and custom actions** with automatic invalidation on create, update, and destroy.
|
1040
|
+
|
1041
|
+
#### 1️ `CacheKeyMixin`
|
1042
|
+
|
1043
|
+
- **Purpose**: Generate unique cache keys for DRF viewset actions.
|
1044
|
+
- **Attributes**:
|
1045
|
+
- `cache_timeout: int = 300`:- default cache duration in seconds.
|
1046
|
+
- **Methods**:
|
1047
|
+
- `get_cache_timeout()`:- returns the cache timeout.
|
1048
|
+
- `get_cache_key(action_type, pk=None, action_name=None)`:- returns a cache key string based on action type:**
|
1049
|
+
- `list` or `custom-list`:- hash of query parameters.
|
1050
|
+
- `retrieve` or `custom-detail`:- uses primary key (`pk`).
|
1051
|
+
|
1052
|
+
#### 2️ `CacheOpsMixin`
|
1053
|
+
|
1054
|
+
- **Purpose**: Get, set, and invalidate cache.
|
1055
|
+
- **Methods**:
|
1056
|
+
- `get_or_set_cache(cache_key, data_fn, timeout=None)`:- fetch from cache or compute and set.
|
1057
|
+
- `invalidate_cache(pk=None, custom_actions=None)`:- delete cached items:
|
1058
|
+
- Deletes retrieve/detail caches for a `pk`.
|
1059
|
+
- Deletes list caches (supports `delete_pattern` if available).
|
1060
|
+
|
1061
|
+
#### 3️ `CacheActionMixin`
|
1062
|
+
|
1063
|
+
- **Purpose**: Decorator for caching custom DRF `@action` methods.
|
1064
|
+
- **Methods**:
|
1065
|
+
- `cache_action(detail=False, action_name=None)`:- returns a decorator that caches action results automatically.
|
1066
|
+
- Works for:
|
1067
|
+
- `detail=False`:- custom-list action cache.
|
1068
|
+
- `detail=True`:- custom-detail action cache.
|
1069
|
+
|
1070
|
+
#### 4️ `CacheListRetrieveMixin`
|
1071
|
+
|
1072
|
+
- **Purpose**: Caches DRF `list()` and `retrieve()` responses.
|
1073
|
+
- **Methods**:
|
1074
|
+
- `list(request, *args, **kwargs)`:- caches list responses.
|
1075
|
+
- `retrieve(request, *args, **kwargs)`:- caches detail responses.
|
1076
|
+
- `_get_list_data(request)`:- internal method to fetch paginated list data.
|
1077
|
+
- `_get_detail_data()`:- internal method to fetch a single object.
|
1078
|
+
|
1079
|
+
#### 5️ `CacheInvalidateMixin`
|
1080
|
+
|
1081
|
+
- **Purpose**: Automatically invalidates caches on write operations.
|
1082
|
+
- **Methods**:
|
1083
|
+
- `create(request, *args, **kwargs)`:- invalidates list caches.
|
1084
|
+
- `update(request, *args, **kwargs)`:- invalidates detail caches for `pk`.
|
1085
|
+
- `destroy(request, *args, **kwargs)`:- invalidates detail caches for `pk`.
|
1086
|
+
|
1087
|
+
#### Example of Caching Mixins
|
1088
|
+
|
1089
|
+
```python
|
1090
|
+
from rest_framework.viewsets import ModelViewSet
|
1091
|
+
from myapp.models import Book
|
1092
|
+
from myapp.serializers import BookSerializer
|
1093
|
+
from djresttoolkit.cache_mixins import CacheInvalidateMixin, CacheActionMixin
|
1094
|
+
|
1095
|
+
class BookViewSet(CacheInvalidateMixin, ModelViewSet):
|
1096
|
+
queryset = Book.objects.all()
|
1097
|
+
serializer_class = BookSerializer
|
1098
|
+
basename = "book"
|
1099
|
+
|
1100
|
+
@CacheActionMixin.cache_action(detail=False)
|
1101
|
+
def popular(self, request):
|
1102
|
+
data = Book.objects.filter(is_popular=True)
|
1103
|
+
serializer = self.get_serializer(data, many=True)
|
1104
|
+
return Response(serializer.data)
|
1105
|
+
```
|
1106
|
+
|
1107
|
+
- Automatically caches `list`, `retrieve`, and `popular` actions.
|
1108
|
+
- Invalidates caches when books are created, updated, or deleted.
|
1109
|
+
- Supports custom cache keys per action.
|
1110
|
+
|
1034
1111
|
## 🛠️ Planned Features
|
1035
1112
|
|
1036
1113
|
- Add more utils
|
@@ -1,5 +1,5 @@
|
|
1
1
|
LICENSE,sha256=8-oZM3yuuTRjySMbVKX9YXYA7Y4M_KhQNBYXPFjeWUo,1074
|
2
|
-
README.md,sha256=
|
2
|
+
README.md,sha256=hLkGp4AnOR6soIuFgzTKW1nM5s1s8tNL8isKJfqLjwQ,29920
|
3
3
|
demo/staticfiles/admin/img/LICENSE,sha256=0RT6_zSIwWwxmzI13EH5AjnT1j2YU3MwM9j3U19cAAQ,1081
|
4
4
|
src/djresttoolkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
5
|
src/djresttoolkit/admin.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -44,10 +44,15 @@ src/djresttoolkit/views/_api_views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQe
|
|
44
44
|
src/djresttoolkit/views/_api_views/_choice_fields_apiview.py,sha256=zABPgqxMVaWd814B_sC64bWL61fDJkyYQZmJXQCa6Xc,1395
|
45
45
|
src/djresttoolkit/views/_exceptions/__init__.py,sha256=DrCUxuPNyBR4WhzNutn5HDxLa--q51ykIxSG7_bFsOI,83
|
46
46
|
src/djresttoolkit/views/_exceptions/_exception_handler.py,sha256=_o7If47bzWLl57LeSXSWsIDsJGo2RIpwYAwNQ-hsHVY,2839
|
47
|
-
src/djresttoolkit/views/mixins/__init__.py,sha256=
|
47
|
+
src/djresttoolkit/views/mixins/__init__.py,sha256=Ge-Dwa6S5-4BpfzMjLfxEkkepk8F3IJn0DNWBETq1Hw,537
|
48
|
+
src/djresttoolkit/views/mixins/_cache_action_mixin.py,sha256=Vle_xbHc6dzI2SqxLiM5HsrDkOzSgqj8GFB85MyPdaw,1516
|
49
|
+
src/djresttoolkit/views/mixins/_cache_invalidate_mixin.py,sha256=Ax6VC1lg8qYGW5IK339hqq40SRKj61J3Jp-mB_2gPWc,1170
|
50
|
+
src/djresttoolkit/views/mixins/_cache_key_mixin.py,sha256=0kt1rgz34q43DCLFwlR4rkyHtoWDYCMLXaTe7XfRZu4,1170
|
51
|
+
src/djresttoolkit/views/mixins/_cache_list_retrieve_mixin.py,sha256=nxJpfkNZWuOpf7wDDkGwq_ZQNT_17-iRisdTB2Cc0iY,1935
|
52
|
+
src/djresttoolkit/views/mixins/_cache_ops_mixin.py,sha256=veaBwH3Bp3GAzFizIxXT8EhmUF1AY3kCZBcF7rHPfyc,1435
|
48
53
|
src/djresttoolkit/views/mixins/_retrieve_object_mixin.py,sha256=Q9znYPb07YXXUhsL7VIrk3BC-zDwjOhwLJKe2GPJ-k0,1155
|
49
|
-
djresttoolkit-0.
|
50
|
-
djresttoolkit-0.
|
51
|
-
djresttoolkit-0.
|
52
|
-
djresttoolkit-0.
|
53
|
-
djresttoolkit-0.
|
54
|
+
djresttoolkit-0.17.0.dist-info/METADATA,sha256=YuYyP_Rcc756w88DI3NyasZaNA1uaBoahMxzlXcBCIY,32930
|
55
|
+
djresttoolkit-0.17.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
56
|
+
djresttoolkit-0.17.0.dist-info/entry_points.txt,sha256=YMhfTF-7mYppO8QqqWnvR_hyMWvoYxD6XI94_ViFu3k,60
|
57
|
+
djresttoolkit-0.17.0.dist-info/licenses/LICENSE,sha256=8-oZM3yuuTRjySMbVKX9YXYA7Y4M_KhQNBYXPFjeWUo,1074
|
58
|
+
djresttoolkit-0.17.0.dist-info/RECORD,,
|
@@ -1,6 +1,16 @@
|
|
1
1
|
from ._retrieve_object_mixin import RetrieveObjectMixin, QuerysetNotDefinedError
|
2
|
+
from ._cache_action_mixin import CacheActionMixin
|
3
|
+
from ._cache_invalidate_mixin import CacheInvalidateMixin
|
4
|
+
from ._cache_key_mixin import CacheKeyMixin
|
5
|
+
from ._cache_list_retrieve_mixin import CacheListRetrieveMixin
|
6
|
+
from ._cache_ops_mixin import CacheOpsMixin
|
2
7
|
|
3
8
|
__all__ = [
|
4
9
|
"RetrieveObjectMixin",
|
5
10
|
"QuerysetNotDefinedError",
|
11
|
+
"CacheActionMixin",
|
12
|
+
"CacheInvalidateMixin",
|
13
|
+
"CacheKeyMixin",
|
14
|
+
"CacheListRetrieveMixin",
|
15
|
+
"CacheOpsMixin",
|
6
16
|
]
|
@@ -0,0 +1,44 @@
|
|
1
|
+
from functools import wraps
|
2
|
+
from typing import Any, Callable
|
3
|
+
|
4
|
+
from rest_framework.response import Response
|
5
|
+
from ._cache_ops_mixin import CacheOpsMixin
|
6
|
+
from rest_framework.request import Request
|
7
|
+
|
8
|
+
|
9
|
+
class CacheActionMixin(CacheOpsMixin):
|
10
|
+
"""Provides decorator for caching custom @action methods."""
|
11
|
+
|
12
|
+
def cache_action(
|
13
|
+
self,
|
14
|
+
detail: bool = False,
|
15
|
+
action_name: str | None = None,
|
16
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
17
|
+
def decorator(view_method: Callable[..., Any]) -> Callable[..., Any]:
|
18
|
+
@wraps(view_method)
|
19
|
+
def wrapper(
|
20
|
+
viewset: Any,
|
21
|
+
request: Request,
|
22
|
+
*args: Any,
|
23
|
+
**kwargs: Any,
|
24
|
+
) -> Response:
|
25
|
+
name = action_name or view_method.__name__
|
26
|
+
pk = kwargs.get("pk") if detail else None
|
27
|
+
action_type = "custom-detail" if detail else "custom-list"
|
28
|
+
key = viewset.get_cache_key(action_type, pk=pk, action_name=name)
|
29
|
+
|
30
|
+
def get_data() -> None | Any:
|
31
|
+
response = view_method(
|
32
|
+
viewset,
|
33
|
+
request,
|
34
|
+
*args,
|
35
|
+
**kwargs,
|
36
|
+
)
|
37
|
+
return response.data if isinstance(response, Response) else response
|
38
|
+
|
39
|
+
data = viewset.get_or_set_cache(key, get_data)
|
40
|
+
return Response(data)
|
41
|
+
|
42
|
+
return wrapper
|
43
|
+
|
44
|
+
return decorator
|
@@ -0,0 +1,38 @@
|
|
1
|
+
from typing import Any
|
2
|
+
from rest_framework.request import Request
|
3
|
+
from rest_framework.response import Response
|
4
|
+
from ._cache_list_retrieve_mixin import CacheListRetrieveMixin
|
5
|
+
|
6
|
+
|
7
|
+
class CacheInvalidateMixin(CacheListRetrieveMixin):
|
8
|
+
"""Invalidate caches after create, update, destroy."""
|
9
|
+
|
10
|
+
def create(
|
11
|
+
self,
|
12
|
+
request: Request,
|
13
|
+
*args: Any,
|
14
|
+
**kwargs: Any,
|
15
|
+
) -> Response:
|
16
|
+
response = super().create(request, *args, **kwargs) # type: ignore
|
17
|
+
self.invalidate_cache()
|
18
|
+
return response # type: ignore
|
19
|
+
|
20
|
+
def update(
|
21
|
+
self,
|
22
|
+
request: Request,
|
23
|
+
*args: Any,
|
24
|
+
**kwargs: Any,
|
25
|
+
) -> Response:
|
26
|
+
response = super().update(request, *args, **kwargs) # type: ignore
|
27
|
+
self.invalidate_cache(pk=self.kwargs.get("pk")) # type: ignore
|
28
|
+
return response # type: ignore
|
29
|
+
|
30
|
+
def destroy(
|
31
|
+
self,
|
32
|
+
request: Request,
|
33
|
+
*args: Any,
|
34
|
+
**kwargs: Any,
|
35
|
+
) -> Response:
|
36
|
+
response = super().destroy(request, *args, **kwargs) # type: ignore
|
37
|
+
self.invalidate_cache(pk=self.kwargs.get("pk")) # type: ignore
|
38
|
+
return response # type: ignore
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import hashlib
|
2
|
+
import json
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
|
6
|
+
class CacheKeyMixin:
|
7
|
+
"""Handles generating unique cache keys for views."""
|
8
|
+
|
9
|
+
cache_timeout: int = 300
|
10
|
+
|
11
|
+
def get_cache_timeout(self) -> int:
|
12
|
+
return self.cache_timeout
|
13
|
+
|
14
|
+
def get_cache_key(
|
15
|
+
self,
|
16
|
+
action_type: str,
|
17
|
+
pk: Any | str = None,
|
18
|
+
action_name: str | None = None,
|
19
|
+
) -> str | None:
|
20
|
+
if action_type in ("list", "custom-list"):
|
21
|
+
query_params = dict(sorted(self.request.query_params.items())) # type: ignore
|
22
|
+
query_string = json.dumps(query_params, separators=(",", ":"))
|
23
|
+
query_hash = hashlib.md5(query_string.encode()).hexdigest()
|
24
|
+
if action_type == "list":
|
25
|
+
return f"{self.basename}_list_{query_hash}" # type: ignore
|
26
|
+
return f"{self.basename}_{action_name}_list_{query_hash}" # type: ignore
|
27
|
+
|
28
|
+
if action_type in ("retrieve", "custom-detail") and pk is not None:
|
29
|
+
if action_type == "retrieve":
|
30
|
+
return f"{self.basename}_detail_{pk}" # type: ignore
|
31
|
+
return f"{self.basename}_{action_name}_detail_{pk}" # type: ignore
|
32
|
+
|
33
|
+
return None
|
@@ -0,0 +1,57 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
from rest_framework.response import Response
|
4
|
+
from rest_framework.request import Request
|
5
|
+
from ._cache_action_mixin import CacheActionMixin
|
6
|
+
|
7
|
+
|
8
|
+
class CacheListRetrieveMixin(CacheActionMixin):
|
9
|
+
"""Caches list() and retrieve() responses."""
|
10
|
+
|
11
|
+
def list(
|
12
|
+
self,
|
13
|
+
request: Request,
|
14
|
+
*args: Any,
|
15
|
+
**kwargs: Any,
|
16
|
+
) -> Response:
|
17
|
+
cache_key = self.get_cache_key("list")
|
18
|
+
if not cache_key:
|
19
|
+
return super().list(request, *args, **kwargs) # type: ignore
|
20
|
+
|
21
|
+
data = self.get_or_set_cache(
|
22
|
+
cache_key,
|
23
|
+
lambda: self._get_list_data(request), # type: ignore
|
24
|
+
)
|
25
|
+
return Response(data)
|
26
|
+
|
27
|
+
def _get_list_data(self, request: Response) -> Any:
|
28
|
+
queryset = self.filter_queryset(self.get_queryset()) # type: ignore
|
29
|
+
page = self.paginate_queryset(queryset) # type: ignore
|
30
|
+
if page is not None:
|
31
|
+
serializer = self.get_serializer(page, many=True) # type: ignore
|
32
|
+
return self.get_paginated_response(serializer.data).data # type: ignore
|
33
|
+
else:
|
34
|
+
serializer = self.get_serializer(queryset, many=True) # type: ignore
|
35
|
+
return serializer.data # type: ignore
|
36
|
+
|
37
|
+
def retrieve(
|
38
|
+
self,
|
39
|
+
request: Request,
|
40
|
+
*args: Any,
|
41
|
+
**kwargs: Any,
|
42
|
+
) -> Response:
|
43
|
+
pk = self.kwargs.get("pk") # type: ignore
|
44
|
+
cache_key = self.get_cache_key("retrieve", pk=pk) # type: ignore
|
45
|
+
if not cache_key:
|
46
|
+
return super().retrieve(request, *args, **kwargs) # type: ignore
|
47
|
+
|
48
|
+
data = self.get_or_set_cache(
|
49
|
+
cache_key,
|
50
|
+
lambda: self._get_detail_data(),
|
51
|
+
)
|
52
|
+
return Response(data)
|
53
|
+
|
54
|
+
def _get_detail_data(self) -> Any:
|
55
|
+
instance = self.get_object() # type: ignore
|
56
|
+
serializer = self.get_serializer(instance) # type: ignore
|
57
|
+
return serializer.data # type: ignore
|
@@ -0,0 +1,47 @@
|
|
1
|
+
from typing import Any, Callable
|
2
|
+
|
3
|
+
from django.core.cache import cache
|
4
|
+
|
5
|
+
from ._cache_key_mixin import CacheKeyMixin
|
6
|
+
|
7
|
+
|
8
|
+
class CacheOpsMixin(CacheKeyMixin):
|
9
|
+
"""Handles getting, setting, and invalidating cache."""
|
10
|
+
|
11
|
+
def get_or_set_cache(
|
12
|
+
self,
|
13
|
+
cache_key: str,
|
14
|
+
data_fn: Callable[[], Any],
|
15
|
+
timeout: int | None = None,
|
16
|
+
) -> Any:
|
17
|
+
data = cache.get(cache_key)
|
18
|
+
if data is None:
|
19
|
+
data = data_fn()
|
20
|
+
cache.set(cache_key, data, timeout or self.get_cache_timeout())
|
21
|
+
return data
|
22
|
+
|
23
|
+
def invalidate_cache(
|
24
|
+
self,
|
25
|
+
pk: Any | None = None,
|
26
|
+
custom_actions: list[str] | None = None,
|
27
|
+
) -> None:
|
28
|
+
if pk:
|
29
|
+
key = self.get_cache_key("retrieve", pk=pk)
|
30
|
+
if key:
|
31
|
+
cache.delete(key)
|
32
|
+
|
33
|
+
if custom_actions:
|
34
|
+
for action in custom_actions:
|
35
|
+
key = self.get_cache_key(
|
36
|
+
"custom-detail",
|
37
|
+
pk=pk,
|
38
|
+
action_name=action,
|
39
|
+
)
|
40
|
+
if key:
|
41
|
+
cache.delete(key)
|
42
|
+
|
43
|
+
if hasattr(cache, "delete_pattern"):
|
44
|
+
cache.delete_pattern(f"{self.basename}_list_*") # type: ignore
|
45
|
+
if custom_actions:
|
46
|
+
for action in custom_actions:
|
47
|
+
cache.delete_pattern(f"{self.basename}_{action}_list_*") # type: ignore
|
File without changes
|
File without changes
|
File without changes
|