Thu 19 Sep 2019

Falcon

https://falcon.readthedocs.io/en/stable/user/tutorial.html

Falcon is a smallish web framework for Python.

Falcon is designed for implementing APIs. It doesn't come with any HTML templating or database access.

Like most other Python web frameworks, it follows the WSGI specification, and can be run by any WSGI supporting web server.

Falcon is becoming quite popular now (perhaps not as much as Django or Flask yet), and can be considered reliable and production-ready.

Falcon has some community extensions available, and also lets you write your own middleware.

Its main advantages are that it is faster than most of the other frameworks, and that it is quite simple to get started with.

Routing

Falcon's other selling point is that it's organised around REST principles and not RPC. Resources are nouns that you implement standard HTTP verbs on. So, your API resembles a file and folder structure.

app = Falcon.API()
app.add_route("/my-resource-type/{id}", SomeResourceType())

Our resource types are classes that implement handlers on_get(self, request, response, **url_parameters), on_post(self, request, response, **url_parameters), and so on.

I think this is an overly restrictive organizing principle for APIs. In reality, many operations look more like function calls than trees of nouns. It looks like Java's AbstractFactoryManagerBeanProvider problem all over again.

I therefore prefer Flask's more flexible approach to API specification. However, Falcon has the app.add_sink(handler_function, route_prefix) method to let you handle things you own way.

You can also implement custom routers, but this doesn't seem useful as they are still tied to the resources model. You also lose Falcon's performance benefits.

URL Parameter Types

Flask has converters for parsing and validating URL parameters.

app.add_route("/resource/{id:uuid}").

Built in converters are uuid, int(length) and dt(date-format).

To add custom converters:

  1. Inherit from falcon.routing.BaseConverter
  2. Implementing the convert(value) method in that class.
  3. Set app.router_options.converters["my-converter-alias"] = MyConverterClass.

Requests and Responses

https://falcon.readthedocs.io/en/stable/api/request_and_response.html

There is a request.context object where you can put any data you want.

request.stream is a file-like-object that you can get bytes from.

request.media is a deserialized version of stream. By default this is JSON, but you can customize it.

request.query_string can be parsed using falcon.uri.parse_query_string(request.query_string)), or you can call request.get_param("my-qs-parameter"). There are also typed versions like request.get_param_as_bool("my-bool-qs-parameter").

You can write responses as:

  • Text response.body = "my-test".
  • JSON response.media = {"prop": "value"}.
  • Bytes response.data = my_bytes.
  • A steam response.set_stream(my_file_like_object). This will automatically sort out the content length for you.

Set the HTTP Content-Type response.content_type = "falcon.MEDIA_TEXT" or some other appropriate MIME type.

You can set the status code and message using response.status = falcon.HTTP_200.

The response also has various ways to set common headers.

JSON Validation

Falcon provides a JSON schema validator falcon.media.validators.jsonschema.validate(req_schema=None, resp_schema=None)[source]. You need to install the jsonschema package to use this validator.

Hooks

You can decorate either the handler or the resource class (which affects all handlers) with hooks using @falcon.before(my_hook_function, my_extra_argument).

A hook looks like def my_hook_function(request, response, resource, url_parameters, *extra_arguments).

The resource parameter will be the instance of the resource class the hook was called on. This corresponds to the self argument in the handler methods above.

Testing

Falcon provides a test simulator:

client = falcon.testing.TestClient(app)
client.simluate_get("some_url")

However, I think this is probably less useful than just running integration tests against a real example of the server.

Errors

Falcon provides default 404 responses for missing routes.

It also provides a Falcon.HTTPError class which it will catch and turn into an HTTP response.

There are built-in subclasses of this for standard HTTP 4xx and 5xx codes: https://falcon.readthedocs.io/en/stable/api/errors.html#predefined-errors.

The same approach is also used for 3xx redirects: https://falcon.readthedocs.io/en/stable/api/redirects.html.

You can add your own error handles using app.add_error_handler(exception_class, handler=my_handler_function).

URI Helpers

The falcon.uri package has URI encoding and decoding.

Sync and Async

Being a WSGI framework means that it is synchronous. You can use gevent to make it asynchronous, but that's probably a fairly dodgy route unless you have some dedicate people to support it.

For long running jobs, you should use a task runner like Celery and immediately return an id to the client that they can use to track that task. Alternatively, you can require them to give you a callback URL.