Alternative formatters for generated code, like `ruff format`?

Hello everyone,

I was wondering if there’s been any discussion about supporting different code formatters in Django, besides black?

For instance, ruff format seems to be a good and quick alternative lately. Maybe these functions in Django could be updated to support more than just black_path in the future?

I know that if you prefer a different formatter than black, you can use it in a pre-commit hook. However, having black run automatically on makemigrations has been really helpful for me and I’ve appreciated it. Also, the function names end with -s (like find_formatters, run_formatters), suggesting the possibility of supporting multiple formatters.

What are your thoughts on this?

If I wanted to use ruff here I’d write a wrapper script (shim) called black and put that on my path.

It’s a bit sad if we do need to add options to Django here. It took something like 3years to get Black agreed to and in, and the promise was “No more discussions about formatting”. Having to handle implementation fragmentation seems quite a lot like the old can of worms. So, I guess I’d see how far we could lean into “Just use a shim” before wanting to add options here.

The other approach would be all the options, but that adds a lot of complexity to Django, largely duplicating what’s already possible with shell tools. :thinking:

I agree with Carlton. I would hope ruff provides such a shim as an installable package.

Thanks for this interesting workaround. I hadn’t considered that.

I completely understand that from a Django maintainer’s perspective, uniformity is key. However, for a regular developer working on multiple Django projects, each with different formatting requirements, the situation becomes more challenging. Dealing with shims or manipulating the PATH could become cumbersome.

That’s an interesting point. I might have missed something in the DEPs and related discussions, but could you provide some context regarding this promise? Was it specifically about the formatting of the Django codebase, or did it also encompass “user space”?

That would be ideal. However, there’s an issue. The current implementation in Django hardcodes the black --fast command. Since ruff format doesn’t have a --fast option (it’s designed to be fast by default), any shim would need to either adapt to or ignore this parameter. So I doubt that such a shim will ever be provided by the ruff maintainers themselves.

In response, I’d be satisfied with a simple FORMATTER setting, "black --fast" by default, which could be executed as is. It could be also overridden in a hypothetical pyproject.toml section:

formatter = "ruff format"

Or in

FORMATTER = "ruff format"

These options might deviate from the current ideology of tweaking Django’s setup. However, as far as I can see, environment variables are utilized in some parts of the codebase to adjust the behavior of CLI commands, so having it in a .env file or passing it inline (FORMATTER="ruff format" ./ makemigrations) seems legitimate.

It’s only a nicety that Django calls Black to format certain files. The first way to run a formatter is always via global execution with e.g. pre-commit, and maybe an editor integration. I think we’d only want to consider any way of supporting ruff format when it’s not alpha and there’s clear community demand.

Did you ask? Also sounds like quite a small thing, you could try making it and offering for adoption.

Django does not read pyproject.toml and I wouldn’t like it to start doing so.

Makes sense. There’s definitely not much demand for ruff format right now.
Actually, I started thinking about this after setting up a new project and encountering a failure with ./ makemigrations due to a broken black shim in my global environment, linked to a no longer existing Python 3.7 installation. Of course, it’s a problem of my local environment, but that was the point when “nicety” became a “nasty default side effect”.

To be honest, no. I thought about raising a question about that shim, but then I found this discussion about Alembic post-write hooks. Learning about this approach made me reconsider my initial thought about requesting a shim for ruff.

Actually, the reason I brought up this topic is because of the find_formatters/run_formatters naming in the codebase, which seems to suggest the possibility of extensions. (Otherwise, they might have been named find_black/run_black, right? :smile:)

So, if we imagine a future where ruff format becomes popular among Django developers, how do you think Django might adapt to this change? For example, could the run_formatters function be altered to support multiple formatters, evolving from its current form:

def run_formatters(written_files, black_path=(sentinel := object())):

to something like this:

def run_formatters(
    black_path=(sentinel := object()),
    ruff_format_path=(sentinel := object()),

with various if statements to manage the different formatters? Or perhaps Django could adopt a method similar to Alembic, not running any formatter by default but allowing developers to define their own hooks?

I think it would be built in support or nothing. Adding a hook when there are two practical options seems like overkill.

I would say let’s see how popular ruff’s formatter ends up being, and when it is considered stable. We only adopted black in Django after its years of “beta” status, the same may happen here.

Hi, I’d like to reopen this topic. I don’t have any numbers on ruff formatter adoption, but it seems to me there are projects switching from black to ruff and having builtin support in Dajngo makes sense to me.

Don’t even need a wrapper script, just add an alias to your shell rc file alias black=ruff :thinking:

Django searches for ruff with shutil.which and I’m not sure it works with alias. More importantly, django calls black with --fast option not supported by ruff format, so the wrapper needs to remove this from args. This is the path we are going in our project but I find it quite strange to force people to have such a shim in their path.

There’s no forcing going on here. You want to use a feature built for Black with an alternative, beta formatter.

Despite Riff’s growing popularity, I think we should at least wait until it leaves beta. Django’s slow and steady release cycle means we can’t quickly adapt to, say, a change in its CLI.

Sorry, I didn’t want to sound rude when using the word “force”. Anyway, ruff 0.3, soon to be released, is removing the “beta” label, see Ruff v0.3 by MichaReiser · Pull Request #10151 · astral-sh/ruff · GitHub

While I understand the conservative approach of Django (and you are right about the CLI stability), it feels to me ruff is a tool that makes python ecosystem so much better that it deserves first-class support (I’m so glad we got rid of the complexity of flake8, isort, pyflake and I don’t even remember all the tools and the slowness of them).

I would still rather Django move slowly here and use established community tools.

Ruff is quicker. Is that speed difference even noticeable at the end of a startproject or makemigrations command? It’s not clear it really is. I don’t think so much better is really the case.

1 Like

I’ve found myself today wishing that run_formatters() was extendable:

I tried to see how to get Django to run isort on generated migrations and it looks like the only option was to shim “black” or to monkey patch run_formatters() ?

Yeah, you’d have to do that currently.

Exposing API there would be a new feature.

Yeah. I’d rather we treated this feature as a “nice to have” cleanup of auto-generated code, than a way to run loads of tools to enforce your project’s standards. But I guess a new overridable API is probably the cleanest, most future-proof solution.

Yes, it was only added because we moved to Black for Django itself.

I’m half-inclined towards not adding anything here, instead pointing folks to pre-commit and the like, as the appropriate place to run your linters/formatters.

(I don’t feel too strongly either way, but what game are we in, is at least worth thinking about.)

I also don’t like the idea of maintaining an API here (I rather rip out black support again). As Carlton said this is something pre-commit would fix for you anyways.

I was almost going to suggest adding an API (whether it be via settings / signal / whatever) but I thought: you know what? monkey patching isn’t so bad here as it’s not a critical feature merely a bolt on to personal dev workflow.

PS: I detest commit hooks :stuck_out_tongue:

1 Like

You can use pre-commit as a tool manager, without the commit hook feature. Then you’d run pre-commit run --all or similar any time you want to format.

And anyway, there are other options too for managing code quality tools.

Yeah, I get that too. I would be happy with that world as well. Also, so many devs run formatters in their text editors already, so that’s another path.