Contents:
fiole.py is a WSGI micro-framework with the following development constraints:
Main features:
Disclaimer: this framework is intentionally limited. If you need a robust and scalable solution, look elsewhere.
Link to the PyPI page: https://pypi.python.org/pypi/fiole
Tested against: Python 2.7, PyPy 2.2 and Python >= 3.2
Either download the single file fiole.py and save it in your project directory, or pip install fiole, preferably in a virtualenv.
Create an application and save it with name hello.py:
from fiole import get, run_fiole
@get('/')
def index(request):
return 'Hello World!'
run_fiole()
Then run this example (default port 8080) with:
python hello.py
or (on port 4000 for example):
python fiole.py -p 4000 hello
# (or)
python -m fiole -p 4000 hello
Clone the examples and run the demo:
git clone git://github.com/florentx/fiole.git
cd fiole/
python fiole.py examples
Open your browser and navigate through the examples: http://127.0.0.1:8080
Read the documentation about Routing requests and Build templates.
Some features are not yet covered in the documentation:
Look at the Fiole API for some crispy details.
Read the documentation of Flask and Bottle for more information about web development in general.
Declare the routes of your application using the Fiole decorators.
Example:
@route('/about')
def about(request):
return "About Fiole sample application"
@get('/home')
def home(request):
return "Sweet"
Both route() and get() declare a route for the GET (and HEAD) methods. The other decorators are post(), put() and delete().
The route() decorator supports an extended syntax to match multiple methods to the same function:
@route('/api', methods=('GET', 'HEAD', 'POST'))
def multi(request):
return "Received %s %s" % (request.method, request.path)
It is also available as a plain function:
def say_hi(request):
return "Hi"
route('/welcome', methods=('GET', 'HEAD'), callback=say_hi)
route('/ciao', methods=('GET', 'HEAD'), callback=say_hi)
Two different syntaxes are supported.
Easy syntax
The simple syntax for URL pattern is like "/hello/<name>". The placeholder matches a non-empty path element (excluding "/"):
@get('/')
@get('/hello/<name>')
def ciao(request, name='Stranger'):
return render_template('Hello {{name}}!', name=name)
Regex syntax
The advanced syntax is regex-based. The variables are extracted from the named groups of the regular expression: (?P<name>...). See Regular Expressions for the full syntax. The unnamed groups are ignored. The initial ^ and the final $ chars should be omitted: they are automatically added when the route is registered.
The pattern parser switches to the advanced syntax when an extension notation is detected: (?.
Example:
@get(r'/api/(?P<action>[^:]+):?(?P<params>.*)')
def call_api(request, action, params):
if action == 'view':
return "Params received {0}".format(params)
raise NotFound("This action is not supported")
There’s a flexible way to define extensions for Fiole: you can register hooks which will be executed for each request. For example you can setup a database connection before each request, and release the connection after the request.
A dumb no-op hook looks like:
app = get_app()
@app.hooks.append
def noop_hook(request):
# acquire the resource
# ...
try:
# pre-process the Request
# ...
response = yield
# post-process the Response
# ...
yield response
finally:
# release the resource
pass
This example setup a database connection:
@app.hooks.append
def hook_db(request):
request.db = connect_db()
try:
# forward the response unchanged
yield (yield)
finally:
request.db.close()
Redirect a request:
@get('/test_redirect')
def test_redirect(request):
raise Redirect('/hello')
Return an error page:
@get('/test_404')
def test_404(request):
raise NotFound('Not here, sorry.')
Register a different error page:
template_404 = get_template("error404.tmpl")
@errorhandler(404)
def not_found(request):
return template_404.render(request)
Send static files:
get_app().static_folder = "/path/to/public/directory"
@get('/static/(?P<path>.+)')
def download(request, path):
return send_file(request, path)
Parts of this page are converted from the wheezy.template documentation. Thank you to the author. (akorn)
Fiole comes with a decent template engine. It supports the usual features of well known engines (Mako, Jinja2). The engine is derived from the wonderful wheezy.template package. It retains the same design goals:
In a nutshell, the syntax looks as simple as Bottle SimpleTemplate:
Simple template:
%require(user, items)
Welcome, {{user.name}}!
%if items:
%for i in items:
{{i.name}}: {{i.price}}.
%endfor
%else:
No item found.
%endif
This is a basic example for a route mapped to a template:
@get('/')
@get('/hello/<name>')
def hello(request, name=None):
return render_template(
source='Hello {{party.title() if party else "stranger"}}!', party=name)
In this case the template is not cached. It is built again for each request.
In order to activate the cache, the string template should be assigned to a name. The previous example becomes:
# Preload the cache
get_template('hello_tpl',
source="""Hello {{party.title() if party else "stranger"}}!""",
require=['party'])
@get('/')
@get('/hello/<name>')
def hello(request, name=None):
return render_template('hello_tpl', party=name)
Templates can be saved to files in the ./templates/ folder of the project. Then they are loaded by filename and cached in memory:
@get('/')
def index(request):
return render_template('hello.tmpl', party='World')
The variables which need to be extracted from the context are listed in the require directive. These names become visible to the end of the template scope (a template is like a Python function). The application passes variables to the template via context:
%require(var1, var2)
{{ var1 }} ... {{ var2 }}
For string templates, you can declare the variables using the require keyword argument:
>>> hello_tmpl = get_template(source='Hello {{ party.capitalize() }}!', require=['party'])
>>> hello_tmpl.render(party='WORLD')
u'Hello World!'
>>> hello_tmpl.render({'party': 'world'})
u'Hello World!'
This declaration is omitted when rendering the string directly:
>>> render_template(source='Hello {{party}}!', party='World')
u'Hello World!'
>>> #
>>> render_template(source='Hello {{ party.capitalize() }}!', party='world')
u'Hello World!'
Variable syntax is not limited to a single name access. You are able to use the full power of Python to access items in dictionary, attributes, function calls, etc...
Variables can be formatted by filters. Filters are separated from the variable by the | symbol. Filter syntax:
{{ variable_name|filter1|filter2 }}
The filters are applied from left to right so above syntax is equivalent to the following call:
{{ filter2(filter1(variable_name)) }}
The built-in filter |e converts any of & < > " ' to HTML-safe sequences & < > " '.
You can define and use custom filters. Here is an example how to switch to a different implementation for the html escape filter:
try:
from webext import escape_html
engine.global_vars['escape'] = escape_html
except ImportError:
pass
It tries to import an optimized version of html escape from the Webext package and assigns it to the escape global variable, which is aliased as e filter. The built-in escape is pure Python.
An example which demonstrates the standard |e filter:
>>> render_template('This: {{ data | e }}', data='"Hello small\' <i>World!<i>" ... & farther')
u'This: "Hello small' <i>World!<i>" ... & farther'
You can enable auto-escaping by default, then use |n as the last filter to bypass the default filters:
>>> engine.default_filters = ['e']
>>> render_template(source='Hello {{ party.capitalize() }}',
... party='<script src="evil" />')
u'Hello <script src="evil" />'
>>> render_template(source='Hello {{ party | n }}',
... party='<em>World</em>')
u'Hello <em>World</em>'
You are able to use engine Engine.global_vars dictionary in order to simplify your template access to some commonly used variables.
Any line starting with a single % contains either a template directive or Python code. Following directives are supported:
Template inheritance (%extends) allows to build a master template that contains common layout of your site and defines areas that child templates can override.
Master template is used to provide common layout of your site. Let define master template (filename shared/master.html):
<html>
<head>
<title>
%def title():
%enddef
{{title()}} - My Site</title>
</head>
<body>
<div id="content">
%def content():
%enddef
{{content()}}
</div>
<div id="footer">
%def footer():
© Copyright 2014 by Him.
%enddef
{{footer()}}
</div>
</body>
</html>
In this example, the %def tags define python functions (substitution areas). These functions are inserted into specific places (right after definition). These places become placeholders for child templates. The %footer placeholder defines default content while %title and %content are just empty.
Child templates are used to extend master templates via placeholders defined:
%extends("shared/master.html")
%def title():
Welcome
%enddef
%def content():
<h1>Home</h1>
<p>
Welcome to My Site!
</p>
%enddef
In this example, the %title and %content placeholders are overriden by the child template.
The include is useful to insert a template content just in place of call:
%include("shared/snippet/script.html")
The import is used to reuse some code stored in other files. So you are able to import all functions defined by that template:
%import "shared/forms.html" as forms
{{ forms.textbox('username') }}
or just a certain name:
%from "shared/forms.html" import textbox
{{ textbox(name='username') }}
Once imported you use these names as variables in the template.
Any line starting with a single % and which is not recognized as a directive is actual Python code. Its content is copied to the generated source code.
The %import and %from lines can be either directives or Python commands, depending on their arguments.
In addition to the special %def, all kinds of Python blocks are supported. The indentation is not significant, blocks must be ended explicitly.
The empty %return directive triggers an early return in the template. The code execution is stopped and the generated content is returned.
Here is a simple example:
%require(items)
%if items:
%for i in items:
{{i.name}}: {{i.price}}.
%endfor
%else:
No items found.
%endif
Only single line comments are supported.
The %# directive introduces a one-line comment. Comments are removed before the template is compiled.
%# TODO:
The line after the %def directive must not enter a new block (%for, %if, etc...). A workaround is to insert an empty comment line before opening the block.
The variables used in the template should be declared, either with a %require directive (recommended for templates loaded from the filesystem), or passed as keyword argument (require=["nav", "body"]) when preparing the template (recommended for string templates). When using the render_template() function with a (source="...") keyword argument, the declaration %require is automatically generated based on the names of the other keyword arguments passed to the function.
These features are not supported (among others):
Register a method for processing requests.
Register a method as capable of processing GET/HEAD requests.
Register a method as capable of processing POST requests.
Register a method as capable of processing PUT requests.
Register a method as capable of processing DELETE requests.
Register a method for processing errors of a certain HTTP code.
Fetch a static file from the filesystem.
Return a compiled template.
Render a template with values of the context dictionary.
Simple HTTPServer that supports WSGI.
Run the Fiole web server.
Web Application.
Enable debugging: don’t catch internal server errors (500) and unhandled exceptions. (default: False)
Secret key used to sign secure cookies. (default: unset)
Directory where static files are located. (default: ./static)
The main handler. Dispatch to the user’s code.
Deal with the exception and present an error page.
Search through the methods registered.
Register a method for processing requests.
Register a method as capable of processing GET/HEAD requests.
Register a method as capable of processing POST requests.
Register a method as capable of processing PUT requests.
Register a method as capable of processing DELETE requests.
Register a method for processing errors of a certain HTTP code.
Return a signed string with timestamp.
Decode a signed string with timestamp or return None.
Fetch a static file from the filesystem.
An object to wrap the environ bits in a friendlier way.
Environment variables are also accessible through Request attributes.
Dictionary of environment variables
Path of the request, decoded and with / appended.
HTTP method (GET, POST, PUT, ...).
Read QUERY_STRING from the environment.
Read SCRIPT_NAME from the environment.
Build host URL.
An instance of EnvironHeaders which wraps HTTP headers.
Header "Content-Length" of the request as integer or 0.
A dictionary of GET parameters.
A dictionary of POST (or PUT) values, including files.
A dictionary of POST (or PUT) values, including files.
Content of the request.
A dictionary of Cookie.Morsel objects.
Get the value of the cookie with the given name, else default.
Return the given signed cookie if it validates, or None.
Build the absolute URL for an application path.
By default it builds the current request URL with leading and trailing / and no query string. The boolean argument full builds a full URL, incl. host.
Status code of the response as integer (default: 200)
Response headers as HTTPHeaders.
Set the given cookie name/value with the given options.
Delete the cookie with the given name.
Sign and timestamp a cookie so it cannot be forged.
Send the headers and return the body of the response.
An object that stores some headers.
An instance of HTTPHeaders is an iterable. It yields tuples (header_name, value). Additionally it provides a dict-like interface to access or change individual headers.
Access the header by name. This method is case-insensitive and the first matching header is returned. It returns None if the header does not exist.
Return the default value if the header doesn’t exist.
Return a list of all the values for the header.
Add a new header tuple to the list.
Remove all header tuples for key and add a new one.
Add a new header if not present. Return the value.
Convert the headers into a list.
Headers from a WSGI environment. Read-only view.
Access the header by name. This method is case-insensitive and returns None if the header does not exist.
Return the default value if the header doesn’t exist.
Return a list of all the values for the header.
Represent an Accept-style header.
Return True if the given offer is listed in the accepted types.
Return the quality of the given offer.
Return the best match in the sequence of offered types.
Base exception for HTTP errors.
Redirect the user to a different URL.
Assemble the template engine.
This mapping contains additional globals which are injected in the generated source. Two special globals are used internally and must not be modified: _r and _i. The functions str and escape (alias e) are also added here. They are used as filters. They can be replaced by C extensions for performance (see Webext). Any object can be added to this registry for usage in the templates, either as function or filter.
The list of filters which are applied to all template expressions {{ ... }}. Set to None to remove all default filters, for performance. (default: [‘str’])
Remove all compiled templates from the internal cache.
Return a compiled template.
The optional keyword argument default_filters overrides the setting which is configured on the Engine.
Remove given name from the internal cache.
Compile and return a template as module.
Simple template class.
Name of the template (it can be None).
Render the template with these arguments (either a dictionary or keyword arguments).
Load templates.
templates - a dict where key corresponds to template name and value to template content.
Directory where template files are located. (default: ./templates)
List all keys from internal dict.
Return template by name.
Tokenize input source per rules supplied.
Translate source into an iterable of tokens.
Include basic statements, variables processing and markup.
Translate source into an iterable of tokens.
If token is continue prepend it with end token so it simulates a closed block.
Process and yield groups of tokens.
A mapping of declared template filters (aliases of globals). This mapping can be extended. The globals can be extended too, see Engine.global_vars.
A mapping of tokens with list of methods, to generate the source code.
Add Python code to the source.
Compile the generated source code.
Fiole is a new challenger in the category of the Python micro-frameworks. As you probably know, there are very good competitors in the market and we don’t really need a new one.
Read the introduction for a quick outline of the development guidelines for the Fiole framework.
So, how Fiole is different or similar to the others?
To sum up:
Of course the above comparison is partial and subjective.
Fiole is thread-safe and you can configure more than one application (for complex projects).
Fiole supports hooks which can be registered for each application. Moreover, the components of Fiole are well thought to allow extensibility. For example the template engine is configurable through attributes, and all the components of the template engine can be subclassed easily.
As an alternative, you can use any template engine, such as Jinja or Mako instead of the built-in template engine. There’s no specific integration between Fiole and the built-in template Engine.
Only the adapter for the wsgiref server is provided. You can write your own adapter for your preferred WSGI server. There are examples available in Bottle or itty.py source code for example.
First, I am not an expert about this topic. Still, there are various places where you can improve the performance of your web application. Some ideas that come to my mind:
The template engine is already very fast. Even so, you can achieve a better performance with small changes:
However the first thing to do is to benchmark your own application with realistic data in order to know where is the bottleneck before doing any random optimization.
Indeed, the template engine has some benefits: it is compact (~450 lines of code) and it is rather intuitive (basically, it’s Python syntax). It is derived from the wheezy.template package which is very fast.
The template engine can be used to process any kind of text.
The good news is that the template engine is not bound to the web framework. Currently there’s no plan to release it separately because Fiole is already a very small module and there’s nothing wrong using only one of its two components: the web framework or the template engine.
The source code is available on GitHub under the terms and conditions of the BSD license. Fork away!
The tests are run against Python 2.7, 3.2 to 3.4 and PyPy on the Travis-CI platform.
Project on PyPI: https://pypi.python.org/pypi/fiole
Project created by Florent Xicluna.
Thank you to Daniel Lindsley (toastdriven) for itty, the itty-bitty web framework which helped me to kick-start the project.
Thank you to Andriy Kornatskyy (akorn) for his blazingly fast and elegant template library wheezy.template: it is the inspiration for the template engine of fiole.py.
The following projects were also a great source of ideas:
This software is provided under the terms and conditions of the BSD license:
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# * The names of the contributors may not be used to endorse or
# promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT OWNERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.