[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:
parent
2ad0b3a14b
commit
6a81c61649
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -87,7 +87,7 @@ def gen_router_routes(m_router, path=[]):
|
|||
|
||||
for route in gen_routes(route_params, path, m_router):
|
||||
yield route
|
||||
|
||||
|
||||
subroutes = route_params.get('SUBROUTES', [])
|
||||
for subroute in subroutes:
|
||||
path.append(subroute)
|
||||
|
|
|
@ -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']])
|
||||
)
|
||||
|
|
|
@ -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) ])
|
Loading…
Reference in New Issue