Flask-RESTive

Flask RESTive is a REST API Flask extension based on Flask-RESTful & Marshmallow.


Keywords
flask, rest, api, flask_restful, marshmallow, ddd, domain-driven-design, flask-restful, rest-api
License
MIT
Install
pip install Flask-RESTive==0.0.3

Documentation

flask-restive

Flask-RESTive is a REST API Flask extension based on Flask-RESTful & Marshmallow.

Build Status Coverage Status Code Health PyPI Version

Installation

pip install flask-restive

Requirements

  • Python >= 2.7 or >= 3.4

Introdution

Reusable resource concept

In many cases we don't need to duplicate resource's methods code. Flask-RESTive adheres to a declarative approach. All that we need it's just define serializer behaviour and repo behaviour. The resource code it is not a place for define any business logic, it's view and we use it just for call serializers, repo and results render.

class ClientResource(StorageResource):
    data_schema_cls = ClientSchema
    storage_cls = ClientStorage

Storage concept

Storage is a repo class in DDD (Domain Driven Design) methodology. Storage can implement workflow with any database or multiple databases. Abstract storage provides interface methods:

def open(self):
    ...

def close(self, exception=None):
    ...

def get_item(self, filter_params, **kwargs):
    ...

def get_count(self, filter_params=None, **kwargs):
    ...

def get_list(self, filter_params=None, slice_params=None, sorting_params=None, **kwargs):
    ...

def create_item(self, data_params, **kwargs):
    ...

def create_list(self, data_params, **kwargs):
    ...

def update_item(self, data_params, **kwargs):
    ...

def update_list(self, data_params, **kwargs):
    ...

def delete_list(self, filter_params=None, **kwargs):
    ...

Anybody can make his own implementation of his special storage. Combine simple storage bricks to implement business logic layer in your storage. Storage supports primary_key_fields meta-attribute and use it to wrap result data to special object with primary_key property.

class ClientStorage(Storage):
    class Meta(Storage.Meta):
        primary_key_fields = ('id',)

Wrapped objects are more useful to work with them on many storage combining and result processing.

Schema concept

Schema is a Marshmallow library class that implements serializer/deserializer logic. It's useful to define model fields in declarative style. It's a right to place to make any data validations or transmutations before or after storage data processing.

class ClientSchema(Schema):
    id = fields.Integer(required=True)
    first_name = fields.String(required=True)
    last_name = fields.String()

Data schema supports primary_key_fields, sortable_fields and default_sorting meta-attributes. Filter schema and sorting schema use it to auto-make filter and sorting fields and validation rules.

class ClientSchema(Schema):
    id = fields.Integer(required=True)
    first_name = fields.String(required=True)
    last_name = fields.String()

    class Meta(Schema.Meta):
        sortable_fields = ('id', 'first_name', 'last_name')
        default_sorting = ('last_name', 'first_name', 'id')

How to use

from datetime import datetime

from flask import Flask
from flask_restive import Api, StorageResource, UUIDSchema, fields
from marshmallow import pre_load
from flask_restive_sqlalchemy import Model, Storage
from sqlalchemy import Column, String, DateTime
from sqlalchemy_utils import UUIDType


app = Flask(__name__)

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'


def utc_time():
    return datetime.utcnow().replace(microsecond=0)


class ClientSchema(UUIDSchema):
    first_name = fields.String(required=True)
    last_name = fields.String(required=True)
    created_on = fields.DateTime(
        required=True,
        missing=lambda: utc_time().isoformat())
    updated_on = fields.DateTime()

    class Meta(UUIDSchema.Meta):
        sortable_fields = ('id', 'created_on', 'updated_on')
        default_sorting = ('-updated_on', '-created_on', 'id')

    @pre_load(pass_many=False)
    def set_updated_on(self, data):
        # update time stamp on each create/update operation
        data['updated_on'] = utc_time().isoformat()
        return data


class ClientModel(Model):
    id = Column(UUIDType, primary_key=True)
    first_name = Column(String)
    last_name = Column(String)
    created_on = Column(DateTime)
    updated_on = Column(DateTime)


class ClientStorage(Storage):

    class Meta(Storage.Meta):
        model_cls = ClientModel
        primary_key_fields = ('id',)


