django-api-versioning 0.1.3__tar.gz → 0.2.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 (43) hide show
  1. django_api_versioning-0.2.0/CHANGELOG.md +53 -0
  2. {django-api-versioning-0.1.3 → django_api_versioning-0.2.0}/MANIFEST.in +5 -4
  3. django_api_versioning-0.2.0/PKG-INFO +434 -0
  4. django_api_versioning-0.2.0/README.md +389 -0
  5. django_api_versioning-0.2.0/django_api_versioning/decorators.py +143 -0
  6. {django-api-versioning-0.1.3 → django_api_versioning-0.2.0}/django_api_versioning/exceptions.py +10 -0
  7. django_api_versioning-0.2.0/django_api_versioning/registry.py +128 -0
  8. django_api_versioning-0.2.0/django_api_versioning/settings.py +132 -0
  9. {django-api-versioning-0.1.3 → django_api_versioning-0.2.0}/django_api_versioning/urls.py +2 -0
  10. django_api_versioning-0.2.0/django_api_versioning.egg-info/PKG-INFO +434 -0
  11. {django-api-versioning-0.1.3 → django_api_versioning-0.2.0}/django_api_versioning.egg-info/SOURCES.txt +1 -2
  12. django_api_versioning-0.2.0/django_api_versioning.egg-info/requires.txt +13 -0
  13. {django-api-versioning-0.1.3 → django_api_versioning-0.2.0}/django_api_versioning.egg-info/top_level.txt +1 -1
  14. django_api_versioning-0.2.0/pyproject.toml +106 -0
  15. django_api_versioning-0.2.0/requirements-dev.txt +13 -0
  16. django_api_versioning-0.2.0/requirements.txt +1 -0
  17. django_api_versioning-0.2.0/tests/test_decorators.py +427 -0
  18. {django-api-versioning-0.1.3 → django_api_versioning-0.2.0}/tests/test_exceptions.py +14 -3
  19. django_api_versioning-0.2.0/tests/test_registry.py +296 -0
  20. django_api_versioning-0.2.0/tests/test_settings.py +174 -0
  21. django_api_versioning-0.2.0/tests/test_urls.py +51 -0
  22. django-api-versioning-0.1.3/.pre-commit-config.yaml +0 -44
  23. django-api-versioning-0.1.3/PKG-INFO +0 -253
  24. django-api-versioning-0.1.3/README.md +0 -229
  25. django-api-versioning-0.1.3/django_api_versioning/decorators.py +0 -100
  26. django-api-versioning-0.1.3/django_api_versioning/registry.py +0 -33
  27. django-api-versioning-0.1.3/django_api_versioning/settings.py +0 -75
  28. django-api-versioning-0.1.3/django_api_versioning.egg-info/PKG-INFO +0 -253
  29. django-api-versioning-0.1.3/django_api_versioning.egg-info/requires.txt +0 -13
  30. django-api-versioning-0.1.3/pyproject.toml +0 -16
  31. django-api-versioning-0.1.3/requirements-dev.txt +0 -11
  32. django-api-versioning-0.1.3/requirements.txt +0 -1
  33. django-api-versioning-0.1.3/setup.py +0 -54
  34. django-api-versioning-0.1.3/tests/test_decorators.py +0 -122
  35. django-api-versioning-0.1.3/tests/test_registry.py +0 -83
  36. django-api-versioning-0.1.3/tests/test_settings.py +0 -147
  37. django-api-versioning-0.1.3/tests/test_urls.py +0 -33
  38. {django-api-versioning-0.1.3 → django_api_versioning-0.2.0}/LICENSE +0 -0
  39. {django-api-versioning-0.1.3 → django_api_versioning-0.2.0}/django_api_versioning/__init__.py +0 -0
  40. {django-api-versioning-0.1.3 → django_api_versioning-0.2.0}/django_api_versioning.egg-info/dependency_links.txt +0 -0
  41. {django-api-versioning-0.1.3 → django_api_versioning-0.2.0}/pytest.ini +0 -0
  42. {django-api-versioning-0.1.3 → django_api_versioning-0.2.0}/setup.cfg +0 -0
  43. {django-api-versioning-0.1.3 → django_api_versioning-0.2.0}/tests/__init__.py +0 -0
