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.
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/PKG-INFO +139 -22
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/README.md +138 -21
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/pyproject.toml +1 -1
- djresttoolkit-0.17.0/src/djresttoolkit/views/mixins/__init__.py +16 -0
- djresttoolkit-0.17.0/src/djresttoolkit/views/mixins/_cache_action_mixin.py +44 -0
- djresttoolkit-0.17.0/src/djresttoolkit/views/mixins/_cache_invalidate_mixin.py +38 -0
- djresttoolkit-0.17.0/src/djresttoolkit/views/mixins/_cache_key_mixin.py +33 -0
- djresttoolkit-0.17.0/src/djresttoolkit/views/mixins/_cache_list_retrieve_mixin.py +57 -0
- djresttoolkit-0.17.0/src/djresttoolkit/views/mixins/_cache_ops_mixin.py +47 -0
- djresttoolkit-0.16.0/src/djresttoolkit/views/mixins/__init__.py +0 -6
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/.gitignore +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/LICENSE +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/demo/staticfiles/admin/img/LICENSE +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/admin.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/apps.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/dbseed/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/dbseed/models/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/dbseed/models/_choice_field.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/dbseed/models/_gen.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/dbseed/models/_seed_model.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/envconfig/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/envconfig/_env_settings.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/mail/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/mail/_email_sender.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/mail/_models.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/mail/_types.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/management/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/management/commands/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/management/commands/dbflush.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/management/commands/dbseed.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/middlewares/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/middlewares/_response_time_middleware.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/migrations/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/models/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/models/mixins/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/models/mixins/_model_choice_fields_mixin.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/pagination/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/pagination/_page_number_pagination.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/pagination/_paginated_data_builder.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/py.typed +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/renderers/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/renderers/_throttle_info_json_renderer.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/serializers/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/serializers/mixins/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/serializers/mixins/_absolute_url_file_mixin.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/serializers/mixins/_bulk_create_mixin.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/throttling/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/throttling/_throttle_inspector.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/urls/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/urls/_build_absolute_uri.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/views/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/views/_api_views/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/views/_api_views/_choice_fields_apiview.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/views/_exceptions/__init__.py +0 -0
- {djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/views/_exceptions/_exception_handler.py +0 -0
- {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.
|
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
|
-
##
|
67
|
+
## 📖 Feature Index (djresttoolkit)
|
68
68
|
|
69
|
-
-
|
70
|
-
|
71
|
-
|
72
|
-
-
|
73
|
-
|
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
|
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
|
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
|
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
|
892
|
-
- `total
|
893
|
-
- `size
|
894
|
-
- `total_items
|
895
|
-
- `next
|
896
|
-
- `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
|
897
940
|
- Standardized API response format.
|
898
941
|
|
899
942
|
### Attributes of Page Number Pagination
|
900
943
|
|
901
|
-
- `page_size_query_param: str
|
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"
|
947
|
-
- `"results"
|
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
|
963
|
-
- `serializer_class: type[BaseSerializer]
|
964
|
-
- `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.
|
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
|
-
##
|
9
|
+
## 📖 Feature Index (djresttoolkit)
|
10
10
|
|
11
|
-
-
|
12
|
-
|
13
|
-
|
14
|
-
-
|
15
|
-
|
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
|
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
|
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
|
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
|
834
|
-
- `total
|
835
|
-
- `size
|
836
|
-
- `total_items
|
837
|
-
- `next
|
838
|
-
- `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
|
839
882
|
- Standardized API response format.
|
840
883
|
|
841
884
|
### Attributes of Page Number Pagination
|
842
885
|
|
843
|
-
- `page_size_query_param: str
|
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"
|
889
|
-
- `"results"
|
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
|
905
|
-
- `serializer_class: type[BaseSerializer]
|
906
|
-
- `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.
|
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.
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/dbseed/models/_choice_field.py
RENAMED
File without changes
|
File without changes
|
{djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/dbseed/models/_seed_model.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/management/commands/__init__.py
RENAMED
File without changes
|
{djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/management/commands/dbflush.py
RENAMED
File without changes
|
{djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/management/commands/dbseed.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/serializers/mixins/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/throttling/_throttle_inspector.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/views/_api_views/__init__.py
RENAMED
File without changes
|
File without changes
|
{djresttoolkit-0.16.0 → djresttoolkit-0.17.0}/src/djresttoolkit/views/_exceptions/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|