[0.5.9] release
Squashed commit of the following: commit 7fe3e22f5e4108b5eb149abf8d608334debc49ca Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr> Date: Thu Sep 2 16:59:52 2021 +0200 [0.5.9] release commit c36c0fcc982388a5acf2f9f937fa8ab54a18f3de Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr> Date: Thu Sep 2 16:53:13 2021 +0200 [conf] fix #19 et ajout du test (test_dummy_project_router/test_get_config) configuration du domaine accessible depuis : l'attribut config de l'argument "halfapi" pour les fonctions request.scope['config'] pour les fonctions async commit cc235eee8c6f8f5d3606dda0f88156697eac296e Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr> Date: Thu Sep 2 14:59:17 2021 +0200 [tests] don't import click two times commit fa418478c76205bb407e536737d8e389b4bf391c Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr> Date: Thu Sep 2 14:57:06 2021 +0200 [clean] remove unused variables, remove [] as default value in fct, raise from exc
This commit is contained in:
parent
74b79120ba
commit
0470f9fa89
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
__version__ = '0.5.8'
|
__version__ = '0.5.9'
|
||||||
|
|
||||||
def version():
|
def version():
|
||||||
return f'HalfAPI version:{__version__}'
|
return f'HalfAPI version:{__version__}'
|
||||||
|
|
|
@ -111,16 +111,15 @@ def read_config():
|
||||||
The highest index in "filenames" are the highest priorty
|
The highest index in "filenames" are the highest priorty
|
||||||
"""
|
"""
|
||||||
config.read(HALFAPI_CONFIG_FILES)
|
config.read(HALFAPI_CONFIG_FILES)
|
||||||
return config_dict()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG = {}
|
CONFIG = {}
|
||||||
IS_PROJECT = False
|
IS_PROJECT = False
|
||||||
if is_project():
|
if is_project():
|
||||||
|
read_config()
|
||||||
|
|
||||||
IS_PROJECT = True
|
IS_PROJECT = True
|
||||||
CONFIG = read_config()
|
|
||||||
|
|
||||||
PROJECT_NAME = config.get('project', 'name', fallback=PROJECT_NAME)
|
PROJECT_NAME = config.get('project', 'name', fallback=PROJECT_NAME)
|
||||||
|
|
||||||
|
@ -154,11 +153,11 @@ if is_project():
|
||||||
'production': PRODUCTION,
|
'production': PRODUCTION,
|
||||||
'secret': SECRET,
|
'secret': SECRET,
|
||||||
'domains': DOMAINS,
|
'domains': DOMAINS,
|
||||||
'config': {}
|
'domain_config': {}
|
||||||
}
|
}
|
||||||
|
|
||||||
for domain in DOMAINS:
|
for domain in DOMAINS:
|
||||||
if domain not in config.sections():
|
if domain not in config.sections():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
CONFIG['config'][domain] = dict(config.items(domain))
|
CONFIG['domain_config'][domain] = dict(config.items(domain))
|
||||||
|
|
|
@ -3,16 +3,14 @@
|
||||||
lib/domain.py The domain-scoped utility functions
|
lib/domain.py The domain-scoped utility functions
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import importlib
|
import importlib
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
from types import ModuleType, FunctionType
|
from types import ModuleType, FunctionType
|
||||||
from typing import Any, Callable, Coroutine, Generator
|
from typing import Coroutine, Generator
|
||||||
from typing import Dict, List, Tuple, Iterator
|
from typing import Dict, List, Tuple, Iterator
|
||||||
import inspect
|
|
||||||
|
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
|
@ -51,7 +49,9 @@ def route_decorator(fct: FunctionType, ret_type: str = 'json') -> Coroutine:
|
||||||
fct_args['halfapi'] = {
|
fct_args['halfapi'] = {
|
||||||
'user': request.user if
|
'user': request.user if
|
||||||
'user' in request else None,
|
'user' in request else None,
|
||||||
'config': request.scope['config']
|
'config': request.scope['config'],
|
||||||
|
'domain': request.scope['domain'],
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ def route_decorator(fct: FunctionType, ret_type: str = 'json') -> Coroutine:
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
# TODO: Write tests
|
# TODO: Write tests
|
||||||
if not isinstance(exc, HTTPException):
|
if not isinstance(exc, HTTPException):
|
||||||
raise HTTPException(500)
|
raise HTTPException(500) from exc
|
||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
|
|
||||||
|
@ -200,8 +200,8 @@ def gen_router_routes(m_router: ModuleType, path: List[str]) -> \
|
||||||
path.append('{{{}:{}}}'.format(
|
path.append('{{{}:{}}}'.format(
|
||||||
param_match.groups()[0].lower(),
|
param_match.groups()[0].lower(),
|
||||||
param_match.groups()[1]))
|
param_match.groups()[1]))
|
||||||
except AssertionError:
|
except AssertionError as exc:
|
||||||
raise UnknownPathParameterType(subroute)
|
raise UnknownPathParameterType(subroute) from exc
|
||||||
else:
|
else:
|
||||||
path.append(subroute)
|
path.append(subroute)
|
||||||
|
|
||||||
|
@ -243,8 +243,6 @@ def d_domains(config) -> Dict[str, ModuleType]:
|
||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
def router_acls(route_params: Dict, path: List, m_router: ModuleType) -> Generator:
|
def router_acls(route_params: Dict, path: List, m_router: ModuleType) -> Generator:
|
||||||
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:
|
||||||
|
@ -253,11 +251,11 @@ def router_acls(route_params: Dict, path: List, m_router: ModuleType) -> Generat
|
||||||
logger.error('No ACL for route [{%s}] %s', verb, "/".join(path))
|
logger.error('No ACL for route [{%s}] %s', verb, "/".join(path))
|
||||||
else:
|
else:
|
||||||
for param in params:
|
for param in params:
|
||||||
acl = param.get('acl')
|
fct_acl = param.get('acl')
|
||||||
if not isinstance(acl, FunctionType):
|
if not isinstance(fct_acl, FunctionType):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
yield acl.__name__, acl
|
yield fct_acl.__name__, fct_acl
|
||||||
|
|
||||||
|
|
||||||
def domain_acls(m_router, path):
|
def domain_acls(m_router, path):
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
"""
|
"""
|
||||||
DomainMiddleware
|
DomainMiddleware
|
||||||
"""
|
"""
|
||||||
import configparser
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from starlette.datastructures import URL
|
from starlette.datastructures import URL
|
||||||
|
@ -9,10 +8,6 @@ from starlette.middleware.base import (BaseHTTPMiddleware,
|
||||||
RequestResponseEndpoint)
|
RequestResponseEndpoint)
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
from starlette.responses import Response
|
from starlette.responses import Response
|
||||||
from starlette.types import Scope, Send, Receive
|
|
||||||
|
|
||||||
from .routes import api_routes
|
|
||||||
from .domain import d_domains
|
|
||||||
|
|
||||||
logger = logging.getLogger('uvicorn.asgi')
|
logger = logging.getLogger('uvicorn.asgi')
|
||||||
|
|
||||||
|
@ -38,6 +33,15 @@ class DomainMiddleware(BaseHTTPMiddleware):
|
||||||
Call of the route fonction (decorated or not)
|
Call of the route fonction (decorated or not)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
l_path = URL(scope=request.scope).path.split('/')
|
||||||
|
cur_domain = l_path[0]
|
||||||
|
if len(cur_domain) == 0 and len(l_path) > 1:
|
||||||
|
cur_domain = l_path[1]
|
||||||
|
|
||||||
|
request.scope['domain'] = cur_domain
|
||||||
|
request.scope['config'] = self.config['domain_config'][cur_domain] \
|
||||||
|
if cur_domain in self.config.get('domain_config', {}) else {}
|
||||||
|
|
||||||
response = await call_next(request)
|
response = await call_next(request)
|
||||||
|
|
||||||
if 'acl_pass' in request.scope:
|
if 'acl_pass' in request.scope:
|
||||||
|
@ -54,5 +58,6 @@ class DomainMiddleware(BaseHTTPMiddleware):
|
||||||
response.headers['x-args-optional'] = \
|
response.headers['x-args-optional'] = \
|
||||||
','.join(request.scope['args']['optional'])
|
','.join(request.scope['args']['optional'])
|
||||||
|
|
||||||
|
response.headers['x-domain'] = cur_domain
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
|
@ -24,7 +24,7 @@ from starlette.routing import Route
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
from starlette.responses import Response, PlainTextResponse
|
from starlette.responses import Response, PlainTextResponse
|
||||||
|
|
||||||
from halfapi.lib.domain import gen_router_routes, VERBS, domain_acls
|
from halfapi.lib.domain import gen_router_routes, domain_acls
|
||||||
from ..conf import DOMAINSDICT
|
from ..conf import DOMAINSDICT
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ class DomainNotFoundError(Exception):
|
||||||
""" Exception when a domain is not importable
|
""" Exception when a domain is not importable
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def route_acl_decorator(fct: Callable = None, params: List[Dict] = []):
|
def route_acl_decorator(fct: Callable = None, params: List[Dict] = None):
|
||||||
"""
|
"""
|
||||||
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
|
||||||
|
@ -51,6 +51,9 @@ def route_acl_decorator(fct: Callable = None, params: List[Dict] = []):
|
||||||
async function
|
async function
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if not params:
|
||||||
|
params = []
|
||||||
|
|
||||||
if not fct:
|
if not fct:
|
||||||
return partial(route_acl_decorator, params=params)
|
return partial(route_acl_decorator, params=params)
|
||||||
|
|
||||||
|
@ -147,7 +150,7 @@ def api_routes(m_dom: ModuleType) -> Tuple[Dict, Dict]:
|
||||||
return l_params
|
return l_params
|
||||||
|
|
||||||
d_res = {}
|
d_res = {}
|
||||||
for path, verb, fct, params in gen_router_routes(m_dom, []):
|
for path, verb, _, params in gen_router_routes(m_dom, []):
|
||||||
if path not in d_res:
|
if path not in d_res:
|
||||||
d_res[path] = {}
|
d_res[path] = {}
|
||||||
d_res[path][verb] = str_acl(params)
|
d_res[path][verb] = str_acl(params)
|
||||||
|
|
|
@ -98,7 +98,7 @@ async def schema_json(request, *args, **kwargs):
|
||||||
SCHEMAS.get_schema(routes=request.app.routes))
|
SCHEMAS.get_schema(routes=request.app.routes))
|
||||||
|
|
||||||
|
|
||||||
def schema_dict_dom(d_domains) -> Dict:
|
def schema_dict_dom(d_domains: Dict[str, ModuleType]) -> Dict:
|
||||||
"""
|
"""
|
||||||
Returns the API schema of the *m_domain* domain as a python dictionnary
|
Returns the API schema of the *m_domain* domain as a python dictionnary
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import importlib
|
||||||
import tempfile
|
import tempfile
|
||||||
from typing import Dict, Tuple
|
from typing import Dict, Tuple
|
||||||
from uuid import uuid1, uuid4, UUID
|
from uuid import uuid1, uuid4, UUID
|
||||||
import click
|
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
import jwt
|
import jwt
|
||||||
import sys
|
import sys
|
||||||
|
@ -70,7 +69,7 @@ def runner():
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def cli_runner():
|
def cli_runner():
|
||||||
"""Yield a click.testing.CliRunner to invoke the CLI."""
|
"""Yield a click.testing.CliRunner to invoke the CLI."""
|
||||||
class_ = click.testing.CliRunner
|
class_ = CliRunner
|
||||||
|
|
||||||
def invoke_wrapper(f):
|
def invoke_wrapper(f):
|
||||||
"""Augment CliRunner.invoke to emit its output to stdout.
|
"""Augment CliRunner.invoke to emit its output to stdout.
|
||||||
|
@ -266,7 +265,9 @@ def dummy_project():
|
||||||
'port = 3050\n',
|
'port = 3050\n',
|
||||||
'loglevel = debug\n',
|
'loglevel = debug\n',
|
||||||
'[domains]\n',
|
'[domains]\n',
|
||||||
f'{domain}= .routers'
|
f'{domain} = .routers',
|
||||||
|
f'[{domain}]',
|
||||||
|
'test = True'
|
||||||
])
|
])
|
||||||
|
|
||||||
with open(halfapi_secret, 'w') as f:
|
with open(halfapi_secret, 'w') as f:
|
||||||
|
@ -297,7 +298,11 @@ def application_domain(routers):
|
||||||
return HalfAPI({
|
return HalfAPI({
|
||||||
'SECRET':'turlututu',
|
'SECRET':'turlututu',
|
||||||
'PRODUCTION':True,
|
'PRODUCTION':True,
|
||||||
'DOMAINS':{'dummy_domain':routers}
|
'DOMAINS':{'dummy_domain':routers},
|
||||||
|
'CONFIG':{
|
||||||
|
'domains': {'dummy_domain':routers},
|
||||||
|
'domain_config': {'dummy_domain': {'test': True}}
|
||||||
|
}
|
||||||
}).application
|
}).application
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
from halfapi.lib import acl
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger('uvicorn.asgi')
|
||||||
|
|
||||||
|
ACLS = {
|
||||||
|
'GET' : [{'acl':acl.public}]
|
||||||
|
}
|
||||||
|
|
||||||
|
def get(halfapi):
|
||||||
|
logger.error('%s', halfapi)
|
||||||
|
return halfapi['config']
|
|
@ -9,6 +9,10 @@ from starlette.testclient import TestClient
|
||||||
|
|
||||||
from halfapi.lib.domain import gen_router_routes
|
from halfapi.lib.domain import gen_router_routes
|
||||||
|
|
||||||
|
def test_get_config_route(dummy_project, application_domain, routers):
|
||||||
|
c = TestClient(application_domain)
|
||||||
|
r = c.get('/dummy_domain/config')
|
||||||
|
assert 'test' in r.json()
|
||||||
|
|
||||||
def test_get_route(dummy_project, application_domain, routers):
|
def test_get_route(dummy_project, application_domain, routers):
|
||||||
c = TestClient(application_domain)
|
c = TestClient(application_domain)
|
||||||
|
|
Loading…
Reference in New Issue