2
\$\begingroup\$

In Python I am trying to create an API for a connected device. I want to be available for both threaded (using request) and async applications (using aiohttp). What I've come up with is wrapping the get method of both requests and aiohttp in a decorator. This decorator is passed at init and API calls are explicitly wrapped using the passed decorator.

It works, but I'd like to know how others think of this approach ? Are there better ways or will I be running into issues later on ?

def threaded_gett(function):
    # The threaded decorator
    def wrapper(*args, **kwargs):
        url, params = function(*args)
        response = requests.get(url, params)
        _json = response.json()
        return function.__self__.process_response(_json)

    return wrapper

def async_gett(function):
    # The async decorator
    def wrapper(*args, **kwargs):
        url, params = function(*args)
        try:
            resp = yield from function.__self__.session.get(url, params=params)
        except Exception as ex:
            lgr.exception(ex)
        else:
            _json = yield from resp.json()
            yield from resp.release()
            return function.__self__.process_response(_json)

    # wrapping the decorator in the async coroutine decorator.
    wrapper = asyncio.coroutine(wrapper)
    return wrapper


class ThreadedApi(BaseApi):
    def __init__(self,threaded_gett):
        BaseApi.__init__(self,threaded_gett)


class AsyncApi(BaseApi):
    def __init__(self,async_gett):
        BaseApi.__init__(self,async_gett)


class BaseApi():
    def __init__(self,get_wrapper):
        self.status = get_wrapper(self.status)

    def status(self):
        return <status path>
\$\endgroup\$
0

1 Answer 1

4
\$\begingroup\$

Your code is not complete but perhaps I've got your idea.

self.status = get_wrapper(self.status) looks slightly strange to me (while it works, sure).

I would prefer to use class decorators:

import asyncio
import functools

import aiohttp
import requests


def public(func):
    func.public = True
    return func


def sync_api(cls):
    def wrapper(func):
        @functools.wraps(func)
        def wrapped(self, *args, **kwargs):
            url, params = func(self, *args, **kwargs)
            response = requests.get(url, params)
            _json = response.json()
            return self.process_response(_json)

    for name in dir(cls):
        func = getattr(cls, name)
        if getattr(func, 'public'):
            setattr(cls, name, wrapper(func))


def async_api(cls):
    def wrapper(func):
        @functools.wraps(func)
        @asyncio.coroutine
        def wrapped(self, *args, **kwargs):
            url, params = func(self, *args, **kwargs)
            session = self.session
            try:
                resp = yield from session.get(url, params=params)
            except Exception as ex:
                logger.exception(ex)
                raise
            try:
                _json = yield from resp.json()
                yield from resp.release()
            except Exception as ex:
                logger.exception(ex)
                resp.close()
                raise
            else:
                return self.process_response(_json)

    for name in dir(cls):
        func = getattr(cls, name)
        if getattr(func, 'public'):
            setattr(cls, name, wrapper(func))


class BaseApi:

    @public
    def status(self):
        return 'http://example.com', {}


@sync_api
class ThreadedApi(BaseApi):
    pass


@async_api
class AsyncApi(BaseApi):

    def __init__(self):
        self.session = aiohttp.Session()

P.S. Please keep in mind that yield from resp.json() may raise an exception too.

P.P.S. If you are using Python 3.4 I strongly suggest upgrading to 3.5.

Otherwise please use famous async def/await syntax. async with session.get() could simplify the snippet a little.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for looking into this ! I didn't know about functools. Learned something new today ! I am bound to 3.4 as I am using this on a raspberry pi. Last time I checked it still has version 3.4... \$\endgroup\$ Commented Nov 23, 2016 at 14:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.