{"id":22896723,"url":"https://github.com/swistakm/graceful","last_synced_at":"2025-05-07T20:30:46.051Z","repository":{"id":33789440,"uuid":"37474146","full_name":"swistakm/graceful","owner":"swistakm","description":"Elegant Python REST toolkit built on top of falcon","archived":false,"fork":false,"pushed_at":"2019-02-03T19:15:02.000Z","size":251,"stargazers_count":75,"open_issues_count":15,"forks_count":12,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-11-01T20:12:35.872Z","etag":null,"topics":["falcon","framework","http","python","rest-api","restful","restful-api"],"latest_commit_sha":null,"homepage":"https://graceful.readthedocs.org","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"cdnjs/cdnjs","license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/swistakm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-06-15T15:35:57.000Z","updated_at":"2024-01-03T14:12:24.000Z","dependencies_parsed_at":"2022-08-07T23:15:09.504Z","dependency_job_id":null,"html_url":"https://github.com/swistakm/graceful","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swistakm%2Fgraceful","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swistakm%2Fgraceful/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swistakm%2Fgraceful/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swistakm%2Fgraceful/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/swistakm","download_url":"https://codeload.github.com/swistakm/graceful/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":229629198,"owners_count":18101264,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["falcon","framework","http","python","rest-api","restful","restful-api"],"created_at":"2024-12-13T23:38:12.096Z","updated_at":"2024-12-13T23:38:12.687Z","avatar_url":"https://github.com/swistakm.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![PyPI](https://img.shields.io/pypi/v/graceful.svg)](https://pypi.python.org/pypi/graceful/)\n[![PyPI](https://img.shields.io/pypi/pyversions/graceful.svg)](https://pypi.python.org/pypi/graceful/)\n[![Build Status](https://travis-ci.org/swistakm/graceful.svg?branch=master)](https://travis-ci.org/swistakm/graceful)\n[![Coverage Status](https://coveralls.io/repos/swistakm/graceful/badge.svg?branch=master)](https://coveralls.io/r/swistakm/graceful?branch=master)\n[![Documentation Status](https://readthedocs.org/projects/graceful/badge/?version=latest)](https://graceful.readthedocs.io/en/latest/)\n[![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\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n\n# graceful\n\n`graceful` is an elegant Python REST toolkit built on top of\n[falcon](http://github.com/falconry/falcon) framework. It is highly inspired\nby [Django REST framework](http://www.django-rest-framework.org/) - mostly by\nhow object serialization is done but more emphasis here is put on API to\nbe self-descriptive.\n\nFeatures:\n\n* generic classes for list and single object resources\n* simple but extendable pagination\n* simple but extendable authentication and authorization\n* structured responses with content/meta separation\n* declarative fields and parameters\n* self-descriptive-everything: API description accessible both in python and\n  through `OPTIONS` requests\n* painless validation\n* 100% tests coverage\n* falcon\u003e=0.3.0 (tested up to 1.4.x)\n* python3 exclusive (tested from 3.3 to 3.6)\n\nCommunity behind graceful is starting to grow but we don't have any mailing\nlist yet. There was one on [Librelist](http://librelist.com/browser/graceful)\nbut no one used it and it seems that librelist became dead (see GitHub\nissue [#36](https://github.com/swistakm/graceful/issues/36)). For now let's use\ngitter chat until we decide on something new.\nChat is available [here](https://gitter.im/graceful-for-falcon/Lobby).\n\n\n## python3 only\n\n**Important**: `graceful` is python3 exclusive because **right now** should be\na good time to forget about python2. There are no plans for making `graceful` \npython2 compatible although it would be pretty straightforward to do so with\nexisting tools (like six).\n\n## usage\nFor extended tutorial and more information please refer to\n[guide](https://graceful.readthedocs.org/en/latest/guide/) included in\ndocumentation. \n\nAnyway here is simple example of working API made made with `graceful`:\n\n```python\nimport falcon\n\nfrom graceful.serializers import BaseSerializer\nfrom graceful.fields import IntField, RawField\nfrom graceful.parameters import StringParam\nfrom graceful.resources.generic import (\n    RetrieveAPI,\n    PaginatedListAPI,\n)\n\napi = application = falcon.API()\n\n# lets pretend that this is our backend storage\nCATS_STORAGE = [\n    {\"id\": 0, \"name\": \"kitty\", \"breed\": \"saimese\"},\n    {\"id\": 1, \"name\": \"lucie\", \"breed\": \"maine coon\"},\n    {\"id\": 2, \"name\": \"molly\", \"breed\": \"sphynx\"},\n]\n\n\n# this is how we represent cats in our API\nclass CatSerializer(BaseSerializer):\n    id = IntField(\"cat identification number\", read_only=True)\n    name = RawField(\"cat name\")\n    breed = RawField(\"official breed name\")\n\n\nclass Cat(RetrieveAPI):\n    \"\"\"\n    Single cat identified by its id\n    \"\"\"\n    serializer = CatSerializer()\n\n    def get_cat(self, cat_id):\n        try:\n            return [\n                cat for cat in CATS_STORAGE if cat['id'] == int(cat_id)\n            ][0]\n        except IndexError:\n            raise falcon.HTTPNotFound\n\n\n    def retrieve(self, params, meta, **kwargs):\n        cat_id = kwargs['cat_id']\n        return self.get_cat(cat_id)\n\nclass CatList(PaginatedListAPI):\n    \"\"\"\n    List of all cats in our API\n    \"\"\"\n    serializer = CatSerializer()\n\n    breed = StringParam(\"set this param to filter cats by breed\")\n\n    def list(self, params, meta, **kwargs):\n        if 'breed' in params:\n            filtered = [\n                cat for cat in CATS_STORAGE\n                if cat['breed'] == params['breed']\n            ]\n            return filtered\n        else:\n            return CATS_STORAGE\n\napi.add_route(\"/v1/cats/{cat_id}\", Cat())\napi.add_route(\"/v1/cats/\", CatList())\n```\n\nAssume this code is in python module named `example.py`.\nNow run it with [gunicorn](https://github.com/benoitc/gunicorn):\n\n    gunicorn -b localhost:8888 example\n\nAnd you're ready to query it (here with awesome [httpie](http://httpie.org)\ntool):\n\n```\n$ http localhost:8888/v0/cats/?breed=saimese\nHTTP/1.1 200 OK\nConnection: close\nDate: Tue, 16 Jun 2015 08:43:05 GMT\nServer: gunicorn/19.3.0\ncontent-length: 116\ncontent-type: application/json\n\n{\n    \"content\": [\n        {\n            \"breed\": \"saimese\",\n            \"id\": 0,\n            \"name\": \"kitty\"\n        }\n    ],\n    \"meta\": {\n        \"params\": {\n            \"breed\": \"saimese\",\n            \"indent\": 0\n        }\n    }\n}\n```\n\nOr access API description issuing `OPTIONS` request:\n\n```\n$ http OPTIONS localhost:8888/v0/cats\nHTTP/1.1 200 OK\nConnection: close\nDate: Tue, 16 Jun 2015 08:40:00 GMT\nServer: gunicorn/19.3.0\nallow: GET, OPTIONS\ncontent-length: 740\ncontent-type: application/json\n\n{\n    \"details\": \"List of all cats in our API\",\n    \"fields\": {\n        \"breed\": {\n            \"details\": \"official breed name\",\n            \"label\": null,\n            \"spec\": null,\n            \"type\": \"string\"\n        },\n        \"id\": {\n            \"details\": \"cat identification number\",\n            \"label\": null,\n            \"spec\": null,\n            \"type\": \"int\"\n        },\n        \"name\": {\n            \"details\": \"cat name\",\n            \"label\": null,\n            \"spec\": null,\n            \"type\": \"string\"\n        }\n    },\n    \"methods\": [\n        \"GET\",\n        \"OPTIONS\"\n    ],\n    \"name\": \"CatList\",\n    \"params\": {\n        \"breed\": {\n            \"default\": null,\n            \"details\": \"set this param to filter cats by breed\",\n            \"label\": null,\n            \"required\": false,\n            \"spec\": null,\n            \"type\": \"string\"\n        },\n        \"indent\": {\n            \"default\": \"0\",\n            \"details\": \"JSON output indentation. Set to 0 if output should not be formated.\",\n            \"label\": null,\n            \"required\": false,\n            \"spec\": null,\n            \"type\": \"integer\"\n        }\n    },\n    \"path\": \"/v0/cats\",\n    \"type\": \"list\"\n}\n```\n\n\n## contributing\n\nAny contribution is welcome. Issues, suggestions, pull requests - whatever. \nThere is only short set of rules that guide this project development you\nshould be aware of before submitting a pull request:\n\n* Only requests that have passing CI builds (Travis) will be merged.\n* Code is checked with `flakes8` and `pydocstyle` during build so this\n  implicitly means that compliance with PEP-8 and PEP-257 is mandatory.\n* No changes that decrease coverage will be merged.\n\nOne thing: if you submit a PR please do not rebase it later unless you\nare asked for that explicitly. Reviewing pull requests that suddenly had\ntheir history rewritten just drives me crazy.\n\n\n## license\n\nSee `LICENSE` file.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswistakm%2Fgraceful","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fswistakm%2Fgraceful","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswistakm%2Fgraceful/lists"}