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:
- Inherit from
falcon.routing.BaseConverter
- Implementing the
convert(value)
method in that class. - 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.