[routing] fix route description when there is multiple verbs
This commit is contained in:
parent
d93fb23bba
commit
e223c0791c
@ -1,10 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import importlib
|
import importlib
|
||||||
import typing as typ
|
from types import ModuleType
|
||||||
|
from typing import Generator, Dict, List
|
||||||
|
|
||||||
VERBS = ('GET', 'POST', 'PUT', 'PATCH', 'DELETE')
|
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
|
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)
|
return '_'.join(fct_name)
|
||||||
|
|
||||||
def gen_routes(route_params, path, m_router):
|
def gen_routes(route_params: Dict, path: List, m_router: ModuleType) -> Generator:
|
||||||
fqtn = route_params.get('FQTN')
|
d_res = {'fqtn': route_params.get('FQTN')}
|
||||||
|
|
||||||
for verb in VERBS:
|
for verb in VERBS:
|
||||||
params = route_params.get(verb)
|
params = route_params.get(verb)
|
||||||
if params is None:
|
if params is None:
|
||||||
continue
|
continue
|
||||||
if not len(params):
|
if len(params) == 0:
|
||||||
print(f'No ACL for route [{verb}] "/".join(path)')
|
print(f'No ACL for route [{verb}] "/".join(path)')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
fct_name = get_fct_name(verb, path[-1])
|
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__}')
|
print(f'{fct_name} is not defined in {m_router.__name__}')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
yield {
|
d_res[verb] = {'fct': fct, 'params': params}
|
||||||
'verb':verb,
|
|
||||||
'path':f"/{'/'.join([ elt for elt in path if elt ])}",
|
yield f"/{'/'.join([ elt for elt in path if elt ])}", d_res
|
||||||
'params':params,
|
|
||||||
'fct': fct,
|
|
||||||
'fqtn': fqtn }
|
|
||||||
|
|
||||||
|
|
||||||
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'):
|
if not hasattr(m_router, 'ROUTES'):
|
||||||
print(f'Missing *ROUTES* constant in *{m_router.__name__}*')
|
print(f'Missing *ROUTES* constant in *{m_router.__name__}*')
|
||||||
|
|
||||||
|
if path is None:
|
||||||
|
path = []
|
||||||
|
|
||||||
routes = m_router.ROUTES
|
routes = m_router.ROUTES
|
||||||
|
|
||||||
for subpath, route_params in routes.items():
|
for subpath, route_params in routes.items():
|
||||||
path.append(subpath)
|
path.append(subpath)
|
||||||
|
|
||||||
for route in gen_routes(route_params, path, m_router):
|
for r_path, d_route in gen_routes(route_params, path, m_router):
|
||||||
yield route
|
yield r_path, d_route
|
||||||
|
|
||||||
subroutes = route_params.get('SUBROUTES', [])
|
subroutes = route_params.get('SUBROUTES', [])
|
||||||
for subroute in subroutes:
|
for subroute in subroutes:
|
||||||
path.append(subroute)
|
path.append(subroute)
|
||||||
submod = importlib.import_module(f'.{subroute}', m_router.__name__)
|
submod = importlib.import_module(f'.{subroute}', m_router.__name__)
|
||||||
for route_scan in gen_router_routes(submod, path):
|
for r_path, d_route in gen_router_routes(submod, path):
|
||||||
yield route_scan
|
yield r_path, d_route
|
||||||
|
|
||||||
|
|
||||||
path.pop()
|
path.pop()
|
||||||
|
|
||||||
|
@ -2,19 +2,14 @@
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
import importlib
|
import importlib
|
||||||
import sys
|
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,
|
from halfapi.conf import (PROJECT_NAME, DB_NAME, HOST, PORT,
|
||||||
PRODUCTION, DOMAINS)
|
PRODUCTION, DOMAINS)
|
||||||
|
|
||||||
# from halfapi.db import (
|
|
||||||
# Domain,
|
|
||||||
# APIRouter,
|
|
||||||
# APIRoute,
|
|
||||||
# AclFunction,
|
|
||||||
# Acl)
|
|
||||||
from halfapi.lib.responses import *
|
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.exceptions import HTTPException
|
||||||
from starlette.routing import Mount, Route
|
from starlette.routing import Mount, Route
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
@ -22,7 +17,7 @@ from starlette.requests import Request
|
|||||||
class DomainNotFoundError(Exception):
|
class DomainNotFoundError(Exception):
|
||||||
pass
|
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
|
Decorator for async functions that calls pre-conditions functions
|
||||||
and appends kwargs to the target function
|
and appends kwargs to the target function
|
||||||
@ -31,8 +26,6 @@ def route_acl_decorator(fct: Callable, acls_mod, params: List[Dict]):
|
|||||||
Parameters:
|
Parameters:
|
||||||
fct (Callable):
|
fct (Callable):
|
||||||
The function to decorate
|
The function to decorate
|
||||||
acls_mod (Module):
|
|
||||||
The module that contains the pre-condition functions (acls)
|
|
||||||
|
|
||||||
params List[Dict]:
|
params List[Dict]:
|
||||||
A list of dicts that have an "acl" key that points to a function
|
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
|
Yields the Route objects for HalfAPI app
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
m_dom (module): the halfapi module
|
m_dom (ModuleType): the halfapi module
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Generator[Route]
|
Generator(Route)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
m_dom_acl = importlib.import_module('.acl', m_dom.__name__)
|
m_dom_acl = importlib.import_module('.acl', m_dom.__name__)
|
||||||
|
|
||||||
for route in gen_domain_routes(m_dom.__name__):
|
for path, d_route in gen_domain_routes(m_dom.__name__):
|
||||||
|
for verb in VERBS:
|
||||||
|
if verb not in d_route.keys():
|
||||||
|
continue
|
||||||
|
|
||||||
yield (
|
yield (
|
||||||
Route(route['path'],
|
Route(path,
|
||||||
route_acl_decorator(
|
route_acl_decorator(
|
||||||
route['fct'],
|
d_route[verb]['fct'],
|
||||||
m_dom_acl,
|
d_route[verb]['params']
|
||||||
route['params'],
|
|
||||||
),
|
),
|
||||||
methods=[route['verb']])
|
methods=[verb])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def api_routes(m_dom):
|
def api_routes(m_dom: ModuleType) -> Generator:
|
||||||
"""
|
"""
|
||||||
Yields the description objects for HalfAPI app routes
|
Yields the description objects for HalfAPI app routes
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
m_dom (module): the halfapi module
|
m_dom (ModuleType): the halfapi module
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Generator[Dict]
|
Generator(Dict)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
m_dom_acl = importlib.import_module('.acl', m_dom.__name__)
|
m_dom_acl = importlib.import_module('.acl', m_dom.__name__)
|
||||||
@ -109,15 +105,26 @@ def api_routes(m_dom):
|
|||||||
def pop_acl(r):
|
def pop_acl(r):
|
||||||
if 'acl' in r.keys():
|
if 'acl' in r.keys():
|
||||||
r.pop('acl')
|
r.pop('acl')
|
||||||
print(r)
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
return {
|
def str_acl(params):
|
||||||
route['path']: {
|
for param in params:
|
||||||
'params': list(map(pop_acl, route['params'])),
|
if 'acl' not in param.keys():
|
||||||
'verb': route['verb'],
|
continue
|
||||||
'fqtn': route['fqtn']
|
param['acl'] = param['acl'].__name__
|
||||||
}
|
return params
|
||||||
for route in gen_domain_routes(m_dom.__name__)
|
|
||||||
|
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
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from types import ModuleType
|
||||||
|
from typing import Dict
|
||||||
from ..conf import DOMAINSDICT
|
from ..conf import DOMAINSDICT
|
||||||
from .routes import gen_starlette_routes, api_routes
|
from .routes import gen_starlette_routes, api_routes
|
||||||
from .responses import *
|
from .responses import *
|
||||||
@ -7,18 +9,66 @@ schemas = SchemaGenerator(
|
|||||||
{"openapi": "3.0.0", "info": {"title": "HalfAPI", "version": "1.0"}}
|
{"openapi": "3.0.0", "info": {"title": "HalfAPI", "version": "1.0"}}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_api_routes(request, *args, **kwargs):
|
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({
|
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()
|
for domain, m_domain in DOMAINSDICT.items()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
async def schema_json(request, *args, **kwargs):
|
async def schema_json(request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Returns the current API routes description (OpenAPI v3)
|
||||||
|
as a JSON object
|
||||||
|
"""
|
||||||
return ORJSONResponse(
|
return ORJSONResponse(
|
||||||
schemas.get_schema(routes=request.app.routes))
|
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 = [
|
routes = [
|
||||||
elt for elt in gen_starlette_routes(m_domain) ]
|
elt for elt in gen_starlette_routes(m_domain) ]
|
||||||
return schemas.get_schema(routes=routes)
|
return schemas.get_schema(routes=routes)
|
||||||
|
Loading…
Reference in New Issue
Block a user