django-small-view-set 0.2.6__tar.gz → 0.2.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-small-view-set
3
- Version: 0.2.6
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,95 @@
1
+ # Django Small View Set
2
+
3
+ A recommended pattern and a lightweight Django ViewSet with minimal abstraction.
4
+
5
+ This pattern supports both standard endpoints and `async` endpoints.
6
+
7
+ ### Example Usage
8
+
9
+ In settings.py
10
+ ```python
11
+ # Register SmallViewSetConfig in settings
12
+ from small_view_set SmallViewSetConfig
13
+
14
+ SMALL_VIEW_SET_CONFIG = SmallViewSetConfig()
15
+ ```
16
+
17
+ ^^^ This will get you up and running, but it is recommended to write your own [Custom exception handler](./README_CUSTOM_EXCEPTION_HANDLER.md)
18
+
19
+ Please note, endpoints cannot be registered in `urls.py` with the
20
+ request method (like POST, or GET), therefore create a `collection` and/or `detail` orchestrator
21
+ method for the standard CRUD operations.
22
+
23
+
24
+ ```python
25
+ import asyncio
26
+ from django.http import JsonResponse
27
+ from django.urls import path
28
+ from small_view_set import SmallViewSet, endpoint, endpoint_disabled
29
+ from urllib.request import Request
30
+
31
+ class BarViewSet(SmallViewSet):
32
+
33
+ def urlpatterns(self):
34
+ return [
35
+ path('api/bars/', self.collection, name='bars_collection'),
36
+ path('api/bars/<int:pk>/', self.detail, name='bars_detail'),
37
+ path('api/bars/items/', self.items, name='bars_items'),
38
+ ]
39
+ @endpoint(allowed_methods=['GET', 'POST'])
40
+ def collection(self, request: Request):
41
+ if request.method == 'GET':
42
+ return self.list(request)
43
+ raise MethodNotAllowed(request.method)
44
+
45
+ @endpoint(allowed_methods=['GET', 'PATCH'])
46
+ async def detail(self, request: Request, pk: int):
47
+ if request.method == 'GET':
48
+ return await self.retrieve(request, pk)
49
+ elif request.method == 'PATCH':
50
+ return self.patch(request, pk)
51
+ raise MethodNotAllowed(request.method)
52
+
53
+ def list(self, request):
54
+ self.protect_list(request)
55
+ return JsonResponse({"message": "Hello, world!"}, status=200)
56
+
57
+ async def retrieve(self, request: Request, pk: int):
58
+ self.protect_retrieve(request)
59
+ return JsonResponse({"message": f"Detail for ID {pk}"}, status=200)
60
+
61
+ def patch(self, request: Request, pk: int):
62
+ self.protect_update(request)
63
+ return JsonResponse({"message": f"Updated {pk}"}, status=200)
64
+
65
+ @endpoint(allowed_methods=['GET'])
66
+ async def items(self, request: Request):
67
+ # Pick the closest protect that matches the endoint. `GET items` is closest to a list
68
+ self.protect_list(request)
69
+ await asyncio.sleep(1)
70
+ return JsonResponse({"message": "List of items"}, status=200)
71
+ ```
72
+
73
+
74
+ ## Registering in `urls.py`
75
+
76
+ To register the viewset in your `urls.py`:
77
+
78
+ ```python
79
+ from api.views.bar import BarViewSet
80
+
81
+ urlpatterns = [
82
+ # Other URLs like admin, static, etc.
83
+
84
+ *BarViewSet().urlpatterns(),
85
+ ]
86
+ ```
87
+
88
+
89
+ ## Deeper learning
90
+
91
+ - [Custom protections](./README_CUSTOM_PROTECTIONS.md): Learn how to subclass `SmallViewSet` to add custom protections like logged-in checks.
92
+ - [Custom exception handler](./README_CUSTOM_EXCEPTION_HANDLER.md): Understand how to write your own exception handler.
93
+ - [DRF compatibility](./README_DRF_COMPATIBILITY.md): Learn how to use some of Django Rest Framework's tools, like Serializers.
94
+ - [Disabling an endpoint](./README_DISABLE_ENDPOINT.md): Learn how to disable an endpoint without needing to delete it or comment it out.
95
+ - [Reason](./README_REASON.md): Reasoning behind this package.
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "django-small-view-set"
7
- version = "0.2.6"
7
+ version = "0.2.7"
8
8
  description = "A lightweight Django ViewSet alternative with minimal abstraction."
