[Python] Read the source code of Bottle Part 2

This article is a continuation of Last time, but you should be able to read it alone without any problems. Well, it's an article for my own organization ...

Honmaru Bottle class

Finally, the Bottle class of the castle tower. First of all, from the last time, from the __call__ method.

bottle.py



class Bottle(object):

    def __call__(self, environ, start_response):
        """ Each instance of :class:'Bottle' is a WSGI application. """
        return self.wsgi(environ, start_response)

    def wsgi(self, environ, start_response):
        """ The bottle WSGI-interface. """
        try:
            out = self._cast(self._handle(environ))
            # rfc2616 section 4.3
            if response._status_code in (100, 101, 204, 304) or environ['REQUEST_METHOD'] == 'HEAD':
                if hasattr(out, 'close'): out.close()
                out = []
            start_response(response._status_line, response.headerlist)
            return out

In other words, the message body part of the response is generated from self._cast (self._handle (environ)). Since self.cast () is the process of encoding data into a format suitable for HTTP messages, the response content is actually created withself._handle (environ).

This is a little long. So I'll try to make it refreshing except for the except block.

bottle.py



#: A thread-safe instance of :class:`LocalResponse`. It is used to change the
#: HTTP response for the *current* request.
response = LocalResponse()

class Bottle(object):

    def _handle(self, environ):
        path = environ['bottle.raw_path'] = environ['PATH_INFO']
        if py3k:
            environ['PATH_INFO'] = path.encode('latin1').decode('utf8', 'ignore')

        environ['bottle.app'] = self
        request.bind(environ)
        response.bind()

        try:
            while True: # Remove in 0.14 together with RouteReset
                out = None
                try:
                    self.trigger_hook('before_request')
                    route, args = self.router.match(environ)
                    environ['route.handle'] = route
                    environ['bottle.route'] = route
                    environ['route.url_args'] = args
                    out = route.call(**args)
                    break
                
                finally:
                    if isinstance(out, HTTPResponse):
                        out.apply(response)
                    try:
                        self.trigger_hook('after_request')

        return out

Response = LocalResponse () suddenly appeared here, but the parent class BaseResponse of the LocalResponse class is defined below.

bottle.py


class BaseResponse(object):
    """ Storage class for a response body as well as headers and cookies.

        This class does support dict-like case-insensitive item-access to
        headers, but is NOT a dict. Most notably, iterating over a response
        yields parts of the body and not the headers. #The following comments are omitted
    """

    default_status = 200
    default_content_type = 'text/html; charset=UTF-8'

    def __init__(self, body='', status=None, headers=None, **more_headers):
        self._cookies = None
        self._headers = {}
        self.body = body

It seems to hold information such as cookies, headers, and bodies. Don't chase any further for now.

Now, Bottle () ._ handler () is

route, args = self.router.match(environ)
out = route.call(**args)
return out

That part is the key, and it seems that the routing process is being performed here. Here, self.router meansRouter ()(defined in Bottle.__ init__), so let's take a look at this class.

bottle.py



