changeset 395:0b3ffc34fa5c

Begin a jailed configuration with access to a sub-tree of the original configuration
author Franz Glasner <f.glasner@feldmann-mg.com>
date Thu, 18 Nov 2021 18:30:59 +0100
parents e05195ee869d
children a50f8fb16b05
files CHANGES.txt configmix/config.py tests/test.py
diffstat 3 files changed, 181 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGES.txt	Wed Nov 10 09:43:15 2021 +0100
+++ b/CHANGES.txt	Thu Nov 18 18:30:59 2021 +0100
@@ -12,6 +12,13 @@
 Pre-1.0 Series
 --------------
 
+not yet (not yet)
+~~~~~~~~~~~~~~~~~
+
+- **[feature]** Jailed Configuration with
+   :py:meth:`~configmix.config.Configuration.jailed`
+
+
 0.16.1 (2021-11-10)
 ~~~~~~~~~~~~~~~~~~~
 
--- a/configmix/config.py	Wed Nov 10 09:43:15 2021 +0100
+++ b/configmix/config.py	Thu Nov 18 18:30:59 2021 +0100
@@ -118,7 +118,7 @@
 
         Quoting of anything in `paths` is *not* needed and wrong.
 
-        """ 
+        """
         default = kwds.pop("default", _MARKER)
         for path in paths:
             if isinstance(path, (list, tuple)):
@@ -648,3 +648,61 @@
             else:
                 raise ValueError("unknown quote syntax string: {}".format(s))
         return ''.join(res)
+
+    def jailed(self, rootpath=None, root=None):
+        """Return a "jailed" configuration.
+
+        """
+        if rootpath is not None and root is not None:
+            raise ValueError("only one of `rootpath' or `root' can be given")
+        if rootpath is None and root is None:
+            raise ValueError("one of `rootpath' or `root' must be given")
+        if root is not None:
+            # convert to path
+            varns, varname = self._split_ns(root)
+            if varns:
+                raise ValueError(
+                    "jailed configurations do not support namespaces")
+            rootpath = [
+                self.unquote(p) for p in root.split(
+                    self._HIER_SEPARATOR)
+            ]
+            
+        return _JailedConfiguration(self, *rootpath)
+
+
+class _JailedConfiguration(object):
+
+    """Chrooted into a subtree of the configuration  -- no namespace support"""
+
+    __slots__ = ("_base", "_path", "_pathstr" )
+
+    def __init__(self, config, *path):
+        super(_JailedConfiguration, self).__init__()
+        self._base = config
+        self._path = path
+        if path:
+            self._pathstr = \
+                Configuration._HIER_SEPARATOR.join(
+                    [Configuration.quote(p) for p in path]) \
+                + Configuration._HIER_SEPARATOR
+        else:
+            self._pathstr = ''
+        #
+        # Early error out if the chroot does not exist but allow
+        # degenerated case if `self._path` is empty.
+        #
+        if self._path:
+            self._base.getvarl(*self._path)
+
+    def getvarl(self, *path, **kwds):
+        return self._base.getvarl(*(self._path + path), **kwds)
+
+    def getvarl_s(self, *path, **kwds):
+        return self._base.getvarl_s(*(self._path + path), **kwds)
+
+    def getvar(self, varname, default=_MARKER):
+        return self._base.getvar(self._pathstr + varname, default=default)
+
+    def getvar_s(self, varname, default=_MARKER):
+        return self._base.getvar_s(self._pathstr + varname, default=default)
--- a/tests/test.py	Wed Nov 10 09:43:15 2021 +0100
+++ b/tests/test.py	Thu Nov 18 18:30:59 2021 +0100
@@ -1157,5 +1157,120 @@
         self.assertEqual("value", v)
 
 
