From 1ccfa0d10ed02107e18a5dd92231441bd8e4ebde Mon Sep 17 00:00:00 2001 From: "Maxime Alves LIRMM@home" Date: Wed, 1 Dec 2021 13:07:01 +0100 Subject: [PATCH] [lib.schemas] router schema update --- halfapi/lib/acl.py | 7 ++ halfapi/lib/constants.py | 27 ++++++- halfapi/lib/router.py | 85 +++++++++++++------- halfapi/lib/routes.py | 2 - tests/dummy_domain/routers/async/__init__.py | 2 +- 5 files changed, 87 insertions(+), 36 deletions(-) diff --git a/halfapi/lib/acl.py b/halfapi/lib/acl.py index 2b83bee..0976212 100644 --- a/halfapi/lib/acl.py +++ b/halfapi/lib/acl.py @@ -99,3 +99,10 @@ def args_check(fct): return await fct(req, *args, **kwargs) return caller + +# ACLS list for doc and priorities +# Write your own constant in your domain or import this one +ACLS = ( + ('private', public.__doc__, 0), + ('public', public.__doc__, 999) +) diff --git a/halfapi/lib/constants.py b/halfapi/lib/constants.py index fb37223..20bc87b 100644 --- a/halfapi/lib/constants.py +++ b/halfapi/lib/constants.py @@ -1,17 +1,28 @@ import re -from schema import Schema, Optional +from schema import Schema, Optional, Or from .. import __version__ VERBS = ('GET', 'POST', 'PUT', 'PATCH', 'DELETE') +ITERABLE_STR = Or([ str ], { str }, ( str )) + ACLS_SCHEMA = Schema([{ 'acl': str, Optional('args'): { - Optional('required'): [ str ], - Optional('optional'): [ str ] + Optional('required'): ITERABLE_STR, + Optional('optional'): ITERABLE_STR }, - Optional('out'): [ str ] + Optional('out'): ITERABLE_STR }]) +ROUTER_ACLS_SCHEMA = Schema([{ + 'acl': lambda n: callable(n), + Optional('args'): { + Optional('required'): ITERABLE_STR, + Optional('optional'): ITERABLE_STR + }, + Optional('out'): ITERABLE_STR +}]) + is_callable_dotted_notation = lambda x: re.match( r'^(([a-zA-Z_])+\.?)*:[a-zA-Z_]+$', 'ab_c.TEST:get') @@ -48,3 +59,11 @@ API_SCHEMA = Schema({ 'domain': DOMAIN_SCHEMA, 'paths': ROUTE_SCHEMA }) + +ROUTER_SCHEMA = Schema({ + Or('', str): { + # Optional('GET'): [],#ACLS_SCHEMA, + Optional(Or(*VERBS)): ROUTER_ACLS_SCHEMA, + Optional('SUBROUTES'): [Optional(str)] + } +}) diff --git a/halfapi/lib/router.py b/halfapi/lib/router.py index cb3a6cf..a3a2c6d 100644 --- a/halfapi/lib/router.py +++ b/halfapi/lib/router.py @@ -1,44 +1,71 @@ import os +import sys +import subprocess from types import ModuleType from typing import Dict +from pprint import pprint -from halfapi.lib.constants import VERBS +from schema import SchemaError +from .constants import VERBS, ROUTER_SCHEMA +from ..logging import logger def read_router(m_router: ModuleType) -> Dict: """ Reads a module and returns a router dict + + If the module has a "ROUTES" constant, it just returns this constant, + Else, if the module has an "ACLS" constant, it builds the accurate dict + + TODO: May be another thing, may be not a part of halfAPI + """ + m_path = None - if not hasattr(m_router, 'ROUTES'): - routes = {'':{}} - acls = getattr(m_router, 'ACLS') if hasattr(m_router, 'ACLS') else None + try: + if not hasattr(m_router, 'ROUTES'): + routes = {'':{}} + acls = getattr(m_router, 'ACLS') if hasattr(m_router, 'ACLS') else None - if acls is not None: - for verb in VERBS: - if not hasattr(m_router, verb.lower()): - continue + if acls is not None: + for verb in VERBS: + if not hasattr(m_router, verb.lower()): + # verb in function names are lowercase + continue - """ There is a "verb" route in the router + """ There is a "verb" route in the router + """ + + if verb.upper() not in acls: + continue + + routes[''][verb.upper()] = [] + routes[''][verb.upper()] = acls[verb.upper()].copy() + + routes['']['SUBROUTES'] = [] + if hasattr(m_router, '__path__'): + """ Module is a package """ + m_path = getattr(m_router, '__path__') + if isinstance(m_path, list) and len(m_path) == 1: + routes['']['SUBROUTES'] = [ + elt.name + for elt in os.scandir(m_path[0]) + if elt.is_dir() + ] + else: + routes = getattr(m_router, 'ROUTES') - if verb.upper() not in acls: - continue + try: + ROUTER_SCHEMA.validate(routes) + except SchemaError as exc: + logger.error(routes) + raise exc - routes[''][verb.upper()] = [] - routes[''][verb.upper()] = acls[verb.upper()].copy() - - routes['']['SUBROUTES'] = [] - if hasattr(m_router, '__path__'): - """ Module is a package - """ - m_path = getattr(m_router, '__path__') - if isinstance(m_path, list) and len(m_path) == 1: - routes['']['SUBROUTES'] = [ - elt.name - for elt in os.scandir(m_path[0]) - if elt.is_dir() - ] - else: - routes = getattr(m_router, 'ROUTES') - - return routes + return routes + except ImportError as exc: + # TODO: Proper exception handling + raise exc + except FileNotFoundError as exc: + # TODO: Proper exception handling + logger.error(m_path) + raise exc diff --git a/halfapi/lib/routes.py b/halfapi/lib/routes.py index 4589d11..2fc8b16 100644 --- a/halfapi/lib/routes.py +++ b/halfapi/lib/routes.py @@ -48,8 +48,6 @@ def JSONRoute(data: Any) -> Coroutine: return wrapped - - def gen_domain_routes(m_domain: ModuleType): """ Yields the Route objects for a domain diff --git a/tests/dummy_domain/routers/async/__init__.py b/tests/dummy_domain/routers/async/__init__.py index 468ef91..cd9c977 100644 --- a/tests/dummy_domain/routers/async/__init__.py +++ b/tests/dummy_domain/routers/async/__init__.py @@ -16,7 +16,7 @@ ROUTES = { 'acl': acl.public, 'args': { 'required': {'foo', 'bar'}, - 'optional': {} + 'optional': set() } }] },