drf-restflow 1.0.0__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.
Files changed (183) hide show
  1. drf_restflow-1.0.0/LICENSE +13 -0
  2. drf_restflow-1.0.0/MANIFEST.in +5 -0
  3. drf_restflow-1.0.0/PKG-INFO +649 -0
  4. drf_restflow-1.0.0/README.md +590 -0
  5. drf_restflow-1.0.0/drf_restflow.egg-info/PKG-INFO +649 -0
  6. drf_restflow-1.0.0/drf_restflow.egg-info/SOURCES.txt +181 -0
  7. drf_restflow-1.0.0/drf_restflow.egg-info/dependency_links.txt +1 -0
  8. drf_restflow-1.0.0/drf_restflow.egg-info/requires.txt +31 -0
  9. drf_restflow-1.0.0/drf_restflow.egg-info/top_level.txt +1 -0
  10. drf_restflow-1.0.0/pyproject.toml +279 -0
  11. drf_restflow-1.0.0/restflow/__init__.py +8 -0
  12. drf_restflow-1.0.0/restflow/authentication/__init__.py +50 -0
  13. drf_restflow-1.0.0/restflow/authentication/apps.py +12 -0
  14. drf_restflow-1.0.0/restflow/authentication/authentication.py +147 -0
  15. drf_restflow-1.0.0/restflow/authentication/jwt.py +609 -0
  16. drf_restflow-1.0.0/restflow/authentication/migrations/0001_initial.py +36 -0
  17. drf_restflow-1.0.0/restflow/authentication/migrations/__init__.py +0 -0
  18. drf_restflow-1.0.0/restflow/authentication/models.py +39 -0
  19. drf_restflow-1.0.0/restflow/authentication/serializers.py +20 -0
  20. drf_restflow-1.0.0/restflow/authentication/simplejwt.py +76 -0
  21. drf_restflow-1.0.0/restflow/authentication/views.py +115 -0
  22. drf_restflow-1.0.0/restflow/caching/__init__.py +64 -0
  23. drf_restflow-1.0.0/restflow/caching/apps.py +16 -0
  24. drf_restflow-1.0.0/restflow/caching/constants.py +33 -0
  25. drf_restflow-1.0.0/restflow/caching/dispatchers/__init__.py +110 -0
  26. drf_restflow-1.0.0/restflow/caching/dispatchers/asyncio.py +56 -0
  27. drf_restflow-1.0.0/restflow/caching/dispatchers/base.py +70 -0
  28. drf_restflow-1.0.0/restflow/caching/dispatchers/celery.py +88 -0
  29. drf_restflow-1.0.0/restflow/caching/dispatchers/django_q.py +81 -0
  30. drf_restflow-1.0.0/restflow/caching/dispatchers/django_rq.py +74 -0
  31. drf_restflow-1.0.0/restflow/caching/dispatchers/dramatiq.py +92 -0
  32. drf_restflow-1.0.0/restflow/caching/dispatchers/inline.py +31 -0
  33. drf_restflow-1.0.0/restflow/caching/dispatchers/threadpool.py +81 -0
  34. drf_restflow-1.0.0/restflow/caching/hashing.py +13 -0
  35. drf_restflow-1.0.0/restflow/caching/key_constructor.py +363 -0
  36. drf_restflow-1.0.0/restflow/caching/key_fields.py +499 -0
  37. drf_restflow-1.0.0/restflow/caching/registry.py +660 -0
  38. drf_restflow-1.0.0/restflow/caching/rules.py +103 -0
  39. drf_restflow-1.0.0/restflow/caching/tasks.py +103 -0
  40. drf_restflow-1.0.0/restflow/caching/wrapper.py +487 -0
  41. drf_restflow-1.0.0/restflow/exceptions.py +232 -0
  42. drf_restflow-1.0.0/restflow/filters/__init__.py +28 -0
  43. drf_restflow-1.0.0/restflow/filters/backends.py +81 -0
  44. drf_restflow-1.0.0/restflow/filters/fields.py +873 -0
  45. drf_restflow-1.0.0/restflow/filters/filters.py +949 -0
  46. drf_restflow-1.0.0/restflow/helpers.py +208 -0
  47. drf_restflow-1.0.0/restflow/pagination/__init__.py +15 -0
  48. drf_restflow-1.0.0/restflow/pagination/pagination.py +219 -0
  49. drf_restflow-1.0.0/restflow/permissions/__init__.py +34 -0
  50. drf_restflow-1.0.0/restflow/permissions/permissions.py +225 -0
  51. drf_restflow-1.0.0/restflow/responses/__init__.py +11 -0
  52. drf_restflow-1.0.0/restflow/responses/streaming.py +97 -0
  53. drf_restflow-1.0.0/restflow/serializers/__init__.py +28 -0
  54. drf_restflow-1.0.0/restflow/serializers/fields.py +94 -0
  55. drf_restflow-1.0.0/restflow/serializers/serializers.py +516 -0
  56. drf_restflow-1.0.0/restflow/serializers/validated_data.py +94 -0
  57. drf_restflow-1.0.0/restflow/settings.py +169 -0
  58. drf_restflow-1.0.0/restflow/spectacular/__init__.py +30 -0
  59. drf_restflow-1.0.0/restflow/spectacular/extensions.py +0 -0
  60. drf_restflow-1.0.0/restflow/spectacular/hooks.py +49 -0
  61. drf_restflow-1.0.0/restflow/spectacular/parameters.py +287 -0
  62. drf_restflow-1.0.0/restflow/spectacular/schema.py +162 -0
  63. drf_restflow-1.0.0/restflow/tasks.py +6 -0
  64. drf_restflow-1.0.0/restflow/test/__init__.py +21 -0
  65. drf_restflow-1.0.0/restflow/test/client.py +227 -0
  66. drf_restflow-1.0.0/restflow/test/testcases.py +27 -0
  67. drf_restflow-1.0.0/restflow/throttling/__init__.py +17 -0
  68. drf_restflow-1.0.0/restflow/throttling/throttling.py +73 -0
  69. drf_restflow-1.0.0/restflow/views/__init__.py +72 -0
  70. drf_restflow-1.0.0/restflow/views/generics.py +194 -0
  71. drf_restflow-1.0.0/restflow/views/mixins.py +257 -0
  72. drf_restflow-1.0.0/restflow/views/post_fetch.py +203 -0
  73. drf_restflow-1.0.0/restflow/views/views.py +453 -0
  74. drf_restflow-1.0.0/restflow/views/viewsets.py +374 -0
  75. drf_restflow-1.0.0/setup.cfg +4 -0
  76. drf_restflow-1.0.0/tests/__init__.py +0 -0
  77. drf_restflow-1.0.0/tests/conftest.py +143 -0
  78. drf_restflow-1.0.0/tests/integration/__init__.py +0 -0
  79. drf_restflow-1.0.0/tests/integration/api/__init__.py +0 -0
  80. drf_restflow-1.0.0/tests/integration/api/conftest.py +16 -0
  81. drf_restflow-1.0.0/tests/integration/api/test_async_crud.py +296 -0
  82. drf_restflow-1.0.0/tests/integration/api/test_authentication_matrix.py +280 -0
  83. drf_restflow-1.0.0/tests/integration/api/test_caching_flow.py +181 -0
  84. drf_restflow-1.0.0/tests/integration/api/test_coverage_fillins.py +233 -0
  85. drf_restflow-1.0.0/tests/integration/api/test_filter_pagination_flow.py +236 -0
  86. drf_restflow-1.0.0/tests/integration/api/test_handler_modes.py +668 -0
  87. drf_restflow-1.0.0/tests/integration/api/test_jwt_auth_flow.py +298 -0
  88. drf_restflow-1.0.0/tests/integration/api/test_pagination_matrix.py +215 -0
  89. drf_restflow-1.0.0/tests/integration/api/test_request_response_serializer_matrix.py +346 -0
  90. drf_restflow-1.0.0/tests/integration/api/test_response_headers.py +168 -0
  91. drf_restflow-1.0.0/tests/integration/api/test_response_matrix.py +215 -0
  92. drf_restflow-1.0.0/tests/integration/api/test_streaming_flow.py +152 -0
  93. drf_restflow-1.0.0/tests/integration/api/test_sync_crud.py +204 -0
  94. drf_restflow-1.0.0/tests/integration/api/test_sync_viewset_actions.py +353 -0
  95. drf_restflow-1.0.0/tests/integration/api/test_sync_viewset_helpers.py +236 -0
  96. drf_restflow-1.0.0/tests/integration/api/test_throttle_matrix.py +230 -0
  97. drf_restflow-1.0.0/tests/integration/api/test_throttling_flow.py +187 -0
  98. drf_restflow-1.0.0/tests/integration/api/test_viewset_actions_flow.py +291 -0
  99. drf_restflow-1.0.0/tests/integration/authentication/__init__.py +0 -0
  100. drf_restflow-1.0.0/tests/integration/authentication/test_async.py +305 -0
  101. drf_restflow-1.0.0/tests/integration/authentication/test_blacklist_backends.py +206 -0
  102. drf_restflow-1.0.0/tests/integration/authentication/test_edge_cases.py +469 -0
  103. drf_restflow-1.0.0/tests/integration/authentication/test_jwt.py +301 -0
  104. drf_restflow-1.0.0/tests/integration/authentication/test_jwt_security.py +114 -0
  105. drf_restflow-1.0.0/tests/integration/authentication/test_jwt_views.py +186 -0
  106. drf_restflow-1.0.0/tests/integration/authentication/test_simplejwt.py +110 -0
  107. drf_restflow-1.0.0/tests/integration/caching/__init__.py +0 -0
  108. drf_restflow-1.0.0/tests/integration/caching/dispatchers/__init__.py +0 -0
  109. drf_restflow-1.0.0/tests/integration/caching/dispatchers/conftest.py +10 -0
  110. drf_restflow-1.0.0/tests/integration/caching/dispatchers/test_asyncio.py +155 -0
  111. drf_restflow-1.0.0/tests/integration/caching/dispatchers/test_celery.py +278 -0
  112. drf_restflow-1.0.0/tests/integration/caching/dispatchers/test_django_q.py +137 -0
  113. drf_restflow-1.0.0/tests/integration/caching/dispatchers/test_django_rq.py +125 -0
  114. drf_restflow-1.0.0/tests/integration/caching/dispatchers/test_dramatiq.py +158 -0
  115. drf_restflow-1.0.0/tests/integration/caching/dispatchers/test_inline.py +149 -0
  116. drf_restflow-1.0.0/tests/integration/caching/dispatchers/test_threadpool.py +164 -0
  117. drf_restflow-1.0.0/tests/integration/caching/test_apply_rules.py +447 -0
  118. drf_restflow-1.0.0/tests/integration/caching/test_async.py +409 -0
  119. drf_restflow-1.0.0/tests/integration/caching/test_cache_register.py +1316 -0
  120. drf_restflow-1.0.0/tests/integration/caching/test_decorator_and_keys.py +353 -0
  121. drf_restflow-1.0.0/tests/integration/caching/test_invalidator.py +498 -0
  122. drf_restflow-1.0.0/tests/integration/caching/test_redis.py +174 -0
  123. drf_restflow-1.0.0/tests/integration/exceptions/__init__.py +0 -0
  124. drf_restflow-1.0.0/tests/integration/exceptions/test_envelope.py +217 -0
  125. drf_restflow-1.0.0/tests/integration/exceptions/test_handler.py +298 -0
  126. drf_restflow-1.0.0/tests/integration/filters/__init__.py +0 -0
  127. drf_restflow-1.0.0/tests/integration/filters/test_async.py +303 -0
  128. drf_restflow-1.0.0/tests/integration/filters/test_backend.py +445 -0
  129. drf_restflow-1.0.0/tests/integration/filters/test_django_fields.py +730 -0
  130. drf_restflow-1.0.0/tests/integration/filters/test_field_application.py +208 -0
  131. drf_restflow-1.0.0/tests/integration/filters/test_field_combinations.py +236 -0
  132. drf_restflow-1.0.0/tests/integration/filters/test_filters.py +989 -0
  133. drf_restflow-1.0.0/tests/integration/filters/test_postgres.py +388 -0
  134. drf_restflow-1.0.0/tests/integration/pagination/__init__.py +0 -0
  135. drf_restflow-1.0.0/tests/integration/pagination/test_async.py +426 -0
  136. drf_restflow-1.0.0/tests/integration/pagination/test_edge_cases.py +236 -0
  137. drf_restflow-1.0.0/tests/integration/permissions/__init__.py +0 -0
  138. drf_restflow-1.0.0/tests/integration/permissions/test_async.py +209 -0
  139. drf_restflow-1.0.0/tests/integration/permissions/test_combinators.py +214 -0
  140. drf_restflow-1.0.0/tests/integration/responses/__init__.py +0 -0
  141. drf_restflow-1.0.0/tests/integration/responses/test_encoding.py +165 -0
  142. drf_restflow-1.0.0/tests/integration/responses/test_streaming.py +158 -0
  143. drf_restflow-1.0.0/tests/integration/serializers/__init__.py +0 -0
  144. drf_restflow-1.0.0/tests/integration/serializers/test_async.py +340 -0
  145. drf_restflow-1.0.0/tests/integration/serializers/test_field_combinations.py +726 -0
  146. drf_restflow-1.0.0/tests/integration/serializers/test_field_kwargs.py +635 -0
  147. drf_restflow-1.0.0/tests/integration/serializers/test_hyperlinked.py +95 -0
  148. drf_restflow-1.0.0/tests/integration/serializers/test_serializers.py +542 -0
  149. drf_restflow-1.0.0/tests/integration/serializers/test_validated_data.py +468 -0
  150. drf_restflow-1.0.0/tests/integration/spectacular/__init__.py +0 -0
  151. drf_restflow-1.0.0/tests/integration/spectacular/test_filterset_parameters.py +274 -0
  152. drf_restflow-1.0.0/tests/integration/spectacular/test_schema.py +698 -0
  153. drf_restflow-1.0.0/tests/integration/test/__init__.py +0 -0
  154. drf_restflow-1.0.0/tests/integration/test/test_client.py +351 -0
  155. drf_restflow-1.0.0/tests/integration/test_apps.py +68 -0
  156. drf_restflow-1.0.0/tests/integration/throttling/__init__.py +0 -0
  157. drf_restflow-1.0.0/tests/integration/throttling/test_async.py +108 -0
  158. drf_restflow-1.0.0/tests/integration/throttling/test_rate_classes.py +184 -0
  159. drf_restflow-1.0.0/tests/integration/views/__init__.py +0 -0
  160. drf_restflow-1.0.0/tests/integration/views/test_action_config.py +480 -0
  161. drf_restflow-1.0.0/tests/integration/views/test_apiview.py +238 -0
  162. drf_restflow-1.0.0/tests/integration/views/test_async_helpers.py +493 -0
  163. drf_restflow-1.0.0/tests/integration/views/test_generics.py +685 -0
  164. drf_restflow-1.0.0/tests/integration/views/test_post_fetch.py +402 -0
  165. drf_restflow-1.0.0/tests/integration/views/test_request_response_split.py +199 -0
  166. drf_restflow-1.0.0/tests/integration/views/test_round_trip.py +580 -0
  167. drf_restflow-1.0.0/tests/integration/views/test_viewset.py +312 -0
  168. drf_restflow-1.0.0/tests/models.py +61 -0
  169. drf_restflow-1.0.0/tests/smoke/__init__.py +0 -0
  170. drf_restflow-1.0.0/tests/smoke/test_smoke.py +307 -0
  171. drf_restflow-1.0.0/tests/unit/__init__.py +0 -0
  172. drf_restflow-1.0.0/tests/unit/caching/__init__.py +0 -0
  173. drf_restflow-1.0.0/tests/unit/caching/dispatchers/__init__.py +0 -0
  174. drf_restflow-1.0.0/tests/unit/caching/dispatchers/test_base.py +213 -0
  175. drf_restflow-1.0.0/tests/unit/caching/test_caching.py +1284 -0
  176. drf_restflow-1.0.0/tests/unit/caching/test_settings.py +219 -0
  177. drf_restflow-1.0.0/tests/unit/filters/__init__.py +0 -0
  178. drf_restflow-1.0.0/tests/unit/filters/test_fields.py +721 -0
  179. drf_restflow-1.0.0/tests/unit/serializers/__init__.py +0 -0
  180. drf_restflow-1.0.0/tests/unit/serializers/test_fields.py +180 -0
  181. drf_restflow-1.0.0/tests/unit/test_helpers.py +63 -0
  182. drf_restflow-1.0.0/tests/unit/views/__init__.py +0 -0
  183. drf_restflow-1.0.0/tests/unit/views/test_async_dispatch.py +88 -0