class Router(object):
    """ A Router is an ordered collection of route->target pairs. It is used to
        efficiently match WSGI requests against a number of routes and return
        the first target that satisfies the request. The target may be anything,
        usually a string, ID or callable object. A route consists of a path-rule
        and a HTTP method.

        The path-rule is either a static path (e.g. `/contact`) or a dynamic
        path that contains wildcards (e.g. `/wiki/<page>`). The wildcard syntax
        and details on the matching order are described in docs:`routing`.
    """

    def __init__(self, strict=False):
        self.rules = []  # All rules in order
        self._groups = {}  # index of regexes to find them in dyna_routes
        self.builder = {}  # Data structure for the url builder
        self.static = {}  # Search structure for static routes
        self.dyna_routes = {}
        self.dyna_regexes = {}  # Search structure for dynamic routes
        #: If true, static routes are no longer checked first.
        self.strict_order = strict
        self.filters = {
            're': lambda conf: (_re_flatten(conf or self.default_pattern),
                                None, None),
            'int': lambda conf: (r'-?\d+', int, lambda x: str(int(x))),
            'float': lambda conf: (r'-?[\d.]+', float, lambda x: str(float(x))),
            'path': lambda conf: (r'.+?', None, None)
        }

    def match(self, environ):
        """ Return a (target, url_args) tuple or raise HTTPError(400/404/405). """
        verb = environ['REQUEST_METHOD'].upper()
        path = environ['PATH_INFO'] or '/'

        if verb == 'HEAD':
            methods = ['PROXY', verb, 'GET', 'ANY']
        else:
            methods = ['PROXY', verb, 'ANY']

        for method in methods:
            if method in self.static and path in self.static[method]:
                target, getargs = self.static[method][path]
                return target, getargs(path) if getargs else {}
            elif method in self.dyna_regexes:
                for combined, rules in self.dyna_regexes[method]:
                    match = combined(path)
                    if match:
                        target, getargs = rules[match.lastindex - 1]
                        return target, getargs(path) if getargs else {}

Around here, it's a little messy and it's hard to understand what the target ofreturn target, get args (path)is, but as you can see fromroute, args = self.router.match (environ) , The Route instance is returned, isn't it?

In other words, before you know it, Route () is registered as self.static [method] [path].

Before you know it! ?? </ b>

If you look at the sources below, you'll gradually see the big picture!

bottle.py



class Bottle(object):

    def route(self,
              path=None,
              method='GET',
              callback=None,
              name=None,
              apply=None,
              skip=None, **config):
        """ A decorator to bind a function to a request URL. Example::

                @app.route('/hello/<name>')
                def hello(name):
                    return 'Hello %s' % name

            The ``<name>`` part is a wildcard. See :class:`Router` for syntax
            details.
        """
        if callable(path): path, callback = None, path
        plugins = makelist(apply)
        skiplist = makelist(skip)

        def decorator(callback):
            if isinstance(callback, basestring): callback = load(callback)
            for rule in makelist(path) or yieldroutes(callback):
                for verb in makelist(method):
                    verb = verb.upper()
                    route = Route(self, rule, verb, callback, name=name, plugins=plugins, skiplist=skiplist, **config)
                    self.add_route(route)
            return callback

        return decorator(callback) if callback else decorator


    def add_route(self, route):
        """ Add a route object, but do not change the :data:`Route.app`
            attribute."""
        self.routes.append(route)
        self.router.add(route.rule, route.method, route, name=route.name)
        if DEBUG: route.prepare()

So we


@app.route('/hello/<name>')
    def hello(name):
        return 'Hello %s' % name

By attaching a decorator, when a request flies


route = Route(self, rule, verb, callback, name=name, plugins=plugins, skiplist=skiplist, **config)
self.add_route(route)

And you gave the Router instance information about the Route instance.

Somehow my head got angry ...

Response data

What I learned from the above

route, args = self.router.match(environ)
out = route.call(**args)
return out  #Includes response data!

The route in is an instance of the Route (although it seems obvious), and the Route (). Call () contains the desired response data!

So if you look at the definition of the Route class

bottle.py



class Route(object):
    """ This class wraps a route callback along with route specific metadata and
        configuration and applies Plugins on demand. It is also responsible for
        turing an URL path rule into a regular expression usable by the Router.
    """

    def __init__(self, app, rule, method, callback,
                 name=None,
                 plugins=None,
                 skiplist=None, **config):
        #: The application this route is installed to.
        self.app = app
        self.callback = callback
        #: A list of route-specific plugins (see :meth:`Bottle.route`).
        self.plugins = plugins or []

    @cached_property
    def call(self):
        """ The route callback with all plugins applied. This property is
            created on demand and then cached to speed up subsequent requests."""
        return self._make_callback()

    def _make_callback(self):
        callback = self.callback
        for plugin in self.all_plugins():
            try:
                if hasattr(plugin, 'apply'):
                    callback = plugin.apply(callback, self)
                else:
                    callback = plugin(callback)
            except RouteReset:  # Try again with changed configuration.
                return self._make_callback()
            if not callback is self.callback:
                update_wrapper(callback, self.callback)
        return callback

