Skip to content

Commit 05409d8

Browse files
authored
Merge pull request #79 from eadwinCode/versioning_documentation
Versioning Documentation
2 parents 2c59b12 + 76165f6 commit 05409d8

11 files changed

+239
-12
lines changed

docs/basics/versioning.md

Lines changed: 224 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,224 @@
1-
## Coming Soon
1+
Versioning allows for the existence of multiple versions of controllers or individual routes within the same application,
2+
which can be useful when making changes that may break previous versions.
3+
This allows developers to support older versions of the application while still making necessary updates.
4+
5+
There are 4 types of versioning that are supported:
6+
7+
- [`URL Versioning`](#url-versioning): The version will be passed within the **URL** of the request
8+
- [`Header Versioning`](#header-versioning): A custom request **header** will specify the version
9+
- [`Query Versioning`](#query-versioning): A custom request **query** will specify the version
10+
- [`Host Versioning`](#host-versioning): The version will be part of the request **client host**
11+
12+
## **URL Versioning**
13+
This scheme requires the client to specify the version as part of the URL path.
14+
```
15+
GET /v1/receipes/ HTTP/1.1
16+
Host: example.com
17+
Accept: application/json
18+
```
19+
20+
To enable **URL Versioning** for your application, do the following:
21+
```python
22+
# project_name/server.py
23+
import os
24+
from ellar.constants import ELLAR_CONFIG_MODULE
25+
from ellar.core.factory import AppFactory
26+
from ellar.core.versioning import VersioningSchemes
27+
from .root_module import ApplicationModule
28+
29+
application = AppFactory.create_from_app_module(
30+
ApplicationModule,
31+
config_module=os.environ.get(
32+
ELLAR_CONFIG_MODULE, "dialerai.config:DevelopmentConfig"
33+
),
34+
global_guards=[]
35+
)
36+
application.enable_versioning(VersioningSchemes.URL, version_parameter='v', default_version=None)
37+
```
38+
The URL path will be parsed with the provided `version_parameter`, `v`, to determine specified version.
39+
For example, `https://example.com/v1/route`, will resolve to `version='1'` and `https://example.com/v3/route`, will resolve to `version='3'`.
40+
41+
If version is not specified in the URL, the `default_version` will be used. Which in this case is `None`.
42+
43+
## **Header Versioning**
44+
This scheme requires the client to specify the version as part of the media type in the `Accept` header.
45+
The version is included as a media type parameter, that supplements the main media type.
46+
47+
Here's an example HTTP request using accept header versioning style.
48+
```
49+
GET /receipes/ HTTP/1.1
50+
Host: example.com
51+
Accept: application/json; version=1
52+
```
53+
54+
To enable **Header Versioning** for your application, do the following:
55+
```python
56+
# project_name/server.py
57+
import os
58+
from ellar.constants import ELLAR_CONFIG_MODULE
59+
from ellar.core.factory import AppFactory
60+
from ellar.core.versioning import VersioningSchemes
61+
from .root_module import ApplicationModule
62+
63+
application = AppFactory.create_from_app_module(
64+
ApplicationModule,
65+
config_module=os.environ.get(
66+
ELLAR_CONFIG_MODULE, "dialerai.config:DevelopmentConfig"
67+
),
68+
global_guards=[]
69+
)
70+
application.enable_versioning(
71+
VersioningSchemes.HEADER,
72+
header_parameter='accept',
73+
version_parameter='version',
74+
default_version=None
75+
)
76+
```
77+
During request handling, request header `accept` value will be parsed to read the version value. A header `accept: application/json; version=2` will resolve to `version='2'`
78+
79+
#### **Using Custom Header**
80+
We can also use a custom header asides `accept`. for example:
81+
```
82+
GET /receipes/ HTTP/1.1
83+
Host: example.com
84+
X-Custom-Header: version=2
85+
```
86+
87+
And then we enable it with the code below:
88+
```python
89+
# project_name/server.py
90+
...
91+
application.enable_versioning(
92+
VersioningSchemes.HEADER,
93+
header_parameter='x-custom-header',
94+
version_parameter='version_header',
95+
default_version=None
96+
)
97+
```
98+
The header property, `x-custom-header`, will be the name of the header that will contain the version of the request.
99+
And the value follow the format `[version_parameter]=version-number;`, for example: `headers={'x-custom-header': 'version_header=3'}` will resolve to `version='3'`.
100+
101+
## **Query Versioning**
102+
This scheme is a simple style that includes the version as a query parameter in the URL. For example:
103+
```
104+
GET /receipes?version=2 HTTP/1.1
105+
Host: example.com
106+
Accept: application/json
107+
```
108+
109+
To enable **Query Versioning** for your application, do the following:
110+
```python
111+
# project_name/server.py
112+
import os
113+
from ellar.constants import ELLAR_CONFIG_MODULE
114+
from ellar.core.factory import AppFactory
115+
from ellar.core.versioning import VersioningSchemes
116+
from .root_module import ApplicationModule
117+
118+
application = AppFactory.create_from_app_module(
119+
ApplicationModule,
120+
config_module=os.environ.get(
121+
ELLAR_CONFIG_MODULE, "dialerai.config:DevelopmentConfig"
122+
),
123+
global_guards=[]
124+
)
125+
application.enable_versioning(
126+
VersioningSchemes.QUERY,
127+
version_parameter='version',
128+
default_version=None
129+
)
130+
```
131+
132+
## **Host Versioning**
133+
The hostname versioning scheme requires the client to specify the requested version as part of the hostname in the URL.
134+
135+
For example the following is an HTTP request to the `http://v1.example.com/receipes/` URL:
136+
```
137+
GET /receipes/ HTTP/1.1
138+
Host: v1.example.com
139+
Accept: application/json
140+
```
141+
142+
To enable **Host Versioning** for your application, do the following:
143+
```python
144+
# project_name/server.py
145+
import os
146+
from ellar.constants import ELLAR_CONFIG_MODULE
147+
from ellar.core.factory import AppFactory
148+
from ellar.core.versioning import VersioningSchemes
149+
from .root_module import ApplicationModule
150+
151+
application = AppFactory.create_from_app_module(
152+
ApplicationModule,
153+
config_module=os.environ.get(
154+
ELLAR_CONFIG_MODULE, "dialerai.config:DevelopmentConfig"
155+
),
156+
global_guards=[]
157+
)
158+
application.enable_versioning(
159+
VersioningSchemes.HOST,
160+
version_parameter='v',
161+
default_version=None
162+
)
163+
```
164+
By default, this implementation expects the hostname to match this simple regular expression:
165+
```regexp
166+
^([a-zA-Z0-9]+)\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$
167+
```
168+
169+
Note that the first group is enclosed in brackets, indicating that this is the matched portion of the hostname.
170+
171+
The `HostNameVersioning` scheme can be awkward to use in debug mode as you will typically be accessing a raw IP address such as 127.0.0.1.
172+
There are various online tutorials on how to [access localhost with a custom subdomain](https://reinteractive.net/posts/199-developing-and-testing-rails-applications-with-subdomains) which you may find helpful in this case.
173+
174+
Hostname based versioning can be particularly useful if you have requirements to route incoming requests to different servers based on the version, as you can configure different DNS records for different API versions.
175+
176+
## **Controller Versions**
177+
A version can be applied to a controller by using `Version` decorator from `ellar.common` package.
178+
179+
To add a version to a controller do the following:
180+
```python
181+
from ellar.common import Controller, Version
182+
183+
@Controller('/example')
184+
@Version('1')
185+
class ExampleControllerV1:
186+
pass
187+
188+
```
189+
## **Route Versions**
190+
A version can be applied to an individual route. This version will override any other version that would effect the route, such as the Controller Version.
191+
192+
To add a version to an individual route do the following:
193+
194+
```python
195+
from ellar.common import Controller, Version, get
196+
197+
@Controller('/example')
198+
class ExampleController:
199+
@Version('1')
200+
@get('/items')
201+
async def get_items_v1(self):
202+
return 'This action returns all items for version 1'
203+
204+
@get('/items')
205+
@Version('2')
206+
async def get_items_v2(self):
207+
return 'This action returns all items for version 2'
208+
209+
```
210+
## **Multiple Versions**
211+
Multiple versions can be applied to a controller or route. To use multiple versions, you would set the version to be an Array.
212+
213+
To add multiple versions do the following:
214+
```python
215+
from ellar.common import Controller, Version, get
216+
217+
@Controller('/example')
218+
@Version('1', '2')
219+
class ExampleControllerV1AndV2:
220+
@get('/items')
221+
async def get_items(self):
222+
return 'This action returns all items for version 1 & 2'
223+
224+
```

ellar/core/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
)
3030
from ellar.core.routing import ApplicationRouter
3131
from ellar.core.templating import AppTemplating, Environment
32-
from ellar.core.versioning import VERSIONING, BaseAPIVersioning
32+
from ellar.core.versioning import BaseAPIVersioning, VersioningSchemes
3333
from ellar.di.injector import EllarInjector
3434
from ellar.logger import logger
3535
from ellar.types import ASGIApp, T, TReceive, TScope, TSend
@@ -253,7 +253,7 @@ def url_path_for(self, name: str, **path_params: t.Any) -> URLPath:
253253

254254
def enable_versioning(
255255
self,
256-
schema: VERSIONING,
256+
schema: VersioningSchemes,
257257
version_parameter: str = "version",
258258
default_version: t.Optional[str] = None,
259259
**init_kwargs: t.Any,

ellar/core/versioning/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
)
1111

1212

13-
class VERSIONING(Enum):
13+
class VersioningSchemes(Enum):
1414
URL = UrlPathAPIVersioning
1515
QUERY = QueryParameterAPIVersioning
1616
HEADER = HeaderAPIVersioning

tests/test_application/test_application_functions.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@
2121
from ellar.core.services import CoreServiceRegistration
2222
from ellar.core.staticfiles import StaticFiles
2323
from ellar.core.templating import Environment
24-
from ellar.core.versioning import VERSIONING, DefaultAPIVersioning, UrlPathAPIVersioning
24+
from ellar.core.versioning import (
25+
DefaultAPIVersioning,
26+
UrlPathAPIVersioning,
27+
VersioningSchemes as VERSIONING,
28+
)
2529
from ellar.di import EllarInjector
2630
from ellar.helper.importer import get_class_import
2731
from ellar.openapi import OpenAPIDocumentModule

tests/test_versioning/test_default_versioning.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22

33
from ellar.core import TestClientFactory
4-
from ellar.core.versioning import VERSIONING
4+
from ellar.core.versioning import VersioningSchemes as VERSIONING
55

66
from .operations import (
77
ControllerIndividualVersioning,

tests/test_versioning/test_default_versioning_for_controllers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22

33
from ellar.core import TestClientFactory
4-
from ellar.core.versioning import VERSIONING
4+
from ellar.core.versioning import VersioningSchemes as VERSIONING
55

66
from .operations import (
77
ControllerIndividualVersioning,

tests/test_versioning/test_header_versioning.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from ellar.constants import NOT_SET
44
from ellar.core import TestClientFactory
5-
from ellar.core.versioning import VERSIONING
5+
from ellar.core.versioning import VersioningSchemes as VERSIONING
66

77
from .operations import mr
88

tests/test_versioning/test_header_versioning_controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from ellar.constants import NOT_SET
44
from ellar.core import TestClientFactory
5-
from ellar.core.versioning import VERSIONING
5+
from ellar.core.versioning import VersioningSchemes as VERSIONING
66

77
from .operations import (
88
ControllerIndividualVersioning,

tests/test_versioning/test_host_versioning.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from ellar.constants import NOT_SET
44
from ellar.core import TestClientFactory
5-
from ellar.core.versioning import VERSIONING
5+
from ellar.core.versioning import VersioningSchemes as VERSIONING
66

77
from .operations import mr
88

tests/test_versioning/test_query_versioning.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from ellar.constants import NOT_SET
44
from ellar.core import TestClientFactory
5-
from ellar.core.versioning import VERSIONING
5+
from ellar.core.versioning import VersioningSchemes as VERSIONING
66

77
from .operations import mr
88

0 commit comments

Comments
 (0)