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.pyis 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
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.
When you run
django-admin startproject myproject, you’ll get a directory structure like this:
myproject/ ├─ manage.py ├─ myproject/ │ ├─ __init__.py │ ├─ ...
If we move
myproject/__main__.py, we can replace our calls to
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.