Flask project is so well known that it needs no introduction. I call it the TOP2 project for python service development, along with django. These two projects have their own strengths and different application scenarios, which require in-depth understanding and mastery. The source code version is 1.1.2, I will use slow reading, do their best to explain it through. The buffet includes:

  • The view parsing
  • The blueprint parsing
  • summary

The view parsing

Flask a simple listening function is as follows:

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        return do_the_login()
    else:
        return show_the_login_form()
Copy the code

If you have too many urls, you need to implement multiple listeners, and your code will get messy. At the same time, for a URL, in the listener function to distinguish HTTP method, different business logic processing. Handling two kinds of logic in a single function is also less consistent with a single responsibility, making the code difficult to maintain.

In this case, you need to use views. Here is an example view:

class CounterAPI(MethodView):

    def get(self):
        return session.get('counter', 0)

    def post(self):
        session['counter'] = session.get('counter', 0) + 1
        return 'OK'

app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
Copy the code

CounterAPI registers instance methods of a class to a URL, automatically separating get and POST methods. Let’s look at the implementation of a View, starting with the base classes of all views:

class View(object):
    
    @classmethod
    def as_view(cls, name, *class_args, **class_kwargs):
        
        def view(*args, **kwargs):
            self = view.view_class(*class_args, **class_kwargs)
            return self.dispatch_request(*args, **kwargs)
        ...
        view.view_class = cls
        view.__name__ = name
        ...
        return view
Copy the code

The as_view function returns a view function in which requests can be sent and processed.

MethodViewType is a metaclass that defines the collection of all HTTP methods supported by the view:

http_method_funcs = frozenset(
    ["get", "post", "head", "options", "delete", "put", "trace", "patch"]
)

class MethodViewType(type):

    def __init__(cls, name, bases, d):
        super(MethodViewType, cls).__init__(name, bases, d)

        if "methods" not in d:
            methods = set()
            ...
            for key in http_method_funcs:
                if hasattr(cls, key):
                    methods.add(key.upper())

            if methods:
                cls.methods = methods
Copy the code

MethodView is a new class created using MethodViewType and the View:

class MethodView(with_metaclass(MethodViewType, View)):

    def dispatch_request(self, *args, **kwargs):
        meth = getattr(self, request.method.lower(), None)
        ...
        return meth(*args, **kwargs)
Copy the code

With_metaclass is designed to be compatible with Python2 syntax and can simply be understood as inheriting from MethodViewType and View

Locate a method based on the http-method of the dispatch_request and execute the method.

View handlers can also add decorators, as shown in the following example:

Class SecretView(View): methods = ['GET'] decorators = [login_required] @classmethod def as_view(cls, name, *class_args, **class_kwargs): ... __name__ = name view.__module__ = cls.__module__ # view = decorator(view) ... Wraps () def login_required(view): @functools.wraps(view) def wrapped_view(**kwargs): if g.user is None: return redirect(url_for('auth.login')) return view(**kwargs) return wrapped_viewCopy the code

The blueprint parsing

View is relatively thin, and large projects are developed in modules, so flask also has the concept of Blueprint. Here is auth.py in the example project Flaskr:

import functools from flask import ( Blueprint, flash, g, redirect, render_template, request, session, url_for ) ... bp = Blueprint('auth', __name__, url_prefix='/auth') @bp.route('/register', methods=('GET', 'POST')) def register(): . @bp.route('/login', methods=('GET', 'POST')) def login(): ... @bp.route('/logout') def logout(): ...Copy the code

A blueprint called Auth is defined, which defines three methods: Register, login, and logout. Blueprints are registered in __init__.py of the app:

def create_app():
    app = ...
    # existing code omitted

    from . import auth
    app.register_blueprint(auth.bp)

    return app
Copy the code

Flask also has a blueprint called Blog, which provides ways to add, delete, change and view blog posts:

bp = Blueprint("blog", __name__)


@bp.route("/")
def index():
    ...
    
@bp.route("/create", methods=("GET", "POST"))
@login_required
def create():
    ...

Copy the code

In this way, it is very convenient to develop programs by modules.

Now that you know how to use Bluerpint, let’s look at how it works.

class Blueprint(_PackageBoundObject):
    
    def __init__(
        self,
        name,
        import_name,
        static_folder=None,
        static_url_path=None,
        template_folder=None,
        url_prefix=None,
        subdomain=None,
        url_defaults=None,
        root_path=None,
        cli_group=_sentinel,
    ):
        _PackageBoundObject.__init__(
            self, import_name, template_folder, root_path=root_path
        )
        self.name = name
        self.url_prefix = url_prefix
        self.subdomain = subdomain
        self.static_folder = static_folder
        self.static_url_path = static_url_path
        self.deferred_functions = []
        if url_defaults is None:
            url_defaults = {}
        self.url_values_defaults = url_defaults
        self.cli_group = cli_group
Copy the code

As shown in Blueprint’s constructor above:

  • Inherits from _PackageBoundObject. _PackageBoundObject is used to implement dynamic loading of local directories. Because blueprints have some template requirements, _PackageBoundObject is inherited.
  • The deferred_functions array is a collection of all views of the blueprint
  • Url_prefix, subdomain, static_folder, and so on are the functional parameters of blueprint modularization

Route decorator for blueprint:

def route(self, rule, **options):
    def decorator(f):
        endpoint = options.pop("endpoint", f.__name__)
        self.add_url_rule(rule, endpoint, f, **options)
        return f

    return decorator

def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
    ...
    self.record(lambda s: s.add_url_rule(rule, endpoint, view_func, **options))

def record(self, func):
    self.deferred_functions.append(func)
Copy the code

The main question here is what is the argument s to the lambda function when adding the view function? Continue to look at the blueprint registration:

Def register_blueprint(self, blueprint, **options): self.blueprints[blueprint.name] = blueprint self._blueprint_order.append(blueprint) first_registration = True Def register(self, options, first_registration) # first_registration=False): self._got_registered_once = True state = self.make_setup_state(app, options, first_registration) for deferred in self.deferred_functions: deferred(state) ...Copy the code

Make_setup_stat creates the BlueprintSetupState object and then executes the method that the blueprint route adds to deferred_Functions. This method is the previous lambda function, and the previous S is the state object.

class BlueprintSetupState(object): def __init__(self, blueprint, app, options, first_registration): #: a reference to the current application self.app = app self.blueprint = blueprint def add_url_rule(self, rule, endpoint=None, view_func=None, **options): """A helper method to register a rule (and optionally a view function) to the application. The endpoint is automatically  prefixed with the blueprint's name. """ if self.url_prefix is not None: if rule: rule = "/".join((self.url_prefix.rstrip("/"), rule.lstrip("/"))) else: rule = self.url_prefix options.setdefault("subdomain", self.subdomain) if endpoint is None: endpoint = _endpoint_from_view_func(view_func) defaults = self.url_defaults if "defaults" in options: defaults = dict(defaults, **options.pop("defaults")) self.app.add_url_rule( rule, "%s.%s" % (self.blueprint.name, endpoint), view_func, defaults=defaults, **options )Copy the code

The association between APP and Blueprint is established in BlueprintSetupState, and the add_URl_rule method of app is used to register blueprint view functions into app.

summary

Flask is a micro framework, but can also (at least) support medium-sized projects. Blueprint and View functions can be used for modularization: The View can differentiate HTTP methods from URLS; Blueprint does a good job of defining subdomains and URL prefixes.