9
9
  readme = "README.md"
10
10
  authors = ["Nate Brooks"]
@@ -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,
@@ -0,0 +1,78 @@
1
+ import json
2
+ import logging
3
+ from urllib.request import Request
4
+
5
+ from .exceptions import BadRequest
6
+
7
+ logger = logging.getLogger('app')
8
+
9
+ class SmallViewSet:
10
+ def parse_json_body(self, request: Request):
11
+ if request.content_type != 'application/json':
12
+ raise BadRequest('Invalid content type')
13
+ return json.loads(request.body)
14
+
15
+ def protect_create(self, request: Request):
16
+ """
17
+ Stub for adding any custom business logic to protect the create method.
18
+ For example:
19
+ - Check if the user is authenticated
20
+ - Check if the user has validated their email
21
+ - Throttle requests
22
+
23
+ Recommended to call super().protect_create(request) in the subclass in case
24
+ this library adds logic in the future.
25
+ """
26
+ pass
27
+
28
+ def protect_list(self, request: Request):
29
+ """
30
+ Stub for adding any custom business logic to protect the list method.
31
+ For example:
32
+ - Check if the user is authenticated
33
+ - Check if the user has validated their email
34
+ - Throttle requests
35
+
36
+ Recommended to call super().protect_create(request) in the subclass in case
37
+ this library adds logic in the future.
38
+ """
39
+ pass
40
+
41
+ def protect_retrieve(self, request: Request):
42
+ """
43
+ Stub for adding any custom business logic to protect the retrieve method.
44
+ For example:
45
+ - Check if the user is authenticated
46
+ - Check if the user has validated their email
47
+ - Throttle requests
48
+
49
+ Recommended to call super().protect_create(request) in the subclass in case
50
+ this library adds logic in the future.
51
+ """
52
+ pass
53
+
54
+ def protect_update(self, request: Request):
55
+ """
56
+ Stub for adding any custom business logic to protect the update method.
57
+ For example:
58
+ - Check if the user is authenticated
59
+ - Check if the user has validated their email
60
+ - Throttle requests
61
+
62
+ Recommended to call super().protect_create(request) in the subclass in case
63
+ this library adds logic in the future.
64
+ """
65
+ pass
66
+
67
+ def protect_delete(self, request: Request):
68
+ """
69
+ Stub for adding any custom business logic to protect the delete method.
70
+ For example:
71
+ - Check if the user is authenticated
72
+ - Check if the user has validated their email
73
+ - Throttle requests
74
+
75
+ Recommended to call super().protect_create(request) in the subclass in case
76
+ this library adds logic in the future.
77
+ """
78
+ pass
@@ -1,83 +0,0 @@
1
- # Django Small View Set
2
-
3
- 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.
4
-
5
- ## Getting Started with Django Small View Set
6
-
7
- This guide provides a simple example to get started with the library.
8
-
9
- ### Example Usage
10
-
11
- Here’s how to define a basic view set:
12
-
13
- In settings.py
14
- ```python
15
- # Register SmallViewSetConfig in settings
16
- from small_view_set SmallViewSetConfig
17
-
18
- SMALL_VIEW_SET_CONFIG = SmallViewSetConfig()
19
- ```
20
-
21
-
22
- ```python
23
- import asyncio
24
- from django.http import JsonResponse
25
- from django.urls import path
26
- from small_view_set import SmallViewSet, endpoint, endpoint_disabled
27
-
28
- class BarViewSet(SmallViewSet):
29
-
30
- def urlpatterns(self):
31
- return [
32
- path('api/bars/', self.default_router, name='bars_collection'),
33
- path('api/bars/items/', self.items, name='bars_items'),
34
- path('api/bars/<int:pk>/', self.default_router, name='bars_detail'),
35
- ]
36
-
37
- @endpoint(allowed_methods=['GET'])
38
- def list(self, request):
39
- self.protect_list(request)
40
- return JsonResponse({"message": "Hello, world!"}, status=200)
41
-
42
- @endpoint(allowed_methods=['GET'])
43
- @endpoint_disabled
44
- async def items(self, request):
45
- self.protect_list(request)
46
- await asyncio.sleep(1)
47
- return JsonResponse({"message": "List of items"}, status=200)
48
-
49
- @endpoint(allowed_methods=['PATCH'])
50
- def patch(self, request, pk):
51
- self.protect_update(request)
52
- return JsonResponse({"message": f"Updated {pk}"}, status=200)
53
-
54
- @endpoint(allowed_methods=['GET'])
55
- async def retrieve(self, request, pk):
56
- self.protect_retrieve(request)
57
- return JsonResponse({"message": f"Detail for ID {pk}"}, status=200)
58
- ```
59
-
60
-
61
- ## Registering in `urls.py`
62
-
63
- To register the viewset in your `urls.py`:
64
-
65
- ```python
66
- from api.views.bar import BarViewSet
67
-
68
- urlpatterns = [
69
- # Other URLs like admin, static, etc.
70
-
71
- *BarViewSet().urlpatterns(),
72
- ]
73
- ```
74
-
75
-
76
- ## Documentation
77
-
78
- - [Custom Endpoints](./README_CUSTOM_ENDPOINT.md): Learn how to define custom endpoints alongside the default router.
79
- - [Handling Endpoint Exceptions](./README_HANDLE_ENDPOINT_EXCEPTIONS.md): Understand how to write your own decorators for exception handling.
80
- - [Custom Protections](./README_CUSTOM_PROTECTIONS.md): Learn how to subclass `SmallViewSet` to add custom protections like logged-in checks.
81
- - [DRF Compatibility](./README_DRF_COMPATIBILITY.md): Learn how to use some of Django Rest Framework's tools, like Serializers.
82
- - [Disabling an endpoint](./README_DISABLE_ENDPOINT.md): Learn how to disable an endpoint without needing to delete it or comment it out.
83
- - [Reason](./README_REASON.md): Reasoning behind this package.
@@ -1,232 +0,0 @@
1
- import inspect
2
- import json
3
- import logging
4
- from urllib.request import Request
5
-
6
-
7
- from .decorators import endpoint
8
- from .exceptions import BadRequest, MethodNotAllowed
9
-
10
- logger = logging.getLogger('app')
11
-
12
- class SmallViewSet:
13
- def parse_json_body(self, request: Request):
14
- if request.content_type != 'application/json':
15
- raise BadRequest('Invalid content type')
16
- return json.loads(request.body)
17
-
18
- def protect_create(self, request: Request):
19
- """
20
- Stub for adding any custom business logic to protect the create method.
21
- For example:
22
- - Check if the user is authenticated
23
- - Check if the user has validated their email
24
- - Throttle requests
25
-
26
- Recommended to call super().protect_create(request) in the subclass in case
27
- this library adds logic in the future.
28
- """
29
- pass
30
-
31
- def protect_list(self, request: Request):
32
- """
33
- Stub for adding any custom business logic to protect the list method.
34
- For example:
35
- - Check if the user is authenticated
36
- - Check if the user has validated their email
37
- - Throttle requests
38
-
39
- Recommended to call super().protect_create(request) in the subclass in case
40
- this library adds logic in the future.
41
- """
42
- pass
43
-
44
- def protect_retrieve(self, request: Request):
45
- """
46
- Stub for adding any custom business logic to protect the retrieve method.
47
- For example:
48
- - Check if the user is authenticated
49
- - Check if the user has validated their email
50
- - Throttle requests
51
-
52
- Recommended to call super().protect_create(request) in the subclass in case
53
- this library adds logic in the future.
54
- """
55
- pass
56
-
57
- def protect_update(self, request: Request):
58
- """
59
- Stub for adding any custom business logic to protect the update method.
60
- For example:
61
- - Check if the user is authenticated
62
- - Check if the user has validated their email
63
- - Throttle requests
64
-
65
- Recommended to call super().protect_create(request) in the subclass in case
66
- this library adds logic in the future.
67
- """
68
- pass
69
-
70
- def protect_delete(self, request: Request):
71
- """
72
- Stub for adding any custom business logic to protect the delete method.
73
- For example:
74
- - Check if the user is authenticated
75
- - Check if the user has validated their email
76
- - Throttle requests
77
-
78
- Recommended to call super().protect_create(request) in the subclass in case
79
- this library adds logic in the future.
80
- """
81
- 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)