blog Brainstorming django-uv

Tags:
django

Inspired by @adamghill ’s recent dj-toml-settings and webology ’s previous post on renaming django-admin I did a quick brainstorm of a django-uv command.

uvx django-uv

As uv has become more popular, a disconnect has appeared when using uv and django-admin.

When bootstrapping a new project, one often wants to run the equivilant of uvx django startproject but this doesn’t work. By default uvx looks for a command of the same name as the package. The correct command is uvx --from django django-admin which is helpfully suggested by uv.

We can make a small wrapper package for this so that it works as expected.

[project.scripts]
django-uv = "django_uv:main"
from django.core.management import ManagementUtility

def main(argv=None) -> None:
    utility = ManagementUtility(argv)
    utility.execute()

With this simple project, we now have a django-uv command that is equivilant of the django-admin command, saving a bit of typing.

Reading DJANGO_SETTINGS_MODULE

This helps for our intial project bootstrap, but with an existing project, it would be nice to reference our project.

Taking inspiration from adamghill, we could read the values out of our pyproject.toml

[tool.django]
settings = 'path.to.our.settings'

and then set enviornment variables as expected.


from django.core.management import ManagementUtility
from pathlib import Path
from tomllib import load
import os


def find_pyproject(path=Path.cwd() / "pyproject.toml") -> dict:
    if path.exists:
        with path.open("rb") as fp:
            data = load(fp)

    try:
        settings = data["tool"]["django"]
    except KeyError:
        # No tool section found
        return {}

    # Populate our settings value
    if "settings" in settings:
        os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings["settings"])


def main(argv=None) -> None:
    find_pyproject()
    utility = ManagementUtility(argv)
    utility.execute()

except we can’t quite do that, because our uvx command is likely running in a separate python environment. Instead, we wrap it a bit to something like this.

from pathlib import Path
from tomllib import load
from subprocess import call
import os
import sys


def find_pyproject(path=Path.cwd() / "pyproject.toml") -> dict:
    if path.exists:
        with path.open("rb") as fp:
            data = load(fp)

    try:
        settings = data["tool"]["django"]
    except KeyError:
        # No tool section found
        return {}
    else:
        return settings


def main(argv=sys.argv) -> None:
    settings = find_pyproject()
    env = os.environ.copy()
    if "settings" in settings:
        # Copy our parent environment and populate our settings
        env["DJANGO_SETTINGS_MODULE"] = settings["settings"]
        # Then call django-admin from the current project
        call(["uv", "run", "django-admin"] + argv[1:], env=env)
    else:
        # Otherwise, if we don't have a setting, we fallback
        # to calling uvx provided django-admin directly
        from django.core.management import ManagementUtility

        utility = ManagementUtility(argv)
        utility.execute()

Is this ultimately a good idea or useful? I’m not sure. Was still an interesting, quick experiment though.

Examples: