Multitenancy¤
This module implements multitenancy, meaning that your application can be used by a number of independent subjects (tenants, for example companies) without interfering with each other.
Getting started¤
To set up an application with multi-tenant web interface, create an application with a web server and initialize asab.web.tenant.TenantService
.
Tenant service automatically tries to install tenant context wrapper to your web handlers, which enables you to access the request's tenant context using asab.contextvars.Tenant.get()
.
import asab
import asab.web
import asab.web.tenant
import asab.contextvars
class MyApplication(asab.Application):
def __init__(self):
super().__init__()
# Initialize web module
asab.web.create_web_server(self)
# Initialize tenant service
self.TenantService = asab.web.tenant.TenantService(self)
Note
If your app has more than one web container, you will need to call TenantService.install(web_container)
to apply the tenant context wrapper.
This also adds the requirement for tenant
parameter in the URL of every request - either in the path or in the query.
Mandatory tenant in path¤
If tenant context is mandatory for your endpoint, it is recommended to require the tenant
parameter in the URL path, such as:
import asab
import asab.web
import asab.web.tenant
import asab.contextvars
class NotesApplication(asab.Application):
def __init__(self):
super().__init__()
web = asab.web.create_web_server(self)
tenant_svc = asab.web.tenant.TenantService(self)
web.add_get("/{tenant}/note", self.list_notes) # Tenant parameter required in path
async def list_notes(self, request):
tenant = asab.contextvars.Tenant.get()
print("Requesting notes for tenant {!r}...".format(tenant))
Note
It is a good practice to have tenant
as the first component of the URL path if possible.
Mandatory tenant in query¤
When the tenant context is mandatory for your endpoint, but it is not feasible to have the tenant parameter hard-baked into the path, define your endpoint path without the tenant
path parameter.
The handler with require tenant
to be present in the URL query.
Requests without the required parameter will result in asab.exceptions.ValidationError
(HTTP 400).
import asab
import asab.web
import asab.web.tenant
import asab.contextvars
class NotesApplication(asab.Application):
def __init__(self):
super().__init__()
web = asab.web.create_web_server(self)
tenant_svc = asab.web.tenant.TenantService(self)
web.add_get("/note", self.list_notes) # No tenant parameter in path!
async def list_notes(self, request):
tenant = asab.contextvars.Tenant.get()
print("Requesting notes for tenant {!r}...".format(tenant))
Optional tenant in query¤
When the tenant context is optional for your endpoint (or when the endpoint does not use tenants at all), define its path without the tenant
parameter in path and decorate the method handler with @asab.web.tenant.allow_no_tenant
.
Requests without the tenant
parameter will have their Tenant context set to None
.
import asab
import asab.web
import asab.web.tenant
import asab.contextvars
class NotesApplication(asab.Application):
def __init__(self):
super().__init__()
web = asab.web.create_web_server(self)
tenant_svc = asab.web.tenant.TenantService(self)
web.add_get("/{tenant}/note", self.list_notes) # No tenant parameter in path!
@asab.web.tenant.allow_no_tenant # Allow requests with undefined tenant!
async def list_notes(self, request):
tenant = asab.contextvars.Tenant.get()
if tenant is None:
print("Requesting notes without any tenant. Not sure what to do...")
else:
print("Requesting notes for tenant {!r}...".format(tenant))
Working with known tenants¤
When you provide tenant_url
or tenant ids
in the configuration, TenantService will make the set of known tenants available through its Tenants
property.
You can also make use of the TenantService.is_tenant_known(tenant)
method.
Note
If you only want to use the service to access known tenants and do not need the web middleware, initialize TenantService with set_up_web_wrapper
argument set to False
.
Configuration¤
The asab.web.tenant
module is configured in the [tenants]
section with the following options:
Option | Type | Meaning |
---|---|---|
ids |
List of strings | (Optional) Known tenant IDs. |
tenant_url |
URL | (Optional) Location of a JSON array of known tenant IDs. |
Reference¤
asab.web.tenant.TenantService
¤
Bases: Service
Provides set of known tenants and tenant extraction for web requests.
Source code in asab/web/tenant/service.py
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
|
Tenants: typing.Set[str]
property
¤
Get the set of known tenant IDs.
Returns:
Type | Description |
---|---|
Set[str]
|
The set of known tenant IDs. |
__init__(app, service_name='asab.TenantService', auto_install_web_wrapper=True)
¤
Initialize and register a new TenantService.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
app
|
ASAB application. |
required | |
service_name
|
str
|
ASAB service identifier. |
'asab.TenantService'
|
auto_install_web_wrapper
|
bool
|
Whether to automatically install tenant context wrapper to WebContainer. |
True
|
Source code in asab/web/tenant/service.py
get_tenants()
¤
Get the set of known tenant IDs.
Returns:
Type | Description |
---|---|
Set[str]
|
The set of known tenant IDs. |
get_web_wrapper_position(web_container)
¤
Check if tenant web wrapper is installed in container and where.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
web_container
|
Web container to inspect. |
required |
Returns:
Type | Description |
---|---|
Optional[int]
|
typing.Optional[int]: The index at which the wrapper is located, or |
Source code in asab/web/tenant/service.py
install(web_container)
¤
Apply tenant context wrappers to all web handlers in the web container.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
web_container
|
Web container to add tenant context to. |
required |
Source code in asab/web/tenant/service.py
is_tenant_known(tenant)
¤
Check if the tenant is among known tenants.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
tenant
|
str
|
Tenant ID to check. |
required |
Returns:
Type | Description |
---|---|
bool
|
Whether the tenant is known. |
Source code in asab/web/tenant/service.py
asab.web.tenant.allow_no_tenant(handler)
¤
Allow receiving requests without tenant parameter.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
handler
|
Web handler method |
required |
Returns:
Type | Description |
---|---|
Wrapped web handler that allows requests with undefined tenant. |