Milla/doc/getting-started.rst

322 lines
10 KiB
ReStructuredText

===============
Getting Started
===============
*Milla* aims to be lightweight and easy to use. As such, it provides only the
tools you need to build your application the way you want, without imposing any
restrictions on how to do it.
.. contents:: Contents
:local:
Milla's Components
==================
*Milla* provides a small set of components that help you build your web
application in a simple, efficient manner:
* WSGI Application wrapper
* Two types of URL Dispatchers:
* Traversal (like CherryPy or Pyramid)
* Routing (like Django or Pylons)
* Authorization framework
* Utility functions
*Milla* does not provide an HTTP server, so you'll have to use one of the many
implementations already available, such as `Meinheld`_ or `Paste`_, or another
application that understands `WSGI`_, like `Apache HTTPD`_ with the `mod_wsgi`_
module.
``Application`` Objects
=======================
The core class in a *Milla*-based project is its
:py:class:`~milla.app.Application` object. ``Application`` objects are used to
set up the environment for the application and handle incoming requests.
``Application`` instances are *WSGI* callables, meaning they implement the
standard ``application(environ, start_response)`` signature.
To set up an ``Application``, you will need a :term:`URL dispatcher`, which is
an object that maps request paths to :term:`controller` callables.
Choosing a URL Dispatcher
=========================
*Milla* provides two types of URL dispatchers by default, but you can create
your own if neither of these suit your needs. The default dispatchers are
modeled after the URL dispatchers of other popular web frameworks, but may have
small differences.
A *Milla* application can only have one URL dispatcher, so make sure you choose
the one that will work for all of your application's needs.
Traversal
*********
Object traversal is the simplest form of URL dispatcher, and is the default for
*Milla* applications. Object traversal works by looking for path segments as
object attributes, beginning with a :term:`root object` until a
:term:`controller` is found.
For example, consider the URL ``http://example.org/myapp/hello``. Assuming the
*Milla* application is available at ``/myapp`` (which is controlled by the HTTP
server), then the ``/hello`` portion becomes the request path. It contains only
one segment, ``hello``. Thus, an attribute called ``hello`` on the :term:`root
object` must be the controller that will produce a response to that request.
The following code snippet will produce just such an object.
.. code-block:: python
class Root(object):
def hello(self, request):
return 'Hello, world!'
To use this class as the :term:`root object` for a *Milla* application, pass an
instance of it to the :py:class:`~milla.app.Application` constructor:
.. code-block:: python
application = milla.Application(Root())
To create URL paths with multiple segments, such as ``/hello/world`` or
``/umbrella/corp/bio``, the root object will need to have other objects
corresponding to path segments as its attributes.
This example uses static methods and nested classes:
.. code-block:: python
class Root(object):
class hello(object):
@staticmethod
def world(request):
return 'Hello, world!'
application = milla.Application(Root)
This example uses instance methods to create the hierarchy at runtime:
.. code-block:: python
class Root(object):
def __init__(self):
self.umbrella = Umbrella()
class Umbrella(object):
def __init__(self):
self.corp = Corp()
class Corp(object):
def bio(self, request):
return 'T-Virus research facility'
application = milla.Application(Root())
If an attribute with the name of the next path segment cannot be found, *Milla*
will look for a ``default`` attribute.
While the object traversal dispatch mechanism is simple, it is not very
flexible. Because path segments correspond to Python object names, they must
adhere to the same restrictions. This means they can only contain ASCII letters
and numbers and the underscore (``_``) character. If you need more complex
names, dynamic segments, or otherwise more control over the path mapping, you
may need to use routing.
Routing
*******
Routing offers more control of how URL paths are mapped to :term:`controller`
callables, but require more specific configuration.
To use routing, you need to instantiate a
:py:class:`~milla.dispatch.routing.Router` object and then populate its routing
table with path-to-controller maps. This is done using the
:py:meth:`~milla.dispatch.routing.Router.add_route` method.
.. code-block:: python
def hello(request):
return 'Hello, world!'
router = milla.dispatch.routing.Router()
router.add_route('/hello', hello)
Aft er you've set up a ``Router`` and populated its routing table, pass it to
the :py:class:`~milla.app.Application` constructor to use it in a *Milla*
application:
.. code-block:: python
application = milla.Application(router)
Using routing allows paths to contain dynamic portions which will be passed to
controller callables as keyword arguments.
.. code-block:: python
def hello(request, name):
return 'Hello, {0}'.format(name)
router = milla.dispatch.routing.Router()
router.add_route('/hello/{name}', hello)
application = milla.Application(router)
In the above example, the path ``/hello/alice`` would map to the ``hello``
function, and would return the response ``Hello, alice`` when visited.
``Router`` instances can have any number of routes in their routing table. To
add more routes, simply call ``add_route`` for each path and controller
combination you want to expose.
.. code-block:: python
def hello(request):
return 'Hello, world!'
def tvirus(request):
return 'Beware of zombies'
router = milla.dispatch.routing.Router()
router.add_route('/hello', hello)
router.add_route('/hello-world', hello)
router.add_route('/umbrellacorp/tvirus', tvirus)
Controller Callables
====================
*Controller callables* are where most of your application's logic will take
place. Based on the :abbr:`MVC (Model, View, Controller)` pattern, controllers
handle the logic of interaction between the user interface (the *view*) and the
data (the *model*). In the context of a *Milla*-based web application,
controllers take input (the HTTP request, represented by a
:py:class:`~milla.Request` object) and deliver output (the HTTP response,
represented by a :py:class:`~milla.Response` object).
Once you've decided which URL dispatcher you will use, it's time to write
controller callables. These can be any type of Python callable, including
functions, instance methods, classmethods, or partials. *Milla* will
automatically determine the callable type and call it appropriately for each
controller callable mapped to a request path.
This example shows a controller callable as a function (using routing):
.. code-block:: python
def index(request):
return 'this is the index page'
def hello(request):
return 'hello, world'
router = milla.dispatch.routing.Router()
router.add_route('/', index)
router.add_route('/hello', hello)
application = milla.Application(router)
This example is equivalent to the first, but shows a controller callable as a
class instance (using traversal):
.. code-block:: python
class Controller(object):
def __call__(self, request):
return 'this is the index page'
def hello(self, request):
return 'hello, world'
application = milla.Application(Controller())
Controller callables must take at least one argument, which will be an instance
of :py:class:`~milla.Request` representing the HTTP request that was made by
the user. The ``Request`` instance wraps the *WSGI* environment and exposes all
of the available information from the HTTP headers, including path, method
name, query string variables, POST data, etc.
If you are using `Routing`_ and have routes with dynamic path segments, these
segments will be passed by name as keyword arguments, so make sure your
controller callables accept the same keywords.
.. _before-after-hooks:
Before and After Hooks
**********************
You can instruct *Milla* to perform additional operations before and after the
controller callable is run. This could, for example, create a `SQLAlchemy`_
session before the controller is called and roll back any outstanding
transactions after it completes.
To define the before and after hooks, create an ``__before__`` and/or an
``__after__`` attribute on your controller callable. These attributes should be
methods that take exactly one argument: the request. For example:
.. code-block:: python
def setup(request):
request.user = 'Alice'
def teardown(request):
del request.user
def controller(request):
return 'Hello, {user}!'.format(user=request.user)
controller.__before__ = setup
controller.__after__ = teardown
To simplify this, *Milla* handles instance methods specially, by looking for
the ``__before__`` and ``__after__`` methods on the controller callable's class
as well as itself.
.. code-block:: python
class Controller(object):
def __before__(self, request):
request.user = 'Alice'
def __after__(self, request):
del request.user
def __call__(self, request):
return 'Hello, {user}'.format(user=request.user)
Returing a Response
===================
Up until now, the examples have shown :term:`controller` callables returning a
string. This is the simplest way to return a plain HTML response; *Milla* will
automatically send the appropriate HTTP headers for you in this case. If,
however, you need to send special headers, change the content type, or stream
data instead of sending a single response, you will need to return a
:py:class:`~milla.Response` object. This object contains all the properties
necessary to instruct *Milla* on what headers to send, etc. for your response.
To create a :py:class:`~milla.Response` instance, use the
:py:attr:`~milla.Request.ResponseClass` attribute from the request:
.. code-block:: python
def controller(request):
response = request.ResponseClass()
response.content_type = 'text/plain'
response.text = 'Hello, world!'
return response
.. _Meinheld: http://meinheld.org/
.. _Paste: http://pythonpaste.org/
.. _WSGI: http://www.python.org/dev/peps/pep-0333/
.. _Apache HTTPD: http://httpd.apache.org/
.. _mod_wsgi: http://code.google.com/p/modwsgi/
.. _SQLAlchemy: http://www.sqlalchemy.org/