3
0
Fork 0

feat: add WuttaConfigProfile base class

convenience for use with various configurable features/services etc.
This commit is contained in:
Lance Edgar 2025-08-08 22:41:33 -05:00
parent a721e63275
commit ad6a3377ab
2 changed files with 105 additions and 1 deletions

View file

@ -1013,3 +1013,89 @@ def make_config(
extension.startup(config)
return config
class WuttaConfigProfile:
"""
Base class to represent a configured "profile" in the context of
some service etc.
:param config: App :term:`config object`.
:param key: Config key for the profile.
Generally each subclass will represent a certain type of config
profile, and each instance will represent a single profile
(identified by the ``key``).
"""
def __init__(self, config, key):
self.config = config
self.app = self.config.get_app()
self.key = key
self.load()
@property
def section(self):
"""
The primary config section under which profiles may be
defined.
There is no default; each subclass must declare it.
This corresponds to the typical INI file section, for instance
a section of ``wutta.telemetry`` assumes file contents like:
.. code-block:: ini
[wutta.telemetry]
default.submit_url = /nodes/telemetry
special.submit_url = /nodes/telemetry-special
"""
raise NotImplementedError
def load(self):
"""
Read all relevant settings from config, and assign attributes
on the profile instance accordingly.
There is no default logic but subclass will generally override.
While a caller can use :meth:`get_str()` to obtain arbitrary
config values dynamically, it is often useful for the profile
to pre-load some config values. This allows "smarter"
interpretation of config values in some cases, and at least
ensures common/shared logic.
There is no constraint or other guidance in terms of which
profile attributes might be set by this method. Subclass
should document if necessary.
"""
def get_str(self, option, **kwargs):
"""
Get a string value for the profile, from config.
:param option: Name of config option for which to return value.
This just calls :meth:`~WuttaConfig.get()` on the config
object, but for a particular setting name which it composes
dynamically.
Assuming a config file like:
.. code-block:: ini
[wutta.telemetry]
default.submit_url = /nodes/telemetry
Then a ``default`` profile under the ``wutta.telemetry``
section would effectively have a ``submit_url`` option::
class TelemetryProfile(WuttaConfigProfile):
section = "wutta.telemetry"
profile = TelemetryProfile("default")
url = profile.get_str("submit_url")
"""
return self.config.get(f'{self.section}.{self.key}.{option}', **kwargs)

View file

@ -12,7 +12,7 @@ from wuttjamaican import conf as mod
from wuttjamaican import conf
from wuttjamaican.exc import ConfigurationError
from wuttjamaican.app import AppHandler
from wuttjamaican.testing import FileTestCase
from wuttjamaican.testing import FileTestCase, ConfigTestCase
class TestWuttaConfig(FileTestCase):
@ -867,3 +867,21 @@ class TestMakeConfig(FileTestCase):
foo_cls.assert_called_once_with()
foo_obj.configure.assert_called_once_with(testconfig)
foo_obj.startup.assert_called_once_with(testconfig)
class TestWuttaConfigProfile(ConfigTestCase):
def make_profile(self, key):
return mod.WuttaConfigProfile(self.config, key)
def test_section(self):
profile = self.make_profile('default')
self.assertRaises(NotImplementedError, getattr, profile, 'section')
def test_get_str(self):
self.config.setdefault('wutta.telemetry.default.submit_url', '/nodes/telemetry')
with patch.object(mod.WuttaConfigProfile, 'section', new='wutta.telemetry'):
profile = self.make_profile('default')
self.assertEqual(profile.section, 'wutta.telemetry')
url = profile.get_str('submit_url')
self.assertEqual(url, '/nodes/telemetry')