From db80968a4cc644e37ccb4ea1fe39304f1c301faf Mon Sep 17 00:00:00 2001 From: Alex Burke Date: Fri, 25 Jul 2025 15:13:48 +0200 Subject: [PATCH 1/2] Add support for adding request contextual data to configuration objects. This change adds a small namespace to configuraton objects that behaves like a dictonary allowing objects to made available to e.g. request handlers. Such functionality it needed to support templates without restorting to globals within the current system architecture; only congifuration objects are already threaded through most of the places this would need to be available with the correct semantics of an instance being created wherever it is needed. --- mig/shared/configuration.py | 21 +++++++++++++++++++++ tests/test_mig_shared_configuration.py | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/mig/shared/configuration.py b/mig/shared/configuration.py index 91a24549a..36456298d 100644 --- a/mig/shared/configuration.py +++ b/mig/shared/configuration.py @@ -759,6 +759,7 @@ def __init__(self, config_file, verbose=False, skip_log=False, self.default_page = None self.auth_logger_obj = None self.gdp_logger_obj = None + self._context = None configuration_options = copy.deepcopy(_CONFIGURATION_DEFAULTS) @@ -770,6 +771,26 @@ def __init__(self, config_file, verbose=False, skip_log=False, disable_auth_log=disable_auth_log, _config_file=config_file) + def context(self, namespace=None): + """Retrieve the context or a previously registered namespace. + """ + + if self._context is None: + self._context = {} + if namespace is None: + return self._context + # allow the KeyError to escape if the registered namespace is missing + return self._context[namespace] + + def context_set(self, value, namespace=None): + """Attach a value as named namespace within the active congifuration. + """ + assert namespace is not None + + context = self.context() + context[namespace] = value + return value + def reload_config(self, verbose, skip_log=False, disable_auth_log=False, _config_file=None): """Re-read and parse configuration file. Optional skip_log arg diff --git a/tests/test_mig_shared_configuration.py b/tests/test_mig_shared_configuration.py index 7c9aef06e..506537466 100644 --- a/tests/test_mig_shared_configuration.py +++ b/tests/test_mig_shared_configuration.py @@ -42,7 +42,7 @@ def _is_method(value): def _to_dict(obj): return {k: v for k, v in inspect.getmembers(obj) - if not (k.startswith('__') or _is_method(v))} + if not (k.startswith('_') or _is_method(v))} class MigSharedConfiguration(MigTestCase): From b2b89e3c634498aef8996c0326b217d594c21f3d Mon Sep 17 00:00:00 2001 From: Alex Burke Date: Wed, 3 Sep 2025 12:35:04 +0200 Subject: [PATCH 2/2] RuntimeConfiguration --- mig/shared/conf.py | 4 +-- mig/shared/configuration.py | 62 ++++++++++++++++++++++++------------- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/mig/shared/conf.py b/mig/shared/conf.py index 0de37b321..32d49852d 100644 --- a/mig/shared/conf.py +++ b/mig/shared/conf.py @@ -32,6 +32,7 @@ import os import sys +from mig.shared.configuration import RuntimeConfiguration from mig.shared.defaults import MIG_ENV from mig.shared.fileio import unpickle @@ -42,7 +43,6 @@ def get_configuration_object(config_file=None, skip_log=False, and disable_auth_log arguments are passed on to allow skipping the default log initialization and disabling auth log for unit tests. """ - from mig.shared.configuration import Configuration if config_file: _config_file = config_file elif os.environ.get('MIG_CONF', None): @@ -63,7 +63,7 @@ def get_configuration_object(config_file=None, skip_log=False, skip_log = True disable_auth_log = True - configuration = Configuration(_config_file, False, skip_log, + configuration = RuntimeConfiguration(_config_file, False, skip_log, disable_auth_log) return configuration diff --git a/mig/shared/configuration.py b/mig/shared/configuration.py index 36456298d..0e7a48b81 100644 --- a/mig/shared/configuration.py +++ b/mig/shared/configuration.py @@ -759,7 +759,6 @@ def __init__(self, config_file, verbose=False, skip_log=False, self.default_page = None self.auth_logger_obj = None self.gdp_logger_obj = None - self._context = None configuration_options = copy.deepcopy(_CONFIGURATION_DEFAULTS) @@ -771,26 +770,6 @@ def __init__(self, config_file, verbose=False, skip_log=False, disable_auth_log=disable_auth_log, _config_file=config_file) - def context(self, namespace=None): - """Retrieve the context or a previously registered namespace. - """ - - if self._context is None: - self._context = {} - if namespace is None: - return self._context - # allow the KeyError to escape if the registered namespace is missing - return self._context[namespace] - - def context_set(self, value, namespace=None): - """Attach a value as named namespace within the active congifuration. - """ - assert namespace is not None - - context = self.context() - context[namespace] = value - return value - def reload_config(self, verbose, skip_log=False, disable_auth_log=False, _config_file=None): """Re-read and parse configuration file. Optional skip_log arg @@ -2872,6 +2851,47 @@ def parse_peers(self, peerfile): return peers_dict +class RuntimeConfiguration(Configuration): + """A more specific version of the Configuration which additionally supports + the notion of a context. + + Contextual information that is relevant to the duration of a request is + required in certain cases e.g. to support templating. Given Configuration + objects are threaded into and throough almost all the necessary codepaths + to make this information available, they are an attractive place to put + this - but a Configuration is currently loaded from static per-site data. + + Resolv this ambiguity with this subclass - a raw Confioguration will + continute to represent the static data while a specialised but entirely + compatible object is handed to request processing codepaths. + """ + + def __init__(self, config_file, verbose=False, skip_log=False, + disable_auth_log=False): + super().__init__(config_file, verbose, skip_log, disable_auth_log) + self._context = None + + def context(self, namespace=None): + """Retrieve the context or a previously registered namespace. + """ + + if self._context is None: + self._context = {} + if namespace is None: + return self._context + # allow the KeyError to escape if the registered namespace is missing + return self._context[namespace] + + def context_set(self, value, namespace=None): + """Attach a value as named namespace within the active congifuration. + """ + assert namespace is not None + + context = self.context() + context[namespace] = value + return value + + if '__main__' == __name__: conf = Configuration(os.path.expanduser('~/mig/server/MiGserver.conf'), True)