@@ -0,0 +1,53 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.2.0] - 2026-10-28
11
+
12
+ The release that introduces **forward inheritance**. Routes declared for
13
+ 0.1.x keep working unchanged unless you opt in by setting
14
+ `API_FORWARD_INHERITANCE = True` or passing `forward=True` to a single
15
+ endpoint.
16
+
17
+ > ⚠️ **Breaking:** Python 3.9 and Django 3.2 / 4.2 are no longer
18
+ > supported. See the *Changed* section below.
19
+
20
+ ### Added
21
+
22
+ - **Forward endpoint inheritance.** An endpoint declared with
23
+ `version=N` is registered for versions `N+1` through `API_MAX_VERSION`
24
+ when `API_FORWARD_INHERITANCE = True`. The per-endpoint `forward`
25
+ argument overrides the global setting in either direction. Opt out
26
+ for a single endpoint with `forward=False`. See the *Forward
27
+ inheritance* section in the README.
28
+ - **`py.typed` marker (PEP 561).** Type hints ship with the package, so
29
+ `mypy` and `pyright` pick them up automatically in downstream
30
+ projects.
31
+
32
+ ### Changed
33
+
34
+ - **Python support raised from 3.9 to 3.10–3.15.** Python 3.9 reached
35
+ end-of-life in October 2025 and received no security updates
36
+ afterwards.
37
+ - **Django support raised from 3.2 to 5.2–6.1.** Django 3.2 and 4.2
38
+ reached end-of-life in April 2024 and April 2026 respectively;
39
+ 5.2 LTS is supported until April 2028.
40
+
41
+ ### Removed
42
+
43
+ - The class-based-view wrapper introduced in 0.1.x. The wrapper replaced
44
+ the original class with a function, which silently dropped
45
+ `view_class` metadata on the URL pattern. This broke
46
+ `resolve().func.view_class` and some DRF integrations (authentication
47
+ class discovery, schema generation). Decorated CBVs now return the
48
+ original class unchanged.
49
+
50
+ ## [0.1.4] - 2025-02-22
51
+
52
+ - Initial release: decorator-based URL versioning, backward compatibility,
53
+ Django and DRF support.
@@ -1,8 +1,9 @@
1
1
  include README.md
2
2
  include LICENSE
3
- include .pre-commit-config.yaml
4
- recursive-include django_api_versioning *.py
5
- recursive-include tests *.py
3
+ include CHANGELOG.md
6
4
  include requirements.txt
7
5
  include requirements-dev.txt
