From c0bd6ddc4313d53fbbe64fb8b21c165b67c1a6b0 Mon Sep 17 00:00:00 2001 From: "Maxime Alves LIRMM@home" Date: Mon, 18 Jul 2022 23:23:09 +0200 Subject: [PATCH] [release] 0.6.21 --- CHANGELOG.md | 8 ++++ Dockerfile | 2 +- halfapi/__init__.py | 2 +- halfapi/cli/domain.py | 18 ++++----- halfapi/half_route.py | 2 + halfapi/halfapi.py | 38 +++++++++---------- halfapi/lib/acl.py | 5 ++- halfapi/lib/domain_middleware.py | 16 +++++++- halfapi/testing/test_domain.py | 20 +++++++--- tests/cli/test_cli_proj.py | 16 +++++++- tests/dummy_domain/routers/config/__init__.py | 15 +++++++- 11 files changed, 101 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc92b77..a24321c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # HalfAPI +## 0.6.21 + +- Store only domain's config in halfapi['config'] +- Should run halfapi domain with config_file argument +- Testing : You can specify a "MODULE" attribute to point out the path to the Api's base module +- Environment : HALFAPI_DOMAIN_MODULE can be set to specify Api's base module +- Config : 'module' attribute can be set to specify Api's base module + ## 0.6.20 - Fix arguments handling diff --git a/Dockerfile b/Dockerfile index 05f2b99..2eaf23f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1 -FROM docker.io/python:3.8.12-slim-bullseye +FROM docker.io/python:3.10.5-slim-bullseye COPY . /halfapi WORKDIR /halfapi RUN apt-get update > /dev/null && apt-get -y install git > /dev/null diff --git a/halfapi/__init__.py b/halfapi/__init__.py index 3fa9e9e..a362c75 100644 --- a/halfapi/__init__.py +++ b/halfapi/__init__.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -__version__ = '0.6.20-rc0' +__version__ = '0.6.21-rc0' def version(): return f'HalfAPI version:{__version__}' diff --git a/halfapi/cli/domain.py b/halfapi/cli/domain.py index 32b88b4..46c2844 100644 --- a/halfapi/cli/domain.py +++ b/halfapi/cli/domain.py @@ -8,6 +8,8 @@ import sys import importlib import subprocess +import json + import click import orjson @@ -120,9 +122,10 @@ def list_api_routes(): @click.option('--create',default=False, is_flag=True) @click.option('--update',default=False, is_flag=True) @click.option('--delete',default=False, is_flag=True) +@click.argument('config_file', type=click.File(mode='rb'), required=False) @click.argument('domain',default=None, required=False) @cli.command() -def domain(domain, delete, update, create, read): #, domains, read, create, update, delete): +def domain(domain, config_file, delete, update, create, read): #, domains, read, create, update, delete): """ The "halfapi domain" command @@ -147,17 +150,14 @@ def domain(domain, delete, update, create, read): #, domains, read, create, upd from ..conf import CONFIG from ..halfapi import HalfAPI - try: - config_domain = CONFIG.pop('domain').get(domain, {}) - except KeyError: - config_domain = {} + if config_file: + CONFIG = json.loads(''.join( + [ line.decode() for line in config_file.readlines() ] + )) halfapi = HalfAPI(CONFIG) - - half_domain = halfapi.add_domain(domain, config=config_domain) - click.echo(orjson.dumps( - half_domain.schema(), + halfapi.domains[domain].schema(), option=orjson.OPT_NON_STR_KEYS, default=ORJSONResponse.default_cast) ) diff --git a/halfapi/half_route.py b/halfapi/half_route.py index ac19101..326037a 100644 --- a/halfapi/half_route.py +++ b/halfapi/half_route.py @@ -84,6 +84,8 @@ class HalfRoute(Route): logger.debug( 'Args for current route (%s)', param.get('args')) + if 'out' in param: + req.scope['out'] = param['out'] if 'out' in param: req.scope['out'] = param['out'].copy() diff --git a/halfapi/halfapi.py b/halfapi/halfapi.py index 906ed0b..74c627c 100644 --- a/halfapi/halfapi.py +++ b/halfapi/halfapi.py @@ -122,12 +122,12 @@ class HalfAPI(Starlette): domain_key = domain.get('name', key) - self.add_domain( - domain_key, - domain.get('module'), - domain.get('router'), - domain.get('acl'), - path) + add_domain_args = { + **domain, + 'path': path + } + + self.add_domain(**add_domain_args) schemas.append(self.__domains[domain_key].schema()) @@ -246,28 +246,26 @@ class HalfAPI(Starlette): def domains(self): return self.__domains - def add_domain(self, name, module=None, router=None, acl=None, path='/', config=None): + def add_domain(self, **kwargs): - # logger.debug('HalfApi.add_domain %s %s %s %s %s', - # name, - # module, - # router, - # acl, - # path, - # config) + if not kwargs.get('enabled'): + raise Exception(f'Domain not enabled ({kwargs})') - if config: - self.config['domain'][name] = config + name = kwargs['name'] - if not module: + self.config['domain'][name] = kwargs.get('config', {}) + + if not kwargs.get('module'): module = name + else: + module = kwargs.get('module') try: self.__domains[name] = HalfDomain( name, module=importlib.import_module(module), - router=router, - acl=acl, + router=kwargs.get('router'), + acl=kwargs.get('acl'), app=self ) @@ -279,6 +277,6 @@ class HalfAPI(Starlette): )) raise exc - self.mount(path, self.__domains[name]) + self.mount(kwargs.get('path', name), self.__domains[name]) return self.__domains[name] diff --git a/halfapi/lib/acl.py b/halfapi/lib/acl.py index 745a144..ba507e9 100644 --- a/halfapi/lib/acl.py +++ b/halfapi/lib/acl.py @@ -108,8 +108,9 @@ def args_check(fct): kwargs['data'] = data - if req.scope.get('out'): - kwargs['out'] = req.scope.get('out').copy() + out_s = req.scope.get('out') + if out_s: + kwargs['out'] = list(out_s) return await fct(req, *args, **kwargs) diff --git a/halfapi/lib/domain_middleware.py b/halfapi/lib/domain_middleware.py index c1f0379..f152dd6 100644 --- a/halfapi/lib/domain_middleware.py +++ b/halfapi/lib/domain_middleware.py @@ -35,7 +35,21 @@ class DomainMiddleware(BaseHTTPMiddleware): request.scope['domain'] = self.domain['name'] if hasattr(request.app, 'config') \ and isinstance(request.app.config, dict): - request.scope['config'] = { **request.app.config } + # Set the config scope to the domain's config + request.scope['config'] = request.app.config.get( + 'domain', {} + ).get( + self.domain['name'], {} + ).copy() + + # TODO: Remove in 0.7.0 + config = request.scope['config'].copy() + request.scope['config']['domain'] = {} + request.scope['config']['domain'][self.domain['name']] = {} + request.scope['config']['domain'][self.domain['name']]['config'] = config + + + else: logger.debug('%s', request.app) logger.debug('%s', getattr(request.app, 'config', None)) diff --git a/halfapi/testing/test_domain.py b/halfapi/testing/test_domain.py index 33fc3ff..da18b4a 100644 --- a/halfapi/testing/test_domain.py +++ b/halfapi/testing/test_domain.py @@ -11,11 +11,16 @@ from ..cli.cli import cli from ..halfapi import HalfAPI from ..half_domain import HalfDomain from pprint import pprint +import tempfile class TestDomain(TestCase): + @property + def module_name(self): + return getattr(self, 'MODULE', self.DOMAIN) + @property def router_module(self): - return '.'.join((self.DOMAIN, self.ROUTERS)) + return '.'.join((self.module_name, self.ROUTERS)) def setUp(self): # CLI @@ -53,6 +58,7 @@ class TestDomain(TestCase): 'name': self.DOMAIN, 'router': self.ROUTERS, 'acl': self.ACL, + 'module': self.module_name, 'prefix': False, 'enabled': True, 'config': { @@ -60,12 +66,16 @@ class TestDomain(TestCase): } } + _, self.config_file = tempfile.mkstemp() + with open(self.config_file, 'w') as fh: + fh.write(json.dumps(self.halfapi_conf)) + self.halfapi = HalfAPI(self.halfapi_conf) self.client = TestClient(self.halfapi.application) self.module = importlib.import_module( - getattr(self, 'MODULE', self.DOMAIN) + self.module_name ) @@ -77,13 +87,13 @@ class TestDomain(TestCase): try: result = self.runner.invoke(cli, '--version') self.assertEqual(result.exit_code, 0) - result = self.runner.invoke(cli, ['domain', self.DOMAIN]) + result = self.runner.invoke(cli, ['domain', self.DOMAIN, self.config_file]) self.assertEqual(result.exit_code, 0) result_d = json.loads(result.stdout) result = self.runner.invoke(cli, ['run', '--help']) self.assertEqual(result.exit_code, 0) - result = self.runner.invoke(cli, ['run', '--dryrun', self.DOMAIN]) - self.assertEqual(result.exit_code, 0) + # result = self.runner.invoke(cli, ['run', '--dryrun', self.DOMAIN]) + # self.assertEqual(result.exit_code, 0) except AssertionError as exc: print(f'Result {result}') print(f'Stdout {result.stdout}') diff --git a/tests/cli/test_cli_proj.py b/tests/cli/test_cli_proj.py index 2fa6ed2..7331dde 100644 --- a/tests/cli/test_cli_proj.py +++ b/tests/cli/test_cli_proj.py @@ -4,6 +4,7 @@ import subprocess import importlib import tempfile from unittest.mock import patch +import json import pytest from click.testing import CliRunner @@ -25,7 +26,20 @@ class TestCliProj(): r = project_runner('domain') print(r.stdout) assert r.exit_code == 1 - r = project_runner('domain dummy_domain') + _, tmp_conf = tempfile.mkstemp() + with open(tmp_conf, 'w') as fh: + fh.write( + json.dumps({ + 'domain': { + 'dummy_domain': { + 'name': 'dummy_domain', + 'enabled': True + } + } + }) + ) + + r = project_runner(f'domain dummy_domain {tmp_conf}') print(r.stdout) assert r.exit_code == 0 diff --git a/tests/dummy_domain/routers/config/__init__.py b/tests/dummy_domain/routers/config/__init__.py index 70e6a33..3adef6b 100644 --- a/tests/dummy_domain/routers/config/__init__.py +++ b/tests/dummy_domain/routers/config/__init__.py @@ -14,4 +14,17 @@ def get(halfapi): returns the configuration of the domain """ logger.error('%s', halfapi) - return halfapi['config']['domain']['dummy_domain']['config'] + # TODO: Remove in 0.7.0 + try: + assert 'test' in halfapi['config']['domain']['dummy_domain']['config'] + except AssertionError as exc: + logger.error('No TEST in halfapi[config][domain][dummy_domain][config]') + raise exc + + try: + assert 'test' in halfapi['config'] + except AssertionError as exc: + logger.error('No TEST in halfapi[config]') + raise exc + + return halfapi['config']