diff --git a/halfapi/app.py b/halfapi/app.py index 062fbdf..4d8a535 100644 --- a/halfapi/app.py +++ b/halfapi/app.py @@ -16,11 +16,10 @@ from halfapi.conf import HOST, PORT, DB_NAME, SECRET, PRODUCTION, DOMAINS from halfapi.lib.jwt_middleware import JWTAuthenticationBackend from halfapi.lib.responses import * -from halfapi.lib.routes import get_starlette_routes -from halfapi.lib.domain import domain_scanner +from halfapi.lib.routes import gen_starlette_routes -debug_routes = [ +routes = [ Route('/', lambda request, *args, **kwargs: PlainTextResponse('It Works!')), Route('/user', lambda request, *args, **kwargs: JSONResponse({'user':request.user.json}) @@ -30,10 +29,12 @@ debug_routes = [ JSONResponse({'payload':str(request.payload)})) ] if not PRODUCTION else [] +for route in gen_starlette_routes(): + routes.append(route) + application = Starlette( debug=not PRODUCTION, - routes=debug_routes + [ route for route in - gen_starlette_routes(domain_scanner()) ], + routes=routes, middleware=[ Middleware(AuthenticationMiddleware, backend=JWTAuthenticationBackend(secret_key=SECRET)) diff --git a/halfapi/cli/domain.py b/halfapi/cli/domain.py index fc3bfcf..ec727fe 100644 --- a/halfapi/cli/domain.py +++ b/halfapi/cli/domain.py @@ -103,57 +103,6 @@ def update_db(domain): acl.insert() - def get_fct_name(http_verb, path): - """ - 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' - """ - - if path[0] != '/': - raise Exception('Malformed path') - - elts = path[1:].split('/') - - fct_name = [http_verb.lower()] - for elt in elts: - if elt and elt[0] == '{': - fct_name.append(elt[1:-1].split(':')[0].upper()) - else: - fct_name.append(elt) - - return '_'.join(fct_name) def add_route(http_verb, path, router, acls): @@ -216,8 +165,11 @@ def update_db(domain): for router_name in dom_mod.ROUTERS: try: - router_mod = getattr(dom_mod.routers, router_name) - except AttributError: + router_mod = None + for router_subname in router_name.split('.'): + router_mod = getattr(router_mod or dom_mod.routers, router_subname) + + except AttributeError: # Missing router, continue click.echo(f'The domain {domain} has no *{router_name}* router', err=True) continue @@ -232,7 +184,23 @@ def update_db(domain): continue - for route_path, route_params in router_mod.ROUTES.items(): + d_routes = {} + + if hasattr(router_mod, 'ROUTES'): + d_routes.update(router_mod.ROUTES) + else: + logger.warning(f'{router_name} is missing a ROUTES variable') + + if hasattr(router_mod, 'ROUTERS'): + for router_router in router_mod.ROUTERS: + if hasattr(router_router, 'ROUTES'): + d_routes.update(router_routes.ROUTES) + else: + logger.warning(f'{router_name}.{router_router.__name__} is missing a ROUTES variable') + else: + logger.warning(f'{router_mod} is missing a ROUTERS variable') + + for route_path, route_params in d_routes.items(): for http_verb, acls in route_params.items(): try: # Insert a route and it's ACLS @@ -274,6 +242,7 @@ def domain(domains, delete, update, create, read): #, domains, read, create, up update (boolean): If set, update the database for the selected domains """ + raise NotImplementedError if not domains: if create: diff --git a/halfapi/lib/domain.py b/halfapi/lib/domain.py index c108ed9..4a14bff 100644 --- a/halfapi/lib/domain.py +++ b/halfapi/lib/domain.py @@ -50,7 +50,7 @@ def get_fct_name(http_verb, path: t.List): return '_'.join(fct_name) -def route_generator(route_params, path, m_router): +def gen_routes(route_params, path, m_router): for verb in VERBS: params = route_params.get(verb) if params is None: @@ -67,12 +67,12 @@ def route_generator(route_params, path, m_router): yield { 'verb':verb, - 'path':'/'.join([ elt for elt in path if elt ]), + 'path':f"/{'/'.join([ elt for elt in path if elt ])}", 'params':params, 'fct': fct } - - -def router_scanner(m_router, path=[]): + + +def gen_router_routes(m_router, path=[]): """ [ ('path', [acl], fct) @@ -80,7 +80,7 @@ def router_scanner(m_router, path=[]): """ if not hasattr(m_router, 'ROUTES'): - print(f'Missing *ROUTES* constant in *{domain}.routers*') + print(f'Missing *ROUTES* constant in *{m_router.__name__}*') routes = m_router.ROUTES @@ -88,14 +88,14 @@ def router_scanner(m_router, path=[]): for subpath, route_params in routes.items(): path.append(subpath) - for route in route_generator(route_params, path, m_router): + for route in gen_routes(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): + for route_scan in gen_router_routes(submod, path): yield route_scan path.pop() @@ -106,7 +106,7 @@ def router_scanner(m_router, path=[]): -def domain_scanner(domain): +def gen_domain_routes(domain): m_domain = importlib.import_module(domain) if not hasattr(m_domain, 'routers'): @@ -114,4 +114,4 @@ def domain_scanner(domain): m_router = importlib.import_module('.routers', domain) - return router_scanner(m_router) + return gen_router_routes(m_router, [domain]) diff --git a/halfapi/lib/routes.py b/halfapi/lib/routes.py index 12c0261..7354f8c 100644 --- a/halfapi/lib/routes.py +++ b/halfapi/lib/routes.py @@ -14,7 +14,7 @@ from halfapi.db import ( AclFunction, Acl) from halfapi.lib.responses import * -from halfapi.lib.domain import domain_scanner +from halfapi.lib.domain import gen_domain_routes from starlette.exceptions import HTTPException from starlette.routing import Mount, Route from starlette.requests import Request @@ -26,8 +26,7 @@ def route_decorator(fct: Callable, acls_mod, params: List[Dict]): @wraps(fct) async def caller(req: Request, *args, **kwargs): for param in params: - acl_fct = getattr(acls_mod, param['acl']) - if acl_fct(req, *args, **kwargs): + if param['acl'](req, *args, **kwargs): """ We the 'acl' and 'keys' kwargs values to let the decorated function know which ACL function answered @@ -37,7 +36,7 @@ def route_decorator(fct: Callable, acls_mod, params: List[Dict]): req, *args, **{ **kwargs, - **params + **param }) raise HTTPException(401) @@ -49,17 +48,7 @@ def gen_starlette_routes(): domain_acl_mod = importlib.import_module( f'{domain}.acl') - ( Route(route['path'], - route_decorator( - route['fct'], - domain_acl_mod, - route['params'], - ), methods=[route['verb']]) - for route in domain_scanner(domain) - ) - - for route in gen_routes(domain): - + for route in gen_domain_routes(domain): yield ( Route(route['path'], route_decorator(