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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-small-view-set
3
- Version: 0.2.5
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 alternative with minimal abstraction. This library provides a simple and transparent way to define API endpoints without relying on complex abstractions.
22
+ A recommended pattern and a lightweight Django ViewSet with minimal abstraction.
23
23
 
24
- ## Getting Started with Django Small View Set
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.default_router, name='bars_collection'),
52
- path('api/bars/items/', self.items, name='bars_items'),
53
- path('api/bars/<int:pk>/', self.default_router, name='bars_detail'),
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
- @endpoint(allowed_methods=['GET'])
62
- @endpoint_disabled
63
- async def items(self, request):
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
- @endpoint(allowed_methods=['PATCH'])
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 retrieve(self, request, pk):
75
- self.protect_retrieve(request)
76
- return JsonResponse({"message": f"Detail for ID {pk}"}, status=200)
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
- ## Documentation
108
+ ## Deeper learning
96
109
 
97
- - [Custom Endpoints](./README_CUSTOM_ENDPOINT.md): Learn how to define custom endpoints alongside the default router.
98
- - [Handling Endpoint Exceptions](./README_HANDLE_ENDPOINT_EXCEPTIONS.md): Understand how to write your own decorators for exception handling.
99
- - [Custom Protections](./README_CUSTOM_PROTECTIONS.md): Learn how to subclass `SmallViewSet` to add custom protections like logged-in checks.
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,,
@@ -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,
@@ -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,,