Elegant Python REST toolkit built on top of falcon
Project description
[![PyPI](https://img.shields.io/pypi/v/graceful.svg)](https://pypi-hypernode.com/pypi/graceful/)
[![PyPI](https://img.shields.io/pypi/pyversions/graceful.svg)](https://pypi-hypernode.com/pypi/graceful/)
[![Build Status](https://travis-ci.org/swistakm/graceful.svg?branch=master)](https://travis-ci.org/swistakm/graceful)
[![Coverage Status](https://coveralls.io/repos/swistakm/graceful/badge.svg?branch=master)](https://coveralls.io/r/swistakm/graceful?branch=master)
[![Documentation Status](https://readthedocs.org/projects/graceful/badge/?version=latest)](https://graceful.readthedocs.io/en/latest/)
[![Join the chat at https://gitter.im/graceful-for-falcon/Lobby](https://badges.gitter.im/graceful-for-falcon/Lobby.svg)](https://gitter.im/graceful-for-falcon/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
# graceful
`graceful` is an elegant Python REST toolkit built on top of
[falcon](http://github.com/falconry/falcon) framework. It is highly inspired
by [Django REST framework](http://www.django-rest-framework.org/) - mostly by
how object serialization is done but more emphasis here is put on API to
be self-descriptive.
Features:
* generic classes for list and single object resources
* simple but extendable pagination
* simple but extendable authentication and authorization
* structured responses with content/meta separation
* declarative fields and parameters
* self-descriptive-everything: API description accessible both in python and
through `OPTIONS` requests
* painless validation
* 100% tests coverage
* falcon>=0.3.0 (tested up to 1.1.x)
* python3 exclusive (tested from 3.3 to 3.6)
Community behind graceful is starting to grow but we don't have any mailing
list yet. There was one on [Librelist](http://librelist.com/browser/graceful)
but no one used it and it seems that librelist became dead (see GitHub
issue [#36](https://github.com/swistakm/graceful/issues/36)). For now let's use
gitter chat until we decide on something new.
Chat is available [here](https://gitter.im/graceful-for-falcon/Lobby).
## python3 only
**Important**: `graceful` is python3 exclusive because **right now** should be
a good time to forget about python2. There are no plans for making `graceful`
python2 compatible although it would be pretty straightforward to do so with
existing tools (like six).
## usage
For extended tutorial and more information please refer to
[guide](https://graceful.readthedocs.org/en/latest/guide/) included in
documentation.
Anyway here is simple example of working API made made with `graceful`:
```python
import falcon
from graceful.serializers import BaseSerializer
from graceful.fields import IntField, RawField
from graceful.parameters import StringParam
from graceful.resources.generic import (
RetrieveAPI,
PaginatedListAPI,
)
api = application = falcon.API()
# lets pretend that this is our backend storage
CATS_STORAGE = [
{"id": 0, "name": "kitty", "breed": "saimese"},
{"id": 1, "name": "lucie", "breed": "maine coon"},
{"id": 2, "name": "molly", "breed": "sphynx"},
]
# this is how we represent cats in our API
class CatSerializer(BaseSerializer):
id = IntField("cat identification number", read_only=True)
name = RawField("cat name")
breed = RawField("official breed name")
class Cat(RetrieveAPI):
"""
Single cat identified by its id
"""
serializer = CatSerializer()
def get_cat(self, cat_id):
try:
return [
cat for cat in CATS_STORAGE if cat['id'] == int(cat_id)
][0]
except IndexError:
raise falcon.HTTPNotFound
def retrieve(self, params, meta, **kwargs):
cat_id = kwargs['cat_id']
return self.get_cat(cat_id)
class CatList(PaginatedListAPI):
"""
List of all cats in our API
"""
serializer = CatSerializer()
breed = StringParam("set this param to filter cats by breed")
def list(self, params, meta, **kwargs):
if 'breed' in params:
filtered = [
cat for cat in CATS_STORAGE
if cat['breed'] == params['breed']
]
return filtered
else:
return CATS_STORAGE
api.add_route("/v1/cats/{cat_id}", Cat())
api.add_route("/v1/cats/", CatList())
```
Assume this code is in python module named `example.py`.
Now run it with [gunicorn](https://github.com/benoitc/gunicorn):
gunicorn -b localhost:8888 example
And you're ready to query it (here with awesome [httpie](http://httpie.org)
tool):
```
$ http localhost:8888/v0/cats/?breed=saimese
HTTP/1.1 200 OK
Connection: close
Date: Tue, 16 Jun 2015 08:43:05 GMT
Server: gunicorn/19.3.0
content-length: 116
content-type: application/json
{
"content": [
{
"breed": "saimese",
"id": 0,
"name": "kitty"
}
],
"meta": {
"params": {
"breed": "saimese",
"indent": 0
}
}
}
```
Or access API description issuing `OPTIONS` request:
```
$ http OPTIONS localhost:8888/v0/cats
HTTP/1.1 200 OK
Connection: close
Date: Tue, 16 Jun 2015 08:40:00 GMT
Server: gunicorn/19.3.0
allow: GET, OPTIONS
content-length: 740
content-type: application/json
{
"details": "List of all cats in our API",
"fields": {
"breed": {
"details": "official breed name",
"label": null,
"spec": null,
"type": "string"
},
"id": {
"details": "cat identification number",
"label": null,
"spec": null,
"type": "int"
},
"name": {
"details": "cat name",
"label": null,
"spec": null,
"type": "string"
}
},
"methods": [
"GET",
"OPTIONS"
],
"name": "CatList",
"params": {
"breed": {
"default": null,
"details": "set this param to filter cats by breed",
"label": null,
"required": false,
"spec": null,
"type": "string"
},
"indent": {
"default": "0",
"details": "JSON output indentation. Set to 0 if output should not be formated.",
"label": null,
"required": false,
"spec": null,
"type": "integer"
}
},
"path": "/v0/cats",
"type": "list"
}
```
## contributing
Any contribution is welcome. Issues, suggestions, pull requests - whatever.
There is only short set of rules that guide this project development you
should be aware of before submitting a pull request:
* Only requests that have passing CI builds (Travis) will be merged.
* Code is checked with `flakes8` and `pydocstyle` during build so this
implicitly means that compliance with PEP-8 and PEP-257 is mandatory.
* No changes that decrease coverage will be merged.
One thing: if you submit a PR please do not rebase it later unless you
are asked for that explicitly. Reviewing pull requests that suddenly had
their history rewritten just drives me crazy.
## license
See `LICENSE` file.
[![PyPI](https://img.shields.io/pypi/pyversions/graceful.svg)](https://pypi-hypernode.com/pypi/graceful/)
[![Build Status](https://travis-ci.org/swistakm/graceful.svg?branch=master)](https://travis-ci.org/swistakm/graceful)
[![Coverage Status](https://coveralls.io/repos/swistakm/graceful/badge.svg?branch=master)](https://coveralls.io/r/swistakm/graceful?branch=master)
[![Documentation Status](https://readthedocs.org/projects/graceful/badge/?version=latest)](https://graceful.readthedocs.io/en/latest/)
[![Join the chat at https://gitter.im/graceful-for-falcon/Lobby](https://badges.gitter.im/graceful-for-falcon/Lobby.svg)](https://gitter.im/graceful-for-falcon/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
# graceful
`graceful` is an elegant Python REST toolkit built on top of
[falcon](http://github.com/falconry/falcon) framework. It is highly inspired
by [Django REST framework](http://www.django-rest-framework.org/) - mostly by
how object serialization is done but more emphasis here is put on API to
be self-descriptive.
Features:
* generic classes for list and single object resources
* simple but extendable pagination
* simple but extendable authentication and authorization
* structured responses with content/meta separation
* declarative fields and parameters
* self-descriptive-everything: API description accessible both in python and
through `OPTIONS` requests
* painless validation
* 100% tests coverage
* falcon>=0.3.0 (tested up to 1.1.x)
* python3 exclusive (tested from 3.3 to 3.6)
Community behind graceful is starting to grow but we don't have any mailing
list yet. There was one on [Librelist](http://librelist.com/browser/graceful)
but no one used it and it seems that librelist became dead (see GitHub
issue [#36](https://github.com/swistakm/graceful/issues/36)). For now let's use
gitter chat until we decide on something new.
Chat is available [here](https://gitter.im/graceful-for-falcon/Lobby).
## python3 only
**Important**: `graceful` is python3 exclusive because **right now** should be
a good time to forget about python2. There are no plans for making `graceful`
python2 compatible although it would be pretty straightforward to do so with
existing tools (like six).
## usage
For extended tutorial and more information please refer to
[guide](https://graceful.readthedocs.org/en/latest/guide/) included in
documentation.
Anyway here is simple example of working API made made with `graceful`:
```python
import falcon
from graceful.serializers import BaseSerializer
from graceful.fields import IntField, RawField
from graceful.parameters import StringParam
from graceful.resources.generic import (
RetrieveAPI,
PaginatedListAPI,
)
api = application = falcon.API()
# lets pretend that this is our backend storage
CATS_STORAGE = [
{"id": 0, "name": "kitty", "breed": "saimese"},
{"id": 1, "name": "lucie", "breed": "maine coon"},
{"id": 2, "name": "molly", "breed": "sphynx"},
]
# this is how we represent cats in our API
class CatSerializer(BaseSerializer):
id = IntField("cat identification number", read_only=True)
name = RawField("cat name")
breed = RawField("official breed name")
class Cat(RetrieveAPI):
"""
Single cat identified by its id
"""
serializer = CatSerializer()
def get_cat(self, cat_id):
try:
return [
cat for cat in CATS_STORAGE if cat['id'] == int(cat_id)
][0]
except IndexError:
raise falcon.HTTPNotFound
def retrieve(self, params, meta, **kwargs):
cat_id = kwargs['cat_id']
return self.get_cat(cat_id)
class CatList(PaginatedListAPI):
"""
List of all cats in our API
"""
serializer = CatSerializer()
breed = StringParam("set this param to filter cats by breed")
def list(self, params, meta, **kwargs):
if 'breed' in params:
filtered = [
cat for cat in CATS_STORAGE
if cat['breed'] == params['breed']
]
return filtered
else:
return CATS_STORAGE
api.add_route("/v1/cats/{cat_id}", Cat())
api.add_route("/v1/cats/", CatList())
```
Assume this code is in python module named `example.py`.
Now run it with [gunicorn](https://github.com/benoitc/gunicorn):
gunicorn -b localhost:8888 example
And you're ready to query it (here with awesome [httpie](http://httpie.org)
tool):
```
$ http localhost:8888/v0/cats/?breed=saimese
HTTP/1.1 200 OK
Connection: close
Date: Tue, 16 Jun 2015 08:43:05 GMT
Server: gunicorn/19.3.0
content-length: 116
content-type: application/json
{
"content": [
{
"breed": "saimese",
"id": 0,
"name": "kitty"
}
],
"meta": {
"params": {
"breed": "saimese",
"indent": 0
}
}
}
```
Or access API description issuing `OPTIONS` request:
```
$ http OPTIONS localhost:8888/v0/cats
HTTP/1.1 200 OK
Connection: close
Date: Tue, 16 Jun 2015 08:40:00 GMT
Server: gunicorn/19.3.0
allow: GET, OPTIONS
content-length: 740
content-type: application/json
{
"details": "List of all cats in our API",
"fields": {
"breed": {
"details": "official breed name",
"label": null,
"spec": null,
"type": "string"
},
"id": {
"details": "cat identification number",
"label": null,
"spec": null,
"type": "int"
},
"name": {
"details": "cat name",
"label": null,
"spec": null,
"type": "string"
}
},
"methods": [
"GET",
"OPTIONS"
],
"name": "CatList",
"params": {
"breed": {
"default": null,
"details": "set this param to filter cats by breed",
"label": null,
"required": false,
"spec": null,
"type": "string"
},
"indent": {
"default": "0",
"details": "JSON output indentation. Set to 0 if output should not be formated.",
"label": null,
"required": false,
"spec": null,
"type": "integer"
}
},
"path": "/v0/cats",
"type": "list"
}
```
## contributing
Any contribution is welcome. Issues, suggestions, pull requests - whatever.
There is only short set of rules that guide this project development you
should be aware of before submitting a pull request:
* Only requests that have passing CI builds (Travis) will be merged.
* Code is checked with `flakes8` and `pydocstyle` during build so this
implicitly means that compliance with PEP-8 and PEP-257 is mandatory.
* No changes that decrease coverage will be merged.
One thing: if you submit a PR please do not rebase it later unless you
are asked for that explicitly. Reviewing pull requests that suddenly had
their history rewritten just drives me crazy.
## license
See `LICENSE` file.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
graceful-0.6.0.tar.gz
(32.6 kB
view details)
Built Distribution
File details
Details for the file graceful-0.6.0.tar.gz
.
File metadata
- Download URL: graceful-0.6.0.tar.gz
- Upload date:
- Size: 32.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | d403a83aaa15542910d1870b25f8f2cefbcbd6b7a902c0e3b9ef33c28576b148 |
|
MD5 | 9ff5d50fe18c741b25bb6e50d6c096b3 |
|
BLAKE2b-256 | 9ed11ba9217c575504bb74f349eb353e71b9b7679864203a444b295fc90c76ef |
File details
Details for the file graceful-0.6.0-py2.py3-none-any.whl
.
File metadata
- Download URL: graceful-0.6.0-py2.py3-none-any.whl
- Upload date:
- Size: 67.7 kB
- Tags: Python 2, Python 3
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | ffb9f640c2afcdf6a5d7be15d25a0d0fe6c04f081c1559ecd2e56568c70a1370 |
|
MD5 | ad47518b50f4e0fa33f439d1f3f3d894 |
|
BLAKE2b-256 | 5bb13a6f40372e63d68171bdad610a974395d1a76ce1037ae0b8a63ec3216181 |