Skip to content

Settings

The following settings are available and will have to be provided in the settings.py module.

CONFIG STORE

Required. Type: dict

This setting holds the configuration for the KV(key/value) store that would be looked up at boot time to pull the necessary configuration and bootstrap various components of this package (like ORM managers etc.,).

Any suitable cache backend compatible with Django's CACHES can be used to provide the required functionality.

Below is an example configuration:

# settings.py
...
CACHES = {
    "tenant_router_config_store": {
        "BACKEND": "django_redis.cache.RedisCache",
        "TIMEOUT": None,
        "LOCATION": "redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}".format(
            REDIS_HOST='your_redis_host',
            REDIS_PORT='your_redis_port',
            REDIS_DB='your_db_index'
        ),
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "SERIALIZER": "django_redis.serializers.json.JSONSerializer"
        }
    }
}
...

Important

Django, by default, associates a timeout (ttl) with all keys stored in a given cache. Since we expect the data in the config store to be persistent (i.e no timeout), the TIMEOUT parameter has to be explicitly set to None.

Note

The tenant_router_config_store is a reserved special key which would be used internally by the library for identifying the cache containing the tenant configuration. If it conflicts with an already existing key, the conflicting key (specified by the user) will have to be changed.

For details about the schema of this configuration, click here.

TENANT_ROUTER_SERVICE_NAME

Required. Type: str

The service name identifier that would be used for construction/de-construction of the keys configured in the KV store.

Example:

...
TENANT_ROUTER_SERVICE_NAME = 'some_string'
...

TENANT_ROUTER_ORM_SETTINGS

Required. Type: dict

A mapping of orm identifier to orm configuration. At a higher level, this orm configuration, behind the scenes, is used for managing connections and routing to the correct database based on the tenant context set by the middleware for a particular request/response cycle.

An application could potentially use multiple ORM libraries like the Django ORM, PyModm/MongoEngine (for MongoDB) etc., So each of these ORMs' should register themselves as part of this configuration to define their respective manager classes and provide a settings key from where they would pick up template configurations which would get replicated across tenants.

The package ships with manager classes for the Django ORM, PyModm orm and Elasticsearch client so that applications can start using this package with minimal effort.

Example:

import os

# assume that in the single-tenant application built, 
# the following configuration was provided.
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "TEST": {
            'DEPENDENCIES': [],
        },

    }
}

MONGO_DB_SETTINGS = {
    "default": {
        "OPTIONS": {...}
    }
}

ES_SETTINGS = {
    "default": {
        "HOSTS": [
            {'host': '', 'port': '', ...},
            {'host': '', 'port': '', ...}
        ],
        "OPTIONS": {
            ...
        }
    }
}


...
# The most basic form of this setting is as follows:
TENANT_ROUTER_ORM_SETTINGS = {
    'django_orm': {
        'SETTINGS_KEY': 'DATABASES'
    },
    'pymodm_orm': {
        'SETTINGS_KEY': 'MONGO_DB_SETTINGS'
    },
    'es_orm': {
        'SETTINGS_KEY': 'ES_SETTINGS'
    }
}
...

# Also if the Django ORM is being used, make sure to configure the 
# DATABASE_ROUTERS setting as follows:
DATABASE_ROUTERS = ['tenant_router.orm_backends.django_orm.router.DjangoOrmRouter']

Note

In the above example, django_orm, pymodm_orm and es_orm are reserved keys since the package already has managers defined for these keys.

If the application uses an ORM library which is not supported out of the box by this package, it is possible to write a new manager class for that ORM and set it up. Also if the default manager provided doesn't account for a custom use-case, it can be overridden as well.

Example:

...
TENANT_ROUTER_ORM_SETTINGS = {
    # overriding the default manager
    'django_orm': {
        'MANAGER': 'full_dotted_path_to_custom_manager_cls',
        'SETTINGS_KEY': 'some_key'
    },
    # providing a new manager for the mongoengine ORM library
    'mongoengine': {
        'MANAGER': 'full_dotted_path_to_custom_manager_cls',
        'SETTINGS_KEY': 'some_key'
    },
}
...

Note

Detailed instructions for how to go about writing a new manager class will be specified as part of the API documentation.

TENANT_ROUTER_PUBSUB_SETTINGS

