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 ...
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 ...
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.
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