[Python] Read the source code of Bottle Part 1

Read bottle.py

I tried to read the code of Bottle, a web framework of Python. The source code for Bottle is aggregated in bottle.py, so you should read this file.

Click here for GitHub. https://github.com/bottlepy/bottle

The code quoted below is from bottle.py with only the necessary parts. In addition, I am adding comments in places.

It is difficult to follow the details from the beginning, so I would like to grasp the overall flow first.

First entrance: run ()

I would like to follow the source in order from startup. Start Bottle as follows, for example, according to the documentation.

from bottle import route, run, template

@route('/hello/<name>')
def index(name):
    return template('<b>Hello {{name}}</b>!', name=name)

run(host='localhost', port=8080)

Let's start with run (). Again, just in case, the source code is (mostly) quoted.

bottle.py


def run(app=None,
        server='wsgiref',
        host='127.0.0.1',
        port=8080,
        interval=1,
        reloader=False,
        quiet=False,
        plugins=None,
        debug=None,
        config=None, **kargs):

    app = app or default_app()

    if server in server_names:
            server = server_names.get(server)
        if isinstance(server, basestring):
            server = load(server)
        if isinstance(server, type):
            server = server(host=host, port=port, **kargs)
        if not isinstance(server, ServerAdapter):
            raise ValueError("Unknown or unsupported server: %r" % server)

    if reloader:
            lockfile = os.environ.get('BOTTLE_LOCKFILE')
            bgcheck = FileCheckerThread(lockfile, interval)
            with bgcheck:
                server.run(app)
            if bgcheck.status == 'reload':
                sys.exit(3)
        else:
            server.run(app)

in short

app = app or default_app()  #The default is app=None
server = server_names.get(server)  #The default is server='wsgiref'
server.run(app)

That's right. First, let's take a look at the definition of default_app () on the first line.

What is the AppStack class?

The definition part is

apps = app = default_app = AppStack()

so,

bottle.py



class AppStack(list):
    """ A stack-like list. Calling it returns the head of the stack. """

    def __call__(self):
        """ Return the current default application. """
        return self.default

    def push(self, value=None):
        """ Add a new :class:`Bottle` instance to the stack """
        if not isinstance(value, Bottle):
            value = Bottle()
        self.append(value)
        return value
    new_app = push

    @property
    def default(self):
        try:
            return self[-1]
        except IndexError:
            return self.push()

I see. AppStack inherits list. From the side that uses ʻAppStack () (server), the important thing is the call` method.

To summarize the review of the call method,

app = AppStack()
app()

It seems that a Bottle instance will be returned (if it is the default argument). At the same time, it's a mechanism to accumulate it as a list.

I'd like to put together the source of the Bottle class, but as the name suggests, this is the core of Bottle and is long. In the first place, the ʻAppStack` instance is


server.run(app)

It is passed as an argument to the run method of server, and it should be called ʻapp ()after that, so let's checkserver` first.

Server class

Looking back a little,


app = app or default_app()  #The default is app=None
server = server_names.get(server)  #The default is server='wsgiref'
server.run(app)

The story of trying to find out who the server defined in the part of.

bottle.py



server_names = {
    'cgi': CGIServer,
    'flup': FlupFCGIServer,
    'wsgiref': WSGIRefServer,  #This is the default
    'waitress': WaitressServer,
    #The following is omitted
}

It is a dictionary, and each value inherits the ServerAdapter class. Since this is a "parent of form only", I suddenly go to the WSGIRefServer class.

bottle.py


class WSGIRefServer(ServerAdapter):
    def run(self, app):  # pragma: no cover
        from wsgiref.simple_server import make_server
        from wsgiref.simple_server import WSGIRequestHandler, WSGIServer
        import socket

        server_cls = self.options.get('server_class', WSGIServer)
        self.srv = make_server(self.host, self.port, app, server_cls,
                               handler_cls)
        self.port = self.srv.server_port  # update port actual port (0 means random)
        try:
            self.srv.serve_forever()
        except KeyboardInterrupt:
            self.srv.server_close()  # Prevent ResourceWarning: unclosed socket
            raise

ʻApp = AppStack ()passed toserver.run ()is passed to themake_serverfunction. Themake_server function itself is from the python standard library wsgiref`.

https://docs.python.org/ja/3/library/wsgiref.html

Web Server Gateway Interface (WSGI) is a standard interface between web server software and web applications written in Python. Having a standard interface makes it easy to use applications that support WSGI on a number of different web servers.

So, let's take a look at the sample in the wsgiref documentation.


from wsgiref.util import setup_testing_defaults
from wsgiref.simple_server import make_server

def simple_app(environ, start_response):
    setup_testing_defaults(environ)

    status = '200 OK'
    headers = [('Content-type', 'text/plain')]

    start_response(status, headers)

    ret = ["%s: %s\n" % (key, value)
           for key, value in environ.iteritems()]
    return ret

httpd = make_server('', 8000, simple_app)
print "Serving on port 8000..."
httpd.serve_forever()

-Pass the function you defined yourself (simple_app function in the sample) as the third argument to the make_server function! -Then, when a request comes in, you can let that function handle it! -However, prepare two arguments (environ, start_response)!

That's right.

So, looking back at the sources so far, that function (simple_app in the sample) wasBottle ().

It's an instance, not a function! but it's okay! If you have a __call__ instance, you can call it like a function!

It seems to be tedious, but if you organize it, it is a sample


simple_app(environ, start_response)

The act to be executed is

Bottle()(environ, start_response)

Is equivalent to being executed.

That is </ b>

It seems obvious, but the Bottle instance is responsible for the actual processing </ b>

I found out.

Actually

bottle.py



class Bottle(object):

    def __call__(self, environ, start_response):

Is defined.

It was so long.

The Bottle class should be Next.

Summary so far

Bottle () is passed as an argument to the make_server function of the wsgiref library, and this is responsible for the actual processing.

Recommended Posts