Welcome to AIOService’s documentation!

Contents:

AIOService overview

AIOService is

Set of routines to build web services.

AIOService is not

A web service, it is a framework that helps to organize web services based on aiohttp.

AIOService: HTTP

AIOService.HTTP contains set of routines to build HTTP web services with nested versioned API controllers. It has:

Root HTTP service
Versioned sub-services
Request controller handlers routines

How to build a HTTP web service

AIOHTTP

Web server:

from aiohttp import web
async def handle(request):
    name = request.match_info.get('name', "Anonymous")
    text = "Hello, " + name
    return web.Response(text=text)

app = web.Application()
app.router.add_get('/', handle)
app.router.add_get('/{name}', handle)

web.run_app(app)

Also, you can collect handlers into classes:

class Collector(object):

    async def handle(self, request):
        name = request.match_info.get('name', "Anonymous")
        text = "Hello, " + name
        return web.Response(text=text)

app = web.Application()
coll_handlers = Collector()
app.router.add_get('/', coll_handlers.handle)
app.router.add_get('/{name}', coll_handlers.handle)

Pros:

* simple

Cons:

* additional custom logic to version handlers

AIOService basics

Using aioservice you can:

  • define controllers in classes with capability to define its API version on class level
  • map versioned controllers to versioned sub-service
  • map any number of versioned sub-services to root service
  • optionally generate swagger docs from root service

AIOService HTTP controllers

Controller is a class that consists of HTTP handlers where each handler pinned to specific route and HTTP method

Rules for writing controllers:

  • each controller should define its version (we enforce developer to build sustainable API)
  • each handler must be a coroutine

Controller example:

class TestController(controller.ServiceController):

    controller_name = "test_controllers"
    version = "v1"

    @requests.api_action(method='GET', route="{some_key}")
    async def get_some_key(self, request):
        return web.json_response(data={
            "some_key": request.match_info["some_key"]
        }, status=200)

So, when this controller will be pinned to versioned sub-service, get_some_key coroutine will handle requests coming to route /v1/{some_key}

AIOService controller action wrapper

Each handler in controller class should be wrapped with requests.api_action decorator. With requests.api_action it is possible to define which HTTP method, HTTP route are being covered by decorated handler. Also it help in cases when exception somehow is not handled properly to send correctly formatter response to the server side:

@requests.api_action(method='GET', route="{some_key}")
async def get_some_key(self, request):
    return web.json_response(data={
        "some_key": request.match_info["some_key"]
    }, status=200)

AIOService versioned services

Version service (or sub-service) is a set of controller classes and middleware(s) that are grouped by specific criteria. Such criteria is - controller version. So, using this criteria it is possible to define versioned service to handle API:

sub_app = service.VersionedService(
    versioned_controllers,
    middleware=[content_type_validator])

Using versioned services it is possible to assign different middleware to different routes grouped by controller(s) version:

async def auth_through_token(app: web.Application, handler):
    async def middleware_handler(request: web.Request):
        headers = request.headers
        x_auth_token = headers.get("X-Auth-Token")
        project_id = request.match_info.get('project_id')
        c = config.Config.config_instance()
        try:
            auth = identity.Token(c.auth_url,
                                  token=x_auth_token,
                                  project_id=project_id)
            sess = session.Session(auth=auth)
            ks = client.Client(session=sess,
                               project_id=project_id)
            ks.authenticate(token=x_auth_token)
        except Exception as ex:
            return web.json_response(status=401, data={
                "error": {
                    "message": ("Not authorized. Reason: {}"
                                .format(str(ex)))
                }
            })
        return await handler(request)
    return middleware_handler

async def content_type_validator(app: web.Application, handler):
    async def middleware_handler(request: web.Request):
        headers = request.headers
        content_type = headers.get("Content-Type")
        if request.has_body:
            if "application/json" != content_type:
                return web.json_response(
                    data={
                        "error": {
                            "message": "Invalid content type"
                        }
                    }, status=400)
        return await handler(request)
    return middleware_handler

sub_app_v1 = service.VersionedService(
    versioned_controllers_v1,
    middleware=[content_type_validator])

sub_app_v2 = service.VersionedService(
    versioned_controllers_v2,
    middleware=[content_type_validator, auth_through_token])

Note, that you can define two versioned services with the same set of controllers, but they would override each other. Such case is similar to:

app.router.add_get('/', handle_v1)
app.router.add_get('/', handle_v2)

AIOService root service

Once versioned services are defined:

sub_app_v1 = service.VersionedService(
    versioned_controllers_v1,
    middleware=[content_type_validator])

sub_app_v2 = service.VersionedService(
    versioned_controllers_v2,
    middleware=[content_type_validator, auth_through_token])

it is possible to bind them to root service:

main_app = service.HTTPService(
    subservice_definitions=[sub_app_v1, sub_app_v2],
    event_loop=event_loop
)

or, as an alternative, bind root service to versioned service:

sub_app_v1.bind_to_service(main_app)
sub_app_v2.bind_to_service(main_app)

Swagger docs

Optionally it is possible to use swagger doc generator. It is requires to install aiohttp_swagger in first place:

pip install aiohttp_swagger==1.0.2

Now only one step left:

main_app.apply_swagger()

It is recommended avoid use of default settings for swagger doc, so apply_swagger allows to pass following parameters:

swagger_url (default: "/api/doc") - defines at which HTTP route swagger doc will be available
description (default:"Swagger API definition") - Swagger doc description
api_version (default:"1.0.0") - Swagger doc version
title (default: "Swagger API") - Swagger doc title
contact (default: "") - developer(s) contacts

Indices and tables