Hello Mike,
I saw your ticket a few days ago, thanks for bringing it up here.
> While using call_command (which simplifies calling management commands), it occurred to me that the API is a little strange. It currently is designed to work like this:
>
> call_command('my_command', *args, **kwargs)
The API is designed like this because it performs the equivalent of `django-admin my_command [arg] [arg] […] [--kwarg] [--kwarg] […]`.
> The problem here is that you pass a string into your command, and then Django does the magic of converting that into something that can be imported, and then uses argparse to parse args and kwargs. I think a better API would be:
>
> from my_project.my_app.management.commands import my_command
> call_command(my_command, *args, **kwargs)
In general I would suggest to factor your business logic into a function that you can call independently from the management command. This is better for testing as well.
Then the management command is just a thin wrapper that validates the command line parameters and calls that function with corresponding arguments.
> There are three big advantages of this. First, if you ever change the name of your command, a good IDE can realize that it needs to update the import statements, and that'll happen automatically. This is good and important in larger projects where you can't keep all the code in your head.
Management commands are usually called from outside the Python project. If you rename a management command, “Find -> Replace All” in your source code is probably going to take less time than updating all external scripts or procedures that depend on it.
> Second, this allows code completion from your IDE, making it less likely to make a typo or a mistake. Another good thing.
Fair enough. [I don’t use an IDE myself. I write tests ;-)]
> Third, this reduces the amount of string to module importing that Django does, and less magic is generally good.
We won’t gain much because django-admin my_command will still need to find my_command in one of the installed apps.
> In terms of process, I propose we follow the standard deprecation process. At first, it should accept either input, and issue a warning. Over time, it should only allow modules.
I’m -1 on removing the Python API that’s equivalent to `django-admin my_command`. It’s needed for testing management commands that override other management commands.
An alternative would be to split call_command() in two functions: one that locates the command and one that figures out the default values of the arguments. The latter is the piece of code you want to reuse.
> The bug I filed was closed saying that, there wasn't "sufficient justification for the additional complexity that would be required." But this should only take a few lines of code to check what the argument is (string or module), and then to do the conversion.
Django used to have lots of APIs that accepted multiple types of arguments. We tend to remove them when possible because they’re a source of bugs and confusions and also because there should be one way to do each task. I’m -0 on taking the opposite route here. This is a theoretical argument that I’ll happily withdraw if a quick type check is the practical option :-)
--
Aymeric.