@@ -0,0 +1,13 @@
1
+ License
2
+
3
+ Copyright © 2025-present, Khan Asfi Reza. All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6
+
7
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8
+
9
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
10
+
11
+ Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,5 @@
1
+ include README.md
2
+ include LICENSE.md
3
+ recursive-include tests/ *
4
+ global-exclude __pycache__
5
+ global-exclude *.py[co]
@@ -0,0 +1,649 @@
1
+ Metadata-Version: 2.4
2
+ Name: drf-restflow
3
+ Version: 1.0.0
4
+ Summary: A Declarative library for Django REST Framework
5
+ Author-email: Khan Asfi Reza <mail@khanasfireza.dev>
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/khan-asfi-reza/drf-restflow
8
+ Project-URL: Repository, https://github.com/khan-asfi-reza/drf-restflow
9
+ Project-URL: Issues, https://github.com/khan-asfi-reza/drf-restflow/issues
10
+ Project-URL: Changelog, https://github.com/khan-asfi-reza/drf-restflow/releases
11
+ Keywords: django,django-rest-framework,drf,filters,queryset,declarative,type-safe,fastapi,rest-api
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Web Environment
14
+ Classifier: Framework :: Django
15
+ Classifier: Framework :: Django :: 3.2
16
+ Classifier: Framework :: Django :: 4.0
17
+ Classifier: Framework :: Django :: 4.1
18
+ Classifier: Framework :: Django :: 4.2
19
+ Classifier: Framework :: Django :: 5.0
20
+ Classifier: Framework :: Django :: 6.0
21
+ Classifier: Intended Audience :: Developers
22
+ Classifier: Operating System :: OS Independent
23
+ Classifier: Programming Language :: Python
24
+ Classifier: Programming Language :: Python :: 3
25
+ Classifier: Programming Language :: Python :: 3.10
26
+ Classifier: Programming Language :: Python :: 3.11
27
+ Classifier: Programming Language :: Python :: 3.12
28
+ Classifier: Programming Language :: Python :: 3.13
29
+ Classifier: Programming Language :: Python :: 3.14
30
+ Classifier: Topic :: Internet :: WWW/HTTP
31
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
32
+ Classifier: Typing :: Typed
33
+ Requires-Python: >=3.10
34
+ Description-Content-Type: text/markdown
35
+ License-File: LICENSE
36
+ Requires-Dist: django>=3.2
37
+ Requires-Dist: djangorestframework>=3.14
38
+ Requires-Dist: pyjwt>=2.8
39
+ Requires-Dist: typing-extensions>=4.15.0
40
+ Provides-Extra: celery
41
+ Requires-Dist: celery>=5.0; extra == "celery"
42
+ Provides-Extra: django-q
43
+ Requires-Dist: django-q2>=1.7; extra == "django-q"
44
+ Provides-Extra: django-rq
45
+ Requires-Dist: django-rq>=2.10; extra == "django-rq"
46
+ Provides-Extra: dramatiq
47
+ Requires-Dist: dramatiq>=1.16; extra == "dramatiq"
48
+ Provides-Extra: postgres
49
+ Requires-Dist: psycopg2-binary>=2.9.10; extra == "postgres"
50
+ Provides-Extra: postgres-psycopg3
51
+ Requires-Dist: psycopg[binary]>=3.1; extra == "postgres-psycopg3"
52
+ Provides-Extra: redis
53
+ Requires-Dist: django-redis>=5.4.0; extra == "redis"
54
+ Provides-Extra: simplejwt
55
+ Requires-Dist: djangorestframework-simplejwt>=5.3; extra == "simplejwt"
56
+ Provides-Extra: spectacular
57
+ Requires-Dist: drf-spectacular>=0.27; extra == "spectacular"
58
+ Dynamic: license-file
59
+
60
+ # Restflow
61
+
62
+ A declarative library on top of Django REST Framework. It uses DRF's
63
+ serializer and validation infrastructure and adds declarative classes for
64
+ the parts of an API that turn into boilerplate over time.
65
+
66
+ The library covers caching, filtering, type-driven serializers, async
67
+ authentication, async permissions, a full async view and viewset stack,
68
+ async pagination, async throttling, streaming responses, a unified
69
+ exception handler, OpenAPI schema generation, and an async test client
70
+ and case suite.
71
+
72
+ Inspired by [FastAPI](https://fastapi.tiangolo.com/) and
73
+ [django-filter](https://django-filter.readthedocs.io). Works alongside
74
+ DRF rather than replacing it.
75
+
76
+ Full documentation:
77
+ [https://restflow.khanasfireza.dev/](https://restflow.khanasfireza.dev/)
78
+
79
+ ## Table of Contents
80
+
81
+ - [Motivation](#motivation)
82
+ - [Installation](#installation)
83
+ - [Caching](#caching)
84
+ - [Filtering](#filtering)
85
+ - [Serializers](#serializers)
86
+ - [Authentication](#authentication)
87
+ - [Permissions](#permissions)
88
+ - [Views](#views)
89
+ - [Pagination](#pagination)
90
+ - [Throttling](#throttling)
91
+ - [Responses](#responses)
92
+ - [Exception handler](#exception-handler)
93
+ - [Spectacular](#spectacular)
94
+ - [Testing](#testing)
95
+ - [Contributing](#contributing)
96
+ - [License](#license)
97
+
98
+ ## Motivation
99
+
100
+ Hi, I am Khan, the author of drf-restflow. This library was born from the
101
+ realities of building APIs in a fast-moving startup environment. Most of
102
+ my work involved large database tables, constantly evolving product
103
+ requirements, and the challenge of exposing clean, reliable REST APIs
104
+ while making sure new developers could onboard quickly and understand the
105
+ codebase and business logic as early as possible.
106
+
107
+ I started with django-filter, which is an excellent and very mature tool.
108
+ But as our product grew (and pivoted repeatedly), the FilterSets became
109
+ harder to maintain. They were getting long, repetitive, and full of
110
+ boilerplate. Some might say this was a skill issue, and honestly, I
111
+ agree. But the truth is, I am a lazy developer. I like writing less
112
+ code. I like being fast. I like tools that let me declare what I want
113
+ instead of wiring everything by hand. Over time, I built small internal
114
+ utilities to reduce repetition and make filtering easier. Those tools
115
+ worked well, so I compiled them into a proper library so I could reuse
116
+ them across projects.
117
+
118
+ The caching layer comes from the same instinct, applied to a different
119
+ problem. In production the part of caching that goes wrong is rarely the
120
+ read or the write; it is the cache-key construction and the
121
+ invalidation. So drf-restflow models the cache key as a declarative
122
+ class made of small, composable fields, and models invalidation as
123
+ rules attached to Django model signals. The function and the rule sit
124
+ side by side in the same file, which makes it much easier to keep them
125
+ in sync as the schema changes.
126
+
127
+ Many of the early internal utilities were built from scratch, which
128
+ brought some inconsistency. Instead of reinventing the wheel everywhere,
129
+ I leaned on what is already battle-tested and borrowed ideas from
130
+ different libraries, including FastAPI, django-filter, and django-ninja.
131
+ That is how drf-restflow took its current shape: a library that does not
132
+ replace Django REST Framework but extends it with declarative classes for
133
+ the parts of an API that turn into boilerplate. There are likely other
134
+ libraries that promise similar things or do more, and feedback,
135
+ contributions, and constructive criticism are very welcome.
136
+
137
+ ## Installation
138
+
139
+ ```bash
140
+ pip install drf-restflow
141
+ ```
142
+
143
+ ```bash
144
+ uv add drf-restflow
145
+ ```
146
+
147
+ Restflow ships two Django apps:
148
+
149
+ - `restflow.caching` -- registers post-save and post-delete signal
150
+ handlers that drive cache invalidation. Required for any project that
151
+ uses `@cache_result` with `invalidates_on=[...]`.
152
+ - `restflow.authentication` -- ships the `BlacklistedToken` model used
153
+ by `ModelBlacklistBackend`. Required only when revoking JWTs through
154
+ the model-backed blacklist.
155
+
156
+ ```python
157
+ # settings.py
158
+ INSTALLED_APPS = [
159
+ "django.contrib.contenttypes",
160
+ "django.contrib.auth",
161
+ "rest_framework",
162
+ "restflow.caching",
163
+ "restflow.authentication",
164
+ ]
165
+ ```
166
+
167
+ The top-level `restflow` import is a regular Python package and does
168
+ not need to appear in `INSTALLED_APPS`.
169
+
170
+ ### Requirements
171
+
172
+ - Python 3.10 or higher
173
+ - Django 3.2 or higher
174
+ - Django REST Framework 3.14 or higher
175
+ - PyJWT 2.8 or higher (installed automatically; powers the built-in
176
+ JWT authentication)
177
+
178
+ PostgreSQL is optional and is only required for the postgres-specific
179
+ filtering features (full-text search, array fields, trigram similarity,
180
+ range fields).
181
+
182
+ ### Optional extras
183
+
184
+ | Extra | Use case | pip | uv |
185
+ | --- | --- | --- | --- |
186
+ | `redis` | Cache backend that supports prefix-based invalidation | `pip install drf-restflow[redis]` | `uv add 'drf-restflow[redis]'` |
187
+ | `celery` | Run cache invalidation as celery tasks | `pip install drf-restflow[celery]` | `uv add 'drf-restflow[celery]'` |
188
+ | `django-rq` | Run cache invalidation through django-rq | `pip install drf-restflow[django-rq]` | `uv add 'drf-restflow[django-rq]'` |
189
+ | `django-q` | Run cache invalidation through django-q2 | `pip install drf-restflow[django-q]` | `uv add 'drf-restflow[django-q]'` |
190
+ | `dramatiq` | Run cache invalidation through dramatiq | `pip install drf-restflow[dramatiq]` | `uv add 'drf-restflow[dramatiq]'` |
191
+ | `postgres` | psycopg2 driver for PostgreSQL filtering features | `pip install drf-restflow[postgres]` | `uv add 'drf-restflow[postgres]'` |
192
+ | `postgres-psycopg3` | psycopg3 driver for PostgreSQL filtering features | `pip install drf-restflow[postgres-psycopg3]` | `uv add 'drf-restflow[postgres-psycopg3]'` |
193
+ | `simplejwt` | Adapter for djangorestframework-simplejwt | `pip install drf-restflow[simplejwt]` | `uv add 'drf-restflow[simplejwt]'` |
194
+ | `spectacular` | OpenAPI schema generation through drf-spectacular | `pip install drf-restflow[spectacular]` | `uv add 'drf-restflow[spectacular]'` |
195
+
196
+ ## Caching
197
+
198
+ The caching layer plugs into Django's cache framework and works with any
199
+ configured backend.
200
+
201
+ A small set of features only works on a redis-compatible backend:
202
+ `delete_by_prefix()`, `invalidate_all()`, and any `InvalidationRule`
203
+ that needs to wipe a partition rather than a single key. Anything that
204
+ relies on `delete_pattern` falls into this group. Without a
205
+ redis-compatible backend, those calls raise; the rest of the caching API
206
+ keeps working on Django's local-memory or database cache. Real-world
207
+ projects usually want partition wipes, so the recommended setup is
208
+ django-redis backed by redis (valkey, keydb, and dragonfly all work as
209
+ drop-in replacements).
210
+
211
+ ```bash
212
+ pip install drf-restflow[redis]
213
+ ```
214
+
215
+ ```bash
216
+ uv add 'drf-restflow[redis]'
217
+ ```
218
+
219
+ ```python
220
+ # settings.py
221
+ CACHES = {
222
+ "default": {
223
+ "BACKEND": "django_redis.cache.RedisCache",
224
+ "LOCATION": "redis://127.0.0.1:6379/1",
225
+ "OPTIONS": {
226
+ "CLIENT_CLASS": "django_redis.client.DefaultClient",
227
+ },
228
+ },
229
+ }
230
+ ```
231
+
232
+ A `KeyConstructor` describes how to build a cache key from a function
233
+ call. Each attribute is a field that pulls a piece of data out of the
234
+ call and stringifies it deterministically. `@cache_result` wraps the
235
+ function in a `CachedWrapper` and registers `InvalidationRule` objects
236
+ against Django model signals.
237
+
238
+ ```python
239
+ from django.contrib.auth import get_user_model
240
+ from restflow.caching import (
241
+ KeyConstructor, ArgsKeyField, ConstantKeyField, QueryParamsKeyField,
242
+ cache_result, InvalidationRule,
243
+ )
244
+
245
+ User = get_user_model()
246
+
247
+
248
+ class UserKey(KeyConstructor):
249
+ user = ArgsKeyField("user_id", partition=True)
250
+ version = ConstantKeyField("v", "1")
251
+ page = QueryParamsKeyField(["page", "size"])
252
+
253
+ class Meta:
254
+ namespace = "users"
255
+
256
+
257
+ @cache_result(
258
+ key_constructor=UserKey,
259
+ ttl=300,
260
+ invalidates_on=[
261
+ InvalidationRule(
262
+ model=User,
263
+ field_mapping={"user_id": "id"},
264
+ watch_fields=["email"],
265
+ rewarm=True,
266
+ ),
267
+ ],
268
+ )
269
+ def get_user_payload(user_id: int, request=None):
270
+ return expensive_lookup(user_id)
271
+ ```
272
+
273
+ The wrapped function exposes `get_with_metadata`, `refresh`,
274
+ `bypass_cache`, `delete_cache`, `delete_by_prefix`, `invalidate_all`,
275
+ and the matching `a`-prefixed async methods.
276
+
277
+ See the [Caching guide](https://restflow.khanasfireza.dev/guide/caching/)
278
+ for the full API.
279
+
280
+ ## Filtering
281
+
282
+ `FilterSet` validates query parameters and applies filters to a Django
283
+ queryset. Fields can be declared with type annotations, explicit field
284
+ classes, model-based generation, or any mix of those.
285
+
286
+ ```python
287
+ from restflow.filters import (
288
+ FilterSet, StringField, IntegerField, BooleanField,
289
+ )
290
+
291
+
292
+ class ProductFilterSet(FilterSet):
293
+ name = StringField(lookups=["icontains"])
294
+ price = IntegerField(lookups=["comparison"])
295
+ category: str
296
+ in_stock: bool
297
+
298
+ class Meta:
299
+ model = Product
300
+ order_fields = [
301
+ ("price", "price"),
302
+ ("name", "name"),
303
+ ("created_at", "created_at"),
304
+ ]
305
+ ```
306
+
307
+ This declaration generates the following query parameters automatically:
308
+
309
+ - `name`, `name__icontains` and the negation variants `name!`,
310
+ `name__icontains!`.
311
+ - `price`, `price__gt`, `price__gte`, `price__lt`, `price__lte` and
312
+ their negation variants.
313
+ - `category`, `category!`, `in_stock`, `in_stock!`.
314
+ - `order_by` accepting `price`, `-price`, `name`, `-name`,
315
+ `created_at`, `-created_at`, or comma-separated combinations.
316
+
317
+ `RestflowFilterBackend` plugs the FilterSet into DRF's filter pipeline
318
+ and emits OpenAPI parameters for every declared field.
319
+
320
+ ```python
321
+ from rest_framework import generics
322
+ from restflow.filters import RestflowFilterBackend
323
+
324
+
325
+ class ProductView(generics.ListAPIView):
326
+ queryset = Product.objects.all()
327
+ serializer_class = ProductSerializer
328
+ filter_backends = [RestflowFilterBackend]
329
+ filterset_class = ProductFilterSet
330
+ ```
331
+
332
+ See the [Filtering guide](https://restflow.khanasfireza.dev/guide/filtering/filterset/)
333
+ for custom methods, processors, ordering, PostgreSQL features, and the
334
+ DRF integration details.
335
+
336
+ ## Serializers
337
+
338
+ `Serializer`, `ModelSerializer`, and `HyperlinkedModelSerializer`
339
+ subclasses driven by Python type annotations, plus an `InlineSerializer`
340
+ factory and an async surface (`ais_valid`, `asave`, `acreate`, `aupdate`,
341
+ `ato_internal_value`, `arun_validation`).
342
+
343
+ ```python
344
+ from restflow.serializers import (
345
+ Serializer, ModelSerializer, Field, Email,
346
+ )
347
+
348
+
349
+ class UserSerializer(Serializer):
350
+ name: str
351
+ age: int
352
+ email: Email
353
+ bio: str | None
354
+ role: str = Field(read_only=True)
355
+
356
+
357
+ class UserModelSerializer(ModelSerializer):
358
+ full_name: str
359
+
360
+ class Meta:
361
+ model = User
362
+ fields = ["id", "username"]
363
+ ```
364
+
365
+ Annotations resolve to DRF fields through `SerializerFieldMap`. Optional
366
+ types (`str | None`, `Optional[T]`) become `allow_null=True`,
367
+ `Literal[...]` becomes `ChoiceField`, `list[T]` becomes `ListField`, and
368
+ nested `Serializer` subclasses nest as expected. See the
369
+ [Serializers guide](https://restflow.khanasfireza.dev/guide/serializers/)
370
+ for the resolution rules and the async hooks.
371
+
372
+ ## Authentication
373
+
374
+ `JWTAuthentication` is a fully async JSON Web Token authenticator backed
375
+ by PyJWT. It validates signature, expiry, issuer, and audience, looks up
376
+ the user with async ORM, and consults a configurable blacklist on every
377
+ request. Built-in token obtain, refresh, and blacklist views ship as
378
+ async APIViews.
379
+
380
+ ```python
381
+ from datetime import timedelta
382
+
383
+ # settings.py
384
+ RESTFLOW_SETTINGS = {
385
+ "JWT": {
386
+ "SIGNING_KEY": "change-me-in-production",
387
+ "ACCESS_TOKEN_LIFETIME": timedelta(minutes=15),
388
+ "REFRESH_TOKEN_LIFETIME": timedelta(days=7),
389
+ },
390
+ }
391
+ ```
392
+
393
+ ```python
394
+ # urls.py
395
+ from restflow.authentication import (
396
+ TokenObtainView, TokenRefreshView, TokenBlacklistView,
397
+ )
398
+
399
+ urlpatterns = [
400
+ path("auth/token/", TokenObtainView.as_view()),
401
+ path("auth/refresh/", TokenRefreshView.as_view()),
402
+ path("auth/blacklist/", TokenBlacklistView.as_view()),
403
+ ]
404
+ ```
405
+
406
+ ```python
407
+ from restflow.authentication import JWTAuthentication
408
+ from restflow.views import AsyncListAPIView
409
+
410
+
411
+ class ProductView(AsyncListAPIView):
412
+ authentication_classes = [JWTAuthentication]
413
+ ```
414
+
415
+ Async-aware wrappers for `BasicAuthentication`, `TokenAuthentication`,
416
+ `SessionAuthentication`, and `RemoteUserAuthentication` are also
417
+ provided, plus a `SimpleJWTAuthentication` adapter for projects already
418
+ on `djangorestframework-simplejwt`. See the
419
+ [Authentication guide](https://restflow.khanasfireza.dev/guide/authentication/)
420
+ for the full configuration surface.
421
+
422
+ ## Permissions
423
+
424
+ Async-aware permission classes that compose through DRF's existing
425
+ `&`, `|`, and `~` operators (with brackets for grouping; precedence
426
+ is `~` highest, then `&`, then `|`). Restflow contributes
427
+ async-native operator classes so combinator branches resolve through
428
+ the async hook , plus async overrides on the
429
+ standard permission set so `ahas_permission` is non-blocking. Custom
430
+ permissions can implement either the sync or async hook; the dispatch
431
+ path picks the async one when present and falls back to a thread for
432
+ legacy classes.
433
+
434
+ ```python
435
+ from restflow.permissions import (
436
+ IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly,
437
+ )
438
+ from restflow.views import AsyncRetrieveUpdateDestroyAPIView
439
+
440
+
441
+ class AdminOrReadOnly(AsyncRetrieveUpdateDestroyAPIView):
442
+ permission_classes = [IsAuthenticated & (IsAdminUser | IsAuthenticatedOrReadOnly)]
443
+ ```
444
+
445
+ `AllowAny`, `IsAuthenticated`, `IsAdminUser`, `IsAuthenticatedOrReadOnly`,
446
+ `DjangoModelPermissions`, `DjangoModelPermissionsOrAnonReadOnly`, and
447
+ `DjangoObjectPermissions` ship out of the box. See the
448
+ [Permissions guide](https://restflow.khanasfireza.dev/guide/permissions/)
449
+ for the async hook contract and combinator behaviour.
450
+
451
+ ## Views
452
+
453
+ A complete async view stack: `AsyncAPIView`, eight generic views
454
+ (`AsyncListAPIView`, `AsyncCreateAPIView`, `AsyncRetrieveAPIView`,
455
+ `AsyncUpdateAPIView`, `AsyncDestroyAPIView`, plus the combined
456
+ `AsyncListCreate`, `AsyncRetrieveUpdate`, `AsyncRetrieveDestroy`, and
457
+ `AsyncRetrieveUpdateDestroy` variants), five model mixins
458
+ (`AsyncCreateModelMixin`, `AsyncListModelMixin`, `AsyncRetrieveModelMixin`,
459
+ `AsyncUpdateModelMixin`, `AsyncDestroyModelMixin`), and the viewset family
460
+ (`AsyncViewSet`, `AsyncGenericViewSet`, `AsyncReadOnlyModelViewSet`,
461
+ `AsyncModelViewSet`).
462
+
463
+ ```python
464
+ from restflow.views import AsyncModelViewSet, ActionConfig
465
+ from restflow.permissions import IsAuthenticated, IsAdminUser
466
+ from restflow.pagination import FastPageNumberPagination
467
+
468
+
469
+ class ProductViewSet(AsyncModelViewSet):
470
+ queryset = Product.objects.all()
471
+ serializer_class = ProductSerializer
472
+ permission_classes = [IsAuthenticated]
473
+ action_configs = {
474
+ "list": ActionConfig(
475
+ response_serializer_class=ProductListSerializer,
476
+ pagination_class=FastPageNumberPagination,
477
+ ),
478
+ "destroy": ActionConfig(permission_classes=[IsAdminUser]),
479
+ }
480
+ ```
481
+
482
+ `ActionConfig` overrides serializer, permission, throttle, parser,
483
+ renderer, pagination, and queryset on a per-action basis. `PostFetch`
484
+ attaches related rows to a list of base objects after pagination, useful
485
+ when `prefetch_related` cannot be used. See the
486
+ [Views guide](https://restflow.khanasfireza.dev/guide/views/) for the
487
+ full async pipeline and per-action override rules.
488
+
489
+ ## Pagination
490
+
491
+ Async-aware paginators that drive the `apaginate_queryset()` hook on
492
+ async views and viewsets. `PageNumberPagination`, `LimitOffsetPagination`,
493
+ and `FastPageNumberPagination` use async ORM iteration directly.
494
+ `CursorPagination` falls back to DRF's sync logic via `sync_to_async`.
495
+
496
+ ```python
497
+ from restflow.pagination import FastPageNumberPagination
498
+ from restflow.views import AsyncListAPIView
499
+
500
+
501
+ class ProductView(AsyncListAPIView):
502
+ pagination_class = FastPageNumberPagination
503
+ ```
504
+
505
+ `FastPageNumberPagination` skips the `COUNT(*)` query and decides whether
506
+ a next page exists based on whether the current page is full. That
507
+ matters on huge tables where a count scan dominates the request budget.
508
+ See the [Pagination guide](https://restflow.khanasfireza.dev/guide/pagination/)
509
+ for selection criteria and tuning.
510
+
511
+ ## Throttling
512
+
513
+ Async-aware throttle classes that use Django's async cache to avoid
514
+ blocking the event loop on rate-limit checks. `AnonRateThrottle`,
515
+ `UserRateThrottle`, `ScopedRateThrottle`, and a `SimpleRateThrottle`
516
+ base class are provided.
517
+
518
+ ```python
519
+ from restflow.throttling import AnonRateThrottle, UserRateThrottle
520
+ from restflow.views import AsyncListAPIView
521
+
522
+
523
+ class ProductView(AsyncListAPIView):
524
+ throttle_classes = [AnonRateThrottle, UserRateThrottle]
525
+ ```
526
+
527
+ ```python
528
+ # settings.py
529
+ REST_FRAMEWORK = {
530
+ "DEFAULT_THROTTLE_RATES": {
531
+ "anon": "100/hour",
532
+ "user": "1000/hour",
533
+ },
534
+ }
535
+ ```
536
+
537
+ See the [Throttling guide](https://restflow.khanasfireza.dev/guide/throttling/)
538
+ for cache-backend selection and per-action scoping.
539
+
540
+ ## Responses
541
+
542
+ Three streaming responses for endpoints that produce large or open-ended
543
+ payloads.
544
+
545
+ ```python
546
+ from restflow.responses import (
547
+ StreamingJSONListResponse, NDJSONResponse, SSEResponse,
548
+ )
549
+
550
+
551
+ async def products(request):
552
+ async def items():
553
+ async for row in Product.objects.all():
554
+ yield {"id": row.id, "name": row.name}
555
+ return StreamingJSONListResponse(items())
556
+ ```
557
+
558
+ `StreamingJSONListResponse` emits a single JSON array element-by-element.
559
+ `NDJSONResponse` emits one JSON object per line. `SSEResponse` formats
560
+ items as Server-Sent Events with `data`, `event`, `id`, and `retry`
561
+ fields. See the [Responses guide](https://restflow.khanasfireza.dev/guide/responses/)
562
+ for buffering, encoder customisation, and SSE reconnection notes.
563
+
564
+ ## Exception handler
565
+
566
+ A drop-in DRF exception handler that renders every error as a uniform
567
+ envelope with a stable error code, message, and details payload.
568
+
569
+ ```python
570
+ # settings.py
571
+ REST_FRAMEWORK = {
572
+ "EXCEPTION_HANDLER": "restflow.exceptions.exception_handler",
573
+ }
574
+ ```
575
+
576
+ ```python
577
+ from restflow.exceptions import APIException, ErrorCode
578
+
579
+
580
+ class ProductLockedException(APIException):
581
+ code = ErrorCode.CONFLICT.value
582
+ status_code = 409
583
+ default_detail = "The product is locked for editing."
584
+ ```
585
+
586
+ Every error -- DRF, Django, or `restflow.exceptions.APIException` --
587
+ is mapped to `{"error": {"code": "...", "message": "...", "details": {...}}}`
588
+ with stable codes for clients to branch on. See the
589
+ [Exception handler guide](https://restflow.khanasfireza.dev/guide/exception-handler/)
590
+ for the full code list and customisation hooks.
591
+
592
+ ## Spectacular
593
+
594
+ `RestflowAutoSchema` is a drop-in replacement for `drf-spectacular`'s
595
+ default schema generator. It resolves serializers from `action_configs`,
596
+ non-generic `serializer_class` plus the request and response variants,
597
+ and pagination classes attached either at the view level or per action.
598
+
599
+ ```bash
600
+ pip install drf-restflow[spectacular]
601
+ ```
602
+
603
+ ```bash
604
+ uv add 'drf-restflow[spectacular]'
605
+ ```
606
+
607
+ ```python
608
+ # settings.py
609
+ REST_FRAMEWORK = {
610
+ "DEFAULT_SCHEMA_CLASS": "restflow.spectacular.RestflowAutoSchema",
611
+ }
612
+ ```
613
+
614
+ OpenAPI parameters from `RestflowFilterBackend` flow through the same
615
+ schema. See the [Spectacular guide](https://restflow.khanasfireza.dev/guide/spectacular/)
616
+ for action-config resolution rules and pagination handling.
617
+
618
+ ## Testing
619
+
620
+ `AsyncAPIClient` and `AsyncAPIRequestFactory` send ASGI requests to
621
+ restflow async views. Four test case bases (`AsyncAPISimpleTestCase`,
622
+ `AsyncAPITestCase`, `AsyncAPITransactionTestCase`,
623
+ `AsyncAPILiveServerTestCase`) wire those into Django's test runner.
624
+
625
+ ```python
626
+ from restflow.test import AsyncAPIClient, AsyncAPITestCase
627
+
628
+
629
+ class TestProducts(AsyncAPITestCase):
630
+ async def test_list(self):
631
+ client = AsyncAPIClient()
632
+ response = await client.get("/api/products/")
633
+ assert response.status_code == 200
634
+ ```
635
+
636
+ `force_authenticate(request, user=...)` bypasses the authenticator chain
637
+ in unit tests. See the [Testing guide](https://restflow.khanasfireza.dev/guide/testing/)
638
+ for picking the right base class and writing signal-driven cache
639
+ invalidation tests.
640
+
641
+ ## Contributing
642
+
643
+ Contributions are welcome. See the
644
+ [contributing guide](https://restflow.khanasfireza.dev/contributing/)
645
+ for the development workflow, code conventions, and test setup.
646
+
647
+ ## License
648
+
649
+ BSD 3-Clause License. See [LICENSE](LICENSE).