Django: a pattern for settings-configured API clients

Step by step!

Here’s an example of a common pattern in Django projects:

from acme.api import APIClient
from django.conf import settings

acme_client = APIClient(api_key=settings.ACME_API_KEY)


def order_anvil() -> None:
    acme_client.anvils.order(...)

An API client is instantiated as a module-level variable based on some settings. This approach has some drawbacks:

Here’s an alternative pattern that avoids these problems:

from functools import cache

from acme.api import APIClient
from django.conf import settings
from django.core.signals import setting_changed
from django.dispatch import receiver


@cache
def get_acme_client() -> APIClient:
    return APIClient(api_key=settings.ACME_API_KEY)


@receiver(setting_changed)
def reset_acme_client(*, setting, **kwargs):
    if setting == "ACME_API_KEY":
        get_acme_client.cache_clear()


def order_anvil() -> None:
    get_acme_client().anvils.order(...)

Notes:

This pattern requires a bit more code, but it is the easiest way to avoid the previous problems. It’s used for several settings-controlled objects inside Django. For example, the loading of password hashers:

@functools.lru_cache
def get_hashers(): ...


@functools.lru_cache
def get_hashers_by_algorithm(): ...


@receiver(setting_changed)
def reset_hashers(*, setting, **kwargs):
    if setting == "PASSWORD_HASHERS":
        get_hashers.cache_clear()
        get_hashers_by_algorithm.cache_clear()

Fin

May your instantiations be lazy and automatically reload,

—Adam


Learn how to make your tests run quickly in my book Speed Up Your Django Tests.


Subscribe via RSS, Twitter, Mastodon, or email:

One summary email a week, no spam, I pinky promise.

Related posts:

Tags: