[cli][domain] Re-implementation of list_routes, using .lib.schemas, add DOMAINSDICT constant in configuration, set a lot of default configurations

This commit is contained in:
Maxime Alves LIRMM@home 2020-09-24 19:48:42 +02:00
parent 2ad0b3a14b
commit 6a81c61649
7 changed files with 131 additions and 75 deletions

View File

@ -11,33 +11,38 @@ from starlette.middleware.authentication import AuthenticationMiddleware
from typing import Any, Awaitable, Callable, MutableMapping
# module libraries
from halfapi.conf import HOST, PORT, DB_NAME, SECRET, PRODUCTION, DOMAINS
from halfapi.conf import HOST, PORT, DB_NAME, SECRET, PRODUCTION, DOMAINS, DOMAINSDICT
from halfapi.lib.jwt_middleware import JWTAuthenticationBackend
from halfapi.lib.responses import *
from halfapi.lib.routes import gen_starlette_routes
from starlette.schemas import SchemaGenerator
schemas = SchemaGenerator(
{"openapi": "3.0.0", "info": {"title": "HalfAPI", "version": "1.0"}}
)
from halfapi.lib.schemas import sch_json
"""
Base routes definition
Only debug or doc routes, that should not be available in production
"""
routes = [
Route('/', lambda request, *args, **kwargs: PlainTextResponse('It Works!')),
Route('/', lambda request, *args, **kwargs: ORJSONResponse('It Works!')),
Route('/user', lambda request, *args, **kwargs:
JSONResponse({'user':request.user.json})
ORJSONResponse({'user':request.user.json})
if type(request.user) != UnauthenticatedUser
else JSONResponse({'user':False})),
else ORJSONResponse({'user':False})),
Route('/payload', lambda request, *args, **kwargs:
JSONResponse({'payload':str(request.payload)})),
Route('/schema', lambda request, *args, **kwargs:
schemas.OpenAPIResponse(request=request))
ORJSONResponse({'payload':str(request.payload)})),
Route('/schema', schema_json)
] if not PRODUCTION else []
for route in gen_starlette_routes():
routes.append(route)
for domain, m_domain in DOMAINSDICT.items():
for route in gen_starlette_routes(m_dom):
routes.append(route)
application = Starlette(
debug=not PRODUCTION,

View File

@ -7,13 +7,15 @@ import importlib
from .cli import cli
from halfapi.conf import DOMAINS, BASE_DIR
from halfapi.db import (
Domain,
APIRouter,
APIRoute,
AclFunction,
Acl)
from halfapi.conf import DOMAINS, DOMAINSDICT, BASE_DIR
from halfapi.lib.schemas import schema_dict_dom
# from halfapi.db import (
# Domain,
# APIRouter,
# APIRoute,
# AclFunction,
# Acl)
logger = logging.getLogger('halfapi')
@ -28,15 +30,18 @@ def create_domain():
###############
def list_routes(domain):
click.echo(f'\nDomain : {domain}')
routers = APIRouter(domain=domain)
for router in routers.select():
routes = APIRoute(domain=domain, router=router['name'])
click.echo('# /{name}'.format(**router))
for route in routes.select():
route.pop('fct_name')
acls = ', '.join([ acl['acl_fct_name'] for acl in Acl(**route).select() ])
route['acls'] = acls
click.echo('- [{http_verb}] {path} ({acls})'.format(**route))
m_dom = DOMAINSDICT[domain]
click.echo(schema_dict_dom(m_dom))
# for router in routers.select():
# routes = APIRoute(domain=domain, router=router['name'])
# click.echo('# /{name}'.format(**router))
# for route in routes.select():
# route.pop('fct_name')
# acls = ', '.join([ acl['acl_fct_name'] for acl in Acl(**route).select() ])
# route['acls'] = acls
# click.echo('- [{http_verb}] {path} ({acls})'.format(**route))
#################
# domain update #
@ -244,10 +249,10 @@ 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:
raise NotImplementedError
return create_domain()
domains = DOMAINS
@ -265,8 +270,10 @@ def domain(domains, delete, update, create, read): #, domains, read, create, up
for domain in domains:
if update:
raise NotImplementedError
update_db(domain)
if delete:
raise NotImplementedError
delete_domain(domain)
else:
list_routes(domain)

View File

@ -7,7 +7,6 @@ import click
import logging
from halfapi import __version__
from halfapi.cli.lib.db import ProjectDB
from .cli import cli
logger = logging.getLogger('halfapi')
@ -33,8 +32,9 @@ halfapi_version = {halfapi_version}
"""
@click.argument('project')
@click.option('--venv', default=None)
@cli.command()
def init(project):
def init(project, venv):
if not re.match('^[a-z0-9_]+$', project, re.I):
click.echo('Project name must match "^[a-z0-9_]+$", retry.', err=True)
sys.exit(1)
@ -46,26 +46,3 @@ def init(project):
click.echo(f'create directory {project}')
os.mkdir(project)
try:
pdb = ProjectDB(project)
pdb.init()
except Exception as e:
logger.warning(e)
logger.debug(os.environ.get('HALFORM_CONF_DIR'))
raise e
os.mkdir(os.path.join(project, '.halfapi'))
open(os.path.join(project, '.halfapi', 'domains'), 'w').write('[domains]\n')
config_file = os.path.join(project, '.halfapi', 'config')
with open(config_file, 'w') as f:
f.write(TMPL_HALFAPI_CONFIG.format(
name=project,
halfapi_version=__version__
))
click.echo(f'Insert this into the HALFAPI_CONF_DIR/{project} file')
click.echo(format_halfapi_etc(
project,
os.path.abspath(project)))

View File

@ -4,6 +4,15 @@ from os import environ
import sys
from configparser import ConfigParser
PROJECT_NAME = ''
DOMAINS = []
DOMAINSDICT = {}
PRODUCTION = False
BASE_DIR = None
HOST='127.0.0.1'
PORT='3000'
DB_NAME = None
IS_PROJECT = os.path.isfile('.halfapi/config')
if IS_PROJECT:
@ -31,6 +40,14 @@ if IS_PROJECT:
if config.has_section('domains') \
else []
try:
DOMAINSDICT = {
dom, importlib.import_module(dom)
for dom in DOMAINS
}
except ImportError as e:
logger.error('Could not load a domain', e)
CONF_DIR = environ.get('HALFAPI_CONF_DIR', '/etc/half_api')
HALFAPI_CONF_FILE=os.path.join(
@ -51,9 +68,9 @@ if IS_PROJECT:
SECRET = secret_file.read()
# Set the secret so we can use it in domains
os.environ['HALFAPI_SECRET'] = SECRET
except FileNotFoundError:
print('There is no file like {}'.format(config.get('project', 'secret')))
sys.exit(1)
except FileNotFoundError as e:
logger.error('There is no file like {}'.format(config.get('project', 'secret')))
logger.debug(e)
PRODUCTION = config.getboolean('project', 'production') or False
os.environ['HALFAPI_PROD'] = str(PRODUCTION)

View File

@ -22,15 +22,33 @@ from starlette.requests import Request
class DomainNotFoundError(Exception):
pass
def route_decorator(fct: Callable, acls_mod, params: List[Dict]):
def route_acl_decorator(fct: Callable, acls_mod, params: List[Dict]):
"""
Decorator for async functions that calls pre-conditions functions
and appends kwargs to the target function
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
Returns:
async function
"""
@wraps(fct)
async def caller(req: Request, *args, **kwargs):
for param in params:
if param['acl'](req, *args, **kwargs):
"""
We the 'acl' and 'keys' kwargs values to let the
decorated function know which ACL function answered
True, and which keys the request will return
We merge the 'acl' and 'keys' kwargs values to let the
decorated function know which ACL function answered
True, and other parameters that you'd need
"""
return await fct(
req, *args,
@ -43,17 +61,33 @@ def route_decorator(fct: Callable, acls_mod, params: List[Dict]):
return caller
def gen_starlette_routes():
for domain in DOMAINS:
domain_acl_mod = importlib.import_module(
f'{domain}.acl')
###
# testing purpose only
def acl_mock(fct):
return lambda r, *a, **kw: True
#
##
for route in gen_domain_routes(domain):
yield (
Route(route['path'],
route_decorator(
def gen_starlette_routes(m_dom):
"""
Yields the Route objects for HalfAPI app
Parameters:
m_dom (module): the halfapi module
Returns:
Generator[Route]
"""
m_dom_acl = importlib.import_module(m_dom '.acl')
for route in gen_domain_routes(m_dom):
yield (
Route(route['path'],
route_acl_decorator(
route['fct'],
domain_acl_mod,
m_dom_acl,
route['params'],
), methods=[route['verb']])
)
),
methods=[route['verb']])
)

16
halfapi/lib/schemas.py Normal file
View File

@ -0,0 +1,16 @@
from .routes import gen_starlette_routes
from .responses import *
from starlette.schemas import SchemaGenerator
schemas = SchemaGenerator(
{"openapi": "3.0.0", "info": {"title": "HalfAPI", "version": "1.0"}}
)
async def schema_json(request, *args, **kwargs):
return ORJSONResponse(
schemas.get_schema(routes=request.app.routes))
def schema_dict_dom(m_domain):
return schemas.get_schema(routes=[
elt for elt in gen_starlette_routes(m_domain) ])