djresttoolkit 0.16.0__tar.gz → 0.17.0__tar.gz

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.
Files changed (57) hide show
  1. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/PKG-INFO +139 -22
  2. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/README.md +138 -21
  3. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/pyproject.toml +1 -1
  4. djresttoolkit-0.17.0/src/djresttoolkit/views/mixins/__init__.py +16 -0
  5. djresttoolkit-0.17.0/src/djresttoolkit/views/mixins/_cache_action_mixin.py +44 -0
  6. djresttoolkit-0.17.0/src/djresttoolkit/views/mixins/_cache_invalidate_mixin.py +38 -0
  7. djresttoolkit-0.17.0/src/djresttoolkit/views/mixins/_cache_key_mixin.py +33 -0
  8. djresttoolkit-0.17.0/src/djresttoolkit/views/mixins/_cache_list_retrieve_mixin.py +57 -0
  9. djresttoolkit-0.17.0/src/djresttoolkit/views/mixins/_cache_ops_mixin.py +47 -0
  10. djresttoolkit-0.16.0/src/djresttoolkit/views/mixins/__init__.py +0 -6
  11. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/.gitignore +0 -0
  12. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/LICENSE +0 -0
  13. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/demo/staticfiles/admin/img/LICENSE +0 -0
  14. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/__init__.py +0 -0
  15. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/admin.py +0 -0
  16. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/apps.py +0 -0
  17. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/dbseed/__init__.py +0 -0
  18. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/dbseed/models/__init__.py +0 -0
  19. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/dbseed/models/_choice_field.py +0 -0
  20. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/dbseed/models/_gen.py +0 -0
  21. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/dbseed/models/_seed_model.py +0 -0
  22. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/envconfig/__init__.py +0 -0
  23. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/envconfig/_env_settings.py +0 -0
  24. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/mail/__init__.py +0 -0
  25. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/mail/_email_sender.py +0 -0
  26. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/mail/_models.py +0 -0
  27. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/mail/_types.py +0 -0
  28. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/management/__init__.py +0 -0
  29. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/management/commands/__init__.py +0 -0
  30. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/management/commands/dbflush.py +0 -0
  31. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/management/commands/dbseed.py +0 -0
  32. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/middlewares/__init__.py +0 -0
  33. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/middlewares/_response_time_middleware.py +0 -0
  34. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/migrations/__init__.py +0 -0
  35. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/models/__init__.py +0 -0
  36. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/models/mixins/__init__.py +0 -0
  37. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/models/mixins/_model_choice_fields_mixin.py +0 -0
  38. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/pagination/__init__.py +0 -0
  39. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/pagination/_page_number_pagination.py +0 -0
  40. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/pagination/_paginated_data_builder.py +0 -0
  41. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/py.typed +0 -0
  42. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/renderers/__init__.py +0 -0
  43. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/renderers/_throttle_info_json_renderer.py +0 -0
  44. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/serializers/__init__.py +0 -0
  45. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/serializers/mixins/__init__.py +0 -0
  46. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/serializers/mixins/_absolute_url_file_mixin.py +0 -0
  47. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/serializers/mixins/_bulk_create_mixin.py +0 -0
  48. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/throttling/__init__.py +0 -0
  49. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/throttling/_throttle_inspector.py +0 -0
  50. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/urls/__init__.py +0 -0
  51. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/urls/_build_absolute_uri.py +0 -0
  52. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/views/__init__.py +0 -0
  53. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/views/_api_views/__init__.py +0 -0
  54. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/views/_api_views/_choice_fields_apiview.py +0 -0
  55. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/views/_exceptions/__init__.py +0 -0
  56. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/views/_exceptions/_exception_handler.py +0 -0
  57. {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/views/mixins/_retrieve_object_mixin.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: djresttoolkit
3
- Version: 0.16.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
@@ -64,13 +64,56 @@ Description-Content-Type: text/markdown
64
64
 
65
65
  djresttoolkit is a collection of utilities and helpers for Django and Django REST Framework (DRF) that simplify common development tasks such as API handling, authentication, and email sending and much more.
66
66
 
67
- ## Features
67
+ ## 📖 Feature Index (djresttoolkit)
68
68
 
69
- - Django REST Framework helpers (serializers, views, pagination, etc.)
70
- - Django utilities (e.g., email sending, model mixins)
71
- - Ready-to-use shortcuts to speed up API development
72
- - Lightweight, no unnecessary dependencies
73
- - Type Safe - written with modern Python type hints.
69
+ - **DB Seed Command (`dbseed`)**
70
+ Seed your database with fake data using Pydantic models powered by **Faker**. Supports relationships, transactions, and a `manage.py dbseed` command.
71
+
72
+ - **DB Flush Command (`dbflush`)**
73
+ Management command to flush all models or a specific model, resetting auto-increment IDs safely with transaction support.
74
+
75
+ - **EnvBaseSettings**
76
+ Typed settings loader using **YAML + .env**, supports nested keys and overrides. Great for structured configuration management.
77
+
78
+ - **EmailSender**
79
+ Custom class to send templated emails (`text` and `html`) with context. Supports error handling and logging.
80
+
81
+ - **Custom DRF Exception Handler**
82
+ Centralized error handler for DRF that extends default behavior and adds throttle support (`429 Too Many Requests` with retry info).
83
+
84
+ - **Response Time Middleware**
85
+ Middleware to measure, log, and inject `X-Response-Time` headers into every response.
86
+
87
+ - **Throttle**
88
+ - `ThrottleInfoJSONRenderer`: Automatically adds throttle headers to responses.
89
+ - `ThrottleInspector`: Inspect view/request throttling and attach structured headers.
90
+
91
+ - **AbsoluteUrlFileMixin**
92
+ DRF serializer mixin that converts `FileField` / `ImageField` URLs to **absolute URLs** automatically.
93
+
94
+ - **BulkCreateMixin**
95
+ Serializer mixin that enables **bulk creation** of objects and syncs field error messages with model fields.
96
+
97
+ - **ModelChoiceFieldMixin**
98
+ Retrieve choice fields (`TextChoices`, etc.) from Django models as structured dictionaries for API responses.
99
+
100
+ - **ChoiceFieldsAPIView**
101
+ Generic API view that exposes model `choices` in a REST-friendly JSON format.
102
+
103
+ - **RetrieveObjectMixin**
104
+ Lightweight mixin to fetch a single object from a queryset with filters, raising a custom error if `queryset` is not defined.
105
+
106
+ - **build\_absolute\_uri**
107
+ Helper to build full absolute URLs for named routes with optional query params. Works with Django + DRF requests.
108
+
109
+ - **PageNumberPagination**
110
+ Custom paginator with a structured `"page"` metadata block and support for dynamic `page-size` query param.
111
+
112
+ - **PaginatedDataBuilder**
113
+ Builder that combines `PageNumberPagination` + serializers to return standardized paginated responses with `"page"` + `"results"`.
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.
74
117
 
75
118
  ## 📦 Installation
76
119
 
@@ -88,7 +131,7 @@ djresttoolkit is a collection of utilities and helpers for Django and Django RES
88
131
 
89
132
  ## 📚 All API Reference
90
133
 
91
- ### 1. DB Seed Utilities — API Reference
134
+ ### 1. DB Seed Command — API Reference
92
135
 
93
136
  #### `Generator`
94
137
 
@@ -272,7 +315,7 @@ print(settings.database_url)
272
315
  #### Features
273
316
 
274
317
  - Prioritizes `.env` variables over YAML.
275
- - Supports nested keys: `DATABASE__HOST` `settings.database.host`.
318
+ - Supports nested keys: `DATABASE__HOST`:- `settings.database.host`.
276
319
  - Designed to be subclassed for project-specific settings.
277
320
 
278
321
  ### 4. EmailSender — API Reference
@@ -411,7 +454,7 @@ X-Response-Time: 0.01234 seconds
411
454
  INFO: Request processed in 0.01234 seconds
412
455
  ```
413
456
 
414
- ### 7. Throttle Utilities — API Reference
457
+ ### 7. Throttle — API Reference
415
458
 
416
459
  #### `ThrottleInfoJSONRenderer`
417
460
 
@@ -888,17 +931,17 @@ from djresttoolkit.pagination import PageNumberPagination
888
931
  - Clients can control items per page using `?page-size=`.
889
932
  - Structured pagination metadata:
890
933
 
891
- - `current` current page number
892
- - `total` total number of pages
893
- - `size` number of items per page
894
- - `total_items` total number of items across all pages
895
- - `next` next page URL
896
- - `previous` previous page URL
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
897
940
  - Standardized API response format.
898
941
 
899
942
  ### Attributes of Page Number Pagination
900
943
 
901
- - `page_size_query_param: str` Query parameter name (`"page-size"`).
944
+ - `page_size_query_param: str`:- Query parameter name (`"page-size"`).
902
945
 
903
946
  ### Page Number Pagination Methods
904
947
 
@@ -943,8 +986,8 @@ from djresttoolkit.pagination import PaginatedDataBuilder
943
986
  - Integrates with **DRF serializers**.
944
987
  - Handles **invalid pages** gracefully by raising `NotFound`.
945
988
  - Returns both:
946
- - `"page"` pagination metadata
947
- - `"results"` serialized data.
989
+ - `"page"`:- pagination metadata
990
+ - `"results"`:- serialized data.
948
991
  - Provides **structured pagination response format**.
949
992
 
950
993
  ---
@@ -959,9 +1002,9 @@ builder = PaginatedDataBuilder(
959
1002
  )
960
1003
  ```
961
1004
 
962
- - `request: Request` DRF request object.
963
- - `serializer_class: type[BaseSerializer]` DRF serializer class for the model.
964
- - `queryset: QuerySet` Django queryset to paginate.
1005
+ - `request: Request`:- DRF request object.
1006
+ - `serializer_class: type[BaseSerializer]`:- DRF serializer class for the model.
1007
+ - `queryset: QuerySet`:- Django queryset to paginate.
965
1008
 
966
1009
  ### Paginated Data Builder Methods
967
1010
 
@@ -991,6 +1034,80 @@ builder = PaginatedDataBuilder(
991
1034
  }
992
1035
  ```
993
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
+
994
1111
  ## 🛠️ Planned Features
995
1112
 
996
1113
  - Add more utils
@@ -6,13 +6,56 @@
6
6
 
7
7
  djresttoolkit is a collection of utilities and helpers for Django and Django REST Framework (DRF) that simplify common development tasks such as API handling, authentication, and email sending and much more.
8
8
 
9
- ## Features
9
+ ## 📖 Feature Index (djresttoolkit)
10
10
 
11
- - Django REST Framework helpers (serializers, views, pagination, etc.)
12
- - Django utilities (e.g., email sending, model mixins)
13
- - Ready-to-use shortcuts to speed up API development
14
- - Lightweight, no unnecessary dependencies
15
- - Type Safe - written with modern Python type hints.
11
+ - **DB Seed Command (`dbseed`)**
12
+ Seed your database with fake data using Pydantic models powered by **Faker**. Supports relationships, transactions, and a `manage.py dbseed` command.
13
+
14
+ - **DB Flush Command (`dbflush`)**
15
+ Management command to flush all models or a specific model, resetting auto-increment IDs safely with transaction support.
16
+
17
+ - **EnvBaseSettings**
18
+ Typed settings loader using **YAML + .env**, supports nested keys and overrides. Great for structured configuration management.
19
+
20
+ - **EmailSender**
21
+ Custom class to send templated emails (`text` and `html`) with context. Supports error handling and logging.
22
+
23
+ - **Custom DRF Exception Handler**
24
+ Centralized error handler for DRF that extends default behavior and adds throttle support (`429 Too Many Requests` with retry info).
25
+
26
+ - **Response Time Middleware**
27
+ Middleware to measure, log, and inject `X-Response-Time` headers into every response.
28
+
29
+ - **Throttle**
30
+ - `ThrottleInfoJSONRenderer`: Automatically adds throttle headers to responses.
31
+ - `ThrottleInspector`: Inspect view/request throttling and attach structured headers.
32
+
33
+ - **AbsoluteUrlFileMixin**
34
+ DRF serializer mixin that converts `FileField` / `ImageField` URLs to **absolute URLs** automatically.
35
+
36
+ - **BulkCreateMixin**
37
+ Serializer mixin that enables **bulk creation** of objects and syncs field error messages with model fields.
38
+
39
+ - **ModelChoiceFieldMixin**
40
+ Retrieve choice fields (`TextChoices`, etc.) from Django models as structured dictionaries for API responses.
41
+
42
+ - **ChoiceFieldsAPIView**
43
+ Generic API view that exposes model `choices` in a REST-friendly JSON format.
44
+
45
+ - **RetrieveObjectMixin**
46
+ Lightweight mixin to fetch a single object from a queryset with filters, raising a custom error if `queryset` is not defined.
47
+
48
+ - **build\_absolute\_uri**
49
+ Helper to build full absolute URLs for named routes with optional query params. Works with Django + DRF requests.
50
+
51
+ - **PageNumberPagination**
52
+ Custom paginator with a structured `"page"` metadata block and support for dynamic `page-size` query param.
53
+
54
+ - **PaginatedDataBuilder**
55
+ Builder that combines `PageNumberPagination` + serializers to return standardized paginated responses with `"page"` + `"results"`.
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.
16
59
 
17
60
  ## 📦 Installation
18
61
 
@@ -30,7 +73,7 @@ djresttoolkit is a collection of utilities and helpers for Django and Django RES
30
73
 
31
74
  ## 📚 All API Reference
32
75
 
33
- ### 1. DB Seed Utilities — API Reference
76
+ ### 1. DB Seed Command — API Reference
34
77
 
35
78
  #### `Generator`
36
79
 
@@ -214,7 +257,7 @@ print(settings.database_url)
214
257
  #### Features
215
258
 
216
259
  - Prioritizes `.env` variables over YAML.
217
- - Supports nested keys: `DATABASE__HOST` `settings.database.host`.
260
+ - Supports nested keys: `DATABASE__HOST`:- `settings.database.host`.
218
261
  - Designed to be subclassed for project-specific settings.
219
262
 
220
263
  ### 4. EmailSender — API Reference
@@ -353,7 +396,7 @@ X-Response-Time: 0.01234 seconds
353
396
  INFO: Request processed in 0.01234 seconds
354
397
  ```
355
398
 
356
- ### 7. Throttle Utilities — API Reference
399
+ ### 7. Throttle — API Reference
357
400
 
358
401
  #### `ThrottleInfoJSONRenderer`
359
402
 
@@ -830,17 +873,17 @@ from djresttoolkit.pagination import PageNumberPagination
830
873
  - Clients can control items per page using `?page-size=`.
831
874
  - Structured pagination metadata:
832
875
 
833
- - `current` current page number
834
- - `total` total number of pages
835
- - `size` number of items per page
836
- - `total_items` total number of items across all pages
837
- - `next` next page URL
838
- - `previous` previous page URL
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
839
882
  - Standardized API response format.
840
883
 
841
884
  ### Attributes of Page Number Pagination
842
885
 
843
- - `page_size_query_param: str` Query parameter name (`"page-size"`).
886
+ - `page_size_query_param: str`:- Query parameter name (`"page-size"`).
844
887
 
845
888
  ### Page Number Pagination Methods
846
889
 
@@ -885,8 +928,8 @@ from djresttoolkit.pagination import PaginatedDataBuilder
885
928
  - Integrates with **DRF serializers**.
886
929
  - Handles **invalid pages** gracefully by raising `NotFound`.
887
930
  - Returns both:
888
- - `"page"` pagination metadata
889
- - `"results"` serialized data.
931
+ - `"page"`:- pagination metadata
932
+ - `"results"`:- serialized data.
890
933
  - Provides **structured pagination response format**.
891
934
 
892
935
  ---
@@ -901,9 +944,9 @@ builder = PaginatedDataBuilder(
901
944
  )
902
945
  ```
903
946
 
904
- - `request: Request` DRF request object.
905
- - `serializer_class: type[BaseSerializer]` DRF serializer class for the model.
906
- - `queryset: QuerySet` Django queryset to paginate.
947
+ - `request: Request`:- DRF request object.
948
+ - `serializer_class: type[BaseSerializer]`:- DRF serializer class for the model.
949
+ - `queryset: QuerySet`:- Django queryset to paginate.
907
950
 
908
951
  ### Paginated Data Builder Methods
909
952
 
@@ -933,6 +976,80 @@ builder = PaginatedDataBuilder(
933
976
  }
934
977
  ```
935
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
+
936
1053
  ## 🛠️ Planned Features
937
1054
 
938
1055
  - Add more utils
@@ -4,7 +4,7 @@
4
4
 
5
5
  [project]
6
6
  name = "djresttoolkit"
7
- version = "0.16.0"
7
+ version = "0.17.0"
8
8
  description = "A collection of Django and DRF utilities to simplify API development."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = { file = "LICENSE" }
@@ -0,0 +1,16 @@
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
7
+
8
+ __all__ = [
9
+ "RetrieveObjectMixin",
10
+ "QuerysetNotDefinedError",
11
+ "CacheActionMixin",
12
+ "CacheInvalidateMixin",
13
+ "CacheKeyMixin",
14
+ "CacheListRetrieveMixin",
15
+ "CacheOpsMixin",
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
@@ -1,6 +0,0 @@
1
- from ._retrieve_object_mixin import RetrieveObjectMixin, QuerysetNotDefinedError
2
-
3
- __all__ = [
4
- "RetrieveObjectMixin",
5
- "QuerysetNotDefinedError",
6
- ]
File without changes