Opened 3 months ago

Last modified 3 months ago

#29152 new Cleanup/optimization

Allow more control over ArgumentParser initialization in management commands

Reported by: Dmitry Owned by: nobody
Component: Core (Management commands) Version: 2.0
Severity: Normal Keywords:
Cc: Tom Forbes Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Hello everyone.
Noticed today that there is no way to customize CommandParser (ArgumentParser subclass), initialized in management commands (https://github.com/django/django/blob/master/django/core/management/base.py#L227). There is no option (or i was unable to find it - please correct me in this case) to pass any kwargs in CommandParser constructor. The public method create_parser contains some django-related hard-coded add_arguments calls, so overriding it directly leads to some copy paste.

I needed to use some custom formatter for ArgumentParser (https://docs.python.org/3/library/argparse.html?highlight=argparse#argparse.ArgumentDefaultsHelpFormatter) to add default values to help page, but was unable to do this properly. There is also some other usefull keyword arguments available in ArgumentParser.

As a proposal a new method could be added to BaseCommand (let's say BaseCommand.get_parser_instance), for example like this:

def get_parser_instance(self):
        return CommandParser(
            self, prog="%s %s" % (os.path.basename(prog_name), subcommand),
            description=self.help or None,
        )

def create_parser(self, prog_name, subcommand)::
       parser = self.get_parser_instance()
       ... etc

This should not break anything in BaseCommand, but will allow have more control on parser's creation overriding just get_parser_instance method.
Please sorry for mistakes.

Change History (7)

comment:1 Changed 3 months ago by Dmitry

Summary: management commands rigid ArgumentParser initializationmanagement commands - more control on ArgumentParser initialization

comment:2 Changed 3 months ago by Dmitry

Type: New featureCleanup/optimization

comment:3 Changed 3 months ago by Tim Graham

Summary: management commands - more control on ArgumentParser initializationAllow more control over ArgumentParser initialization in management commands
Triage Stage: UnreviewedAccepted

Would a hook to provide some additional keyword arguments be sufficient?

comment:4 in reply to:  3 Changed 3 months ago by Dmitry

Replying to Tim Graham:

Would a hook to provide some additional keyword arguments be sufficient?

Yeah, this will be great, maybe the better way. I was thinking about way to implement this, but was unable to find beautiful enough way to add such hook. Maybe you have some good ideas.

As a reference my idea was something like this:

  1. Add parser_kwargs argument in BaseCommand.init, defaults to {}
            def __init__(self, stdout=None, stderr=None, no_color=False, parser_kwargs=None):
            self.stdout = OutputWrapper(stdout or sys.stdout)
            self.stderr = OutputWrapper(stderr or sys.stderr)
            if no_color:
                self.style = no_style()
            else:
                self.style = color_style()
                self.stderr.style_func = self.style.ERROR
           if not parser_kwargs:
               parser_kwargs = {}
    
  1. use parser_kwargs in create_parser method
        def create_parser(self, prog_name, subcommand):
            """
            Create and return the ``ArgumentParser`` which will be used to
            parse the arguments to this command.
            """
            if not 'prog' in self.parser_kwargs:
                self.parser_kwargs['prog'] = "%s %s" % (os.path.basename(prog_name), subcommand)
            if not 'description' in self.parser_kwargs:
                self.parser_kwargs['description'] = self.help or None
            parser = CommandParser(
                self, **self.parser_kwargs
            )
    

The problem is that in this case you will have two conflicting way to define description (using parser_kwargs or help), but possibly it's not a big issue.

Last edited 3 months ago by Dmitry (previous) (diff)

comment:5 Changed 3 months ago by Tom Forbes

The create_parser method could just pass in all kwargs to CommandParser, rather than having a parser_kwargs instance variable?

def create_parser(self, **kwargs):
    return super().create_parser(my_custom_arg=123)

# In BaseCommand
def create_parser(self, **kwargs):
    kwargs.setdefault('description', ...)
    return CommandParser(self, **kwargs)

Might be a bit more flexible

comment:6 Changed 3 months ago by Tom Forbes

Cc: Tom Forbes added

comment:7 in reply to:  5 Changed 3 months ago by Dmitry

Replying to Tom Forbes:

The create_parser method could just pass in all kwargs to CommandParser, rather than having a parser_kwargs instance variable?

def create_parser(self, **kwargs):
    return super().create_parser(my_custom_arg=123)

# In BaseCommand
def create_parser(self, **kwargs):
    kwargs.setdefault('description', ...)
    return CommandParser(self, **kwargs)

Might be a bit more flexible

I agree, this looks better.

Note: See TracTickets for help on using tickets.
Back to Top