[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
|
from typing import Any, Awaitable, Callable, MutableMapping
|
||||||
|
|
||||||
# module libraries
|
# 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.jwt_middleware import JWTAuthenticationBackend
|
||||||
|
|
||||||
from halfapi.lib.responses import *
|
from halfapi.lib.responses import *
|
||||||
from halfapi.lib.routes import gen_starlette_routes
|
from halfapi.lib.routes import gen_starlette_routes
|
||||||
|
from halfapi.lib.schemas import sch_json
|
||||||
from starlette.schemas import SchemaGenerator
|
|
||||||
schemas = SchemaGenerator(
|
|
||||||
{"openapi": "3.0.0", "info": {"title": "HalfAPI", "version": "1.0"}}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Base routes definition
|
||||||
|
|
||||||
|
Only debug or doc routes, that should not be available in production
|
||||||
|
"""
|
||||||
routes = [
|
routes = [
|
||||||
Route('/', lambda request, *args, **kwargs: PlainTextResponse('It Works!')),
|
Route('/', lambda request, *args, **kwargs: ORJSONResponse('It Works!')),
|
||||||
|
|
||||||
Route('/user', lambda request, *args, **kwargs:
|
Route('/user', lambda request, *args, **kwargs:
|
||||||
JSONResponse({'user':request.user.json})
|
ORJSONResponse({'user':request.user.json})
|
||||||
if type(request.user) != UnauthenticatedUser
|
if type(request.user) != UnauthenticatedUser
|
||||||
else JSONResponse({'user':False})),
|
else ORJSONResponse({'user':False})),
|
||||||
|
|
||||||
Route('/payload', lambda request, *args, **kwargs:
|
Route('/payload', lambda request, *args, **kwargs:
|
||||||
JSONResponse({'payload':str(request.payload)})),
|
ORJSONResponse({'payload':str(request.payload)})),
|
||||||
Route('/schema', lambda request, *args, **kwargs:
|
|
||||||
schemas.OpenAPIResponse(request=request))
|
Route('/schema', schema_json)
|
||||||
] if not PRODUCTION else []
|
] if not PRODUCTION else []
|
||||||
|
|
||||||
for route in gen_starlette_routes():
|
for domain, m_domain in DOMAINSDICT.items():
|
||||||
routes.append(route)
|
for route in gen_starlette_routes(m_dom):
|
||||||
|
routes.append(route)
|
||||||
|
|
||||||
|
|
||||||
application = Starlette(
|
application = Starlette(
|
||||||
debug=not PRODUCTION,
|
debug=not PRODUCTION,
|
||||||
|
|
|
@ -7,13 +7,15 @@ import importlib
|
||||||
|
|
||||||
|
|
||||||
from .cli import cli
|
from .cli import cli
|
||||||
from halfapi.conf import DOMAINS, BASE_DIR
|
from halfapi.conf import DOMAINS, DOMAINSDICT, BASE_DIR
|
||||||
from halfapi.db import (
|
|
||||||
Domain,
|
from halfapi.lib.schemas import schema_dict_dom
|
||||||
APIRouter,
|
# from halfapi.db import (
|
||||||
APIRoute,
|
# Domain,
|
||||||
AclFunction,
|
# APIRouter,
|
||||||
Acl)
|
# APIRoute,
|
||||||
|
# AclFunction,
|
||||||
|
# Acl)
|
||||||
|
|
||||||
logger = logging.getLogger('halfapi')
|
logger = logging.getLogger('halfapi')
|
||||||
|
|
||||||
|
@ -28,15 +30,18 @@ def create_domain():
|
||||||
###############
|
###############
|
||||||
def list_routes(domain):
|
def list_routes(domain):
|
||||||
click.echo(f'\nDomain : {domain}')
|
click.echo(f'\nDomain : {domain}')
|
||||||
routers = APIRouter(domain=domain)
|
|
||||||
for router in routers.select():
|
m_dom = DOMAINSDICT[domain]
|
||||||
routes = APIRoute(domain=domain, router=router['name'])
|
click.echo(schema_dict_dom(m_dom))
|
||||||
click.echo('# /{name}'.format(**router))
|
|
||||||
for route in routes.select():
|
# for router in routers.select():
|
||||||
route.pop('fct_name')
|
# routes = APIRoute(domain=domain, router=router['name'])
|
||||||
acls = ', '.join([ acl['acl_fct_name'] for acl in Acl(**route).select() ])
|
# click.echo('# /{name}'.format(**router))
|
||||||
route['acls'] = acls
|
# for route in routes.select():
|
||||||
click.echo('- [{http_verb}] {path} ({acls})'.format(**route))
|
# 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 #
|
# 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
|
update (boolean): If set, update the database for the selected domains
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
if not domains:
|
if not domains:
|
||||||
if create:
|
if create:
|
||||||
|
raise NotImplementedError
|
||||||
return create_domain()
|
return create_domain()
|
||||||
|
|
||||||
domains = DOMAINS
|
domains = DOMAINS
|
||||||
|
@ -265,8 +270,10 @@ def domain(domains, delete, update, create, read): #, domains, read, create, up
|
||||||
|
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
if update:
|
if update:
|
||||||
|
raise NotImplementedError
|
||||||
update_db(domain)
|
update_db(domain)
|
||||||
if delete:
|
if delete:
|
||||||
|
raise NotImplementedError
|
||||||
delete_domain(domain)
|
delete_domain(domain)
|
||||||
else:
|
else:
|
||||||
list_routes(domain)
|
list_routes(domain)
|
||||||
|
|
|
@ -7,7 +7,6 @@ import click
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from halfapi import __version__
|
from halfapi import __version__
|
||||||
from halfapi.cli.lib.db import ProjectDB
|
|
||||||
from .cli import cli
|
from .cli import cli
|
||||||
|
|
||||||
logger = logging.getLogger('halfapi')
|
logger = logging.getLogger('halfapi')
|
||||||
|
@ -33,8 +32,9 @@ halfapi_version = {halfapi_version}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@click.argument('project')
|
@click.argument('project')
|
||||||
|
@click.option('--venv', default=None)
|
||||||
@cli.command()
|
@cli.command()
|
||||||
def init(project):
|
def init(project, venv):
|
||||||
if not re.match('^[a-z0-9_]+$', project, re.I):
|
if not re.match('^[a-z0-9_]+$', project, re.I):
|
||||||
click.echo('Project name must match "^[a-z0-9_]+$", retry.', err=True)
|
click.echo('Project name must match "^[a-z0-9_]+$", retry.', err=True)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
@ -46,26 +46,3 @@ def init(project):
|
||||||
|
|
||||||
click.echo(f'create directory {project}')
|
click.echo(f'create directory {project}')
|
||||||
os.mkdir(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
|
import sys
|
||||||
from configparser import ConfigParser
|
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')
|
IS_PROJECT = os.path.isfile('.halfapi/config')
|
||||||
|
|
||||||
if IS_PROJECT:
|
if IS_PROJECT:
|
||||||
|
@ -31,6 +40,14 @@ if IS_PROJECT:
|
||||||
if config.has_section('domains') \
|
if config.has_section('domains') \
|
||||||
else []
|
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')
|
CONF_DIR = environ.get('HALFAPI_CONF_DIR', '/etc/half_api')
|
||||||
|
|
||||||
HALFAPI_CONF_FILE=os.path.join(
|
HALFAPI_CONF_FILE=os.path.join(
|
||||||
|
@ -51,9 +68,9 @@ if IS_PROJECT:
|
||||||
SECRET = secret_file.read()
|
SECRET = secret_file.read()
|
||||||
# Set the secret so we can use it in domains
|
# Set the secret so we can use it in domains
|
||||||
os.environ['HALFAPI_SECRET'] = SECRET
|
os.environ['HALFAPI_SECRET'] = SECRET
|
||||||
except FileNotFoundError:
|
except FileNotFoundError as e:
|
||||||
print('There is no file like {}'.format(config.get('project', 'secret')))
|
logger.error('There is no file like {}'.format(config.get('project', 'secret')))
|
||||||
sys.exit(1)
|
logger.debug(e)
|
||||||
|
|
||||||
PRODUCTION = config.getboolean('project', 'production') or False
|
PRODUCTION = config.getboolean('project', 'production') or False
|
||||||
os.environ['HALFAPI_PROD'] = str(PRODUCTION)
|
os.environ['HALFAPI_PROD'] = str(PRODUCTION)
|
||||||
|
|
|
@ -22,15 +22,33 @@ from starlette.requests import Request
|
||||||
class DomainNotFoundError(Exception):
|
class DomainNotFoundError(Exception):
|
||||||
pass
|
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)
|
@wraps(fct)
|
||||||
async def caller(req: Request, *args, **kwargs):
|
async def caller(req: Request, *args, **kwargs):
|
||||||
for param in params:
|
for param in params:
|
||||||
if param['acl'](req, *args, **kwargs):
|
if param['acl'](req, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
We the 'acl' and 'keys' kwargs values to let the
|
We merge the 'acl' and 'keys' kwargs values to let the
|
||||||
decorated function know which ACL function answered
|
decorated function know which ACL function answered
|
||||||
True, and which keys the request will return
|
True, and other parameters that you'd need
|
||||||
"""
|
"""
|
||||||
return await fct(
|
return await fct(
|
||||||
req, *args,
|
req, *args,
|
||||||
|
@ -43,17 +61,33 @@ def route_decorator(fct: Callable, acls_mod, params: List[Dict]):
|
||||||
|
|
||||||
return caller
|
return caller
|
||||||
|
|
||||||
def gen_starlette_routes():
|
###
|
||||||
for domain in DOMAINS:
|
# testing purpose only
|
||||||
domain_acl_mod = importlib.import_module(
|
def acl_mock(fct):
|
||||||
f'{domain}.acl')
|
return lambda r, *a, **kw: True
|
||||||
|
#
|
||||||
|
##
|
||||||
|
|
||||||
for route in gen_domain_routes(domain):
|
def gen_starlette_routes(m_dom):
|
||||||
yield (
|
"""
|
||||||
Route(route['path'],
|
Yields the Route objects for HalfAPI app
|
||||||
route_decorator(
|
|
||||||
|
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'],
|
route['fct'],
|
||||||
domain_acl_mod,
|
m_dom_acl,
|
||||||
route['params'],
|
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