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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-small-view-set
3
- Version: 0.1.3
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, *args, **kwargs):
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
- def retrieve(self, request, pk, *args, **kwargs):
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**:
@@ -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
- @functools.wraps(func)
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
- return func(*args, **kwargs)
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 (EndpointDisabledException, Http404, ObjectDoesNotExist):
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
- return wrapper
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
- def disable_endpoint(view_func):
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
- @wraps(view_func)
180
- def wrapper(*args, **kwargs):
181
- if settings.SMALL_VIEWSET_RESPECT_DISABLED_ENDPOINTS:
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 view_func(*args, **kwargs)
184
- return wrapper
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
 
@@ -1,7 +1,7 @@
1
1
  class EndpointDisabledException(Exception):
2
- status_code = 404
3
- message = "Not Found"
4
- error_code = "not_found"
2
+ status_code = 405
3
+ message = "Method Not Allowed"
4
+ error_code = "method_not_allowed"
5
5
 
6
6
  class Unauthorized(Exception):
7
7
  status_code = 401
@@ -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
- return self.list(request, *args, **kwargs)
106
+ if hasattr(self, 'list'):
107
+ func = self.list
108
+
125
109
  elif request.method == 'POST':
126
- return self.create(request, *args, **kwargs)
110
+ if hasattr(self, 'create'):
111
+ func = self.create
127
112
  else:
128
113
  if request.method == 'GET':
129
- return self.retrieve(request, pk, *args, **kwargs)
114
+ if hasattr(self, 'retrieve'):
115
+ func = self.retrieve
116
+
130
117
  elif request.method == 'PUT':
131
- return self.put(request, pk, *args, **kwargs)
118
+ if hasattr(self, 'put'):
119
+ func = self.put
120
+
132
121
  elif request.method == 'PATCH':
133
- return self.patch(request, pk, *args, **kwargs)
122
+ if hasattr(self, 'patch'):
123
+ func = self.patch
124
+
134
125
  elif request.method == 'DELETE':
135
- return self.delete(request, pk, *args, **kwargs)
136
- endpoint_type = "detail" if pk else "collection"
137
- logger.error(f'Got a none response from request_router for {endpoint_type} method {request.method}')
138
- return JsonResponse(data=None, safe=False, status=500)
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,,