django-small-view-set 0.2.5__py3-none-any.whl → 0.2.7__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.
- {django_small_view_set-0.2.5.dist-info → django_small_view_set-0.2.7.dist-info}/METADATA +39 -27
- django_small_view_set-0.2.7.dist-info/RECORD +11 -0
- small_view_set/decorators.py +6 -4
- small_view_set/helpers.py +1 -1
- small_view_set/small_view_set.py +1 -155
- django_small_view_set-0.2.5.dist-info/RECORD +0 -11
- {django_small_view_set-0.2.5.dist-info → django_small_view_set-0.2.7.dist-info}/LICENSE +0 -0
- {django_small_view_set-0.2.5.dist-info → django_small_view_set-0.2.7.dist-info}/WHEEL +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: django-small-view-set
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.7
|
4
4
|
Summary: A lightweight Django ViewSet alternative with minimal abstraction.
|
5
5
|
Home-page: https://github.com/nateonguitar/django-small-view-set
|
6
6
|
License: MIT
|
@@ -19,16 +19,12 @@ Description-Content-Type: text/markdown
|
|
19
19
|
|
20
20
|
# Django Small View Set
|
21
21
|
|
22
|
-
A lightweight Django ViewSet
|
22
|
+
A recommended pattern and a lightweight Django ViewSet with minimal abstraction.
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
This guide provides a simple example to get started with the library.
|
24
|
+
This pattern supports both standard endpoints and `async` endpoints.
|
27
25
|
|
28
26
|
### Example Usage
|
29
27
|
|
30
|
-
Here’s how to define a basic view set:
|
31
|
-
|
32
28
|
In settings.py
|
33
29
|
```python
|
34
30
|
# Register SmallViewSetConfig in settings
|
@@ -37,43 +33,60 @@ from small_view_set SmallViewSetConfig
|
|
37
33
|
SMALL_VIEW_SET_CONFIG = SmallViewSetConfig()
|
38
34
|
```
|
39
35
|
|
36
|
+
^^^ This will get you up and running, but it is recommended to write your own [Custom exception handler](./README_CUSTOM_EXCEPTION_HANDLER.md)
|
37
|
+
|
38
|
+
Please note, endpoints cannot be registered in `urls.py` with the
|
39
|
+
request method (like POST, or GET), therefore create a `collection` and/or `detail` orchestrator
|
40
|
+
method for the standard CRUD operations.
|
41
|
+
|
40
42
|
|
41
43
|
```python
|
42
44
|
import asyncio
|
43
45
|
from django.http import JsonResponse
|
44
46
|
from django.urls import path
|
45
47
|
from small_view_set import SmallViewSet, endpoint, endpoint_disabled
|
48
|
+
from urllib.request import Request
|
46
49
|
|
47
50
|
class BarViewSet(SmallViewSet):
|
48
51
|
|
49
52
|
def urlpatterns(self):
|
50
53
|
return [
|
51
|
-
path('api/bars/', self.
|
52
|
-
path('api/bars
|
53
|
-
path('api/bars
|
54
|
+
path('api/bars/', self.collection, name='bars_collection'),
|
55
|
+
path('api/bars/<int:pk>/', self.detail, name='bars_detail'),
|
56
|
+
path('api/bars/items/', self.items, name='bars_items'),
|
54
57
|
]
|
58
|
+
@endpoint(allowed_methods=['GET', 'POST'])
|
59
|
+
def collection(self, request: Request):
|
60
|
+
if request.method == 'GET':
|
61
|
+
return self.list(request)
|
62
|
+
raise MethodNotAllowed(request.method)
|
63
|
+
|
64
|
+
@endpoint(allowed_methods=['GET', 'PATCH'])
|
65
|
+
async def detail(self, request: Request, pk: int):
|
66
|
+
if request.method == 'GET':
|
67
|
+
return await self.retrieve(request, pk)
|
68
|
+
elif request.method == 'PATCH':
|
69
|
+
return self.patch(request, pk)
|
70
|
+
raise MethodNotAllowed(request.method)
|
55
71
|
|
56
|
-
@endpoint(allowed_methods=['GET'])
|
57
72
|
def list(self, request):
|
58
73
|
self.protect_list(request)
|
59
74
|
return JsonResponse({"message": "Hello, world!"}, status=200)
|
60
75
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
self.protect_list(request)
|
65
|
-
await asyncio.sleep(1)
|
66
|
-
return JsonResponse({"message": "List of items"}, status=200)
|
76
|
+
async def retrieve(self, request: Request, pk: int):
|
77
|
+
self.protect_retrieve(request)
|
78
|
+
return JsonResponse({"message": f"Detail for ID {pk}"}, status=200)
|
67
79
|
|
68
|
-
|
69
|
-
def patch(self, request, pk):
|
80
|
+
def patch(self, request: Request, pk: int):
|
70
81
|
self.protect_update(request)
|
71
82
|
return JsonResponse({"message": f"Updated {pk}"}, status=200)
|
72
83
|
|
73
84
|
@endpoint(allowed_methods=['GET'])
|
74
|
-
async def
|
75
|
-
|
76
|
-
|
85
|
+
async def items(self, request: Request):
|
86
|
+
# Pick the closest protect that matches the endoint. `GET items` is closest to a list
|
87
|
+
self.protect_list(request)
|
88
|
+
await asyncio.sleep(1)
|
89
|
+
return JsonResponse({"message": "List of items"}, status=200)
|
77
90
|
```
|
78
91
|
|
79
92
|
|
@@ -92,12 +105,11 @@ urlpatterns = [
|
|
92
105
|
```
|
93
106
|
|
94
107
|
|
95
|
-
##
|
108
|
+
## Deeper learning
|
96
109
|
|
97
|
-
- [Custom
|
98
|
-
- [
|
99
|
-
- [
|
100
|
-
- [DRF Compatibility](./README_DRF_COMPATIBILITY.md): Learn how to use some of Django Rest Framework's tools, like Serializers.
|
110
|
+
- [Custom protections](./README_CUSTOM_PROTECTIONS.md): Learn how to subclass `SmallViewSet` to add custom protections like logged-in checks.
|
111
|
+
- [Custom exception handler](./README_CUSTOM_EXCEPTION_HANDLER.md): Understand how to write your own exception handler.
|
112
|
+
- [DRF compatibility](./README_DRF_COMPATIBILITY.md): Learn how to use some of Django Rest Framework's tools, like Serializers.
|
101
113
|
- [Disabling an endpoint](./README_DISABLE_ENDPOINT.md): Learn how to disable an endpoint without needing to delete it or comment it out.
|
102
114
|
- [Reason](./README_REASON.md): Reasoning behind this package.
|
103
115
|
|
@@ -0,0 +1,11 @@
|
|
1
|
+
small_view_set/README.md,sha256=oovLoOhBsg5wK8AwGe9iyuWXStHUo9x4fsrlb-oxHt4,1061
|
2
|
+
small_view_set/__init__.py,sha256=lJ09qERCFrf4WAY6mRFyFsEG1R3ErpWqblIKi4TVggI,625
|
3
|
+
small_view_set/config.py,sha256=nGu840qa6zYyXoQa1EO7xIlWh3dFPd0oYwN-2Qmj7Ws,1446
|
4
|
+
small_view_set/decorators.py,sha256=o5qkbnhFKVL47PVZKozyQoC3beuzl6MtsleKhKHofj8,3322
|
5
|
+
small_view_set/exceptions.py,sha256=5VaPS9m9syhag7p-gveG7Dep4DV3YQ7WjI1Sv5C_1N0,966
|
6
|
+
small_view_set/helpers.py,sha256=C05Q-KVeAvmOg7hhNVO3-ti8k0esidZ5OpRQUQNEH40,5126
|
7
|
+
small_view_set/small_view_set.py,sha256=-MYj1qbGRD0mLuWoJFxua7fwAry7crgMSZHCzqL2PVc,2581
|
8
|
+
django_small_view_set-0.2.7.dist-info/LICENSE,sha256=M4ZuHeiGHHuewaZyqz7tol4E6E2GMz7fF1ywNoXD1tA,1069
|
9
|
+
django_small_view_set-0.2.7.dist-info/METADATA,sha256=nIBZh1WeqohXuuLhJ5SmyojWX5vT1rw41uvVZBE9Cic,4125
|
10
|
+
django_small_view_set-0.2.7.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
11
|
+
django_small_view_set-0.2.7.dist-info/RECORD,,
|
small_view_set/decorators.py
CHANGED
@@ -10,6 +10,7 @@ def endpoint(
|
|
10
10
|
func_name = func.__name__
|
11
11
|
def sync_wrapper(viewset, *args, **kwargs):
|
12
12
|
request = args[0]
|
13
|
+
args = args[1:]
|
13
14
|
try:
|
14
15
|
config: SmallViewSetConfig = getattr(settings, 'SMALL_VIEW_SET_CONFIG', SmallViewSetConfig())
|
15
16
|
pre_response = config.options_and_head_handler(request, allowed_methods)
|
@@ -17,14 +18,15 @@ def endpoint(
|
|
17
18
|
return pre_response
|
18
19
|
pk = kwargs.pop('pk', None)
|
19
20
|
if pk is None:
|
20
|
-
return func(viewset, request=request)
|
21
|
+
return func(viewset, request=request, *args, **kwargs)
|
21
22
|
else:
|
22
|
-
return func(viewset, request=request, pk=pk)
|
23
|
+
return func(viewset, request=request, pk=pk, *args, **kwargs)
|
23
24
|
except Exception as e:
|
24
25
|
return config.exception_handler(request, func_name, e)
|
25
26
|
|
26
27
|
async def async_wrapper(viewset, *args, **kwargs):
|
27
28
|
request = args[0]
|
29
|
+
args = args[1:]
|
28
30
|
try:
|
29
31
|
config: SmallViewSetConfig = getattr(settings, 'SMALL_VIEW_SET_CONFIG', SmallViewSetConfig())
|
30
32
|
pre_response = config.options_and_head_handler(request, allowed_methods)
|
@@ -32,9 +34,9 @@ def endpoint(
|
|
32
34
|
return pre_response
|
33
35
|
pk = kwargs.pop('pk', None)
|
34
36
|
if pk is None:
|
35
|
-
return await func(viewset, request=request)
|
37
|
+
return await func(viewset, request=request, *args, **kwargs)
|
36
38
|
else:
|
37
|
-
return await func(viewset, request=request, pk=pk)
|
39
|
+
return await func(viewset, request=request, pk=pk, *args, **kwargs)
|
38
40
|
except Exception as e:
|
39
41
|
return config.exception_handler(request, func_name, e)
|
40
42
|
|
small_view_set/helpers.py
CHANGED
@@ -18,7 +18,7 @@ if not _logger.hasHandlers():
|
|
18
18
|
_logger.setLevel(logging.INFO)
|
19
19
|
|
20
20
|
|
21
|
-
def default_options_and_head_handler(request, allowed_methods: list[str]):
|
21
|
+
def default_options_and_head_handler(request: Request, allowed_methods: list[str]):
|
22
22
|
if request.method == 'OPTIONS':
|
23
23
|
response = JsonResponse(
|
24
24
|
data=None,
|
small_view_set/small_view_set.py
CHANGED
@@ -1,11 +1,8 @@
|
|
1
|
-
import inspect
|
2
1
|
import json
|
3
2
|
import logging
|
4
3
|
from urllib.request import Request
|
5
4
|
|
6
|
-
|
7
|
-
from .decorators import endpoint
|
8
|
-
from .exceptions import BadRequest, MethodNotAllowed
|
5
|
+
from .exceptions import BadRequest
|
9
6
|
|
10
7
|
logger = logging.getLogger('app')
|
11
8
|
|
@@ -79,154 +76,3 @@ class SmallViewSet:
|
|
79
76
|
this library adds logic in the future.
|
80
77
|
"""
|
81
78
|
pass
|
82
|
-
|
83
|
-
@endpoint(allowed_methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD'])
|
84
|
-
async def default_router_async(self, request: Request, pk=None, *args, **kwargs):
|
85
|
-
"""
|
86
|
-
This method routes requests to the appropriate method based on the HTTP method and presence of a primary key (pk).
|
87
|
-
|
88
|
-
It also handles errors and returns appropriate JSON responses by using the decorator @endpoint(allowed_method=[]).
|
89
|
-
|
90
|
-
GET/POST for collection endpoints and GET/PUT/PATCH/DELETE for detail endpoints.
|
91
|
-
|
92
|
-
Example:
|
93
|
-
```
|
94
|
-
# Note: AppViewSet is a subclass of SmallViewSet with overridden protect methods with more specific logic.
|
95
|
-
|
96
|
-
class CommentViewSet(AppViewSet):
|
97
|
-
def urlpatterns(self):
|
98
|
-
return [
|
99
|
-
path('api/comments/', self.default_router_async, name='comments_collection'),
|
100
|
-
path('api/comments/<int:pk>/', self.default_router_async, name='comments_detail'),
|
101
|
-
path('api/comments/<int:pk>/custom_put/', self.custom_put, name='comments_custom_put_detail'),
|
102
|
-
]
|
103
|
-
|
104
|
-
@endpoint(allowed_method=['POST'])
|
105
|
-
def create(self, request: Request):
|
106
|
-
self.protect_create(request)
|
107
|
-
. . .
|
108
|
-
|
109
|
-
@endpoint(allowed_method=['PUT', 'PATCH'])
|
110
|
-
def update(self, request: Request, pk: int):
|
111
|
-
self.protect_update(request)
|
112
|
-
. . .
|
113
|
-
|
114
|
-
@endpoint(allowed_method=['PUT'])
|
115
|
-
def custom_put(self, request: Request, pk: int):
|
116
|
-
self.protect_update(request)
|
117
|
-
. . .
|
118
|
-
|
119
|
-
@endpoint(allowed_method=['GET'])
|
120
|
-
@disable_endpoint
|
121
|
-
def some_disabled_endpoint(self, request: Request):
|
122
|
-
self.protect_retrieve(request)
|
123
|
-
. . .
|
124
|
-
```
|
125
|
-
"""
|
126
|
-
func = None
|
127
|
-
if pk is None:
|
128
|
-
if request.method == 'GET':
|
129
|
-
if hasattr(self, 'list'):
|
130
|
-
func = self.list
|
131
|
-
|
132
|
-
elif request.method == 'POST':
|
133
|
-
if hasattr(self, 'create'):
|
134
|
-
func = self.create
|
135
|
-
else:
|
136
|
-
if request.method == 'GET':
|
137
|
-
if hasattr(self, 'retrieve'):
|
138
|
-
func = self.retrieve
|
139
|
-
|
140
|
-
elif request.method == 'PUT':
|
141
|
-
if hasattr(self, 'put'):
|
142
|
-
func = self.put
|
143
|
-
|
144
|
-
elif request.method == 'PATCH':
|
145
|
-
if hasattr(self, 'patch'):
|
146
|
-
func = self.patch
|
147
|
-
|
148
|
-
elif request.method == 'DELETE':
|
149
|
-
if hasattr(self, 'delete'):
|
150
|
-
func = self.delete
|
151
|
-
|
152
|
-
if func is None:
|
153
|
-
raise MethodNotAllowed(request.method)
|
154
|
-
if pk is not None:
|
155
|
-
kwargs['pk'] = pk
|
156
|
-
return await func(request, *args, **kwargs)
|
157
|
-
|
158
|
-
|
159
|
-
@endpoint(allowed_methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD'])
|
160
|
-
def default_router(self, request: Request, pk=None, *args, **kwargs):
|
161
|
-
"""
|
162
|
-
This method routes requests to the appropriate method based on the HTTP method and presence of a primary key (pk).
|
163
|
-
|
164
|
-
It also handles errors and returns appropriate JSON responses by using the decorator @endpoint(allowed_method=[]).
|
165
|
-
|
166
|
-
GET/POST for collection endpoints and GET/PUT/PATCH/DELETE for detail endpoints.
|
167
|
-
|
168
|
-
Example:
|
169
|
-
```
|
170
|
-
# Note: AppViewSet is a subclass of SmallViewSet with overridden protect methods with more specific logic.
|
171
|
-
|
172
|
-
class CommentViewSet(AppViewSet):
|
173
|
-
def urlpatterns(self):
|
174
|
-
return [
|
175
|
-
path('api/comments/', self.default_router, name='comments_collection'),
|
176
|
-
path('api/comments/<int:pk>/', self.default_router, name='comments_detail'),
|
177
|
-
path('api/comments/<int:pk>/custom_put/', self.custom_put, name='comments_custom_put_detail'),
|
178
|
-
]
|
179
|
-
|
180
|
-
@endpoint(allowed_method=['POST'])
|
181
|
-
def create(self, request: Request):
|
182
|
-
self.protect_create(request)
|
183
|
-
. . .
|
184
|
-
|
185
|
-
@endpoint(allowed_method=['PUT', 'PATCH'])
|
186
|
-
def update(self, request: Request, pk: int):
|
187
|
-
self.protect_update(request)
|
188
|
-
. . .
|
189
|
-
|
190
|
-
@endpoint(allowed_method=['PUT'])
|
191
|
-
def custom_put(self, request: Request, pk: int):
|
192
|
-
self.protect_update(request)
|
193
|
-
. . .
|
194
|
-
|
195
|
-
@disable_endpoint
|
196
|
-
@endpoint(allowed_method=['GET'])
|
197
|
-
def some_disabled_endpoint(self, request: Request):
|
198
|
-
self.protect_retrieve(request)
|
199
|
-
. . .
|
200
|
-
```
|
201
|
-
"""
|
202
|
-
func = None
|
203
|
-
if pk is None:
|
204
|
-
if request.method == 'GET':
|
205
|
-
if hasattr(self, 'list'):
|
206
|
-
func = self.list
|
207
|
-
|
208
|
-
elif request.method == 'POST':
|
209
|
-
if hasattr(self, 'create'):
|
210
|
-
func = self.create
|
211
|
-
else:
|
212
|
-
if request.method == 'GET':
|
213
|
-
if hasattr(self, 'retrieve'):
|
214
|
-
func = self.retrieve
|
215
|
-
|
216
|
-
elif request.method == 'PUT':
|
217
|
-
if hasattr(self, 'put'):
|
218
|
-
func = self.put
|
219
|
-
|
220
|
-
elif request.method == 'PATCH':
|
221
|
-
if hasattr(self, 'patch'):
|
222
|
-
func = self.patch
|
223
|
-
|
224
|
-
elif request.method == 'DELETE':
|
225
|
-
if hasattr(self, 'delete'):
|
226
|
-
func = self.delete
|
227
|
-
|
228
|
-
if func is None:
|
229
|
-
raise MethodNotAllowed(request.method)
|
230
|
-
if pk is not None:
|
231
|
-
kwargs['pk'] = pk
|
232
|
-
return func(request, *args, **kwargs)
|
@@ -1,11 +0,0 @@
|
|
1
|
-
small_view_set/README.md,sha256=oovLoOhBsg5wK8AwGe9iyuWXStHUo9x4fsrlb-oxHt4,1061
|
2
|
-
small_view_set/__init__.py,sha256=lJ09qERCFrf4WAY6mRFyFsEG1R3ErpWqblIKi4TVggI,625
|
3
|
-
small_view_set/config.py,sha256=nGu840qa6zYyXoQa1EO7xIlWh3dFPd0oYwN-2Qmj7Ws,1446
|
4
|
-
small_view_set/decorators.py,sha256=1d56mPMAj3NgMlZIu8908iMBmhVu7_Lz14vZbDQpXi8,3198
|
5
|
-
small_view_set/exceptions.py,sha256=5VaPS9m9syhag7p-gveG7Dep4DV3YQ7WjI1Sv5C_1N0,966
|
6
|
-
small_view_set/helpers.py,sha256=EKx71YLS-5cN-JrMJXYR39O15qf4O6TDiaQsal7BVwY,5117
|
7
|
-
small_view_set/small_view_set.py,sha256=E__QMDVnGItktSlIIcQzbDPk_XrsI7U5IXxeI7AZjZw,8415
|
8
|
-
django_small_view_set-0.2.5.dist-info/LICENSE,sha256=M4ZuHeiGHHuewaZyqz7tol4E6E2GMz7fF1ywNoXD1tA,1069
|
9
|
-
django_small_view_set-0.2.5.dist-info/METADATA,sha256=0yu-LCs-3Iq8WtnuFZxGTrBaA3YErFdUCF7jOthGWTw,3560
|
10
|
-
django_small_view_set-0.2.5.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
11
|
-
django_small_view_set-0.2.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|