From 0470f9fa893b14cda3344a4f8c4a52fc4eea1ea9 Mon Sep 17 00:00:00 2001 From: Maxime Alves LIRMM Date: Thu, 2 Sep 2021 17:01:36 +0200 Subject: [PATCH] [0.5.9] release Squashed commit of the following: commit 7fe3e22f5e4108b5eb149abf8d608334debc49ca Author: Maxime Alves LIRMM Date: Thu Sep 2 16:59:52 2021 +0200 [0.5.9] release commit c36c0fcc982388a5acf2f9f937fa8ab54a18f3de Author: Maxime Alves LIRMM 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 Date: Thu Sep 2 14:59:17 2021 +0200 [tests] don't import click two times commit fa418478c76205bb407e536737d8e389b4bf391c Author: Maxime Alves LIRMM Date: Thu Sep 2 14:57:06 2021 +0200 [clean] remove unused variables, remove [] as default value in fct, raise from exc --- halfapi/__init__.py | 2 +- halfapi/conf.py | 9 ++++---- halfapi/lib/domain.py | 22 +++++++++---------- halfapi/lib/domain_middleware.py | 15 ++++++++----- halfapi/lib/routes.py | 9 +++++--- halfapi/lib/schemas.py | 2 +- tests/conftest.py | 13 +++++++---- tests/dummy_domain/routers/config/__init__.py | 11 ++++++++++ tests/test_dummy_project_router.py | 4 ++++ 9 files changed, 56 insertions(+), 31 deletions(-) create mode 100644 tests/dummy_domain/routers/config/__init__.py diff --git a/halfapi/__init__.py b/halfapi/__init__.py index 3666358..c7e11fc 100644 --- a/halfapi/__init__.py +++ b/halfapi/__init__.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -__version__ = '0.5.8' +__version__ = '0.5.9' def version(): return f'HalfAPI version:{__version__}' diff --git a/halfapi/conf.py b/halfapi/conf.py index 7b809db..69d3527 100644 --- a/halfapi/conf.py +++ b/halfapi/conf.py @@ -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)) diff --git a/halfapi/lib/domain.py b/halfapi/lib/domain.py index 9a4ff70..e19c2ba 100644 --- a/halfapi/lib/domain.py +++ b/halfapi/lib/domain.py @@ -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): diff --git a/halfapi/lib/domain_middleware.py b/halfapi/lib/domain_middleware.py index 260cfe7..2f78c0d 100644 --- a/halfapi/lib/domain_middleware.py +++ b/halfapi/lib/domain_middleware.py @@ -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 diff --git a/halfapi/lib/routes.py b/halfapi/lib/routes.py index f3f61d7..ce86c66 100644 --- a/halfapi/lib/routes.py +++ b/halfapi/lib/routes.py @@ -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) diff --git a/halfapi/lib/schemas.py b/halfapi/lib/schemas.py index 7cbf405..5a56fa3 100644 --- a/halfapi/lib/schemas.py +++ b/halfapi/lib/schemas.py @@ -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 diff --git a/tests/conftest.py b/tests/conftest.py index c75a14c..c1c1850 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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 diff --git a/tests/dummy_domain/routers/config/__init__.py b/tests/dummy_domain/routers/config/__init__.py new file mode 100644 index 0000000..75a6449 --- /dev/null +++ b/tests/dummy_domain/routers/config/__init__.py @@ -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'] diff --git a/tests/test_dummy_project_router.py b/tests/test_dummy_project_router.py index 7dcb0eb..d6f4416 100644 --- a/tests/test_dummy_project_router.py +++ b/tests/test_dummy_project_router.py @@ -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)