Required. Type: dict

This setting holds all the information required by the pub/sub backend. This in-turn is used by the package to achieve reactive configuration

Example:

...
TENANT_ROUTER_PUBSUB_SETTINGS = {
    "BACKEND": "tenant_router.pubsub.backends.redis.RedisPubSub",
    "LOCATION": {
        "HOST": "some_host",
        "PORT": "some_port"
    },
    "OPTIONS": {
        "CLIENT_KWARGS": "some_kwargs_to_be_passed to the underlying pub/sub backend"
    }
}
...

Info

Currently the package ships with support for using Redis as the pub/sub backend. Backends for other pub/sub providers would be added in the future

TENANT_ROUTER_WORKER_TYPE

Optional. Default value: sync. Type: str

This setting helps the package to identify the underlying mechanism to be used for spawning the background event listener (thread) and also determines its internal behaviour. The list of valid values are as follows:

  1. sync: The default value. This tells the package to use a proper OS thread for the background event listener.
  2. asgi: This tells the package that the underlying server uses the ASGI protocol and hence the background event listener should be a co-routine instead of an OS thread.

Example:

...
TENANT_ROUTER_WORKER_TYPE = 'sync'
...

TENANT_ROUTER_MIDDLEWARE_SETTINGS

Optional. Default value: {}, Type: dict

The middleware which is responsible for injecting the tenant context in every request can be customized as follows:

  • WHITELIST_ROUTES => A set of route identifiers for which the tenant context need not be injected. In other words, these routes by-pass the middleware. Each route identifier must take one of the following values:

    1. View name => Full dotted path to the view that should be whitelisted.
    2. Route name => The route part of the path instance in urlpatterns.
    3. Url name => The value given to the name parameter, if specified in the path instance. For namespaced urls, it would be of the form namespace_identifier:url_name.

    In order to resolve the request path into one of the above, the django.urls.resolve method is used internally. For more info about how this method behaves, click here.

    Example:

    ## urls.py
    from django.urls import path, include
    ...
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('api/sample/', SampleView.as_view(), name='sample_view')
    ]
    
    ## settings.py
    
    #### In order to whitelist `SampleView` from the above urls.py, any of the following approaches
    #### would work fine.
    ...
    TENANT_ROUTER_MIDDLEWARE_SETTINGS = {
        'WHITELIST_ROUTES': {
            # Full dotted path to view (or)
            'path.to.SampleView',
            # Route part of the `path` instance (or)
            'api/sample/',
            # Url name specified in the `path` instance
            'sample_view'
            # if namespaced, then the above becomes
            'sample_app:sample_view' 
            # where 'sample_app' is the namespace identifier 
        }
    }
    ...
    

  • TENANT_ID_RESOLVER => A callable which will be called with the http request object in order to resolve the tenant identifier. This will subsequently be used to get the corresponding tenant context to be injected for that particular http request.

    Example:

    ## some_module.py
    ...
    def resolve_tenant_id(request):
        # logic to resolve tenant id based on something in the 
        # request object.
        return 'tenant_identifier'
    
    ## settings.py
    ...
    TENANT_ROUTER_MIDDLEWARE_SETTINGS = {
        'TENANT_ID_RESOLVER': 'path.to.some_module.resolve_tenant_id'
    }
    ...
    

TENANT_ROUTER_BOOTSTRAP_SETTINGS

Optional. Default value: []. Type: list

There are a bunch of internal components in the package that get bootstrapped in a particular sequence when Django fires the ready signal.

During this bootstrap phase, the application can potentially configure this setting with a sequence of callables if there is some activity to be performed after the pre-defined sequence gets executed.

Example:

...
TENANT_ROUTER_BOOTSTRAP_SETTINGS = [
    'callable_1', 
    'callable_2',
    ...
]
...

TENANT_ROUTER_CACHE_SETTINGS

Optional. Default value: {}. Type: dict

Since the concept of template and reserved aliases can be extended to caches as well, they can be configured, as applicable, in the following way.

# settings.py
...
TENANT_ROUTER_CACHE_SETTINGS = {
    'RESERVED_ALIASES': set(['some_alias', 'some_other_alias']) 
}
...

To get a detailed understanding of what this means, click here