From 1431555605d7c857662b7b1a265bfc2c287a8369 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 21 Nov 2023 22:25:45 -0600 Subject: [PATCH] Allow factory override in `make_config()` also add `winsvc` param for `get_config_paths()` to support RattailFileMonitor service on windows --- src/wuttjamaican/conf.py | 89 ++++++++++++++++++++++++++++++++++------ tests/test_conf.py | 12 ++++++ 2 files changed, 89 insertions(+), 12 deletions(-) diff --git a/src/wuttjamaican/conf.py b/src/wuttjamaican/conf.py index d8fcb61..b65ca32 100644 --- a/src/wuttjamaican/conf.py +++ b/src/wuttjamaican/conf.py @@ -620,11 +620,12 @@ def generic_default_files(appname): def get_config_paths( files=None, plus_files=None, + appname='wutta', env_files_name=None, env_plus_files_name=None, env=None, default_files=None, - appname='wutta'): + winsvc=None): """ This function determines which files should ultimately be provided to the config constructor. It is normally called by @@ -696,6 +697,42 @@ def get_config_paths( files = get_config_paths(default_files=mydefaults) + :param winsvc: Optional internal name of the Windows service for + which the config object is being made. + + This is only needed for true Windows services running via + "Python for Windows Extensions" - which probably only includes + the Rattail File Monitor service. + + In this context there is no way to tell the app which config + files to read on startup, so it can only look for "default" + files. But by passing a ``winsvc`` name to this function, it + will first load the default config file, then read a particular + value to determine the "real" config file(s) it should use. + + So for example on Windows you might have a config file at + ``C:\\ProgramData\\rattail\\rattail.conf`` with contents: + + .. code-block:: ini + + [rattail.config] + winsvc.RattailFileMonitor = C:\\ProgramData\\rattail\\filemon.conf + + And then ``C:\\ProgramData\\rattail\\filemon.conf`` would have + the actual config for the filemon service. + + When the service starts it calls:: + + make_config(winsvc='RattailFileMonitor') + + which first reads the ``rattail.conf`` file (since that is the + only sensible default), but then per config it knows to swap + that out for ``filemon.conf`` at startup. This is because it + finds a config value matching the requested service name. The + end result is as if it called this instead:: + + make_config(files=[r'C:\\ProgramData\\rattail\\filemon.conf']) + :returns: List of file paths. """ if env is None: @@ -748,6 +785,22 @@ def get_config_paths( # combine all files files.extend(plus_files) + + # when running as a proper windows service, must first read + # "default" file(s) and then consult config to see which file + # should "really" be used. because there isn't a way to specify + # which config file as part of the actual service definition in + # windows, so the service name is used for magic lookup here. + if winsvc: + config = configparser.SafeConfigParser() + config.read(files) + section = f'{appname}.config' + if config.has_section(section): + option = f'winsvc.{winsvc}' + if config.has_option(section, option): + # replace file paths with whatever config value says + files = parse_list(config.get(section, option)) + return files @@ -759,10 +812,13 @@ def make_config( env_plus_files_name=None, env=None, default_files=None, + winsvc=None, usedb=None, preferdb=None, + factory=None, extend=True, - extension_entry_points=None): + extension_entry_points=None, + **kwargs): """ Make a new config object (presumably for global use), initialized per the given parameters and (usually) further modified by all @@ -771,20 +827,25 @@ def make_config( This function really does 3 things: * determine the set of config files to use - * pass those files to config constructor + * pass those files to config factory * apply extensions to the resulting config object Some params are described in :func:`get_config_paths()` since they are passed as-is to that function for the first step. - :param appname: The "app name" to use as basis for other things - - namely, it affects how config files are located. This name is - also passed to the config constructor at which point it becomes - :attr:`wuttjamaican.conf.WuttaConfig.appname`. + :param appname: The :term:`app name` to use as basis for other + things - namely, it affects how config files are located. This + name is also passed to the config factory at which point it + becomes :attr:`~wuttjamaican.conf.WuttaConfig.appname`. - :param usedb: Passed to the :class:`WuttaConfig` constructor. + :param usedb: Passed to the config factory; becomes + :attr:`~wuttjamaican.conf.WuttaConfig.usedb`. - :param preferdb: Passed to the :class:`WuttaConfig` constructor. + :param preferdb: Passed to the config factory; becomes + :attr:`~wuttjamaican.conf.WuttaConfig.preferdb`. + + :param factory: Optional factory to use when making the object. + Default factory is :class:`WuttaConfig`. :param extend: Whether to "auto-extend" the config with all registered extensions. @@ -813,11 +874,15 @@ def make_config( env_files_name=env_files_name, env_plus_files_name=env_plus_files_name, env=env, - default_files=default_files) + default_files=default_files, + winsvc=winsvc) # make config object - config = WuttaConfig(files, appname=appname, - usedb=usedb, preferdb=preferdb) + if not factory: + factory = WuttaConfig + config = factory(files, appname=appname, + usedb=usedb, preferdb=preferdb, + **kwargs) # maybe extend config object if extend: diff --git a/tests/test_conf.py b/tests/test_conf.py index 789a545..15a5c2d 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -417,6 +417,18 @@ class TestGenericDefaultFiles(TestCase): self.assertEqual(len(files), 0) +class TestGetConfigPaths(FileConfigTestCase): + + def test_winsvc(self): + myconf = self.write_file('my.conf', """ +[wutta.config] +winsvc.RattailFileMonitor = /path/to/other/file +""") + + files = conf.get_config_paths(files=[myconf], winsvc='RattailFileMonitor') + self.assertEqual(files, ['/path/to/other/file']) + + class TestMakeConfig(FileConfigTestCase): # nb. we use appname='wuttatest' in this suite to avoid any