Skip to content

Commit

Permalink
Merge pull request #412 from kgriffs/issues/296
Browse files Browse the repository at this point in the history
Edit docs for correctness and clarity
  • Loading branch information
kgriffs committed Jan 23, 2015
2 parents 02c6100 + 6beba38 commit fba3632
Show file tree
Hide file tree
Showing 18 changed files with 653 additions and 413 deletions.
189 changes: 118 additions & 71 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,29 @@ Falcon is a [high-performance Python framework][home] for building cloud APIs. I

### Features ###

* Highly-optimized, extensible code base
* Intuitive routing via URI templates and resource classes
* Easy access to headers and bodies through request and response classes
* Does not use WebOb (some of us do indeed consider this a feature)
* Idiomatic HTTP error responses via a handy exception base class
* DRY request processing using global, resource, and method hooks
* Snappy unit testing through WSGI helpers and mocks
* 20% speed boost when Cython is available
* Python 2.6, Python 2.7, PyPy and Python 3.3/3.4 support
* Speed, speed, and more speed!
* 20% speed boost when Cython is available

### Install ###

> This documentation targets the upcoming 0.2 release of Falcon,
> currently in beta and available on PyPI. You will need to use the
> ``--pre`` flag with pip in order to install the Falcon 0.2 betas
> and release candidates.
If available, Falcon will compile itself with Cython for an extra
speed boost. The following will make sure Cython is installed first, and
that you always have the latest and greatest.

```bash
$ pip install cython falcon
$ pip install --upgrade cython falcon
```

**Installing on OS X Mavericks with Xcode 5.1**
Expand Down Expand Up @@ -85,9 +95,6 @@ We have started documenting the library at http://falcon.readthedocs.org and we

The docstrings in the Falcon code base are quite extensive, and we recommend keeping a REPL running while learning the framework so that you can query the various modules and classes as you have questions.