8
- include pytest.ini
6
+ include pytest.ini
7
+ recursive-include django_api_versioning *.py
8
+ recursive-include django_api_versioning py.typed
9
+ recursive-include tests *.py
@@ -0,0 +1,434 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-api-versioning
3
+ Version: 0.2.0
4
+ Summary: Django API versioning for Django and Django REST Framework (DRF), with decorator-based endpoint versioning, automatic URL registration, backward compatibility, and forward endpoint inheritance.
5
+ Author-email: Mojtaba Arvin <ArvinDevDay@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Source, https://github.com/mojtaba-arvin/django-api-versioning
8
+ Project-URL: Issues, https://github.com/mojtaba-arvin/django-api-versioning/issues
9
+ Project-URL: Documentation, https://github.com/mojtaba-arvin/django-api-versioning#readme
10
+ Project-URL: Changelog, https://github.com/mojtaba-arvin/django-api-versioning/blob/main/CHANGELOG.md
11
+ Keywords: django api versioning,api versioning,django,django framework,django rest framework,drf,url-based api versioning,api versioning decorator,versioned api endpoints,automatic url registration,backward compatibility,endpoint inheritance
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Programming Language :: Python :: 3.15
19
+ Classifier: Framework :: Django
20
+ Classifier: Framework :: Django :: 5.2
21
+ Classifier: Framework :: Django :: 6.0
22
+ Classifier: Framework :: Django :: 6.1
23
+ Classifier: Development Status :: 3 - Alpha
24
+ Classifier: Intended Audience :: Developers
25
+ Classifier: Operating System :: OS Independent
26
+ Classifier: Topic :: Internet :: WWW/HTTP
27
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
+ Classifier: Typing :: Typed
29
+ Requires-Python: >=3.10
30
+ Description-Content-Type: text/markdown
31
+ License-File: LICENSE
32
+ Requires-Dist: Django>=5.2
33
+ Provides-Extra: dev
34
+ Requires-Dist: black>=26.10.1; extra == "dev"
35
+ Requires-Dist: flake8>=7.4.1; extra == "dev"
36
+ Requires-Dist: isort>=9.0.2; extra == "dev"
37
+ Requires-Dist: mypy>=2.4.0; extra == "dev"
38
+ Requires-Dist: pytest>=9.2.0; extra == "dev"
39
+ Requires-Dist: pytest-django>=4.14.0; extra == "dev"
40
+ Requires-Dist: pytest-cov>=7.1.0; extra == "dev"
41
+ Requires-Dist: pre-commit>=4.6.2; extra == "dev"
42
+ Requires-Dist: twine>=7.0.0; extra == "dev"
43
+ Requires-Dist: build>=1.6.1; extra == "dev"
44
+ Dynamic: license-file
45
+
46
+ # Django API Versioning
47
+
48
+ [![PyPI version](https://badge.fury.io/py/django-api-versioning.svg)](https://badge.fury.io/py/django-api-versioning)
49
+ [![PyPI Downloads](https://img.shields.io/pypi/dm/django-api-versioning.svg)](https://pypi.org/project/django-api-versioning/)
50
+ [![Build Status](https://github.com/mojtaba-arvin/django-api-versioning/actions/workflows/tests.yml/badge.svg)](https://github.com/mojtaba-arvin/django-api-versioning/actions)
51
+ [![Python versions](https://img.shields.io/pypi/pyversions/django-api-versioning.svg)](https://pypi.org/project/django-api-versioning/)
52
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
53
+
54
+ **Django API Versioning** is a Python package for [API versioning in Django](https://github.com/mojtaba-arvin/django-api-versioning) and Django REST Framework (DRF) projects. Stop copy-pasting
55
+ `path()` declarations for every API version — declare an endpoint once,
56
+ and let the registry handle v1, v2, v3, and beyond.
57
+
58
+ ```python
59
+ from django.http import JsonResponse
60
+ from django_api_versioning.decorators import endpoint
61
+
62
+ @endpoint("users", version=2)
63
+ def user_list(request):
64
+ return JsonResponse({"users": []})
65
+
66
+ # With API_MIN_VERSION = 1, API_MAX_VERSION = 3, this single
67
+ # decorator registers:
68
+ #
69
+ # GET /api/v1/users/ -> user_list
70
+ # GET /api/v2/users/ -> user_list
71
+ # GET /api/v3/users/ -> user_list (with forward inheritance)‍‍‍
72
+ ```
73
+
74
+ It registers versioned URL routes from a simple `@endpoint` decorator, helps preserve backward compatibility, and can optionally inherit unchanged endpoints into newer API versions.
75
+
76
+ It is especially useful for large APIs where many endpoints stay the same between releases. Instead of creating duplicate views or manually maintaining every route as the API grows, you can declare each endpoint once and control version routing centrally. Use `app_name` to group related resources by a domain or application area, while keeping each `postfix` focused on the resource itself.
77
+
78
+ - **Package:** [`django-api-versioning`](https://pypi.org/project/django-api-versioning/)
79
+ - **Source code:** [mojtaba-arvin/django-api-versioning](https://github.com/mojtaba-arvin/django-api-versioning)
80
+ - **License:** MIT
81
+
82
+ ## Why not just use DRF's built-in versioning?
83
+
84
+ DRF ships with `URLPathVersioning` and friends — and they work. But they
85
+ only handle *which* version a request is on. You still write every
86
+ `path()` declaration by hand, duplicate unchanged routes, and decide
87
+ per-version which view to call.
88
+
89
+
90
+ | Feature | DRF `URLPathVersioning` | `django-api-versioning` |
91
+ |---|---|---|
92
+ | Route registration | Manual `path()` per version | Automatic via decorator |
93
+ | Backward compatibility | Manual | `backward=True` (default) |
94
+ | Forward inheritance (new versions reuse old views) | ❌ | ✅ Opt-in |
95
+ | Function-based views | ✅ | ✅ |
96
+ | Class-based views | ⚠️ DRF only | ✅ Django + DRF |
97
+ | `resolve().view_class` preserved | ✅ | ✅ |
98
+ | Unversioned routes | Manual | `version=None` |
99
+ | Works without DRF | ❌ | ✅ |
100
+ | Django `reverse()` support | ✅ | ✅ (highest version wins) |
101
+
102
+ If you already use DRF, you can keep it — this package handles routing,
103
+ not request parsing.
104
+
105
+ ---
106
+
107
+ ## Installation
108
+
109
+ Install [Django API Versioning](https://pypi.org/project/django-api-versioning/) package from PyPI:
110
+
111
+ ```bash
112
+ pip install django-api-versioning
113
+ ```
114
+
115
+ Requires **Django >= 5.2** and **Python >= 3.11**.
116
+ Django REST Framework is optional and auto-detected.
117
+
118
+ ## Quick Start
119
+
120
+ ### 1. Configure Django settings
121
+
122
+ ```python
123
+ # settings.py
124
+ INSTALLED_APPS = [
125
+ # ... your other apps
126
+ "django_api_versioning",
127
+ ]
128
+
129
+ API_BASE_PATH = "api/v{version}/"
130
+ API_MIN_VERSION = 1
131
+ API_MAX_VERSION = 3
132
+ ```
133
+
134
+ `API_BASE_PATH` **must** contain the `{version}` placeholder.
135
+
136
+ ### 2. Include the generated URL patterns
137
+
138
+ If your project doesn't have a root URLconf yet:
139
+
140
+ ```python
141
+ # settings.py
142
+ ROOT_URLCONF = "django_api_versioning.urls"
143
+ ```
144
+
145
+ If it does, include them:
146
+
147
+ ```python
148
+ # project/urls.py
149
+ from django.urls import include, path
150
+ from django_api_versioning.urls import urlpatterns as api_urlpatterns
151
+
152
+ urlpatterns = [
153
+ # ... your other URL patterns
154
+ path("", include(api_urlpatterns)),
155
+ ]
156
+ ```
157
+
158
+ ### 3. Decorate your views
159
+
160
+ ```python
161
+ from django.http import JsonResponse
162
+ from django_api_versioning.decorators import endpoint
163
+
164
+
165
+ @endpoint(
166
+ "products/",
167
+ app_name="catalog",
168
+ version=2,
169
+ view_name="catalog-products-list",
170
+ )
171
+ def product_list(request):
172
+ return JsonResponse({"results": []})
173
+ ```
174
+
175
+ That's it. You now have:
176
+
177
+ ```text
178
+ GET /api/v1/catalog/products/
179
+ GET /api/v2/catalog/products/
180
+ ```
181
+
182
+ Class-based views, DRF views, and path converters (`<int:pk>`) work
183
+ the same way:
184
+
185
+ ```python
186
+ from rest_framework.response import Response
187
+ from rest_framework.views import APIView
188
+
189
+
190
+ @endpoint("invoices/<int:invoice_id>/", app_name="billing", version=2)
191
+ class InvoiceDetailView(APIView):
192
+ def get(self, request, invoice_id):
193
+ return Response({"id": invoice_id})
194
+ ```
195
+
196
+ ## Before and after
197
+
198
+ **Without `django-api-versioning`** — you maintain one `path()` per
199
+ version, and copy unchanged views when a new version ships:
200
+
201
+ ```python
202
+ # urls.py — manual, error-prone, hard to keep in sync
203
+ from django.urls import path
204
+ from . import views
205
+
206
+ urlpatterns = [
207
+ path("api/v1/users/", views.user_list_v1),
208
+ path("api/v2/users/", views.user_list_v2),
209
+ path("api/v3/users/", views.user_list_v3), # same body as v2
210
+ path("api/v1/orders/", views.order_list_v1),
211
+ path("api/v2/orders/", views.order_list_v2),
212
+ # ... × 150 endpoints
213
+ ]
214
+ ```
215
+
216
+ **With `django-api-versioning`** — one declaration, routing handled:
217
+
218
+ ```python
219
+ from django_api_versioning.decorators import endpoint
220
+
221
+ @endpoint("users/", version=2)
222
+ def user_list(request):
223
+ ...
224
+
225
+ @endpoint("orders/", version=2)
226
+ def order_list(request):
227
+ ...
228
+ ```
229
+
230
+ Add `API_FORWARD_INHERITANCE = True` and the same declarations serve
231
+ v3 automatically, without redeclaring anything that hasn't changed.
232
+
233
+ ---
234
+
235
+ ## Forward inheritance (the new feature)
236
+
237
+ Big APIs change in small increments. Suppose v3 changes only 8 endpoints
238
+ out of 150. You don't want to redeclare the other 142.
239
+
240
+ Enable **forward inheritance** to let unchanged endpoints serve future
241
+ versions automatically:
242
+
243
+ ```python
244
+ # settings.py
245
+ API_FORWARD_INHERITANCE = True
246
+ ```
247
+
248
+ Now a single `@endpoint("products/", version=2)` declaration serves
249
+ `/api/v1/...`, `/api/v2/...`, and `/api/v3/...` — subject to
250
+ `API_MAX_VERSION`.
251
+
252
+ ### Opt one endpoint out
253
+
254
+
255
+ ```python
256
+ @endpoint("events/", app_name="audit", version=2, forward=False)
257
+ def audit_events(request):
258
+ ...
259
+ ```
260
+
261
+ The per-endpoint value overrides the global setting, in either
262
+ direction.
263
+
264
+ ### Override an inherited endpoint
265
+
266
+
267
+ Keep the v1/v2 implementation, and register a separate view for v3:
268
+
269
+ ```python
270
+ @endpoint("reports/", app_name="analytics", version=2, view_name="reports")
271
+ def reports_v2(request):
272
+ return JsonResponse({"api_version": 2})
273
+
274
+
275
+ @endpoint(
276
+ "reports/",
277
+ app_name="analytics",
278
+ version=3,
279
+ backward=False,
280
+ forward=False,
281
+ view_name="reports",
282
+ )
283
+ def reports_v3(request):
284
+ return JsonResponse({"api_version": 3})
285
+ ```
286
+
287
+ Result:
288
+
289
+ ```text
290
+ /api/v1/analytics/reports/ -> reports_v2
291
+ /api/v2/analytics/reports/ -> reports_v2
292
+ /api/v3/analytics/reports/ -> reports_v3
293
+ ```
294
+
295
+ The registry resolves collisions by **declared-version priority**, so
296
+ the override always wins regardless of module import order.
297
+
298
+ ---
299
+
300
+
301
+ ## Configuration reference
302
+
303
+ ### Django settings
304
+
305
+ | Setting | Default | Purpose |
306
+ |---|---|---|
307
+ | `API_BASE_PATH` | `"api/v{version}/"` | URL prefix template. Must contain `{version}`. |
308
+ | `API_MIN_VERSION` | `1` | Lowest API version to register. |
309
+ | `API_MAX_VERSION` | `1` | Highest configured API version. |
310
+ | `API_FORWARD_INHERITANCE` | `False` | Default for whether endpoints may be inherited into newer versions. |
311
+ | `ROOT_URLCONF` | `"django_api_versioning.urls"` | Django URLconf. Use the package one directly, or include its `urlpatterns` in your own. |
312
+
313
+ ### `@endpoint` arguments
314
+
315
+ | Argument | Default | Purpose |
316
+ |---|---|---|
317
+ | `postfix` | required | Resource path, e.g. `"products/"` or `"products/<int:pk>/"`. |
318
+ | `app_name` | `None` | Optional path segment for grouping (e.g. `"catalog"`). This is a URL path segment, not Django's namespace `app_name`. |
319
+ | `version` | `None` | Version where this implementation is declared. `None` → unversioned route. |
320
+ | `backward` | `True` | Also register for earlier versions, down to `API_MIN_VERSION`. |
321
+ | `forward` | `None` | Per-endpoint override for forward inheritance. `None` → follow `API_FORWARD_INHERITANCE`. |
322
+ | `view_name` | `None` | Django URL name for `reverse()`. Use stable, unique names. |
323
+
324
+ `backward` and `forward` control different directions:
325
+
326
+ - `backward=True` → expose this view to earlier versions.
327
+ - `forward=True` → inherit this view into later versions.
328
+ - `backward=False, forward=False` → pin to the specified version only.
329
+
330
+ Both affect **routing**, not whether the response is semantically
331
+ compatible. You remain responsible for API contract design.
332
+
333
+ ---
334
+
335
+
336
+ ## URL reversing
337
+
338
+ Give each route a stable `view_name` and use `reverse()` instead of
339
+ hard-coding versioned URLs:
340
+
341
+ ```python
342
+ from django.urls import reverse
343
+
344
+ url = reverse("catalog-products-list") # -> "/api/v3/catalog/products/"
345
+ ```
346
+
347
+ If multiple registrations share a name, the registry orders routes so
348
+ Django resolves the **highest registered version**.
349
+
350
+ ---
351
+
352
+ ## FAQ
353
+
354
+ **Does this work with DRF `APIView`?**
355
+ Yes. Django's `View` is a base class of DRF's `APIView`, so the
356
+ decorator handles both. `resolve().func.view_class` is preserved, which
357
+ means DRF's authentication, permissions, and schema generation all
358
+ continue to work.
359
+
360
+ **Does this conflict with DRF's `DEFAULT_VERSIONING_CLASS`?**
361
+ No, but they solve different problems: DRF's versioning tells *your
362
+ code* which version a request is on; this package decides *which view
363
+ is reachable at which URL*. You can use both together.
364
+
365
+ **What happens with duplicate `view_name`s?**
366
+ `reverse()` returns the highest-version route. Each versioned path is
367
+ still uniquely resolvable.
368
+
369
+ **Can I use this with Django < 5.2?**
370
+ No. Django 5.2 LTS is the minimum supported version. Django 4.2
371
+ reached end-of-life in April 2026.
372
+
373
+ **How do I deprecate a version?**
374
+ Remove the corresponding declarations from your code (or set
375
+ `API_MIN_VERSION` upward) and run your test suite. The package does not
376
+ manage deprecation policy — that's an API design decision.
377
+
378
+ **Does the package require DRF?**
379
+ No. DRF is optional. Pure Django projects work out of the box.
380
+
381
+ **Where do I report a routing issue?**
382
+ Open an issue at [GitHub Issues](https://github.com/mojtaba-arvin/django-api-versioning/issues)
383
+ with your settings, endpoint declarations, expected URL patterns, and
384
+ actual behavior.
385
+
386
+ ---
387
+
388
+ ## Contributing
389
+
390
+ ### Setup
391
+
392
+ ```bash
393
+ python -m venv .venv && source .venv/bin/activate
394
+ pip install -r requirements-dev.txt && pip install -e .
395
+ pre-commit install
396
+ ```
397
+
398
+ ### Running tests
399
+
400
+ ```bash
401
+ pytest
402
+ pre-commit run --all-files
403
+ ```
404
+
405
+ ### Docker
406
+
407
+ The `Dockerfile` parameterizes Python via `PYTHON_VERSION` and delegates
408
+ to `entrypoint.sh`:
409
+
410
+ ```bash
411
+ docker build -t dapi .
412
+ docker run --rm dapi # tests
413
+ docker run --rm -e RUN_PRE_COMMIT=true dapi # + lint
414
+ docker build --build-arg PYTHON_VERSION=3.15 -t dapi:py315 . # other Python
415
+ docker run --rm dapi:py311
416
+ ```
417
+
418
+ ### Releasing
419
+
420
+ 1. Bump `version` in `pyproject.toml` and update `CHANGELOG.md`
421
+ 2. Commit, push to `main`
422
+ 3. `git tag v0.2.1 && git push origin v0.2.1` → CI publishes to PyPI
423
+
424
+ ## Reporting issues
425
+
426
+ Include your Django and package settings, the `@endpoint` declarations
427
+ involved, expected vs actual URL patterns, and your Python and Django
428
+ versions.
429
+
430
+ ---
431
+
432
+ ## License
433
+
434
+ Released under the [MIT License](LICENSE).