bareasgi-rest


Namebareasgi-rest JSON
Version 4.0.1 PyPI version JSON
download
home_pagehttps://github.com/rob-blackbourn/bareASGI-rest
SummaryREST support for bareASGI
upload_time2023-05-08 06:08:30
maintainer
docs_urlNone
authorRob Blackbourn
requires_python>=3.8,<4.0
licenseApache-2.0
keywords bareasgi rest swagger
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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"
}
        
Elapsed time: 0.07048s