diff --git a/halfapi/lib/domain.py b/halfapi/lib/domain.py index 3e2ddd5..f24893b 100644 --- a/halfapi/lib/domain.py +++ b/halfapi/lib/domain.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 import importlib -import typing as typ +from types import ModuleType +from typing import Generator, Dict, List VERBS = ('GET', 'POST', 'PUT', 'PATCH', 'DELETE') -def get_fct_name(http_verb, path: str): +def get_fct_name(http_verb: str, path: str) -> str: """ Returns the predictable name of the function for a route @@ -48,15 +49,15 @@ def get_fct_name(http_verb, path: str): return '_'.join(fct_name) -def gen_routes(route_params, path, m_router): - fqtn = route_params.get('FQTN') +def gen_routes(route_params: Dict, path: List, m_router: ModuleType) -> Generator: + d_res = {'fqtn': route_params.get('FQTN')} 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)') + if len(params) == 0: + print(f'No ACL for route [{verb}] "/".join(path)') try: fct_name = get_fct_name(verb, path[-1]) @@ -65,38 +66,40 @@ def gen_routes(route_params, path, m_router): print(f'{fct_name} is not defined in {m_router.__name__}') continue - yield { - 'verb':verb, - 'path':f"/{'/'.join([ elt for elt in path if elt ])}", - 'params':params, - 'fct': fct, - 'fqtn': fqtn } + d_res[verb] = {'fct': fct, 'params': params} + + yield f"/{'/'.join([ elt for elt in path if elt ])}", d_res -def gen_router_routes(m_router, path=[]): +def gen_router_routes(m_router, path=None): """ - [ - ('path', [acl], fct, fqtn) - ] + { + '/truc/toto': { + } + } """ if not hasattr(m_router, 'ROUTES'): print(f'Missing *ROUTES* constant in *{m_router.__name__}*') + if path is None: + path = [] + routes = m_router.ROUTES for subpath, route_params in routes.items(): path.append(subpath) - for route in gen_routes(route_params, path, m_router): - yield route + for r_path, d_route in gen_routes(route_params, path, m_router): + yield r_path, d_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 gen_router_routes(submod, path): - yield route_scan + for r_path, d_route in gen_router_routes(submod, path): + yield r_path, d_route + path.pop() diff --git a/halfapi/lib/routes.py b/halfapi/lib/routes.py index 38ee11a..ec56602 100644 --- a/halfapi/lib/routes.py +++ b/halfapi/lib/routes.py @@ -2,19 +2,14 @@ from functools import wraps import importlib import sys -from typing import Callable, List, Tuple, Dict +from typing import Callable, List, Tuple, Dict, Generator +from types import ModuleType from halfapi.conf import (PROJECT_NAME, DB_NAME, HOST, PORT, PRODUCTION, DOMAINS) -# from halfapi.db import ( -# Domain, -# APIRouter, -# APIRoute, -# AclFunction, -# Acl) from halfapi.lib.responses import * -from halfapi.lib.domain import gen_domain_routes +from halfapi.lib.domain import gen_domain_routes, VERBS from starlette.exceptions import HTTPException from starlette.routing import Mount, Route from starlette.requests import Request @@ -22,7 +17,7 @@ from starlette.requests import Request class DomainNotFoundError(Exception): pass -def route_acl_decorator(fct: Callable, acls_mod, params: List[Dict]): +def route_acl_decorator(fct: Callable, params: List[Dict]): """ Decorator for async functions that calls pre-conditions functions and appends kwargs to the target function @@ -31,8 +26,6 @@ def route_acl_decorator(fct: Callable, acls_mod, params: List[Dict]): Parameters: fct (Callable): The function to decorate - acls_mod (Module): - The module that contains the pre-condition functions (acls) params List[Dict]: A list of dicts that have an "acl" key that points to a function @@ -68,40 +61,43 @@ def acl_mock(fct): # ## -def gen_starlette_routes(m_dom): +def gen_starlette_routes(m_dom: ModuleType) -> Generator: """ Yields the Route objects for HalfAPI app Parameters: - m_dom (module): the halfapi module + m_dom (ModuleType): the halfapi module Returns: - Generator[Route] + Generator(Route) """ m_dom_acl = importlib.import_module('.acl', m_dom.__name__) - for route in gen_domain_routes(m_dom.__name__): - yield ( - Route(route['path'], - route_acl_decorator( - route['fct'], - m_dom_acl, - route['params'], - ), - methods=[route['verb']]) - ) + for path, d_route in gen_domain_routes(m_dom.__name__): + for verb in VERBS: + if verb not in d_route.keys(): + continue + + yield ( + Route(path, + route_acl_decorator( + d_route[verb]['fct'], + d_route[verb]['params'] + ), + methods=[verb]) + ) -def api_routes(m_dom): +def api_routes(m_dom: ModuleType) -> Generator: """ Yields the description objects for HalfAPI app routes Parameters: - m_dom (module): the halfapi module + m_dom (ModuleType): the halfapi module Returns: - Generator[Dict] + Generator(Dict) """ m_dom_acl = importlib.import_module('.acl', m_dom.__name__) @@ -109,15 +105,26 @@ def api_routes(m_dom): def pop_acl(r): if 'acl' in r.keys(): r.pop('acl') - print(r) return r - return { - route['path']: { - 'params': list(map(pop_acl, route['params'])), - 'verb': route['verb'], - 'fqtn': route['fqtn'] - } - for route in gen_domain_routes(m_dom.__name__) - } - \ No newline at end of file + def str_acl(params): + for param in params: + if 'acl' not in param.keys(): + continue + param['acl'] = param['acl'].__name__ + return params + + d_res = {} + for path, d_route in gen_domain_routes(m_dom.__name__): + d_res[path] = {'fqtn': d_route['fqtn'] } + + for verb in VERBS: + if verb not in d_route.keys(): + continue + d_res[path][verb] = { + 'params': str_acl(d_route[verb]['params']), + 'fct': d_route[verb]['fct'].__name__ + } + + yield path, d_res + diff --git a/halfapi/lib/schemas.py b/halfapi/lib/schemas.py index 6fd658c..c2ee907 100644 --- a/halfapi/lib/schemas.py +++ b/halfapi/lib/schemas.py @@ -1,3 +1,5 @@ +from types import ModuleType +from typing import Dict from ..conf import DOMAINSDICT from .routes import gen_starlette_routes, api_routes from .responses import * @@ -7,18 +9,66 @@ schemas = SchemaGenerator( {"openapi": "3.0.0", "info": {"title": "HalfAPI", "version": "1.0"}} ) + async def get_api_routes(request, *args, **kwargs): + """ + responses: + 200: + description: Returns the current API routes description (HalfAPI 0.2.1) + as a JSON object + example: + { + "dummy_domain": { + "/abc/alphabet/organigramme": { + "fqtn": null, + "params": [ + {} + ], + "verb": "GET" + }, + "/act/personne/": { + "fqtn": "acteur.personne", + "params": [ + {} + + "verb": "GET" + } + + } + } + """ + #TODO: LADOC return ORJSONResponse({ - domain: api_routes(m_domain) + domain: { path: route for path, route in api_routes(m_domain) } for domain, m_domain in DOMAINSDICT.items() }) + async def schema_json(request, *args, **kwargs): + """ + responses: + 200: + description: Returns the current API routes description (OpenAPI v3) + as a JSON object + """ return ORJSONResponse( schemas.get_schema(routes=request.app.routes)) -def schema_dict_dom(m_domain): +def schema_dict_dom(m_domain: ModuleType) -> Dict: + """ + Returns the API schema of the *m_domain* domain as a python dictionnary + + Parameters: + + m_domain (ModuleType): The module to scan for routes + + + Returns: + + Dict: A dictionnary containing the description of the API using the + | OpenAPI standard + """ routes = [ elt for elt in gen_starlette_routes(m_domain) ] return schemas.get_schema(routes=routes)