From 9fcc9630bddda5d1ffec66c29ea83518d87b71e5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 16 Nov 2021 18:15:09 +0100 Subject: [PATCH] Rework actionsmap and m18n init, drop multiple actionsmap support --- moulinette/__init__.py | 54 +++---- moulinette/actionsmap.py | 289 ++++++++++++++-------------------- moulinette/core.py | 61 ++----- moulinette/interfaces/api.py | 16 +- moulinette/interfaces/cli.py | 3 +- pytest.ini | 1 - setup.py | 5 +- test/actionsmap/moulitest.yml | 3 +- test/conftest.py | 44 +++--- test/test_actionsmap.py | 12 +- 10 files changed, 185 insertions(+), 303 deletions(-) diff --git a/moulinette/__init__.py b/moulinette/__init__.py index 7c39a673..94bd2d7e 100755 --- a/moulinette/__init__.py +++ b/moulinette/__init__.py @@ -3,7 +3,6 @@ from moulinette.core import ( MoulinetteError, Moulinette18n, - env, ) __title__ = "moulinette" @@ -54,35 +53,8 @@ class Moulinette: return cls._interface -# Package functions - - -def init(logging_config=None, **kwargs): - """Package initialization - - Initialize directories and global variables. It must be called - before any of package method is used - even the easy access - functions. - - Keyword arguments: - - logging_config -- A dict containing logging configuration to load - - **kwargs -- See core.Package - - At the end, the global variable 'pkg' will contain a Package - instance. See core.Package for available methods and variables. - - """ - import sys - from moulinette.utils.log import configure_logging - - configure_logging(logging_config) - - # Add library directory to python path - sys.path.insert(0, env["LIB_DIR"]) - - # Easy access to interfaces -def api(host="localhost", port=80, routes={}): +def api(host="localhost", port=80, routes={}, actionsmap=None, locales_dir=None): """Web server (API) interface Run a HTTP server with the moulinette for an API usage. @@ -96,8 +68,16 @@ def api(host="localhost", port=80, routes={}): """ from moulinette.interfaces.api import Interface as Api + m18n.set_locales_dir(locales_dir) + try: - Api(routes=routes).run(host, port) + Api( + routes=routes, + actionsmap=actionsmap, + ).run( + host, + port + ) except MoulinetteError as e: import logging @@ -110,7 +90,7 @@ def api(host="localhost", port=80, routes={}): return 0 -def cli(args, top_parser, output_as=None, timeout=None): +def cli(args, top_parser, output_as=None, timeout=None, actionsmap=None, locales_dir=None): """Command line interface Execute an action with the moulinette from the CLI and print its @@ -125,10 +105,18 @@ def cli(args, top_parser, output_as=None, timeout=None): """ from moulinette.interfaces.cli import Interface as Cli + m18n.set_locales_dir(locales_dir) + try: load_only_category = args[0] if args and not args[0].startswith("-") else None - Cli(top_parser=top_parser, load_only_category=load_only_category).run( - args, output_as=output_as, timeout=timeout + Cli( + top_parser=top_parser, + load_only_category=load_only_category, + actionsmap=actionsmap, + ).run( + args, + output_as=output_as, + timeout=timeout ) except MoulinetteError as e: import logging diff --git a/moulinette/actionsmap.py b/moulinette/actionsmap.py index 386018ec..358575de 100644 --- a/moulinette/actionsmap.py +++ b/moulinette/actionsmap.py @@ -16,7 +16,6 @@ from moulinette.core import ( MoulinetteError, MoulinetteLock, MoulinetteValidationError, - env, ) from moulinette.interfaces import BaseActionsMapParser, TO_RETURN_PROP from moulinette.utils.log import start_action_logging @@ -382,9 +381,6 @@ class ActionsMap(object): It is composed by categories which contain one or more action(s). Moreover, the action can have specific argument(s). - This class allows to manipulate one or several actions maps - associated to a namespace. - Keyword arguments: - top_parser -- A BaseActionsMapParser-derived instance to use for parsing the actions map @@ -394,84 +390,71 @@ class ActionsMap(object): purposes... """ - def __init__(self, top_parser, load_only_category=None): + def __init__(self, actionsmap_yml, top_parser, load_only_category=None): assert isinstance(top_parser, BaseActionsMapParser), ( "Invalid parser class '%s'" % top_parser.__class__.__name__ ) - DATA_DIR = env["DATA_DIR"] - CACHE_DIR = env["CACHE_DIR"] - - actionsmaps = OrderedDict() - self.from_cache = False - # Iterate over actions map namespaces - for n in self.get_namespaces(): - logger.debug("loading actions map namespace '%s'", n) - actionsmap_yml = "%s/actionsmap/%s.yml" % (DATA_DIR, n) - actionsmap_yml_stat = os.stat(actionsmap_yml) - actionsmap_pkl = "%s/actionsmap/%s-%d-%d.pkl" % ( - CACHE_DIR, - n, - actionsmap_yml_stat.st_size, - actionsmap_yml_stat.st_mtime, - ) + logger.debug("loading actions map") - def generate_cache(): + actionsmap_yml_dir = os.path.dirname(actionsmap_yml) + actionsmap_yml_file = os.path.basename(actionsmap_yml) + actionsmap_yml_stat = os.stat(actionsmap_yml) - # Iterate over actions map namespaces - logger.debug("generating cache for actions map namespace '%s'", n) + actionsmap_pkl = f"{actionsmap_yml_dir}/.{actionsmap_yml_file}.{actionsmap_yml_stat.st_size}-{actionsmap_yml_stat.st_mtime}.pkl" - # Read actions map from yaml file - actionsmap = read_yaml(actionsmap_yml) + def generate_cache(): - # Delete old cache files - for old_cache in glob.glob("%s/actionsmap/%s-*.pkl" % (CACHE_DIR, n)): - os.remove(old_cache) + logger.debug("generating cache for actions map") - # at installation, cachedir might not exists - dir_ = os.path.dirname(actionsmap_pkl) - if not os.path.isdir(dir_): - os.makedirs(dir_) + # Read actions map from yaml file + actionsmap = read_yaml(actionsmap_yml) - # Cache actions map into pickle file - with open(actionsmap_pkl, "wb") as f: - pickle.dump(actionsmap, f) + # Delete old cache files + for old_cache in glob.glob(f"{actionsmap_yml_dir}/.{actionsmap_yml_file}.*.pkl"): + os.remove(old_cache) - return actionsmap + # at installation, cachedir might not exists + dir_ = os.path.dirname(actionsmap_pkl) + if not os.path.isdir(dir_): + os.makedirs(dir_) - if os.path.exists(actionsmap_pkl): - try: - # Attempt to load cache - with open(actionsmap_pkl, "rb") as f: - actionsmaps[n] = pickle.load(f) + # Cache actions map into pickle file + with open(actionsmap_pkl, "wb") as f: + pickle.dump(actionsmap, f) - self.from_cache = True - # TODO: Switch to python3 and catch proper exception - except (IOError, EOFError): - actionsmaps[n] = generate_cache() - else: # cache file doesn't exists - actionsmaps[n] = generate_cache() + return actionsmap - # If load_only_category is set, and *if* the target category - # is in the actionsmap, we'll load only that one. - # If we filter it even if it doesn't exist, we'll end up with a - # weird help message when we do a typo in the category name.. - if load_only_category and load_only_category in actionsmaps[n]: - actionsmaps[n] = { - k: v - for k, v in actionsmaps[n].items() - if k in [load_only_category, "_global"] - } + if os.path.exists(actionsmap_pkl): + try: + # Attempt to load cache + with open(actionsmap_pkl, "rb") as f: + actionsmap = pickle.load(f) - # Load translations - m18n.load_namespace(n) + self.from_cache = True + # TODO: Switch to python3 and catch proper exception + except (IOError, EOFError): + actionsmap = generate_cache() + else: # cache file doesn't exists + actionsmap = generate_cache() + + # If load_only_category is set, and *if* the target category + # is in the actionsmap, we'll load only that one. + # If we filter it even if it doesn't exist, we'll end up with a + # weird help message when we do a typo in the category name.. + if load_only_category and load_only_category in actionsmap: + actionsmap = { + k: v + for k, v in actionsmap.items() + if k in [load_only_category, "_global"] + } # Generate parsers self.extraparser = ExtraArgumentParser(top_parser.interface) - self.parser = self._construct_parser(actionsmaps, top_parser) + self.parser = self._construct_parser(actionsmap, top_parser) def get_authenticator(self, auth_method): @@ -479,7 +462,7 @@ class ActionsMap(object): auth_method = self.default_authentication # Load and initialize the authenticator module - auth_module = "%s.authenticators.%s" % (self.main_namespace, auth_method) + auth_module = "%s.authenticators.%s" % (self.namespace, auth_method) logger.debug(f"Loading auth module {auth_module}") try: mod = import_module(auth_module) @@ -591,7 +574,6 @@ class ActionsMap(object): logger.debug("processing action [%s]: %s", log_id, full_action_name) # Load translation and process the action - m18n.load_namespace(namespace) start = time() try: return func(**arguments) @@ -599,43 +581,14 @@ class ActionsMap(object): stop = time() logger.debug("action [%s] executed in %.3fs", log_id, stop - start) - @staticmethod - def get_namespaces(): - """ - Retrieve available actions map namespaces - - Returns: - A list of available namespaces - - """ - namespaces = [] - - DATA_DIR = env["DATA_DIR"] - - # This var is ['*'] by default but could be set for example to - # ['yunohost', 'yml_*'] - NAMESPACE_PATTERNS = env["NAMESPACES"].split() - - # Look for all files that match the given patterns in the actionsmap dir - for namespace_pattern in NAMESPACE_PATTERNS: - namespaces.extend( - glob.glob("%s/actionsmap/%s.yml" % (DATA_DIR, namespace_pattern)) - ) - - # Keep only the filenames with extension - namespaces = [os.path.basename(n)[:-4] for n in namespaces] - - return namespaces - # Private methods - def _construct_parser(self, actionsmaps, top_parser): + def _construct_parser(self, actionsmap, top_parser): """ Construct the parser with the actions map Keyword arguments: - - actionsmaps -- A dict of multi-level dictionnary of - categories/actions/arguments list for each namespaces + - actionsmap -- A dictionnary of categories/actions/arguments list - top_parser -- A BaseActionsMapParser-derived instance to use for parsing the actions map @@ -658,52 +611,85 @@ class ActionsMap(object): # * namespace define the top "name", for us it will always be # "yunohost" and there well be only this one # * actionsmap is the actual actionsmap that we care about - for namespace, actionsmap in actionsmaps.items(): - # Retrieve global parameters - _global = actionsmap.pop("_global", {}) - if _global: - if getattr(self, "main_namespace", None) is not None: - raise MoulinetteError( - "It is not possible to have several namespaces with a _global section" - ) - else: - self.main_namespace = namespace - self.name = _global["name"] - self.default_authentication = _global["authentication"][ - interface_type - ] + # Retrieve global parameters + _global = actionsmap.pop("_global", {}) - if top_parser.has_global_parser(): - top_parser.add_global_arguments(_global["arguments"]) + self.namespace = _global["namespace"] + self.cookie_name = _global["cookie_name"] + self.default_authentication = _global["authentication"][ + interface_type + ] - if not hasattr(self, "main_namespace"): - raise MoulinetteError("Did not found the main namespace", raw_msg=True) + if top_parser.has_global_parser(): + top_parser.add_global_arguments(_global["arguments"]) - for namespace, actionsmap in actionsmaps.items(): - # category_name is stuff like "user", "domain", "hooks"... - # category_values is the values of this category (like actions) - for category_name, category_values in actionsmap.items(): + # category_name is stuff like "user", "domain", "hooks"... + # category_values is the values of this category (like actions) + for category_name, category_values in actionsmap.items(): - actions = category_values.pop("actions", {}) - subcategories = category_values.pop("subcategories", {}) + actions = category_values.pop("actions", {}) + subcategories = category_values.pop("subcategories", {}) - # Get category parser - category_parser = top_parser.add_category_parser( - category_name, **category_values + # Get category parser + category_parser = top_parser.add_category_parser( + category_name, **category_values + ) + + # action_name is like "list" of "domain list" + # action_options are the values + for action_name, action_options in actions.items(): + arguments = action_options.pop("arguments", {}) + authentication = action_options.pop("authentication", {}) + tid = (self.namespace, category_name, action_name) + + # Get action parser + action_parser = category_parser.add_action_parser( + action_name, tid, **action_options ) - # action_name is like "list" of "domain list" + if action_parser is None: # No parser for the action + continue + + # Store action identifier and add arguments + action_parser.set_defaults(_tid=tid) + action_parser.add_arguments( + arguments, + extraparser=self.extraparser, + format_arg_names=top_parser.format_arg_names, + validate_extra=validate_extra, + ) + + action_parser.authentication = self.default_authentication + if interface_type in authentication: + action_parser.authentication = authentication[interface_type] + + # subcategory_name is like "cert" in "domain cert status" + # subcategory_values is the values of this subcategory (like actions) + for subcategory_name, subcategory_values in subcategories.items(): + + actions = subcategory_values.pop("actions") + + # Get subcategory parser + subcategory_parser = category_parser.add_subcategory_parser( + subcategory_name, **subcategory_values + ) + + # action_name is like "status" of "domain cert status" # action_options are the values for action_name, action_options in actions.items(): arguments = action_options.pop("arguments", {}) authentication = action_options.pop("authentication", {}) - tid = (namespace, category_name, action_name) + tid = (self.namespace, category_name, subcategory_name, action_name) - # Get action parser - action_parser = category_parser.add_action_parser( - action_name, tid, **action_options - ) + try: + # Get action parser + action_parser = subcategory_parser.add_action_parser( + action_name, tid, **action_options + ) + except AttributeError: + # No parser for the action + continue if action_parser is None: # No parser for the action continue @@ -719,52 +705,9 @@ class ActionsMap(object): action_parser.authentication = self.default_authentication if interface_type in authentication: - action_parser.authentication = authentication[interface_type] - - # subcategory_name is like "cert" in "domain cert status" - # subcategory_values is the values of this subcategory (like actions) - for subcategory_name, subcategory_values in subcategories.items(): - - actions = subcategory_values.pop("actions") - - # Get subcategory parser - subcategory_parser = category_parser.add_subcategory_parser( - subcategory_name, **subcategory_values - ) - - # action_name is like "status" of "domain cert status" - # action_options are the values - for action_name, action_options in actions.items(): - arguments = action_options.pop("arguments", {}) - authentication = action_options.pop("authentication", {}) - tid = (namespace, category_name, subcategory_name, action_name) - - try: - # Get action parser - action_parser = subcategory_parser.add_action_parser( - action_name, tid, **action_options - ) - except AttributeError: - # No parser for the action - continue - - if action_parser is None: # No parser for the action - continue - - # Store action identifier and add arguments - action_parser.set_defaults(_tid=tid) - action_parser.add_arguments( - arguments, - extraparser=self.extraparser, - format_arg_names=top_parser.format_arg_names, - validate_extra=validate_extra, - ) - - action_parser.authentication = self.default_authentication - if interface_type in authentication: - action_parser.authentication = authentication[ - interface_type - ] + action_parser.authentication = authentication[ + interface_type + ] logger.debug("building parser took %.3fs", time() - start) return top_parser diff --git a/moulinette/core.py b/moulinette/core.py index 6f6fddd7..785cbeec 100644 --- a/moulinette/core.py +++ b/moulinette/core.py @@ -9,24 +9,10 @@ import moulinette logger = logging.getLogger("moulinette.core") -env = { - "DATA_DIR": "/usr/share/moulinette", - "LIB_DIR": "/usr/lib/moulinette", - "LOCALES_DIR": "/usr/share/moulinette/locale", - "CACHE_DIR": "/var/cache/moulinette", - "NAMESPACES": "*", # By default we'll load every namespace we find -} - -for key in env.keys(): - value_from_environ = os.environ.get(f"MOULINETTE_{key}") - if value_from_environ: - env[key] = value_from_environ - def during_unittests_run(): return "TESTS_RUN" in os.environ - # Internationalization ------------------------------------------------- @@ -51,11 +37,7 @@ class Translator(object): # Attempt to load default translations if not self._load_translations(default_locale): logger.error( - "unable to load locale '%s' from '%s'. Does the file '%s/%s.json' exists?", - default_locale, - locale_dir, - locale_dir, - default_locale, + f"unable to load locale '{default_locale}' from '{locale_dir}'. Does the file '{locale_dir}/{default_locale}.json' exists?", ) self.default_locale = default_locale @@ -207,44 +189,23 @@ class Moulinette18n(object): self.default_locale = default_locale self.locale = default_locale - self.locales_dir = env["LOCALES_DIR"] - # Init global translator - self._global = Translator(self.locales_dir, default_locale) + global_locale_dir = "/usr/share/moulinette/locales" + if during_unittests_run(): + global_locale_dir = os.path.dirname(__file__) + "/../locales" - # Define namespace related variables - self._namespaces = {} - self._current_namespace = None + self._global = Translator(global_locale_dir, default_locale) - def load_namespace(self, namespace): - """Load the namespace to use + def set_locales_dir(self, locales_dir): - Load and set translations of a given namespace. Those translations - are accessible with Moulinette18n.n(). - - Keyword arguments: - - namespace -- The namespace to load - - """ - if namespace not in self._namespaces: - # Create new Translator object - lib_dir = env["LIB_DIR"] - translator = Translator( - "%s/%s/locales" % (lib_dir, namespace), self.default_locale - ) - translator.set_locale(self.locale) - self._namespaces[namespace] = translator - - # Set current namespace - self._current_namespace = namespace + self.translator = Translator(locales_dir, self.default_locale) def set_locale(self, locale): """Set the locale to use""" - self.locale = locale + self.locale = locale self._global.set_locale(locale) - for n in self._namespaces.values(): - n.set_locale(locale) + self.translator.set_locale(locale) def g(self, key: str, *args, **kwargs) -> str: """Retrieve proper translation for a moulinette key @@ -269,7 +230,7 @@ class Moulinette18n(object): - key -- The key to translate """ - return self._namespaces[self._current_namespace].translate(key, *args, **kwargs) + return self.translator.translate(key, *args, **kwargs) def key_exists(self, key: str) -> bool: """Check if a key exists in the translation files @@ -278,7 +239,7 @@ class Moulinette18n(object): - key -- The key to translate """ - return self._namespaces[self._current_namespace].key_exists(key) + return self.translator.key_exists(key) # Moulinette core classes ---------------------------------------------- diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index 7be0c3b4..b51e79d2 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -237,14 +237,14 @@ class _HTTPArgumentParser(object): class Session: secret = random_ascii() - actionsmap_name = None # This is later set to the actionsmap name + cookie_name = None # This is later set to the actionsmap name def set_infos(infos): assert isinstance(infos, dict) response.set_cookie( - f"session.{Session.actionsmap_name}", + f"session.{Session.cookie_name}", infos, secure=True, secret=Session.secret, @@ -256,7 +256,7 @@ class Session: try: infos = request.get_cookie( - f"session.{Session.actionsmap_name}", secret=Session.secret, default={} + f"session.{Session.cookie_name}", secret=Session.secret, default={} ) except Exception: if not raise_if_no_session_exists: @@ -271,8 +271,8 @@ class Session: @staticmethod def delete_infos(): - response.set_cookie(f"session.{Session.actionsmap_name}", "", max_age=-1) - response.delete_cookie(f"session.{Session.actionsmap_name}") + response.set_cookie(f"session.{Session.cookie_name}", "", max_age=-1) + response.delete_cookie(f"session.{Session.cookie_name}") class _ActionsMapPlugin(object): @@ -294,7 +294,7 @@ class _ActionsMapPlugin(object): self.actionsmap = actionsmap self.log_queues = log_queues - Session.actionsmap_name = actionsmap.name + Session.cookie_name = actionsmap.cookie_name def setup(self, app): """Setup plugin on the application @@ -734,9 +734,9 @@ class Interface: type = "api" - def __init__(self, routes={}): + def __init__(self, routes={}, actionsmap=None): - actionsmap = ActionsMap(ActionsMapParser()) + actionsmap = ActionsMap(actionsmap, ActionsMapParser()) # Attempt to retrieve log queues from an APIQueueHandler handler = log.getHandlersByClass(APIQueueHandler, limit=1) diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index 92b2c3d5..318a0c2f 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -461,12 +461,13 @@ class Interface: type = "cli" - def __init__(self, top_parser=None, load_only_category=None): + def __init__(self, top_parser=None, load_only_category=None, actionsmap=None, locales_dir=None): # Set user locale m18n.set_locale(get_locale()) self.actionsmap = ActionsMap( + actionsmap, ActionsMapParser(top_parser=top_parser), load_only_category=load_only_category, ) diff --git a/pytest.ini b/pytest.ini index 55416dc0..a32c2f36 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,5 +3,4 @@ addopts = --cov=moulinette -s -v --no-cov-on-fail norecursedirs = dist doc build .tox .eggs testpaths = test/ env = - MOULINETTE_LOCALES_DIR = {PWD}/locales TESTS_RUN = True diff --git a/setup.py b/setup.py index 5b8049e6..37e55773 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,6 @@ import sys import subprocess from setuptools import setup, find_packages -from moulinette import env version = ( subprocess.check_output( @@ -15,8 +14,6 @@ version = ( .strip() ) -LOCALES_DIR = env["LOCALES_DIR"] - # Extend installation locale_files = [] @@ -62,7 +59,7 @@ setup( url="https://yunohost.org", license="AGPL", packages=find_packages(exclude=["test"]), - data_files=[(LOCALES_DIR, locale_files)], + data_files=[("/usr/share/moulinette/locales", locale_files)], python_requires=">=3.7.*, <3.10", install_requires=install_deps, tests_require=test_deps, diff --git a/test/actionsmap/moulitest.yml b/test/actionsmap/moulitest.yml index 4a51e48d..88ddeae0 100644 --- a/test/actionsmap/moulitest.yml +++ b/test/actionsmap/moulitest.yml @@ -3,7 +3,8 @@ # Global parameters # ############################# _global: - name: moulitest + namespace: moulitest + cookie_name: moulitest authentication: api: dummy cli: dummy diff --git a/test/conftest.py b/test/conftest.py index d40e1116..b7fc0470 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,24 +1,15 @@ """Pytest fixtures for testing.""" +import sys import toml import yaml import json import os + import shutil import pytest -def patch_init(moulinette): - """Configure moulinette to use the YunoHost namespace.""" - old_init = moulinette.core.Moulinette18n.__init__ - - def monkey_path_i18n_init(self, package, default_locale="en"): - old_init(self, package, default_locale) - self.load_namespace("moulinette") - - moulinette.core.Moulinette18n.__init__ = monkey_path_i18n_init - - def patch_translate(moulinette): """Configure translator to raise errors when there are missing keys.""" old_translate = moulinette.core.Translator.translate @@ -38,7 +29,7 @@ def patch_translate(moulinette): moulinette.core.Moulinette18n.g = new_m18nn -def patch_logging(moulinette): +def logging_configuration(moulinette): """Configure logging to use the custom logger.""" handlers = set(["tty", "api"]) root_handlers = set(handlers) @@ -85,27 +76,28 @@ def patch_lock(moulinette): @pytest.fixture(scope="session", autouse=True) def moulinette(tmp_path_factory): + import moulinette + import moulinette.core + from moulinette.utils.log import configure_logging # Can't call the namespace just 'test' because # that would lead to some "import test" not importing the right stuff namespace = "moulitest" - tmp_cache = str(tmp_path_factory.mktemp("cache")) - tmp_data = str(tmp_path_factory.mktemp("data")) - tmp_lib = str(tmp_path_factory.mktemp("lib")) - moulinette.env["CACHE_DIR"] = tmp_cache - moulinette.env["DATA_DIR"] = tmp_data - moulinette.env["LIB_DIR"] = tmp_lib - shutil.copytree("./test/actionsmap", "%s/actionsmap" % tmp_data) - shutil.copytree("./test/src", "%s/%s" % (tmp_lib, namespace)) - shutil.copytree("./test/locales", "%s/%s/locales" % (tmp_lib, namespace)) + tmp_dir = str(tmp_path_factory.mktemp(namespace)) + shutil.copy("./test/actionsmap/moulitest.yml", f"{tmp_dir}/moulitest.yml") + shutil.copytree("./test/src", f"{tmp_dir}/lib/{namespace}/") + shutil.copytree("./test/locales", f"{tmp_dir}/locales") + sys.path.insert(0, f"{tmp_dir}/lib") - patch_init(moulinette) patch_translate(moulinette) patch_lock(moulinette) - logging = patch_logging(moulinette) - moulinette.init(logging_config=logging, _from_source=False) + configure_logging(logging_configuration(moulinette)) + moulinette.m18n.set_locales_dir(f"{tmp_dir}/locales") + + # Dirty hack to pass this path to Api() and Cli() init later + moulinette._actionsmap_path = f"{tmp_dir}/moulitest.yml" return moulinette @@ -125,7 +117,7 @@ def moulinette_webapi(moulinette): from moulinette.interfaces.api import Interface as Api - return TestApp(Api(routes={})._app) + return TestApp(Api(routes={}, actionsmap=moulinette._actionsmap_path)._app) @pytest.fixture @@ -142,7 +134,7 @@ def moulinette_cli(moulinette, mocker): mocker.patch("os.isatty", return_value=True) from moulinette.interfaces.cli import Interface as Cli - cli = Cli(top_parser=parser) + cli = Cli(top_parser=parser, actionsmap=moulinette._actionsmap_path) mocker.stopall() return cli diff --git a/test/test_actionsmap.py b/test/test_actionsmap.py index 0abdd7f4..178fc8d2 100644 --- a/test/test_actionsmap.py +++ b/test/test_actionsmap.py @@ -164,7 +164,7 @@ def test_actions_map_unknown_authenticator(monkeypatch, tmp_path): from moulinette.interfaces.api import ActionsMapParser - amap = ActionsMap(ActionsMapParser()) + amap = ActionsMap("test/actionsmap/moulitest.yml", ActionsMapParser()) with pytest.raises(MoulinetteError) as exception: amap.get_authenticator("unknown") @@ -233,9 +233,9 @@ def test_actions_map_api(): from moulinette.interfaces.api import ActionsMapParser parser = ActionsMapParser() - amap = ActionsMap(parser) + amap = ActionsMap("test/actionsmap/moulitest.yml", parser) - assert amap.main_namespace == "moulitest" + assert amap.namespace == "moulitest" assert amap.default_authentication == "dummy" assert ("GET", "/test-auth/default") in amap.parser.routes assert ("POST", "/test-auth/subcat/post") in amap.parser.routes @@ -248,7 +248,7 @@ def test_actions_map_api(): def test_actions_map_import_error(mocker): from moulinette.interfaces.api import ActionsMapParser - amap = ActionsMap(ActionsMapParser()) + amap = ActionsMap("test/actionsmap/moulitest.yml", ActionsMapParser()) from moulinette.core import MoulinetteLock @@ -287,9 +287,9 @@ def test_actions_map_cli(): ) parser = ActionsMapParser(top_parser=top_parser) - amap = ActionsMap(parser) + amap = ActionsMap("test/actionsmap/moulitest.yml", parser) - assert amap.main_namespace == "moulitest" + assert amap.namespace == "moulitest" assert amap.default_authentication == "dummy" assert "testauth" in amap.parser._subparsers.choices assert "none" in amap.parser._subparsers.choices["testauth"]._actions[1].choices