In other words, well, the plug-in is sandwiched, and the important part is self.callback, but this one is in Bottle.route in the first place

bottle.py



        def decorator(callback):
            if isinstance(callback, basestring): callback = load(callback)
            for rule in makelist(path) or yieldroutes(callback):
                for verb in makelist(method):
                    verb = verb.upper()
                    route = Route(self, rule, verb, callback, name=name, plugins=plugins, skiplist=skiplist, **config)
                    self.add_route(route)
            return callback

        return decorator(callback) if callback else decorator

You just passed the callback that came out in. this is

@app.route('/hello/<name>')
    def hello(name):
        return 'Hello %s' % name

It corresponds to the hello function in, which is also quite natural, but the response data is generated according to the function defined here.

Chan-chan.

somebody help

So I finally got the whole picture (although I've ignored the logging and error handling parts).

But the question is

from bottle import route

I have no idea why the route decorator is loaded. It feels like the decorator function defined in the Route class is registered in the environment under the name route.

If anyone understands, please let me know ... crying

Recommended Posts

[Python] Read the source code of Bottle Part 2
[Python] Read the source code of Bottle Part 1
[Python] Read the Flask source code
[Python3] Rewrite the code object of the function
[Python] Get the character code of the file
Why the Python implementation of ISUCON 5 used Bottle
the zen of Python
Code for checking the operation of Python Matplotlib
Convert the character code of the file with Python3
[Python + OpenCV] Whiten the transparent part of the image
Template of python script to read the contents of the file
Explanation of the concept of regression analysis using python Part 2
Cut a part of the string using a Python slice
Let's break down the basics of TensorFlow Python code
Get the return code of the Python script from bat
Explanation of the concept of regression analysis using Python Part 1
Towards the retirement of Python2
About the ease of Python
Explain the code of Tensorflow_in_ROS
2.x, 3.x character code of python
About the features of Python
Source installation and installation of Python
Basics of Python × GIS (Part 1)
I downloaded the python source
The Power of Pandas: Python
I just changed the sample source of Python a little.
The story of how the Python bottle worked on Sakura Internet
The process of making Python code object-oriented and improving it
Get the source of the page to load infinitely with python.
Basics of Python x GIS (Part 3)
The story of Python and the story of NaN
[Python] The stumbling block of import
First Python 3 ~ The beginning of repetition ~
Existence from the viewpoint of Python
pyenv-change the python version of virtualenv
Change the Python version of Homebrew
Have python read the command output
[Python] Understanding the potential_field_planning of Python Robotics
Review of the basics of Python (FizzBuzz)
Basics of Python x GIS (Part 2)
About the basics list of Python basics
Learn the basics of Python ① Beginners
Count Source lines of code (SLOC)
Wrap (part of) the AtCoder Library in Cython for use in Python
Follow the flow of QAOA (VQE) at the source code level of Blueqat
Let's measure the test coverage of pushed python code on GitHub.
[PEP8] Take over the Python source code and write it neatly
First python ② Try to write code while examining the features of python
Read the standard output of a subprocess line by line in Python
I wrote the code to write the code of Brainf * ck in python
Let's summarize the degree of coupling between modules with Python code
Installation of Visual studio code and installation of python
View the implementation source code in iPython
Change the length of Python csv strings
Check the behavior of destructor in Python
[Python3] Understand the basics of Beautiful Soup
How to crop the lower right part of the image with Python OpenCV
Pass the path of the imported python module
The story of making Python an exe
Implement part of the process in C ++
Learning notes from the beginning of Python 1