From c54101c3e6f91e20d54de668160e36bc6847e44f Mon Sep 17 00:00:00 2001 From: Maxime Alves LIRMM Date: Tue, 22 Sep 2020 12:57:36 +0200 Subject: [PATCH] =?UTF-8?q?[wip][routing]=C2=A0add=20routing=20functions?= =?UTF-8?q?=20in=20/lib/domains?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- halfapi/lib/domain.py | 117 ++++++++++++++++++ tests/dummy_domain/routers/__init__.py | 5 + tests/dummy_domain/routers/abc/__init__.py | 5 + .../routers/abc/alphabet/__init__.py | 12 ++ .../routers/abc/pinnochio/__init__.py | 2 + tests/dummy_domain/routers/act/__init__.py | 5 + .../routers/act/personne/__init__.py | 13 ++ tests/dummy_domain/routers/act/personne/eo.py | 10 ++ tests/test_lib_domain.py | 12 ++ 9 files changed, 181 insertions(+) create mode 100644 halfapi/lib/domain.py create mode 100644 tests/dummy_domain/routers/__init__.py create mode 100644 tests/dummy_domain/routers/abc/__init__.py create mode 100644 tests/dummy_domain/routers/abc/alphabet/__init__.py create mode 100644 tests/dummy_domain/routers/abc/pinnochio/__init__.py create mode 100644 tests/dummy_domain/routers/act/__init__.py create mode 100644 tests/dummy_domain/routers/act/personne/__init__.py create mode 100644 tests/dummy_domain/routers/act/personne/eo.py create mode 100644 tests/test_lib_domain.py diff --git a/halfapi/lib/domain.py b/halfapi/lib/domain.py new file mode 100644 index 0000000..c108ed9 --- /dev/null +++ b/halfapi/lib/domain.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +import importlib +import typing as t + +VERBS = ('GET', 'POST', 'PUT', 'PATCH', 'DELETE') + +def get_fct_name(http_verb, path: t.List): + """ + Returns the predictable name of the function for a route + + Parameters: + - http_verb (str): The Route's HTTP method (GET, POST, ...) + - path (str): A path beginning by '/' for the route + + Returns: + str: The *unique* function name for a route and it's verb + + + Examples: + + >>> get_fct_name('foo', 'bar') + Traceback (most recent call last): + ... + Exception: Malformed path + + >>> get_fct_name('get', '/') + 'get_' + + >>> get_fct_name('GET', '/') + 'get_' + + >>> get_fct_name('POST', '/foo') + 'post_foo' + + >>> get_fct_name('POST', '/foo/bar') + 'post_foo_bar' + + >>> get_fct_name('DEL', '/foo/{boo}/{far}/bar') + 'del_foo_BOO_FAR_bar' + + >>> get_fct_name('DEL', '/foo/{boo:zoo}') + 'del_foo_BOO' + """ + fct_name = [http_verb.lower()] + for elt in path: + if elt and elt[0] == '{': + fct_name.append(elt[1:-1].split(':')[0].upper()) + else: + fct_name.append(elt) + + return '_'.join(fct_name) + +def route_generator(route_params, path, m_router): + for verb in VERBS: + params = route_params.get(verb) + if params is None: + continue + if not len(params): + print(f'No ACL for route [{verb}] "/".join(path)') + + try: + fct_name = get_fct_name(verb, [path[-1]]) + fct = getattr(m_router, fct_name) + except AttributeError: + print(f'{fct_name} is not defined in {m_router.__name__}') + continue + + yield { + 'verb':verb, + 'path':'/'.join([ elt for elt in path if elt ]), + 'params':params, + 'fct': fct } + + +def router_scanner(m_router, path=[]): + """ + [ + ('path', [acl], fct) + ] + """ + + if not hasattr(m_router, 'ROUTES'): + print(f'Missing *ROUTES* constant in *{domain}.routers*') + + routes = m_router.ROUTES + + pathlen = len(path) + for subpath, route_params in routes.items(): + path.append(subpath) + + for route in route_generator(route_params, path, m_router): + yield route + + subroutes = route_params.get('SUBROUTES', []) + for subroute in subroutes: + path.append(subroute) + submod = importlib.import_module(f'.{subroute}', m_router.__name__) + for route_scan in router_scanner(submod, path): + yield route_scan + + path.pop() + + if pathlen < len(path): + path.pop() + + + + +def domain_scanner(domain): + m_domain = importlib.import_module(domain) + + if not hasattr(m_domain, 'routers'): + raise Exception(f'No *routers* module in *{domain}*') + + m_router = importlib.import_module('.routers', domain) + + return router_scanner(m_router) diff --git a/tests/dummy_domain/routers/__init__.py b/tests/dummy_domain/routers/__init__.py new file mode 100644 index 0000000..c90a1c1 --- /dev/null +++ b/tests/dummy_domain/routers/__init__.py @@ -0,0 +1,5 @@ +ROUTES={ + '': { + 'SUBROUTES': ['abc','act'] + } +} diff --git a/tests/dummy_domain/routers/abc/__init__.py b/tests/dummy_domain/routers/abc/__init__.py new file mode 100644 index 0000000..82dc85e --- /dev/null +++ b/tests/dummy_domain/routers/abc/__init__.py @@ -0,0 +1,5 @@ +ROUTES={ + '': { + 'SUBROUTES': ['alphabet', 'pinnochio'] + } +} diff --git a/tests/dummy_domain/routers/abc/alphabet/__init__.py b/tests/dummy_domain/routers/abc/alphabet/__init__.py new file mode 100644 index 0000000..e8cb9a9 --- /dev/null +++ b/tests/dummy_domain/routers/abc/alphabet/__init__.py @@ -0,0 +1,12 @@ +ROUTES={ + '': { + 'GET': [{'acl':None, 'in':None}] + }, + '{test:uuid}': { + 'GET': [{'acl':None, 'in':None}], + 'POST': [{'acl':None, 'in':None}], + 'PATCH': [{'acl':None, 'in':None}], + 'PUT': [{'acl':None, 'in':None}] + } + +} diff --git a/tests/dummy_domain/routers/abc/pinnochio/__init__.py b/tests/dummy_domain/routers/abc/pinnochio/__init__.py new file mode 100644 index 0000000..5925edf --- /dev/null +++ b/tests/dummy_domain/routers/abc/pinnochio/__init__.py @@ -0,0 +1,2 @@ +ROUTES={ +} diff --git a/tests/dummy_domain/routers/act/__init__.py b/tests/dummy_domain/routers/act/__init__.py new file mode 100644 index 0000000..7d25160 --- /dev/null +++ b/tests/dummy_domain/routers/act/__init__.py @@ -0,0 +1,5 @@ +ROUTES={ + '': { + 'SUBROUTES': ['personne'] + } +} diff --git a/tests/dummy_domain/routers/act/personne/__init__.py b/tests/dummy_domain/routers/act/personne/__init__.py new file mode 100644 index 0000000..5978620 --- /dev/null +++ b/tests/dummy_domain/routers/act/personne/__init__.py @@ -0,0 +1,13 @@ +ROUTES={ + '': { + 'GET': [ + {'acl':None, 'out':('id')} + ], + }, + '{user_id:uuid}': { + 'GET': [ + {'acl':None, 'out':('id')} + ], + 'SUBROUTES': ['eo'] + } +} diff --git a/tests/dummy_domain/routers/act/personne/eo.py b/tests/dummy_domain/routers/act/personne/eo.py new file mode 100644 index 0000000..176e0b9 --- /dev/null +++ b/tests/dummy_domain/routers/act/personne/eo.py @@ -0,0 +1,10 @@ +from starlette.responses import Response + +ROUTES={ + '': { + 'GET': [{'acl': 'None', 'in': ['ok']}] + } +} + +async def get_(req): + return Response() diff --git a/tests/test_lib_domain.py b/tests/test_lib_domain.py new file mode 100644 index 0000000..da79b7f --- /dev/null +++ b/tests/test_lib_domain.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +from halfapi.lib.domain import VERBS, router_scanner + +def test_route_scanner(): + from .dummy_domain import routers + for route in router_scanner(routers): + print(f'[{route["verb"]}] {route["path"]} {route["fct"]}') + assert route['verb'] in VERBS + assert isinstance(route['path'], str) + assert len(route['params']) > 0 + assert hasattr(route['fct'], '__call__') +