class ClientResource(StorageResource):
    data_schema_cls = ClientSchema
    storage_cls = ClientStorage


api = Api(app, prefix='/api/v1', api_resources=[
    (ClientResource, ('/clients', '/clients/<uuid:id>')),
])


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Let's create new client:

curl -X POST "http://localhost:5000/api/v1/clients" -H "Content-Type: application/json" -d '{"first_name": "Alice", "last_name": "Liddell"}'
{
    "id": "0372be43-a668-421e-b8df-7246cdb40857",
    "first_name": "Alice", "last_name": "Liddell",
    "created_on": "2017-09-08T20:44:37",
    "updated_on": "2017-09-08T20:44:37"
}

Let's create two more:

curl -X POST "http://localhost:5000/api/v1/clients" -H "Content-Type: application/json" -d '[{"first_name": "Mad", "last_name": "Hatter"}, {"first_name": "Cheshire", "last_name": "Cat"}]'
[
    {
        "id": "a593f5e2-e588-4e2a-ae57-c4dd8a3faed5",
        "first_name": "Mad",
        "last_name": "Hatter",
        "created_on": "2017-09-08T20:45:15",
        "updated_on": "2017-09-08T20:45:15"
    },
    {
        "id": "c761ef71-d4b0-4b14-aa45-549ffcb72234",
        "first_name": "Cheshire",
        "last_name": "Cat",
        "created_on": "2017-09-08T20:45:15",
        "updated_on": "2017-09-08T20:45:15"
    }
]

Let's list created clients:

curl -X GET "http://localhost:5000/api/v1/clients"
{
    "offset": 0,
    "limit": null,
    "total_count": 3,
    "items_count": 3,
    "items_list": [
        {
            "id": "a593f5e2-e588-4e2a-ae57-c4dd8a3faed5",
            "first_name": "Mad",
            "last_name": "Hatter",
            "created_on": "2017-09-08T20:45:15",
            "updated_on": "2017-09-08T20:45:15"
        },
        {
            "id": "c761ef71-d4b0-4b14-aa45-549ffcb72234",
            "first_name": "Cheshire",
            "last_name": "Cat", "created_on": "2017-09-08T20:45:15",
            "updated_on": "2017-09-08T20:45:15"
        },
        {
            "id": "0372be43-a668-421e-b8df-7246cdb40857",
            "first_name": "Alice",
            "last_name": "Liddell",
            "created_on": "2017-09-08T20:44:37",
            "updated_on": "2017-09-08T20:44:37"
        }
    ]
}

Let's take one client:

curl -X GET "http://localhost:5000/api/v1/clients/0372be43-a668-421e-b8df-7246cdb40857"
{
    "id": "0372be43-a668-421e-b8df-7246cdb40857",
    "first_name": "Alice",
    "last_name": "Liddell",
    "created_on": "2017-09-08T20:44:37",
    "updated_on": "2017-09-08T20:44:37"
}

Let's paginate list of clients:

curl -X GET "http://localhost:5000/api/v1/clients?offset=2&limit=2"
{
    "offset": 2,
    "limit": 2,
    "total_count": 3,
    "items_count": 1,
    "items_list": [
        {
            "id": "0372be43-a668-421e-b8df-7246cdb40857",
            "first_name": "Alice",
            "last_name": "Liddell",
            "created_on": "2017-09-08T20:44:37",
            "updated_on": "2017-09-08T20:44:37"
        }
    ]
}

Let's update one client:

curl -X PATCH "http://localhost:5000/api/v1/clients/0372be43-a668-421e-b8df-7246cdb40857" -H "Content-Type: application/json" -d '{"last_name": "Hatter"}'
{
    "id": "0372be43-a668-421e-b8df-7246cdb40857",
    "first_name": "Alice",
    "last_name": "Hatter",
    "created_on": "2017-09-08T20:44:37",
    "updated_on": "2017-09-08T20:52:07"
}

Let's list clients again:

curl -X GET "http://localhost:5000/api/v1/clients"
{
    "offset": 0,
    "limit": null,
    "total_count": 3,
    "items_count": 3,
    "items_list": [
        {
            "id": "0372be43-a668-421e-b8df-7246cdb40857",
            "first_name": "Alice",
            "last_name": "Hatter",
            "created_on": "2017-09-08T20:44:37",
            "updated_on": "2017-09-08T20:52:07"
        },
        {
            "id": "a593f5e2-e588-4e2a-ae57-c4dd8a3faed5",
            "first_name": "Mad",
            "last_name": "Hatter",
            "created_on": "2017-09-08T20:45:15",
            "updated_on": "2017-09-08T20:45:15"
        },
        {
            "id": "c761ef71-d4b0-4b14-aa45-549ffcb72234",
            "first_name": "Cheshire",
            "last_name": "Cat",
            "created_on": "2017-09-08T20:45:15",
            "updated_on": "2017-09-08T20:45:15"
        }
    ]
}

Let's change sorting order:

curl -X GET "http://localhost:5000/api/v1/clients?sort_by=updated_on,created_on,-id"
{
    "offset": 0,
    "limit": null,
    "total_count": 3,
    "items_count": 3,
    "items_list": [
        {
            "id": "c761ef71-d4b0-4b14-aa45-549ffcb72234",
            "first_name": "Cheshire",
            "last_name": "Cat",
            "created_on": "2017-09-08T20:45:15",
            "updated_on": "2017-09-08T20:45:15"
        },
        {
            "id": "a593f5e2-e588-4e2a-ae57-c4dd8a3faed5",
            "first_name": "Mad",
            "last_name": "Hatter",
            "created_on": "2017-09-08T20:45:15",
            "updated_on": "2017-09-08T20:45:15"
        },
        {
            "id": "0372be43-a668-421e-b8df-7246cdb40857",
            "first_name": "Alice",
            "last_name": "Hatter",
            "created_on": "2017-09-08T20:44:37",
            "updated_on": "2017-09-08T20:52:07"
        }
    ]
}

Let's filter clients:

curl -X GET "http://localhost:5000/api/v1/clients?last_name=Hatter"
{
    "offset": 0,
    "limit": null,
    "total_count": 2,
    "items_count": 2,
    "items_list": [
        {
            "id": "0372be43-a668-421e-b8df-7246cdb40857",
            "first_name": "Alice",
            "last_name": "Hatter",
            "created_on": "2017-09-08T20:44:37",
            "updated_on": "2017-09-08T20:52:07"
        },
        {
            "id": "a593f5e2-e588-4e2a-ae57-c4dd8a3faed5",
            "first_name": "Mad",
            "last_name": "Hatter",
            "created_on": "2017-09-08T20:45:15",
            "updated_on": "2017-09-08T20:45:15"
        }
    ]
}

Let's filter clients by date range:
```bash
curl -X GET "http://localhost:5000/api/v1/clients?created_on__min=2017-09-08T20:00:00&created_on__max=2017-09-08T20:45:00"
{
    "offset": 0,
    "limit": null,
    "total_count": 1,
    "items_count": 1,
    "items_list": [
        {
            "id": "0372be43-a668-421e-b8df-7246cdb40857",
            "first_name": "Alice",
            "last_name": "Hatter",
            "created_on": "2017-09-08T20:44:37",
            "updated_on": "2017-09-08T20:52:07"
        }
    ]
}

Let's filter clients by list of id:

curl -X GET "http://localhost:5000/api/v1/clients?id__in=0372be43-a668-421e-b8df-7246cdb40857,c761ef71-d4b0-4b14-aa45-549ffcb72234"
{
    "offset": 0,
    "limit": null,
    "total_count": 2,
    "items_count": 2,
    "items_list": [
        {
            "id": "0372be43-a668-421e-b8df-7246cdb40857",
            "first_name": "Alice",
            "last_name": "Hatter",
            "created_on": "2017-09-08T20:44:37",
            "updated_on": "2017-09-08T20:52:07"
        },
        {
            "id": "c761ef71-d4b0-4b14-aa45-549ffcb72234",
            "first_name": "Cheshire",
            "last_name": "Cat",
            "created_on": "2017-09-08T20:45:15",
            "updated_on": "2017-09-08T20:45:15"
        }
    ]
}