Introduction
Bottle is a Python mini-framework that allows you to write web applications at high speed.
That's just the word "mini" adds restrictions, for example, there is no quick way to create an administrative panel. If you need to work with the database, then it must be connected separately. Thus, bottle is a tool for writing linear web-based applications that do not require too much interaction between application elements.
If you need to write a handler that will accept the link to the file, and then download it to s3 with some kind of processing, then the bottle functionality is perfect for checking the functionality.
')
To work with bottle, it is sufficient to describe the handlers themselves, for example:
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)
(Example from
documentation .)
When writing more semantic functions (for example, the phone book with preservation in the database), very quickly there is a need to work with the database, then with the cache, then with the sessions. This creates the need to push the functionality of working with the database into the handler itself, then render it into separate modules in order not to duplicate the code. After that, the code CRUDL for different objects is rewritten as something like meta-functions.
But you can go the other way: start using a
bottle plugin . On the mechanism of plug-ins and will be discussed in this publication.
About plugins bottle
Python has a strong non-rewriting feature expansion mechanism -
decorators .
The same mechanism is used as the main one for plugins.
In essence, a plugin is a decorator, which is called for each handler when a request falls on it.
You can even write such code and it will be considered as a plugin:
from bottle import response, install import time def stopwatch(callback): def wrapper(*args, **kwargs): start = time.time() body = callback(*args, **kwargs) end = time.time() response.headers['X-Exec-Time'] = str(end - start) return body return wrapper install(stopwatch)
(Example from
Documentation .)
However, it is better to write plugins according to the interface described in the
documentation .
The plugin features include:
- Getting information about the incoming request
- which url is called
- HTTP request content, i.e. all about request
- Formation of the output request
- can change the HTTP header
- add your variable
- set your answer content (at least empty)
In other words, plugins are a tool for complete control over the processing of a request.
How to use the plugin
I will not reprint the
bottle-sqlite plugin here, but the use itself is worthy of attention:
sqlite = SQLitePlugin(dbfile='/tmp/test.db') bottle.install(sqlite) @route('/show/:page') def show(page, db): row = db.execute('SELECT * from pages where name=?', page).fetchone() if row: return template('showpage', page=row) return HTTPError(404, "Page not found") @route('/admin/set/:db#[a-zA-Z]+#', skip=[sqlite]) def change_dbfile(db): sqlite.dbfile = '/tmp/%s.db' % db return "Switched DB to %s.db" % db
(Example from
Documentation .)
The example shows how to install the plugin, as well as how to use it. This is what I wrote above. When using the plugin, it is possible to include an object in the handler itself (in this case, db - sqlite database), which can be safely used.
Having reviewed the examples from the documentation, I’ll move on to the actual application.
Use cases to use plugins
The first use case can be called probros of some object to the handler itself. This can be seen in the example of using bottle-sqlite (see code above).
The second option can be called this.
When writing a web application in the development team, some agreements may be formed on the types of data that are received and returned.
In order not to go far, I will give a fictional code:
@route('/report/generate/:filename') def example_handler(filename): try result = generate_report(filename) except Exception as e: result = {'ok': False, 'error': str(e)} response.content_type = 'application/json' return json.dumps(result)
That is, the team agreed that the return type will be json. You can duplicate lines each time:
response.content_type = 'application/json' return json.dumps(result)
This seems to be okay, but only calluses the same lines from function to function. And if this “nothing terrible” lasts not two lines, but ten? In this case, plugins can save, we write an elementary function:
def wrapper(*args, **kwargs): response.content_type = 'application/json' return json.dumps(callback(*args, **kwargs))
(I will not give the rest of the plugin, because it is very similar to sqlite.)
And reduce the amount of code.
Let's go further. In the agreements we agreed not only to give to json, but also to accept. It would be great not only to check the HTTP header for a type, but also to check the existence of certain keys. This can be done, for example, as follows:
def wrapper(*args, **kwargs): def gen_error(default_error, text_msg): res = default_error res.update(dict(error=text_msg)) return res if request.get_header('CONTENT_TYPE', '') == 'application/json': if request.json: not_found_keys = [] not_found_keys_flag = False for key in keys: if key not in request.json: not_found_keys.append(key) not_found_keys_flag = True if not_found_keys_flag: wr_res = gen_error(self.default_error, 'Not found keys: | %s | in request' % ', '.join(not_found_keys)) else: wr_res = callback(*args, **kwargs) else: wr_res = gen_error( self.default_error, 'Not found json in request') else: wr_res = gen_error( self.default_error, 'it is not json request') response.content_type = 'application/json' return json.dumps(wr_res)
And use something like this:
@route('/observer/add', keys=['name', 'latitude', 'longitude', 'elevation']) def observer_add(): return set_observer(request.json)
The plugin will check for the existence of keys in json, and then the answer will also wrap it in json.
Use options, of course, more, as with decorators. It depends on who and how comes up with them.
Existing plugins
The list of plug-ins for bottle is not very
extensive .
On github, you can find plugins for session management, i18n, facebook, matplotlib, cql, logging, registration and authorization. However, their number is significantly inferior to flask and django.
findings
Bottle-plug-ins allow you to reduce the amount of duplication of code, pull out general checks (such as “authorized by the user”) to a common place, extend the functionality and create modules that can be reused.