The “backend” pattern in Django settings

In the context of Django reusable apps, I often need to provide a way for the user to configure a connection (or set of) to some database or third-party service, and optionally be able to substitute the driver/adapter class with a custom one.

In that case I’ve settled on what I call the “backend” pattern (not sure if there is a generally-accepted name for this), similar to what Django already does with database backends.

It allows you to define the connections as follows:

PROCESS_CONTROLLERS = {
    'default': {
        'BACKEND': 'project.backends.TCPIPConnector',
        'OPTIONS': {
            'host': 'process-controller.example.com'
        }
    },
    'backup': {
        'BACKEND': 'project.backends.SerialConnector',
        'OPTIONS': {
            'port': '/dev/ttyUSB0'
        }
    }
}

And then get an instance of the BACKEND, instantiated with all the OPTIONS passed as **kwargs by calling it from anywhere in the code.

main_process_controller = get_and_instantiate_backend_from_settings('PROCESS_CONTROLLERS')

Here’s the implementation:

def get_and_instantiate_backend_from_settings(backend_type: str, key: str = "default"):
    """
    Returns a backend instance from a Django settings structure as follows:

    SOME_BACKEND_TYPE = {
        'backend_key': {
            'BACKEND': 'import.path.to.backend',
            'OPTIONS: {}  # optional dict of kwargs passed to backend constructor
        }
    }

    So, `get_and_instantiate_backend_from_settings('SOME_BACKEND_TYPE', 'backend_key')`
    would import `import.path.to.backend`, call it with `OPTIONS` passed as kwargs,
    and returns the resulting instance.
    """
    try:
        configs = getattr(settings, backend_type)
    except AttributeError:
        raise ImproperlyConfigured(f"settings.{backend_type} is undefined.")

    if config := configs.get(key):
        if backend_import_path := config.get("BACKEND"):
            try:
                backend_class = import_string(backend_import_path)
            except ImportError as e:
                raise ImproperlyConfigured(
                    f'settings.{backend_type}["{key}"]["BACKEND"] could not be imported.'
                ) from e

            backend_kwargs = config.get("OPTIONS", {})
            return backend_class(**backend_kwargs)
        else:
            raise ImproperlyConfigured(
                f'settings.{backend_type}["{key}"]["BACKEND"] does not exist or is falsy.'
            )
    else:
        raise ImproperlyConfigured(
            f'settings.{backend_type}["{key}"] does not exist or is falsy.'
        )
 
0
Kudos
 
0
Kudos

Now read this

Zero-downtime Django database change strategies

Let’s say we have the following model and we have the current version of the application already running and writing to it: class LogRecord(models.Model): timestamp = models.DateTimeField(auto_now_add=True) message = models.TextField()... Continue →