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:
Alllaviewset
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
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
Algorithm | Hash digest | |
---|---|---|
SHA256 | 17e6a129fb96dbdf496ddd95b89c56e2405ad5decd4b7d0e09c394f699727de8 |
|
MD5 | bc1dc5855ab51c4d712e1222e6aac7b7 |
|
BLAKE2b-256 | facb46026c866042b0964d218da6242d8a79bfbcda872e42208817f9a19a9757 |
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
Algorithm | Hash digest | |
---|---|---|
SHA256 | 2c26913098f16d82b23a2da2770d7df174bdf6e7b04b778d6d79cd22df048a8c |
|
MD5 | caf63c43c577a2e22702d4b77cc928a9 |
|
BLAKE2b-256 | 2cd108670843b467247818e56f86818c53c13d533f849558c254e91ed7a4ee4d |