You can also check out [Zaqar's WSGI driver](https://github.com/openstack/zaqar/tree/master/zaqar/queues/transport/wsgi) to get a feel for how you might
leverage Falcon in building a REST API.

The Falcon community maintains a mailing list that you can use to share
your ideas and ask questions about the framework. We use the appropriately
minimalistic [Librelist](http://librelist.com/) to host the discussions.
Expand Down Expand Up @@ -159,24 +166,27 @@ $ curl localhost:8000/things
Here is a more involved example that demonstrates reading headers and query parameters, handling errors, and working with request and response bodies.

```python

import json
import logging
import uuid
from wsgiref import simple_server

import falcon
import requests


class StorageEngine(object):

def get_things(self, marker, limit):
return []
return [{'id': str(uuid.uuid4()), 'color': 'green'}]

def add_thing(self, thing):
return {'id': str(uuid.uuid4())}
thing['id'] = str(uuid.uuid4())
return thing


class StorageError(Exception):

@staticmethod
def handle(ex, req, resp, params):
description = ('Sorry, couldn\'t write your thing to the '
Expand All @@ -187,82 +197,110 @@ class StorageError(Exception):
description)


class Proxy(object):
def forward(self, req):
return falcon.HTTP_503


class SinkAdapter(object):

def __init__(self):
self._proxy = Proxy()
engines = {
'ddg': 'https://duckduckgo.com',
'y': 'https://search.yahoo.com/search',
}

def __call__(self, req, resp, engine):
url = self.engines[engine]
params = {'q': req.get_param('q', True)}
result = requests.get(url, params=params)

def __call__(self, req, resp, **kwargs):
resp.status = self._proxy.forward(req)
self.kwargs = kwargs
resp.status = str(result.status_code) + ' ' + result.reason
resp.content_type = result.headers['content-type']
resp.body = result.text


def token_is_valid(token, user_id):
return True # Suuuuuure it's valid...
class AuthMiddleware(object):

def process_request(self, req, resp):
token = req.get_header('X-Auth-Token')
project = req.get_header('X-Project-ID')

def auth(req, resp, params):
# Alternatively, use Talons or do this in WSGI middleware...
token = req.get_header('X-Auth-Token')
if token is None:
description = ('Please provide an auth token '
'as part of the request.')

if token is None:
description = ('Please provide an auth token '
'as part of the request.')
raise falcon.HTTPUnauthorized('Auth token required',
description,
href='http://docs.example.com/auth')

raise falcon.HTTPUnauthorized('Auth token required',
description,
href='http://docs.example.com/auth')
if not self._token_is_valid(token, project):
description = ('The provided auth token is not valid. '
'Please request a new token and try again.')

if not token_is_valid(token, params['user_id']):
description = ('The provided auth token is not valid. '
'Please request a new token and try again.')
raise falcon.HTTPUnauthorized('Authentication required',
description,
href='http://docs.example.com/auth',
scheme='Token; UUID')

raise falcon.HTTPUnauthorized('Authentication required',
description,
href='http://docs.example.com/auth',
scheme='Token; UUID')
def _token_is_valid(self, token, project):
return True # Suuuuuure it's valid...


def check_media_type(req, resp, params):
if not req.client_accepts_json:
raise falcon.HTTPNotAcceptable(
'This API only supports responses encoded as JSON.',
href='http://docs.examples.com/api/json')
class RequireJSON(object):

if req.method in ('POST', 'PUT'):
if not req.content_type == 'application/json':
raise falcon.HTTPUnsupportedMediaType(
'This API only supports requests encoded as JSON.',
def process_request(self, req, resp):
if not req.client_accepts_json:
raise falcon.HTTPNotAcceptable(
'This API only supports responses encoded as JSON.',
href='http://docs.examples.com/api/json')

if req.method in ('POST', 'PUT'):
if 'application/json' not in req.content_type:
raise falcon.HTTPUnsupportedMediaType(
'This API only supports requests encoded as JSON.',
href='http://docs.examples.com/api/json')

def deserialize(req, resp, resource, params):
# req.stream corresponds to the WSGI wsgi.input environ variable,
# and allows you to read bytes from the request body.
#
# See also: PEP 3333
body = req.stream.read()
if not body:
raise falcon.HTTPBadRequest('Empty request body',
'A valid JSON document is required.')

try:
params['doc'] = json.loads(body.decode('utf-8'))
class JSONTranslator(object):

except (ValueError, UnicodeDecodeError):
raise falcon.HTTPError(falcon.HTTP_753,
'Malformed JSON',
'Could not decode the request body. The '
'JSON was incorrect or not encoded as UTF-8.')
def process_request(self, req, resp):
# req.stream corresponds to the WSGI wsgi.input environ variable,
# and allows you to read bytes from the request body.
#
# See also: PEP 3333
if req.content_length in (None, 0):
# Nothing to do
return

body = req.stream.read()
if not body:
raise falcon.HTTPBadRequest('Empty request body',
'A valid JSON document is required.')

def serialize(req, resp, resource):
resp.body = json.dumps(req.context['doc'])
try:
req.context['doc'] = json.loads(body.decode('utf-8'))

except (ValueError, UnicodeDecodeError):
raise falcon.HTTPError(falcon.HTTP_753,
'Malformed JSON',
'Could not decode the request body. The '
'JSON was incorrect or not encoded as '
'UTF-8.')

def process_response(self, req, resp):
if 'result' not in req.context:
return

resp.body = json.dumps(req.context['result'])


def max_body(limit):

def hook(req, resp, resource, params):
length = req.content_length
if length is not None and length > limit:
msg = ('The size of the request is too large. The body must not '
'exceed ' + str(limit) + ' bytes in length.')

raise falcon.HTTPRequestEntityTooLarge(
'Request body is too large', msg)

return hook


class ThingsResource:
Expand All @@ -271,7 +309,6 @@ class ThingsResource:
self.db = db
self.logger = logging.getLogger('thingsapp.' + __name__)

@falcon.after(serialize)
def on_get(self, req, resp, user_id):
marker = req.get_param('marker') or ''
limit = req.get_param_as_int('limit') or 50
Expand All @@ -294,21 +331,32 @@ class ThingsResource:
# create a custom class that inherits from falcon.Request. This
# class could, for example, have an additional 'doc' property
# that would serialize to JSON under the covers.
req.context['doc'] = result
req.context['result'] = result

resp.set_header('X-Powered-By', 'Small Furry Creatures')
resp.status = falcon.HTTP_200

@falcon.before(deserialize)
def on_post(self, req, resp, user_id, doc):
@falcon.before(max_body(64 * 1024))
def on_post(self, req, resp, user_id):
try:
doc = req.context['doc']
except KeyError:
raise falcon.HTTPBadRequest(
'Missing thing',
'A thing must be submitted in the request body.')

proper_thing = self.db.add_thing(doc)

resp.status = falcon.HTTP_201
resp.location = '/%s/things/%s' % (user_id, proper_thing['id'])


# Configure your WSGI server to load "things.app" (app is a WSGI callable)
app = falcon.API(before=[auth, check_media_type])
app = falcon.API(middleware=[
AuthMiddleware(),
RequireJSON(),
JSONTranslator(),
])

db = StorageEngine()
things = ThingsResource(db)
Expand All @@ -322,13 +370,12 @@ app.add_error_handler(StorageError, StorageError.handle)
# send parts of an API off to a legacy system that hasn't been upgraded
# yet, or perhaps is a single cluster that all data centers have to share.
sink = SinkAdapter()
app.add_sink(sink, r'/v1/[charts|inventory]')
app.add_sink(sink, r'/search/(?P<engine>ddg|y)\Z')

# Useful for debugging problems in your API; works with pdb.set_trace()
if __name__ == '__main__':
httpd = simple_server.make_server('127.0.0.1', 8000, app)
httpd.serve_forever()

```

### Contributing ###
Expand Down

0 comments on commit fba3632

Please sign in to comment.