README.md
![Travis status](https://travis-ci.org/santiagobasulto/flask-rest-toolkit.svg) [![codecov.io](https://codecov.io/github/santiagobasulto/flask-rest-toolkit/coverage.svg?branch=master)](https://codecov.io/github/santiagobasulto/flask-rest-toolkit?branch=master) [![Code Climate](https://codeclimate.com/github/santiagobasulto/flask-rest-toolkit/badges/gpa.svg)](https://codeclimate.com/github/santiagobasulto/flask-rest-toolkit)
**Work in progress. Everything is subject to changes**
# Flask Rest Toolkit
A set of tools to create simple Flask REST web services and APIs.
### Why not just Flask-RESTful
[Flask-restful](https://github.com/flask-restful/flask-restful) is great. I've used it and I definitively love it. But it's a full fledged framework to build APIs.
This toolkit is conceived just as a set of tools to ease development of a simple REST API. Plus, it's not tied to resources and it's really easy to extend.
### Basic usage
I recommend checking the `tests/` directory for every possible usage. But this would be the minimum required to build an API:
```python
app = Flask("TODO App")
tasks = [
{'id': 1, 'task': 'Do the laundry'},
{'id': 2, 'task': 'Do the dishes'},
]
def get_task(request):
return tasks
def post_task(request):
data = request.json
tasks.append({'task': data['task']})
return {}, 201
api_v1 = Api(version="v1", name="Tasks API") # name is optional,
# but recommended.
api_v1.register_endpoint(ApiEndpoint(
http_method="GET",
endpoint="/task/",
handler=get_task
))
api_v1.register_endpoint(ApiEndpoint(
http_method="POST",
endpoint="/task/",
handler=post_task
))
app.register_blueprint(api_v1)
```
Flask REST toolkit supports serialization. The default serialization method is JSON. We plan to add more serialization options and make them configurable in an API and EndPoint level.
### How it works
**1) Create an API**
```python
api = Api(version="v1")
```
**2) Write your function**
```python
def get_tasks(request):
return [{'id': 1, 'task': 'Do the dishes'}]
```
Functions receive a _request_ as first parameter and must return `data[, status_code, additional_headers]`. Full example:
```python
def get_tasks(request):
return [{'id': 1, 'task': 'Do the dishes'}], 201, {'X-API-version': 'v1'}
```
**3) Hook up an endpoint**
```python
api.register_endpoint(ApiEndpoint(
http_method="GET",
endpoint="/task/",
handler=get_task
))
```
Every endpoint receives an _HTTP method_ for which it'll act, a _path_ and a _function_ (previous step) that will be executed.
**4) Hook up the API to the App. (this will probably change)**
```python
app.register_blueprint(api_v1)
```
### Authentication and Authorization
Flask REST toolkit support a simple Auth scheme along with several helpful classes to ease your development. To use it just indicate the authentication class in your endpoint:
```python
api_v1.register_endpoint(ApiEndpoint(
http_method="GET",
endpoint="/task/",
handler=get_task,
authentication=YourAuthenticationClass()
))
```
An authentication class can must implement two methods:
* `def authenticate(self, request)`
* `def authorize(self, request)`
If any method returns None the user is considered to pass your auth method. On the contrary, you can raise a Werkzeug exception to flag the user is not authorized to access a given resource. Example:
```python
from werkzeug.exceptions import Unauthorized, Forbidden
class YourAuthenticationClass(object):
"Dummy example to show how auth works"
ADMINS = [
'10.160.156.24', '50.170.200.24'
]
def authenticate(self, request):
if request.remote_addr != 10.160.156.24:
raise Unauthorized()
def authorize(self, request):
if request.path.startswith('/admin/') and request.remote_addr not in self.ADMINS:
raise Forbidden()
```
There are a few classes to ease development. Currently the most interesting one is `flask_rest_toolkit.auth.BasicAuth`. Check out the source code for more details.
### Middleware
Each endpoint can define a list of middleware classes that **will be invoked in order before the request**. Each middleware must implement a `process_request` method that will take place before the actual endpoint handler is invoked.
Simple example:
```python
from werkzeug.exceptions import Forbidden
class UserMiddleware(object):
def process_request(self, request):
request.user = User(...)
class AdminMiddleware(object):
def process_request(self, request):
if request.user.role != 'admin':
raise Forbidden()
api_v1.register_endpoint(ApiEndpoint(
http_method="GET",
endpoint="/admin/task/",
handler=get_task_admin,
middleware=[UserMiddleware, AdminMiddleware]
))
```
Check `tests/test_middleware.py` for more details.
### Expected exceptions
An endpoint could possibly raise an exception that is expected. You can specify a list of exceptions to expect and how to react to them. Example:
We have a simple method `register_user` tied to an endpoint. That method could raise a known and expected exception when the email used is already present in the database. You can specify how to react to that exception if it happens in your endpoint.
```python
def register_user(request):
if email_exists(request.json['email']):
raise UserEmailAlreadyExistsException()
if len(request.json['password']) < 5:
raise PasswordNotStrongEnoughException()
api_v1.register_endpoint(ApiEndpoint(
http_method="POST",
endpoint="/user/",
handler=register_user,
exceptions=[
(UserEmailAlreadyExistsException, 409), # Conflict
(PasswordNotStrongEnoughException, 400)
]
))
```
In that case when a `UserEmailAlreadyExistsException` happens a 409 will be returned to the client.
**All the exceptions not specified there will be treated as errors and return a 500**
You can also work with hierarchies of exceptions. Please check tests for more details: `tests/errors.py`
# Contributions/Developing
You'll need to install the dev requirements: `pip install dev-requirements.txt`.
## Run tests
The easiest way is through the regular `setup.py` module:
```bash
$ python setup.py test
```
Internally we're using `py.test`:
```bash
$ py.test tests/
```
## Packaging and publishing
```bash
$ python setup.py sdist
$ python setup.py bdist_wheel --universal
$ twine upload dist/*
```