Package: graceful
gracefulgracefulis an elegant Python REST toolkit built on top offalconframework. It is highly inspired
byDjango REST framework- 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 resourcessimple but extendable paginationsimple but extendable authentication and authorizationstructured responses with content/meta separationdeclarative fields and parametersself-descriptive-everything: API description accessible both in python and
throughOPTIONSrequestspainless validation100% tests coveragefalcon>=0.3.0 (tested up to 1.4.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 onLibrelistbut no one used it and it seems that librelist became dead (see GitHub
issue#36). For now let's use
gitter chat until we decide on something new.
Chat is availablehere.python3 onlyImportant:gracefulis python3 exclusive becauseright nowshould be
a good time to forget about python2. There are no plans for makinggracefulpython2 compatible although it would be pretty straightforward to do so with
existing tools (like six).usageFor extended tutorial and more information please refer toguideincluded in
documentation.Anyway here is simple example of working API made made withgraceful:importfalconfromgraceful.serializersimportBaseSerializerfromgraceful.fieldsimportIntField,RawFieldfromgraceful.parametersimportStringParamfromgraceful.resources.genericimport(RetrieveAPI,PaginatedListAPI,)api=application=falcon.API()# lets pretend that this is our backend storageCATS_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 APIclassCatSerializer(BaseSerializer):id=IntField("cat identification number",read_only=True)name=RawField("cat name")breed=RawField("official breed name")classCat(RetrieveAPI):"""Single cat identified by its id"""serializer=CatSerializer()defget_cat(self,cat_id):try:return[catforcatinCATS_STORAGEifcat['id']==int(cat_id)][0]exceptIndexError:raisefalcon.HTTPNotFounddefretrieve(self,params,meta,**kwargs):cat_id=kwargs['cat_id']returnself.get_cat(cat_id)classCatList(PaginatedListAPI):"""List of all cats in our API"""serializer=CatSerializer()breed=StringParam("set this param to filter cats by breed")deflist(self,params,meta,**kwargs):if'breed'inparams:filtered=[catforcatinCATS_STORAGEifcat['breed']==params['breed']]returnfilteredelse:returnCATS_STORAGEapi.add_route("/v1/cats/{cat_id}",Cat())api.add_route("/v1/cats/",CatList())Assume this code is in python module namedexample.py.
Now run it withgunicorn:gunicorn -b localhost:8888 exampleAnd you're ready to query it (here with awesomehttpietool):$ 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 issuingOPTIONSrequest:$ 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"
}contributingAny 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 withflakes8andpydocstyleduring 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.licenseSeeLICENSEfile.