django-small-view-set 0.1.3__py3-none-any.whl → 0.1.4__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.1.3.dist-info → django_small_view_set-0.1.4.dist-info}/METADATA +19 -5
- django_small_view_set-0.1.4.dist-info/RECORD +9 -0
- small_view_set/README.md +4 -4
- small_view_set/decorators.py +41 -15
- small_view_set/exceptions.py +3 -3
- small_view_set/small_view_set.py +30 -30
- django_small_view_set-0.1.3.dist-info/RECORD +0 -9
- {django_small_view_set-0.1.3.dist-info → django_small_view_set-0.1.4.dist-info}/LICENSE +0 -0
- {django_small_view_set-0.1.3.dist-info → django_small_view_set-0.1.4.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.1.
|
3
|
+
Version: 0.1.4
|
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
|
@@ -30,26 +30,39 @@ This guide provides a simple example to get started with the library.
|
|
30
30
|
Here’s how you can define a basic API endpoint with one collection route and one detail route:
|
31
31
|
|
32
32
|
```python
|
33
|
+
import asyncio
|
33
34
|
from django.http import JsonResponse
|
34
35
|
from django.urls import path
|
35
|
-
from small_view_set.small_view_set import SmallViewSet
|
36
|
-
from small_view_set.decorators import default_handle_endpoint_exceptions
|
36
|
+
from small_view_set.small_view_set import SmallViewSet, default_handle_endpoint_exceptions, disable_endpoint
|
37
37
|
|
38
38
|
class BarViewSet(SmallViewSet):
|
39
39
|
|
40
40
|
def urlpatterns(self):
|
41
41
|
return [
|
42
42
|
path('api/bars/', self.default_router, name='bars_collection'),
|
43
|
+
path('api/bars/items/', self.items, name='bars_items'),
|
43
44
|
path('api/bars/<int:pk>/', self.default_router, name='bars_detail'),
|
44
45
|
]
|
45
46
|
|
46
47
|
@default_handle_endpoint_exceptions
|
47
|
-
def list(self, request
|
48
|
+
def list(self, request):
|
48
49
|
self.protect_list(request)
|
49
50
|
return JsonResponse({"message": "Hello, world!"}, status=200)
|
50
51
|
|
51
52
|
@default_handle_endpoint_exceptions
|
52
|
-
|
53
|
+
@disable_endpoint
|
54
|
+
async def items(self, request):
|
55
|
+
self.protect_list(request)
|
56
|
+
await asyncio.sleep(1)
|
57
|
+
return JsonResponse({"message": "List of items"}, status=200)
|
58
|
+
|
59
|
+
@default_handle_endpoint_exceptions
|
60
|
+
def patch(self, request, pk):
|
61
|
+
self.protect_update(request)
|
62
|
+
return JsonResponse({"message": f"Updated {pk}"}, status=200)
|
63
|
+
|
64
|
+
@default_handle_endpoint_exceptions
|
65
|
+
async def retrieve(self, request, pk):
|
53
66
|
self.protect_retrieve(request)
|
54
67
|
return JsonResponse({"message": f"Detail for ID {pk}"}, status=200)
|
55
68
|
```
|
@@ -76,5 +89,6 @@ urlpatterns = [
|
|
76
89
|
- [Handling Endpoint Exceptions](./README_HANDLE_ENDPOINT_EXCEPTIONS.md): Understand how to write your own decorators for exception handling.
|
77
90
|
- [Custom Protections](./README_CUSTOM_PROTECTIONS.md): Learn how to subclass `SmallViewSet` to add custom protections like logged-in checks.
|
78
91
|
- [DRF Compatibility](./README_DRF_COMPATIBILITY.md): Learn how to use some of Django Rest Framework's tools, like Serializers.
|
92
|
+
- [Disabling an endpoint](./README_DISABLE_ENDPOINT.md): Learn how to disable an endpoint without needing to delete it or comment it out.
|
79
93
|
- [Reason](./README_REASON.md): Reasoning behind this package.
|
80
94
|
|
@@ -0,0 +1,9 @@
|
|
1
|
+
small_view_set/README.md,sha256=oovLoOhBsg5wK8AwGe9iyuWXStHUo9x4fsrlb-oxHt4,1061
|
2
|
+
small_view_set/__init__.py,sha256=FoYcTj03W41-w0jC1EOv1_VD4iBxO_03fIr_plSKnp8,441
|
3
|
+
small_view_set/decorators.py,sha256=f_1K7YVvWe409UdwnX3rTTypzkV0IsKqS9oBaBICqRQ,7509
|
4
|
+
small_view_set/exceptions.py,sha256=5VaPS9m9syhag7p-gveG7Dep4DV3YQ7WjI1Sv5C_1N0,966
|
5
|
+
small_view_set/small_view_set.py,sha256=t_oRePTjjHhMjc1g3vrJtY9C5oW_dtfrhdKNA3PzU7Q,4775
|
6
|
+
django_small_view_set-0.1.4.dist-info/LICENSE,sha256=M4ZuHeiGHHuewaZyqz7tol4E6E2GMz7fF1ywNoXD1tA,1069
|
7
|
+
django_small_view_set-0.1.4.dist-info/METADATA,sha256=yxIVHh9dcAYCrw9157Q6SwGscdCNdhP3W5lLGyewmtc,3475
|
8
|
+
django_small_view_set-0.1.4.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
9
|
+
django_small_view_set-0.1.4.dist-info/RECORD,,
|
small_view_set/README.md
CHANGED
@@ -12,25 +12,25 @@
|
|
12
12
|
3. **Install Dependencies**:
|
13
13
|
- Install dependencies inside the container after mounting the directory:
|
14
14
|
```bash
|
15
|
-
docker compose run django-small-view-set-builder poetry install
|
15
|
+
docker compose run --remove-orphans django-small-view-set-builder poetry install
|
16
16
|
```
|
17
17
|
|
18
18
|
4. **Run Tests**:
|
19
19
|
- Run tests before releasing:
|
20
20
|
```bash
|
21
|
-
docker compose run django-small-view-set-builder python /app/tests/manage.py test
|
21
|
+
docker compose run --remove-orphans django-small-view-set-builder python /app/tests/manage.py test
|
22
22
|
```
|
23
23
|
|
24
24
|
5. **Build the Package**:
|
25
25
|
Inside the Docker container, build the package:
|
26
26
|
```bash
|
27
|
-
docker compose run django-small-view-set-builder poetry build
|
27
|
+
docker compose run --remove-orphans django-small-view-set-builder poetry build
|
28
28
|
```
|
29
29
|
|
30
30
|
6. **Publish to PyPI**:
|
31
31
|
Upload the package to PyPI:
|
32
32
|
```bash
|
33
|
-
docker compose run django-small-view-set-builder poetry publish
|
33
|
+
docker compose run --remove-orphans django-small-view-set-builder poetry publish
|
34
34
|
```
|
35
35
|
|
36
36
|
7. **Verify the Release**:
|
small_view_set/decorators.py
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
+
import inspect
|
1
2
|
import logging
|
2
3
|
from django.conf import settings
|
3
|
-
from functools import wraps
|
4
4
|
|
5
5
|
import json
|
6
|
-
import functools
|
7
6
|
from django.conf import settings
|
8
7
|
from django.core.exceptions import ObjectDoesNotExist, SuspiciousOperation, PermissionDenied
|
9
8
|
from django.http import (
|
@@ -58,12 +57,11 @@ def _get_logger(name):
|
|
58
57
|
|
59
58
|
|
60
59
|
def default_handle_endpoint_exceptions(func):
|
61
|
-
|
62
|
-
def wrapper(*args, **kwargs):
|
63
|
-
logger = _get_logger('django-small-view-set.default_handle_endpoint_exceptions')
|
60
|
+
logger = _get_logger('django-small-view-set.default_handle_endpoint_exceptions')
|
64
61
|
|
62
|
+
def _exception_handler(e):
|
65
63
|
try:
|
66
|
-
|
64
|
+
raise e
|
67
65
|
|
68
66
|
except json.JSONDecodeError:
|
69
67
|
return JsonResponse(data={"errors": "Invalid JSON"}, status=400)
|
@@ -81,8 +79,11 @@ def default_handle_endpoint_exceptions(func):
|
|
81
79
|
except (PermissionDenied, SuspiciousOperation):
|
82
80
|
return JsonResponse(data=None, safe=False, status=403)
|
83
81
|
|
84
|
-
except (
|
82
|
+
except (Http404, ObjectDoesNotExist):
|
85
83
|
return JsonResponse(data=None, safe=False, status=404)
|
84
|
+
|
85
|
+
except EndpointDisabledException:
|
86
|
+
return JsonResponse(data=None, safe=False, status=405)
|
86
87
|
|
87
88
|
except MethodNotAllowed as e:
|
88
89
|
return JsonResponse(
|
@@ -151,10 +152,25 @@ def default_handle_endpoint_exceptions(func):
|
|
151
152
|
status=status_code,
|
152
153
|
content_type='application/json')
|
153
154
|
|
154
|
-
|
155
|
+
def sync_wrapper(*args, **kwargs):
|
156
|
+
try:
|
157
|
+
return func(*args, **kwargs)
|
158
|
+
except Exception as e:
|
159
|
+
return _exception_handler(e)
|
160
|
+
|
161
|
+
async def async_wrapper(*args, **kwargs):
|
162
|
+
try:
|
163
|
+
return await func(*args, **kwargs)
|
164
|
+
except Exception as e:
|
165
|
+
return _exception_handler(e)
|
155
166
|
|
167
|
+
if inspect.iscoroutinefunction(func):
|
168
|
+
return async_wrapper
|
169
|
+
else:
|
170
|
+
return sync_wrapper
|
156
171
|
|
157
|
-
|
172
|
+
|
173
|
+
def disable_endpoint(func):
|
158
174
|
"""
|
159
175
|
Temporarily disables an API endpoint based on the SMALL_VIEWSET_RESPECT_DISABLED_ENDPOINTS setting.
|
160
176
|
|
@@ -169,17 +185,27 @@ def disable_endpoint(view_func):
|
|
169
185
|
```python
|
170
186
|
class ExampleViewSet(SmallViewSet):
|
171
187
|
|
172
|
-
@disable_endpoint
|
173
188
|
@default_handle_endpoint_exceptions
|
189
|
+
@disable_endpoint
|
174
190
|
def retrieve(self, request: Request) -> JsonResponse:
|
175
191
|
self.protect_retrieve(request)
|
176
192
|
. . .
|
177
193
|
```
|
178
194
|
"""
|
179
|
-
|
180
|
-
def
|
181
|
-
if
|
195
|
+
respect_disabled_endpoints = getattr(settings, 'SMALL_VIEWSET_RESPECT_DISABLED_ENDPOINTS', True)
|
196
|
+
def sync_wrapper(*args, **kwargs):
|
197
|
+
if respect_disabled_endpoints:
|
182
198
|
raise EndpointDisabledException()
|
183
|
-
return
|
184
|
-
|
199
|
+
return func(*args, **kwargs)
|
200
|
+
|
201
|
+
async def async_wrapper(*args, **kwargs):
|
202
|
+
if respect_disabled_endpoints:
|
203
|
+
raise EndpointDisabledException()
|
204
|
+
return await func(*args, **kwargs)
|
205
|
+
|
206
|
+
|
207
|
+
if inspect.iscoroutinefunction(func):
|
208
|
+
return async_wrapper
|
209
|
+
else:
|
210
|
+
return sync_wrapper
|
185
211
|
|
small_view_set/exceptions.py
CHANGED
small_view_set/small_view_set.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
+
import inspect
|
1
2
|
import json
|
2
3
|
import logging
|
3
|
-
from django.http import JsonResponse
|
4
4
|
from urllib.request import Request
|
5
5
|
|
6
6
|
from .exceptions import BadRequest, MethodNotAllowed
|
@@ -8,24 +8,6 @@ from .exceptions import BadRequest, MethodNotAllowed
|
|
8
8
|
logger = logging.getLogger('app')
|
9
9
|
|
10
10
|
class SmallViewSet:
|
11
|
-
def create(self, request: Request, *args, **kwargs):
|
12
|
-
raise MethodNotAllowed('POST')
|
13
|
-
|
14
|
-
def list(self, request: Request, *args, **kwarg):
|
15
|
-
raise MethodNotAllowed('GET')
|
16
|
-
|
17
|
-
def retrieve(self, request: Request, pk, *args, **kwargs):
|
18
|
-
raise MethodNotAllowed('GET')
|
19
|
-
|
20
|
-
def put(self, request: Request, pk, *args, **kwargs):
|
21
|
-
raise MethodNotAllowed('PUT')
|
22
|
-
|
23
|
-
def patch(self, request: Request, pk, *args, **kwargs):
|
24
|
-
raise MethodNotAllowed('PATCH')
|
25
|
-
|
26
|
-
def delete(self, request: Request, pk, *args, **kwargs):
|
27
|
-
raise MethodNotAllowed('DELETE')
|
28
|
-
|
29
11
|
def parse_json_body(self, request: Request):
|
30
12
|
if request.content_type != 'application/json':
|
31
13
|
raise BadRequest('Invalid content type')
|
@@ -76,7 +58,7 @@ class SmallViewSet:
|
|
76
58
|
if request.method != 'DELETE':
|
77
59
|
raise MethodNotAllowed(request.method)
|
78
60
|
|
79
|
-
def default_router(self, request: Request, pk=None, *args, **kwargs):
|
61
|
+
async def default_router(self, request: Request, pk=None, *args, **kwargs):
|
80
62
|
"""
|
81
63
|
This method routes requests to the appropriate method based on the HTTP method and presence of a primary key (pk).
|
82
64
|
|
@@ -116,23 +98,41 @@ class SmallViewSet:
|
|
116
98
|
def some_disabled_endpoint(self, request: Request):
|
117
99
|
self.protect_retrieve(request)
|
118
100
|
. . .
|
119
|
-
|
120
101
|
```
|
121
102
|
"""
|
103
|
+
func = None
|
122
104
|
if pk is None:
|
123
105
|
if request.method == 'GET':
|
124
|
-
|
106
|
+
if hasattr(self, 'list'):
|
107
|
+
func = self.list
|
108
|
+
|
125
109
|
elif request.method == 'POST':
|
126
|
-
|
110
|
+
if hasattr(self, 'create'):
|
111
|
+
func = self.create
|
127
112
|
else:
|
128
113
|
if request.method == 'GET':
|
129
|
-
|
114
|
+
if hasattr(self, 'retrieve'):
|
115
|
+
func = self.retrieve
|
116
|
+
|
130
117
|
elif request.method == 'PUT':
|
131
|
-
|
118
|
+
if hasattr(self, 'put'):
|
119
|
+
func = self.put
|
120
|
+
|
132
121
|
elif request.method == 'PATCH':
|
133
|
-
|
122
|
+
if hasattr(self, 'patch'):
|
123
|
+
func = self.patch
|
124
|
+
|
134
125
|
elif request.method == 'DELETE':
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
126
|
+
if hasattr(self, 'delete'):
|
127
|
+
func = self.delete
|
128
|
+
|
129
|
+
if func is None:
|
130
|
+
raise MethodNotAllowed(request.method)
|
131
|
+
|
132
|
+
if pk is not None:
|
133
|
+
kwargs['pk'] = pk
|
134
|
+
|
135
|
+
if inspect.iscoroutinefunction(func):
|
136
|
+
return await func(request, *args, **kwargs)
|
137
|
+
else:
|
138
|
+
return func(request, *args, **kwargs)
|
@@ -1,9 +0,0 @@
|
|
1
|
-
small_view_set/README.md,sha256=AVHm5FrLxNzFKt9-PGvx0vkAcfMEO9ZtFoCG__lQp-s,993
|
2
|
-
small_view_set/__init__.py,sha256=FoYcTj03W41-w0jC1EOv1_VD4iBxO_03fIr_plSKnp8,441
|
3
|
-
small_view_set/decorators.py,sha256=OSwGz5SfTw7hUjOcPNmCmUl8mLqaHOMXaLcqg9JBHXo,6750
|
4
|
-
small_view_set/exceptions.py,sha256=8CAnzvNZFlqjez8z66jLo-4P3Tlj5Y5MioDPmrb9wQg,948
|
5
|
-
small_view_set/small_view_set.py,sha256=THDeJp1PmodaWzFUUnowpDmhH8w1El4p97jvQnjyU4k,5192
|
6
|
-
django_small_view_set-0.1.3.dist-info/LICENSE,sha256=M4ZuHeiGHHuewaZyqz7tol4E6E2GMz7fF1ywNoXD1tA,1069
|
7
|
-
django_small_view_set-0.1.3.dist-info/METADATA,sha256=CfEGIqD7i2PPsYsDf-k8ksloNXlOGCutgaaqhH_BQd8,2881
|
8
|
-
django_small_view_set-0.1.3.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
9
|
-
django_small_view_set-0.1.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|