Skip to main content

A Lyte (light) Async Viewset.

Project description

LAViewSet

A Lyte (light) Asynchronous ViewSet.

A ViewSets package, a-la Django Rest Framework - ViewSets, built on top of aiohttp.web.

*********************************************************

Getting Started


Quick Start

# laviewset_intro.py

from aiohttp import web
from laviewset import Route, ViewSet, HttpMethods

app = web.Application()
base_route = Route.create_base(app.router)      # '/'


class ListingsViewSet(ViewSet):

    route = base_route.extend('listings')  # '/listings'

    @route('/', HttpMethods.GET)
    async def list(self, request):
        assert isinstance(request, web.Request)
        return web.Response(text='GET at '/listings')


web.run_app(app)

For a step-by-step walkthrough, continue reading below.

Or, skip ahead for a more thorough look at fully extending laviewset.ViewSet.


Intro

The first step is to create a base route by passing the aiohttp.web.UrlDispatcher of your current application into Route.create_base:

# laviewset_intro.py

from aiohttp import web
from laviewset import Route

app = web.Application()
base_route = Route.create_base(app.router)      # '/'

base_route can then be extended into resources that you want to include in your ViewSets:

listings_route = base_route.extend('listings')  # '/listings'
events_route = base_route.extend('/events')     # '/events'

# We can further extend a resource
sessions_route = listings.extend('sessions')    # '/listings/sessions'

Now that we have the resource we want a ViewSet to manage, we can create our ViewSet. This is done by subclassing laviewset.ViewSet, including your route as the route attribute, and overriding the ViewSet methods and/or including your custom views:

# laviewset_intro.py

from aiohttp import web
from laviewset import Route, ViewSet, HttpMethods

app = web.Application()
base_route = Route.create_base(app.router)      # '/'


class ListingsViewSet(ViewSet):

    route = base_route.extend('listings')  # '/listings'
    serializer = 'some_serializer'

    @route('/', HttpMethods.GET)
    async def list(self, request):
        ...
        return web.Response(text='GET at /listings with {self.serializer}')


web.run_app(app)

Note, the code above is similar to the following:

from aiohttp import web


serializer = 'some_serializer'

def handler(request):
    ...
    return web.Response(text='GET at /listings with {self.serializer}')


app = web.Application()
app.add_routes([web.get('/', handler)])
web.run_app(app)

ViewSet methods

@route decorator

In order to create a view on the ViewSet, the @route decorator is required. Since each view is essentially a wrapper over aiohttp.web.route, the arguments passed into the decorator correlate with the arguments for web.route: the first argument is the path, the second argument is the HTTP method for the view (method), any other keyword argument passed into the decorator will be included as kwargs to the web.route method, and finally, the view itself will be the handler.

    @route('/', HttpMethods.GET, z=20, f='abc')     # z=20 and f='abc' will be
    async def list(self, request):                  # passed into web.route
        ...
        return web.Response(text='GET at '/listings')

Since the idea behind laviewset is an asynchronous ViewSet a-la Django Rest Framework - ViewSets, the methods list, create, retrieve, update, partial_update, and delete are included on the base class laviewset.ViewSet. However, unlike Rest Framework, they are not complete: the user still needs to declare the view using the @route decorator. One reason for this design decision is to allow more flexibility to the user, e.g. to decide on what kwargs to pass into web.route. Trying to access any of the aforementioned methods without overriding and completing them will return a 404NotFound.

View method signatures

The signatures of the views are important. Each view signature requires at least the self and request arguments. The request is in fact a web.Request object, and can be accessed as such: request.query, request.rel_url, etc are all accessible. If the path declared in the @route decorator is a variable path, then the {identifier} should be included in the view signature as a KEYWORD_ONLY argument and have the same name as the identifier included in the path, otherwise an laviewset.ViewSignatureError will be raised:

# Correct

    @route(r'/{pk:\d+}', HttpMethods.GET)  # /listings/123
    async def retrieve(self, request, *, pk):   # `pk` is KEYWORD_ONLY and
        assert pk == 123                        # `pk` is same as identifier
        return web.Response(text=f'retrieved {pk}')

# Incorrect

    @route(r'/{pk:\d+}', HttpMethods.GET)
    async def retrieve(self, request, pk):      # `pk` is not KEYWORD_ONLY
        ...
        return web.Response(text=f'retrieved {pk}')

# Incorrect

    @route(r'/{fk:\d+}', HttpMethods.GET)
    async def retrieve(self, request, *, pk):  # `pk` != `fk`
        ...
        return web.Response(text=f'retrieved {pk}')


Custom views

Custom views can also be defined. Simply wrap a method with the @route decorator and follow the rules described above:

    # Custom GET view
    # '/listings/123/events/Coachella'
    @route(r'/{pk:\d+}/events/{name:\w+}', HttpMethods.GET)
    async def custom_get(self, request, *, pk, name):
        assert pk == 123
        assert name == 'Coachella'
        return web.Response(text=f'GET at /listings/{pk}/events/{name}')

    # Custom DELETE view
    # '/listings/custom_delete/123/Coachella'
    @route(r'/custom_delete/{pk:\d+}/{name:\w+}', HttpMethods.DELETE)
    async def custom_delete(self, request, *, pk, name):
        assert pk == 123
        assert name == 'Coachella'
        return web.Response(text=f'Deleting something to do with {pk} {name}')

A short note on errors:
All laviewset errors are raised "statically", i.e. before your server is up and running.


Project structure

Since ViewSets do not need to be initialized, it is important to let the module your app is running in know about each ViewSet. Therefore, for more complex project structures, the following structure is recommended:

proj/
├── package/
│   ├── __init__.py
│   ├── __main__.py
│   ├── server.py
│   ├── app1/
│   │   └── views.py
│   └── app2/
│       └── views.py
├── conf.py
└── README.md
# package/server.py

from aiohttp import web
from laviewset import Route

app = web.Application()
base_route = Route.create_base(app.router)


def run_server(app: web.Application) -> None:
    web.run_app(app, host='localhost', port=8000)

# package/app1/views.py

from laviewset import ViewSet
from ..server import base_route
...
# package/app2/views.py

from laviewset import ViewSet
from ..server import base_route
...
# package/__init__.py

# Now `server.app` knows about `ViewSet`s.
from .app1 import views
from .app2 import views
# package/__main__.py

from .server import app, run_server

run_server(app)

*********************************************************

Cheatsheet

# quicksetup.py

from aiohttp import web
from laviewset import Route, ViewSet, HttpMethods

app = web.Application()
base_route = Route.create_base(app.router)


class ListingsViewSet(ViewSet):

    route = base_route.extend('listings')  # '/listings'

    @route('/', HttpMethods.GET)
    async def list(self, request):
        # GET at '/listings'
        ...
        return web.Response(...)

    @route(r'/{pk:\d+}', HttpMethods.GET)
    async def retrieve(self, request, *, pk):
        # GET at '/listings/{pk}'
        # where the dynamic value {pk} can be accessed 
        # through `pk`.
        ...
        return web.Response(...)

    @route('/', HttpMethods.POST)
    async def create(self, request):
        # POST at '/listings'
        data = await request.json()
        return web.Response(text=f'Metrics Created data: {data}')

    @route(r'/{pk:\d+}', HttpMethods.DELETE)
    async def delete(self, request, *, pk):
        # DELETE at '/listings/{pk}'
        ...
        return web.Response(...)

    @route(r'/{pk:\d+}', HttpMethods.PUT)
    async def update(self, request, *, pk):
        # PUT at '/listings/{pk}'
        ...
        return web.Response(...)

    @route(r'/{pk:\d+}', HttpMethods.PATCH)
    async def partial_update(self, request, *, pk):
        # PATCH at '/listings/{pk}'
        ...
        return web.Response(...)

    @route(r'/{pk:\d+}/do_thing/{name:\w+}', HttpMethods.GET)
    async def custom_view(self, request, *, pk, name):
        # GET at '/listings/{pk}/do_thing/{name}'
        ...
        return web.Response(...)

*********************************************************

Requirements

  • Python >= 3.8
  • aiohttp==3.6.2

*********************************************************

Installation

This package does not exist on PyPI yet, so the only way to install it is through LAViewSet/setup.py.

*********************************************************

License

laviewset is offered under the MIT license.

This package uses the aiohttp package, which is distributed under the Apache 2 license.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

LAViewSet-0.0.1.tar.gz (9.5 kB view details)

Uploaded Source

Built Distribution

LAViewSet-0.0.1-py3-none-any.whl (10.1 kB view details)

Uploaded Python 3

File details

Details for the file LAViewSet-0.0.1.tar.gz.

File metadata

  • Download URL: LAViewSet-0.0.1.tar.gz
  • Upload date:
  • Size: 9.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.2.0 pkginfo/1.6.1 requests/2.25.0 setuptools/49.2.1 requests-toolbelt/0.9.1 tqdm/4.53.0 CPython/3.8.6

File hashes

Hashes for LAViewSet-0.0.1.tar.gz
Algorithm Hash digest
SHA256 17e6a129fb96dbdf496ddd95b89c56e2405ad5decd4b7d0e09c394f699727de8
MD5 bc1dc5855ab51c4d712e1222e6aac7b7
BLAKE2b-256 facb46026c866042b0964d218da6242d8a79bfbcda872e42208817f9a19a9757

See more details on using hashes here.

File details

Details for the file LAViewSet-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: LAViewSet-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 10.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.2.0 pkginfo/1.6.1 requests/2.25.0 setuptools/49.2.1 requests-toolbelt/0.9.1 tqdm/4.53.0 CPython/3.8.6

File hashes

Hashes for LAViewSet-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2c26913098f16d82b23a2da2770d7df174bdf6e7b04b778d6d79cd22df048a8c
MD5 caf63c43c577a2e22702d4b77cc928a9
BLAKE2b-256 2cd108670843b467247818e56f86818c53c13d533f849558c254e91ed7a4ee4d

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page