[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:
Maxime Alves LIRMM 2021-09-02 17:01:36 +02:00
parent 74b79120ba
commit 0470f9fa89
9 changed files with 56 additions and 31 deletions

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python3
__version__ = '0.5.8'
__version__ = '0.5.9'
def version():
return f'HalfAPI version:{__version__}'

View File

@ -111,16 +111,15 @@ def read_config():
The highest index in "filenames" are the highest priorty
"""
config.read(HALFAPI_CONFIG_FILES)
return config_dict()
CONFIG = {}
IS_PROJECT = False
if is_project():
read_config()
IS_PROJECT = True
CONFIG = read_config()
PROJECT_NAME = config.get('project', 'name', fallback=PROJECT_NAME)
@ -154,11 +153,11 @@ if is_project():
'production': PRODUCTION,
'secret': SECRET,
'domains': DOMAINS,
'config': {}
'domain_config': {}
}
for domain in DOMAINS:
if domain not in config.sections():
continue
CONFIG['config'][domain] = dict(config.items(domain))
CONFIG['domain_config'][domain] = dict(config.items(domain))

View File

@ -3,16 +3,14 @@
lib/domain.py The domain-scoped utility functions
"""
import os
import re
import sys
import importlib
import inspect
import logging
from types import ModuleType, FunctionType
from typing import Any, Callable, Coroutine, Generator
from typing import Coroutine, Generator
from typing import Dict, List, Tuple, Iterator
import inspect
from starlette.exceptions import HTTPException
@ -51,7 +49,9 @@ def route_decorator(fct: FunctionType, ret_type: str = 'json') -> Coroutine:
fct_args['halfapi'] = {
'user': request.user if
'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:
# TODO: Write tests
if not isinstance(exc, HTTPException):
raise HTTPException(500)
raise HTTPException(500) from exc
raise exc
@ -200,8 +200,8 @@ def gen_router_routes(m_router: ModuleType, path: List[str]) -> \
path.append('{{{}:{}}}'.format(
param_match.groups()[0].lower(),
param_match.groups()[1]))
except AssertionError:
raise UnknownPathParameterType(subroute)
except AssertionError as exc:
raise UnknownPathParameterType(subroute) from exc
else:
path.append(subroute)
@ -243,8 +243,6 @@ def d_domains(config) -> Dict[str, ModuleType]:
raise exc
def router_acls(route_params: Dict, path: List, m_router: ModuleType) -> Generator:
d_res = {'fqtn': route_params.get('FQTN')}
for verb in VERBS:
params = route_params.get(verb)
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))
else:
for param in params:
acl = param.get('acl')
if not isinstance(acl, FunctionType):
fct_acl = param.get('acl')
if not isinstance(fct_acl, FunctionType):
continue
yield acl.__name__, acl
yield fct_acl.__name__, fct_acl
def domain_acls(m_router, path):

View File

@ -1,7 +1,6 @@
"""
DomainMiddleware
"""
import configparser
import logging
from starlette.datastructures import URL
@ -9,10 +8,6 @@ from starlette.middleware.base import (BaseHTTPMiddleware,
RequestResponseEndpoint)
from starlette.requests import Request
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')
@ -38,6 +33,15 @@ class DomainMiddleware(BaseHTTPMiddleware):
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)
if 'acl_pass' in request.scope:
@ -54,5 +58,6 @@ class DomainMiddleware(BaseHTTPMiddleware):
response.headers['x-args-optional'] = \
','.join(request.scope['args']['optional'])
response.headers['x-domain'] = cur_domain
return response

View File

@ -24,7 +24,7 @@ from starlette.routing import Route
from starlette.requests import Request
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
@ -34,7 +34,7 @@ class DomainNotFoundError(Exception):
""" 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
and appends kwargs to the target function
@ -51,6 +51,9 @@ def route_acl_decorator(fct: Callable = None, params: List[Dict] = []):
async function
"""
if not params:
params = []
if not fct:
return partial(route_acl_decorator, params=params)
@ -147,7 +150,7 @@ def api_routes(m_dom: ModuleType) -> Tuple[Dict, Dict]:
return l_params
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:
d_res[path] = {}
d_res[path][verb] = str_acl(params)

View File

@ -98,7 +98,7 @@ async def schema_json(request, *args, **kwargs):
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

View File

@ -8,7 +8,6 @@ import importlib
import tempfile
from typing import Dict, Tuple
from uuid import uuid1, uuid4, UUID
import click
from click.testing import CliRunner
import jwt
import sys
@ -70,7 +69,7 @@ def runner():
@pytest.fixture
def cli_runner():
"""Yield a click.testing.CliRunner to invoke the CLI."""
class_ = click.testing.CliRunner
class_ = CliRunner
def invoke_wrapper(f):
"""Augment CliRunner.invoke to emit its output to stdout.
@ -266,7 +265,9 @@ def dummy_project():
'port = 3050\n',
'loglevel = debug\n',
'[domains]\n',
f'{domain}= .routers'
f'{domain} = .routers',
f'[{domain}]',
'test = True'
])
with open(halfapi_secret, 'w') as f:
@ -297,7 +298,11 @@ def application_domain(routers):
return HalfAPI({
'SECRET':'turlututu',
'PRODUCTION':True,
'DOMAINS':{'dummy_domain':routers}
'DOMAINS':{'dummy_domain':routers},
'CONFIG':{
'domains': {'dummy_domain':routers},
'domain_config': {'dummy_domain': {'test': True}}
}
}).application

View File

@ -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']

View File

@ -9,6 +9,10 @@ from starlette.testclient import TestClient
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):
c = TestClient(application_domain)