+class T08Jailed(unittest.TestCase):
+
+    def setUp(self):
+        pass
+
+    def tearDown(self):
+        self._reset()
+
+    def _reset(self):
+        configmix.clear_assoc()
+        for pat, fmode in configmix.DEFAULT_ASSOC:
+            configmix.set_assoc(pat, fmode)
+
+    def test_root(self):
+        cfg = configmix.load(os.path.join(TESTDATADIR, "conf10.py"))
+        jcfg = cfg.jailed(root=u"tree1")
+
+        self.assertTrue(jcfg.getvarl(u"tree2", u"key5"))
+        self.assertTrue(jcfg.getvarl_s(u"tree2", u"key5"))
+        self.assertEqual(
+            u"get this as `tree1.tree2.key4'",
+            jcfg.getvarl(u"tree2", u"key4"))
+        self.assertEqual(
+            u"get this as `tree1.tree2.key4'",
+            jcfg.getvarl_s(u"tree2", u"key4"))
+
+        self.assertTrue(jcfg.getvar(u"tree2.key5"))
+        self.assertTrue(jcfg.getvar_s(u"tree2.key5"))
+        self.assertEqual(
+            u"get this as `tree1.tree2.key4'",
+            jcfg.getvar(u"tree2.key4"))
+        self.assertEqual(
+            u"get this as `tree1.tree2.key4'",
+            jcfg.getvar_s(u"tree2.key4"))
+
+    def test_root2(self):
+        cfg = configmix.load(os.path.join(TESTDATADIR, "conf10.py"))
+        jcfg = cfg.jailed(root=u"tree1.tree2")
+
+        self.assertTrue(jcfg.getvarl(u"key5"))
+        self.assertTrue(jcfg.getvarl_s(u"key5"))
+        self.assertEqual(
+            u"get this as `tree1.tree2.key4'",
+            jcfg.getvarl(u"key4"))
+        self.assertEqual(
+            u"get this as `tree1.tree2.key4'",
+            jcfg.getvarl_s(u"key4"))
+
+        self.assertTrue(jcfg.getvar(u"key5"))
+        self.assertTrue(jcfg.getvar_s(u"key5"))
+        self.assertEqual(
+            u"get this as `tree1.tree2.key4'",
+            jcfg.getvar(u"key4"))
+        self.assertEqual(
+            u"get this as `tree1.tree2.key4'",
+            jcfg.getvar_s(u"key4"))
+
+    def test_root_non_existing_raises(self):
+        cfg = configmix.load(os.path.join(TESTDATADIR, "conf10.py"))
+        self.assertRaises(KeyError, cfg.jailed, root=u"tree-non-existing")
+
+    def test_rootpath(self):
+        cfg = configmix.load(os.path.join(TESTDATADIR, "conf10.py"))
+        jcfg = cfg.jailed(rootpath=[u"tree1"])
+
+        self.assertTrue(jcfg.getvarl(u"tree2", u"key5"))
+        self.assertTrue(jcfg.getvarl_s(u"tree2", u"key5"))
+        self.assertEqual(
+            u"get this as `tree1.tree2.key4'",
+            jcfg.getvarl_s(u"tree2", u"key4"))
+
+        self.assertTrue(jcfg.getvar(u"tree2.key5"))
+        self.assertTrue(jcfg.getvar_s(u"tree2.key5"))
+        self.assertEqual(
+            u"get this as `tree1.tree2.key4'",
+            jcfg.getvar_s(u"tree2.key4"))
+
+    def test_rootpath_non_existing_raises(self):
+        cfg = configmix.load(os.path.join(TESTDATADIR, "conf10.py"))
+        self.assertRaises(
+            KeyError, cfg.jailed, rootpath=[u"tree-non-existing"])
+
+    def test_root_empty(self):
+        cfg = configmix.load(os.path.join(TESTDATADIR, "conf10.py"))
+        jcfg = cfg.jailed(rootpath=u"")
+
+        self.assertTrue(jcfg.getvarl(u"tree1", u"tree2", u"key5"))
+        self.assertTrue(jcfg.getvarl_s(u"tree1", u"tree2", u"key5"))
+        self.assertEqual(
+            u"get this as `tree1.tree2.key4'",
+            jcfg.getvarl_s(u"tree1", u"tree2", u"key4"))
+
+        self.assertTrue(jcfg.getvar(u"tree1.tree2.key5"))
+        self.assertTrue(jcfg.getvar_s(u"tree1.tree2.key5"))
+        self.assertEqual(
+            u"get this as `tree1.tree2.key4'",
+            jcfg.getvar_s(u"tree1.tree2.key4"))
+
+    def test_rootpath_empty(self):
+        cfg = configmix.load(os.path.join(TESTDATADIR, "conf10.py"))
+        jcfg = cfg.jailed(rootpath=tuple())
+
+        self.assertTrue(jcfg.getvarl(u"tree1", u"tree2", u"key5"))
+        self.assertTrue(jcfg.getvarl_s(u"tree1", u"tree2", u"key5"))
+        self.assertEqual(
+            u"get this as `tree1.tree2.key4'",
+            jcfg.getvarl_s(u"tree1", u"tree2", u"key4"))
+
+        self.assertTrue(jcfg.getvar(u"tree1.tree2.key5"))
+        self.assertTrue(jcfg.getvar_s(u"tree1.tree2.key5"))
+        self.assertEqual(
+            u"get this as `tree1.tree2.key4'",
+            jcfg.getvar_s(u"tree1.tree2.key4"))
+
+
 if __name__ == "__main__":
     unittest.main()