I don’t like Django’s manage.py. My gripes against it are:
- The plethora of files that clutter the root directory of our repos annoys me.
manage.py
is just one more in a long line of those. - In my opinion, there are more “Pythonic” ways to execute code.
In a previous post, I talked about how you can move the code into your project and use packaging tools to create a manage.py
on your PATH
during installation. In this post, we’ll look at another approach using __main__.py
What is __main__.py
(and __main__
)?
The Python docs do a good job of explaining this topic, so I’ll give you the tl;dr here. __main__.py
provides a command line interface to a Python package. It can be executed with python -m mypackage
. Here are some common ones you may have seen in the wild:
python -m pip
python -m venv
python -m json.tool
You may also be surprised to learn that python -m django
can be used in place of the django-admin
command. 🤯
The “magic” here is that each one of those packages has a __main__.py
which defines what it should do when run from the command line.
In each of these, you’ll see a section like this at the bottom of the file:
__name__
is only set to __main__
when it is the entrypoint for execution. If another Python module imports it, __name__
will not equal __main__
. We can use this to define the function we want to call when the file is executed from the command line.
Moving manage.py
When you run django-admin startproject myproject
, you’ll get a directory structure like this:
myproject/
├─ manage.py
├─ myproject/
│ ├─ __init__.py
│ ├─ ...
If we move manage.py
to myproject/__main__.py
, we can replace our calls to manage.py
with python -m myproject ...
.
Is it a good idea?
Despite my personal preference, I’d be hesitant to do this on anything beyond a personal project. While it might not be a Python convention, manage.py
is a Django convention. Following conventions leads to less confusion when onboarding new developers. The existence of the file signals (to Django developers, at least) that this is a Django project. They’ll know what that file means and how to use it. Another benefit is that it’s tab-completable in your shell. Typing python -m myproject ...
for every command is less than ergonomic 😅.
That being said, you might find this technique handy when you publish a library that will get installed in your Python path. In that case, manage.py
won’t get distributed without jumping through some extra packaging hoops. Another nice place for entrypoints is when using shiv
to turn your project into a single-file zipapp. Check out my talk from DjangoCon 2019 if you’re more interested in that.