#33205 closed Bug (fixed)
call_command() fails when required mutually exclusive arguments use the same `dest`.
| Reported by: | Peter Law | Owned by: | Hasan Ramezani |
|---|---|---|---|
| Component: | Core (Management commands) | Version: | 3.2 |
| Severity: | Normal | Keywords: | |
| Cc: | Hasan Ramezani | Triage Stage: | Ready for checkin |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
I have a command which accepts two different ways to specify a time -- either as a timestamp or as a duration in the future:
pause (--for duration | --until time)
class Command(BaseCommand): def add_arguments(self, parser) -> None: group = parser.add_mutually_exclusive_group(required=True) group.add_argument('--for', dest='until', action='store', type=parse_duration_to_time) group.add_argument('--until', action='store', type=parse_time) def handle(self, until: datetime, **_): pass
This works fine on the command line, however there doesn't seem to be a way to make this work through call_command. Specifically there are two sides to the failure:
- while I can provide an
untilvalue (as a string, which is processed byparse_time) there is no mechanism to pass aforvalue if that's how I want to spell the input - the
forvalue is always required and attempts to parse the (string)untilvalue passed, which then errors since the input formats are very different
Change History (11)
comment:1 by , 4 years ago
| Cc: | added |
|---|---|
| Component: | Uncategorized → Core (Management commands) |
| Summary: | call_command fails when required mutually exclusive group arguments use the same `dest` → call_command() fails when required mutually exclusive arguments use the same `dest`. |
| Triage Stage: | Unreviewed → Accepted |
| Type: | Uncategorized → Bug |
follow-up: 4 comment:2 by , 4 years ago
I can create a patch to fix the two above-mentioned issues but using the command with both options together:
management.call_command('pause', **{'for': '1', 'util': '1'})
won't raise an error because both options use the same dest. I will investigate it more.
I don't know do we have to support passing both dest and arg name as keyword arguments? in the example of this ticket, if we call management.call_command('pause', until='1'), it should be considered as util arg or for(because dest of for is util as well)
comment:3 by , 4 years ago
Ah, interesting, I wasn't aware that those other spellings worked! I'd be happy to switch to using those spellings instead (I've confirmed they work for my original case).
Perhaps just documenting this limitation (and the fact that those spellings work as alternatives) and/or improving the related error messages could be a way to go here?
follow-up: 5 comment:4 by , 4 years ago
Replying to Hasan Ramezani:
I don't know do we have to support passing both dest and arg name as keyword arguments? in the example of this ticket, if we call
management.call_command('pause', until='1'), it should be considered asuntilarg orfor(becausedestofforisuntilas well)
We should support option names as documented:
** options
named options accepted on the command-line.
so
management.call_command('pause', until='1')should work the same as callingpause --until 1management.call_command('pause', **{'for': '1'})should work the same as callingpause --for 1management.call_command('pause', **{'for': '1', 'until': '1'})should work the same as callingpause --for 1 --until 1and raise an exception
comment:5 by , 4 years ago
Replying to Mariusz Felisiak:
I am not sure about your second example:
management.call_command('pause', **{'for': '1'})should work the same as callingpause --for 1
Based on the documentation, it seems we have to pass dest as keyword argument name when we define dest for arguments.
Some command options have different names when using call_command() instead of django-admin or manage.py. For example, django-admin createsuperuser --no-input translates to call_command('createsuperuser', interactive=False). To find what keyword argument name to use for call_command(), check the command’s source code for the dest argument passed to parser.add_argument().
Also, when Django adds required arguments in call command, it search for dest in options.
comment:6 by , 4 years ago
You're right, sorry, I missed "... check the command’s source code for the dest argument passed to parser.add_argument().". In that case I would raise an error that passing dest with multiple arguments via **options is not supported.
comment:7 by , 4 years ago
| Owner: | changed from to |
|---|---|
| Status: | new → assigned |
comment:9 by , 4 years ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
Thanks for the report. The following calls work as expected for me :
however I confirmed an issue when passing arguments in keyword arguments:
management.call_command('pause', until='1') management.call_command('pause', **{'for': '1'}) # Using "call_command('pause', for='1')" raises SyntaxErrorThis is caused by using
destfor mapping**optionsto arguments, see call_command().