Opened 12 months ago
Closed 12 months ago
#35926 closed New feature (wontfix)
Support capturing the remainder of a command-line arguments in BaseCommand
| Reported by: | Daniel Quinn | Owned by: | |
|---|---|---|---|
| Component: | Core (Management commands) | Version: | dev |
| Severity: | Normal | Keywords: | unknown args |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
I wanted to write a management command that would accept a few known arguments and then capture the remaining arbitrary arguments passed as a separate value. For example:
./manage.py mycommand --expected=argument --something-arbitrary -x -y -z
So --expected-argument would be defined in .add_arguments(), but the other args would just be available as something like options["remainder"] or whatever.
This is usually done by calling parse_known_args() rather than the more common .parse_args(), but unfortunately there's currently no way to cleanly indicate this with BaseCommand. The only way seems to be to copy/paste the entirety of BaseCommand.run_from_argv() and then change where.parse_args() is invoked:
def run_from_argv(self, argv):
"""
Copypasta from the parent class, so I can change `.parse_args()` to
`.parse_known_args()`.
"""
self._called_from_command_line = True
parser = self.create_parser(argv[0], argv[1])
# Change --------------------------------------------------------------
options, remainder = parser.parse_known_args(argv[2:])
cmd_options = vars(options)
cmd_options["remainder"] = remainder
# /Change -------------------------------------------------------------
# Move positional args out of options to mimic legacy optparse
args = cmd_options.pop("args", ())
handle_default_options(options)
try:
self.execute(*args, **cmd_options)
except CommandError as e:
if options.traceback:
raise
# SystemCheckError takes care of its own formatting.
if isinstance(e, SystemCheckError):
self.stderr.write(str(e), lambda x: x)
else:
self.stderr.write("%s: %s" % (e.__class__.__name__, e))
sys.exit(e.returncode)
finally:
try:
connections.close_all()
except ImproperlyConfigured:
# Ignore if connections aren't setup at this point (e.g. no
# configured settings).
pass
Obviously that's not ideal.
One option might be to just use .parse_known_args() and then allow the user to indicate whether they want to capture the remainder or not, and if so, as what attribute, but I have no strong feelings about implementation.
Change History (1)
comment:1 by , 12 months ago
| Keywords: | unknown args added; management command remainder removed |
|---|---|
| Resolution: | → wontfix |
| Status: | new → closed |
| Version: | 5.1 → dev |
Hello Daniel, thank you for taking the time to create this ticket. I see two sides in your request:
Side A
As a Django Fellow, I think that the provided example seems a very specific need arising from a niche use case. I don't think this applies to the broader ecosystem, and Django is a framework designed to offer robust and accurate solutions for common scenarios. Furthermore, as a seasoned developer, I think that allowing any amount of unknown named params in a command is a bad programming pattern, so I would advice against that pattern.
What you could do instead, is to allow any number of arguments associated with a single argument name that is defined in your parser, something similar to what the docs shows for custom management commands when defining nargs for
polls_ids:(Yes, this limits the type of the nargs but I think that is a good thing!)
Side B
Assuming that we consider the provided use case and want to build a solution, I think the best approach would be, in your code, to monkeypatch
CommandParserto be replaced with a custom class that reimplementsparse_argsand stores the unknown args to do something with them when needed:django/core/management/base.py
Given the above, I'll close the ticket accordingly, but if you disagree, you can consider starting a new conversation on the Django Forum, where you'll reach a wider audience and likely get extra feedback. More information in the documented guidelines for requesting features.