3
\$\begingroup\$

I want to simplify aiohttp routes/resources configuration (see this paragraphs). For this task I have written this _resources.py file:

from aiohttp.web import Application


def _function(): pass


def _check_handler_types(*objects):
    names = ['endpoint', 'method', 'controller', 'args_cont']
    types = ['str', 'str', _function, ArgumentsContainer]
    for i, pair in enumerate(zip(objects, types)):
        if type(pair[0]) != type(pair[1]):
            raise RuntimeError('Wrong "{}"({}th) argument type, must be {}.'
                               'Full order: {}'
                               .format(names[i], i, types[i], zip(names, types)))


class Handlers:
    _handlers_list = None
    """ This field must be the list of tuples, where every tuple contain:
    <Endpoint:string>, <HTTPMethod:string>, <RequestHandler:function>
    For example:

        _handlers_list = [
            ('endpoint', 'GET', handler)
        ]
    """
    _prefix = None

    _endpoint_separator = '/'
    _resource_separator = '.'
    _methods = ['POST', 'PUT', 'GET']

    @property
    def handlers_list(self):
        return self._check_implementation('_handlers_list')

    @property
    def prefix(self):
        return self._check_implementation('_prefix')

    def format_endpoint(self, endpoint: str) -> str:
        return self._endpoint_separator + self.prefix + self._endpoint_separator + endpoint

    def format_resource(self, resource: str) -> str:
        return self.prefix + self._resource_separator + resource

    def format_method(self, method: str) -> str:
        method.capitalize()
        if method in self._methods:
            return method
        else:
            raise ValueError('"method" must be one of the {}'.format(self._methods))

    def format_controller(self, controller, wrappers_arguments: list):
        return controller

    def __init__(self, app: Application):
        for endpoint, method, controller, args_cont in self.handlers_list:
            _check_handler_types(endpoint, method, controller)
            app.router.add_resource(self.format_endpoint(endpoint),
                                    name=self.format_resource(endpoint)) \
                .add_route(self.format_method(method),
                           self.format_controller(controller, args_cont))

    def _check_implementation(self, name):
        value = self.__getattribute__(name)
        if value is None:
            raise NotImplementedError('You need to implement "{}"'
                                      ' class field.'.format(name))
        return value


class ArgumentsContainer:
    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs

    def get(self, key: str, default=None, force=False):
        try:
            value = self.kwargs[key]
        except KeyError:
            if force:
                raise RuntimeError('"{}" have not "{}" key in "kwargs".'.format(self, key))
            else:
                return default
        else:
            return value

    def num(self, index: int, default=None, force=False):
        try:
            value = self.args[index]
        except IndexError:
            if force:
                raise RuntimeError('"{}" have not "{}" index in "args".'.format(self, index))
            else:
                return default
        else:
            return value

For using these code it is resources.py file:

### import statements are ommited ###

GET_METHOD = 'GET'
TEMPLATE_ARG = 'template'


class APIHandlers(Handlers):
    _prefix = API_PREFIX

    _handlers_list = [
        (SOLVE_ENDPOINT, GET_METHOD, solve, ArgsCont()),
    ]


class TemplateHandlers(Handlers):
    _prefix = TEMPLATE_PREFIX

    _handlers_list = [
        (CONSOLE_ENDPOINT, GET_METHOD, console, ArgsCont(**{TEMPLATE_ARG: CONSOLE_TEMPLATE})),
        ### other tuples like the one above ###
    ]

    def format_controller(self, controller, args_container):
        return aiohttp_jinja2.template(
            template_name=args_container.get(TEMPLATE_ARG)
        )(controller)

and routes.py now looks like:

### import tatements are ommited ###    

def setup_routes(app: Application) -> None:
    app.router.add_get(INDEX_ENDPOINT, index)
    # static
    app.router.add_static(STATIC_ENDPOINT, STATIC_PATH)
    # classes
    APIHandlers(app)
    TemplateHandlers(app)

All ### ... ### comments are just for this question post.

Idea was to create classes that match common situations of defining aiohttp resource objects. But something that I created doesn't look like normal class implementation. How can it be improved, refactored for more developer-friendly usage?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.