# bareASGI-rest
This package provides enhanced support for writing REST
APIs with [bareASGI](https://bareasgi.com),
(read the [docs](https://rob-blackbourn.github.io/bareASGI-rest/)).
It includes:
- A router to simplify the creation of REST APIs,
- A swagger API endpoint
This is a Python 3.8+ package.
## Installation
The package can be installed from pypi.
```bash
$ pip install bareASGI-rest
```
An ASGI server will be required to run the code. The examples below use
[uvicorn](https://www.uvicorn.org/).
```bash
$ pip install uvicorn
```
## Usage
The router provided by this package maps the arguments and
types of request handlers.
We will create a mock book repository.
### Creating typed dictionaries
Here is the type of a book. We use `TypedDict` to allow automatic type discovery
```python
from datetime import datetime
from typing import TypedDict
class Book(TypedDict):
"""A Book
Args:
book_id (int): The book id
title (str): The title
author (str): The author
published (datetime): The publication date
"""
book_id: int
title: str
author: str
published: datetime
```
Note: the docstring will be used to provide documentation for swagger.
### Creating the API
Now we can build the API.
```python
from typing import Dict, List
from bareasgi_rest import RestError
BOOKS: Dict[int, Book] = {}
NEXT_ID: int = 0
async def get_books() -> List[Book]:
"""Get all the books.
This method gets all the books in the shop.
Returns:
List[Book]: All the books
"""
return list(BOOKS.values())
async def get_book(book_id: int) -> Book:
"""Get a book for a given id
Args:
book_id (int): The id of the book
Raises:
RestError: 404, when a book is not found
Returns:
Book: The book
"""
if book_id not in BOOKS:
raise RestError(404, "Book not found")
return BOOKS[book_id]
async def create_book(
author: str,
title: str,
published: datetime
) -> int:
"""Add a book
Args:
author (str): The author
title (str): The title
published (datetime): The publication date
Returns:
int: The id of the new book
"""
NEXT_ID += 1
BOOKS[NEXT_ID] = Book(
book_id=NEXT_ID,
title=title,
author=author,
published=published
)
return NEXT_ID
async def update_book(
book_id: int,
author: str,
title: str,
published: datetime
) -> None:
"""Update a book
Args:
book_id (int): The id of the book to update
author (str): The new author
title (str): The title
published (datetime): The publication date
Raises:
RestError: 404, when a book is not found
"""
if book_id not in BOOKS:
raise RestError(404, "Book not found")
BOOKS[book_id]['title'] = title
BOOKS[book_id]['author'] = author
BOOKS[book_id]['published'] = published
```
We can see that errors are handler by raising ResetError.
A convention has been applied such that the status code MUST
appear before the message, separated by a comma.
### Adding support for the REST router
Now we must create our application and add support for the router.
```python
from bareasgi import Application
from bareasgi_rest import RestHttpRouter, add_swagger_ui
router = RestHttpRouter(
None,
title="Books",
version="1",
description="A book api",
base_path='/api/1',
tags=[
{
'name': 'Books',
'description': 'The book store API'
}
]
)
app = Application(http_router=router)
add_swagger_ui(app)
```
Note the `base_path` argument can be used to prefix all
paths.
The `RestHttpRouter` is a subclass of the basic router, so
all those methods are also available.
### Creating the routes
Now we can create the routes:
```python
tags = ['Books']
router.add_rest({'GET'}, '/books', get_books,tags=tags)
router.add_rest({'GET'}, '/books/{bookId:int}', get_book, tags=tags)
router.add_rest({'POST'}, '/books', create_book, tags=tags, status_code=201)
router.add_rest({'PUT'}, '/books/{bookId:int}', update_book, tags=tags, status_code=204)
```
First we should note that the paths will be prefixed with the
`base_path` provided to the router.
Referring back to the implementation of `get_book` we can
see that the camel-case path variable `bookId` has been
mapped to the snake-case `book_id` parameter. The JSON object provided in the body of the `create_book` will
similarly map camel-cased properties to the snake-cased
function parameters.
We can also see how the status codes have been overridden
for the `POST` and `PUT` endpoints, and all the routes
have the "Books" tag for grouping in the UI.
### Serving the API
Finally we can serve the API:
```python
import uvicorn
uvicorn.run(app, port=9009)
```
Browsing to http://localhost/api/1/swagger we should see:
![Top Level](screenshot1.png)
When we expand `GET /books/{bookId}` we can see all the
information provided in the docstring and typing has been
passed through to the swagger UI.
![GET /books/{bookId}](screenshot2.png)
## Thanks
Thanks to [rr-](https://github.com/rr-) and contributors
for the excellent
[docstring-parser](https://github.com/rr-/docstring_parser)
package.
Raw data
{
"_id": null,
"home_page": "https://github.com/rob-blackbourn/bareASGI-rest",
"name": "bareasgi-rest",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.8,<4.0",
"maintainer_email": "",
"keywords": "bareASGI,rest,swagger",
"author": "Rob Blackbourn",
"author_email": "rob.blackbourn@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/35/17/6a5e0ae4cd774c9ce21cb3013f029a55d4b6240b8e208c86fe7875e214a4/bareASGI-rest-4.0.1.tar.gz",
"platform": null,
"description": "# bareASGI-rest\n\nThis package provides enhanced support for writing REST\nAPIs with [bareASGI](https://bareasgi.com),\n(read the [docs](https://rob-blackbourn.github.io/bareASGI-rest/)).\n\nIt includes:\n\n- A router to simplify the creation of REST APIs,\n- A swagger API endpoint\n\nThis is a Python 3.8+ package.\n\n## Installation\n\nThe package can be installed from pypi.\n\n```bash\n$ pip install bareASGI-rest\n```\n\nAn ASGI server will be required to run the code. The examples below use\n[uvicorn](https://www.uvicorn.org/).\n\n```bash\n$ pip install uvicorn\n```\n\n## Usage\n\nThe router provided by this package maps the arguments and\ntypes of request handlers.\n\nWe will create a mock book repository.\n\n### Creating typed dictionaries\n\nHere is the type of a book. We use `TypedDict` to allow automatic type discovery\n\n```python\nfrom datetime import datetime\nfrom typing import TypedDict\n\n\nclass Book(TypedDict):\n \"\"\"A Book\n\n Args:\n book_id (int): The book id\n title (str): The title\n author (str): The author\n published (datetime): The publication date\n \"\"\"\n book_id: int\n title: str\n author: str\n published: datetime\n```\n\nNote: the docstring will be used to provide documentation for swagger.\n\n### Creating the API\n\nNow we can build the API.\n\n```python\nfrom typing import Dict, List\n\nfrom bareasgi_rest import RestError\n\n\nBOOKS: Dict[int, Book] = {}\nNEXT_ID: int = 0\n\nasync def get_books() -> List[Book]:\n \"\"\"Get all the books.\n\n This method gets all the books in the shop.\n\n Returns:\n List[Book]: All the books\n \"\"\"\n return list(BOOKS.values())\n\n\nasync def get_book(book_id: int) -> Book:\n \"\"\"Get a book for a given id\n\n Args:\n book_id (int): The id of the book\n\n Raises:\n RestError: 404, when a book is not found\n\n Returns:\n Book: The book\n \"\"\"\n\n if book_id not in BOOKS:\n raise RestError(404, \"Book not found\")\n\n return BOOKS[book_id]\n\n\nasync def create_book(\n author: str,\n title: str,\n published: datetime\n) -> int:\n \"\"\"Add a book\n\n Args:\n author (str): The author\n title (str): The title\n published (datetime): The publication date\n\n Returns:\n int: The id of the new book\n \"\"\"\n NEXT_ID += 1\n BOOKS[NEXT_ID] = Book(\n book_id=NEXT_ID,\n title=title,\n author=author,\n published=published\n )\n return NEXT_ID\n\n\nasync def update_book(\n book_id: int,\n author: str,\n title: str,\n published: datetime\n) -> None:\n \"\"\"Update a book\n\n Args:\n book_id (int): The id of the book to update\n author (str): The new author\n title (str): The title\n published (datetime): The publication date\n\n Raises:\n RestError: 404, when a book is not found\n \"\"\"\n if book_id not in BOOKS:\n raise RestError(404, \"Book not found\")\n BOOKS[book_id]['title'] = title\n BOOKS[book_id]['author'] = author\n BOOKS[book_id]['published'] = published\n```\n\nWe can see that errors are handler by raising ResetError.\nA convention has been applied such that the status code MUST\nappear before the message, separated by a comma.\n\n### Adding support for the REST router\n\nNow we must create our application and add support for the router.\n\n```python\nfrom bareasgi import Application\nfrom bareasgi_rest import RestHttpRouter, add_swagger_ui\n\n\nrouter = RestHttpRouter(\n None,\n title=\"Books\",\n version=\"1\",\n description=\"A book api\",\n base_path='/api/1',\n tags=[\n {\n 'name': 'Books',\n 'description': 'The book store API'\n }\n ]\n)\napp = Application(http_router=router)\nadd_swagger_ui(app)\n```\n\nNote the `base_path` argument can be used to prefix all\npaths.\n\nThe `RestHttpRouter` is a subclass of the basic router, so\nall those methods are also available.\n\n### Creating the routes\n\nNow we can create the routes:\n\n```python\ntags = ['Books']\nrouter.add_rest({'GET'}, '/books', get_books,tags=tags)\nrouter.add_rest({'GET'}, '/books/{bookId:int}', get_book, tags=tags)\nrouter.add_rest({'POST'}, '/books', create_book, tags=tags, status_code=201)\nrouter.add_rest({'PUT'}, '/books/{bookId:int}', update_book, tags=tags, status_code=204)\n```\n\nFirst we should note that the paths will be prefixed with the\n`base_path` provided to the router.\n\nReferring back to the implementation of `get_book` we can\nsee that the camel-case path variable `bookId` has been\nmapped to the snake-case `book_id` parameter. The JSON object provided in the body of the `create_book` will\nsimilarly map camel-cased properties to the snake-cased\nfunction parameters.\n\nWe can also see how the status codes have been overridden\nfor the `POST` and `PUT` endpoints, and all the routes\nhave the \"Books\" tag for grouping in the UI.\n\n### Serving the API\n\nFinally we can serve the API:\n\n```python\nimport uvicorn\n\nuvicorn.run(app, port=9009)\n```\n\nBrowsing to http://localhost/api/1/swagger we should see:\n\n![Top Level](screenshot1.png)\n\nWhen we expand `GET /books/{bookId}` we can see all the\ninformation provided in the docstring and typing has been\npassed through to the swagger UI.\n\n![GET /books/{bookId}](screenshot2.png)\n\n## Thanks\n\nThanks to [rr-](https://github.com/rr-) and contributors\nfor the excellent\n[docstring-parser](https://github.com/rr-/docstring_parser)\npackage.\n",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "REST support for bareASGI",
"version": "4.0.1",
"project_urls": {
"Homepage": "https://github.com/rob-blackbourn/bareASGI-rest",
"Repository": "https://github.com/rob-blackbourn/bareASGI-rest"
},
"split_keywords": [
"bareasgi",
"rest",
"swagger"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "639d28b604ed0b80850c47add6d93eedc766a94c6dbc260901521185324b1396",
"md5": "6bd2b4d7361dfd51caa06ba6c3ef10e1",
"sha256": "7d0b8821238af79cd98c1b79a41bc7d88a3155200af6f3a3bd3820d80f7b8ac0"
},
"downloads": -1,
"filename": "bareASGI_rest-4.0.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "6bd2b4d7361dfd51caa06ba6c3ef10e1",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8,<4.0",
"size": 22649,
"upload_time": "2023-05-08T06:08:33",
"upload_time_iso_8601": "2023-05-08T06:08:33.730210Z",
"url": "https://files.pythonhosted.org/packages/63/9d/28b604ed0b80850c47add6d93eedc766a94c6dbc260901521185324b1396/bareASGI_rest-4.0.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "35176a5e0ae4cd774c9ce21cb3013f029a55d4b6240b8e208c86fe7875e214a4",
"md5": "7f5734a9a3e445c65bfe366ccd7f26ea",
"sha256": "1a5c3cecf0a1db9ad88ecf6551e9682cc23e8737d3bb9aeebb922e91b080e574"
},
"downloads": -1,
"filename": "bareASGI-rest-4.0.1.tar.gz",
"has_sig": false,
"md5_digest": "7f5734a9a3e445c65bfe366ccd7f26ea",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8,<4.0",
"size": 18882,
"upload_time": "2023-05-08T06:08:30",
"upload_time_iso_8601": "2023-05-08T06:08:30.916722Z",
"url": "https://files.pythonhosted.org/packages/35/17/6a5e0ae4cd774c9ce21cb3013f029a55d4b6240b8e208c86fe7875e214a4/bareASGI-rest-4.0.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-05-08 06:08:30",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "rob-blackbourn",
"github_project": "bareASGI-rest",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "bareasgi-rest"
}