An in-depth understanding of flask_script and Manager for flask

Keywords: Python

@TOC
The Manager source code in flask is as follows:

class Manager(object):
    """
    Controller class for handling a set of commands.

    Typical usage::

        class Print(Command):

            def run(self):
                print "hello"

        app = Flask(__name__)

        manager = Manager(app)
        manager.add_command("print", Print())

        if __name__ == "__main__":
            manager.run()

    On command line::

        python manage.py print
        > hello

As I can see from the above, we have defined a class that inherits Command and overrides the run method.It can be executed from the command line, that is, Command provides us with an interface, and the contents of the run method are executed by the command line.
Now, look at what Command has done for us. The source code is as follows:

class Command(object):
    """
    Base class for creating commands.

    :param func:  Initialize this command by introspecting the function.
    """

    option_list = ()
    help_args = None

    def __init__(self, func=None):
        if func is None:
            if not self.option_list:
                self.option_list = []
            return

        args, varargs, keywords, defaults = inspect.getargspec(func)
        if inspect.ismethod(func):
            args = args[1:]

        options = []

        # first arg is always "app" : ignore

        defaults = defaults or []
        kwargs = dict(izip(*[reversed(l) for l in (args, defaults)]))

        for arg in args:

            if arg in kwargs:

                default = kwargs[arg]

                if isinstance(default, bool):
                    options.append(Option('-%s' % arg[0],
                                          '--%s' % arg,
                                          action="store_true",
                                          dest=arg,
                                          required=False,
                                          default=default))
                else:
                    options.append(Option('-%s' % arg[0],
                                          '--%s' % arg,
                                          dest=arg,
                                          type=text_type,
                                          required=False,
                                          default=default))

            else:
                options.append(Option(arg, type=text_type))

        self.run = func
        self.__doc__ = func.__doc__
        self.option_list = options

    @property
    def description(self):
        description = self.__doc__ or ''
        return description.strip()

    def add_option(self, option):
        """
        Adds Option to option list.
        """
        self.option_list.append(option)

    def get_options(self):
        """
        By default, returns self.option_list. Override if you
        need to do instance-specific configuration.
        """
        return self.option_list

    def create_parser(self, *args, **kwargs):
        func_stack = kwargs.pop('func_stack',())
        parent = kwargs.pop('parent',None)
        parser = argparse.ArgumentParser(*args, add_help=False, **kwargs)
        help_args = self.help_args
        while help_args is None and parent is not None:
            help_args = parent.help_args
            parent = getattr(parent,'parent',None)

        if help_args:
            from flask_script import add_help
            add_help(parser,help_args)

        for option in self.get_options():
            if isinstance(option, Group):
                if option.exclusive:
                    group = parser.add_mutually_exclusive_group(
                        required=option.required,
                    )
                else:
                    group = parser.add_argument_group(
                        title=option.title,
                        description=option.description,
                    )
                for opt in option.get_options():
                    group.add_argument(*opt.args, **opt.kwargs)
            else:
                parser.add_argument(*option.args, **option.kwargs)

        parser.set_defaults(func_stack=func_stack+(self,))

        self.parser = parser
        self.parent = parent
        return parser

    def __call__(self, app=None, *args, **kwargs):
        """
        Handles the command with the given app.
        Default behaviour is to call ``self.run`` within a test request context.
        """
        with app.test_request_context():
            return self.run(*args, **kwargs)

    def run(self):
        """
        Runs a command. This must be implemented by the subclass. Should take
        arguments as configured by the Command options.
        """
        raise NotImplementedError

The following analysis describes the execution process:
1. Other functions add or delete options [list]
2. The create_parser function creates a Command line parser = argparse.ArgumentParser(*args, add_help=False, **kwargs), obtains and saves data from options and help_args, and parser.set_defaults(func_stack=func_stack+(self,)) adds itself to the parser parameters.
3. In the flask application code, we add code such as manager.add_command("db", Print()), passing in an instance object of Command, and add_command creates an instance object of Command and saves it in the namespace of slef._commands or in the value of key_value.

Note >> In the flask application code, we add code such as manager.add_command ("db", "MigrateCommand"), which passes in an instance object of Manager, MigrateCommand - - another Manager object (this object, which has added commands such as migration, etc.), which will then be associated with the current manager object in the flask application.parent)

4. The call method shows that when an instance object of Command is called, it is executed (in this case, the context of the app instance is introduced, and the run method is executed).At this point, we simply look for when the Command instance was called.

At this point, the Manage object wraps the app and executes its own run method. As shown below.

The run method receives command line parameters through sys.argv and submits them to slef.handle for execution.
The handle method creates app_parser = self.create_parser (prog) (this function takes the Commad object) and gets all the app_parser information (func, args, and config).

When is parser executed?

At this point, app_parser is still an ArgumentParser object in argparse.
Still in Manage's create_parser method, app_namespace is executed, remaining_args = app_parser.parse_known_args (args), and _parse_known_args is called inside the method

Here's the main point: in _parse_known_args, the internal function consume_optional first calls self._option_string_actions to map the action class through a string,
On the other hand, the internal function take_action is called to create an instance object of the action (as in the case of Command above).

func_stack is available in app_namespace.dict.
Finally, traverse the for handle in func_stack:, and execute the handle, which may be a Command instance object. After the call, execute the call method, execute the run method, or possibly a func function, which is executed directly.

    def run(self, commands=None, default_command=None):
        """
        Prepares manager to receive command line input. Usually run
        inside "if __name__ == "__main__" block in a Python script.

        :param commands: optional dict of commands. Appended to any commands
                         added using add_command().

        :param default_command: name of default command to run if no
                                arguments passed.
        """

        if commands:
            self._commands.update(commands)

        # Make sure all of this is Unicode
        argv = list(text_type(arg) for arg in sys.argv)
        if default_command is not None and len(argv) == 1:
            argv.append(default_command)

        try:
            result = self.handle(argv[0], argv[1:])
        except SystemExit as e:
            result = e.code

        sys.exit(result or 0)

Posted by Bendude14 on Sat, 20 Jul 2019 15:13:16 -0700