changeset 539:9546d38cd3f8

Refactor: the parsing of the quoted and dot-separated path string is put into a function that handles also empty inputs properly
author Franz Glasner <fzglas.hg@dom66.de>
date Tue, 28 Dec 2021 17:28:19 +0100
parents e85d1eddf539
children 33856ae1cc0b
files configmix/__init__.py configmix/config.py tests/_perf_config.py
diffstat 3 files changed, 65 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- a/configmix/__init__.py	Tue Dec 28 16:30:48 2021 +0100
+++ b/configmix/__init__.py	Tue Dec 28 17:28:19 2021 +0100
@@ -32,7 +32,7 @@
 import re
 
 from .compat import u2fs
-from .config import Configuration, quote, unquote          # noqa: F401
+from .config import Configuration, quote, unquote, pathstr2path  # noqa: F401
 from . import constants
 
 
--- a/configmix/config.py	Tue Dec 28 16:30:48 2021 +0100
+++ b/configmix/config.py	Tue Dec 28 17:28:19 2021 +0100
@@ -317,6 +317,34 @@
     return _EMPTY_STR.join(res)
 
 
+def pathstr2path(varname):
+    """Parse a dot-separated path string `varname` into a tuple of
+    unquoted path items
+
+    :param str varname: The quoted and dot-separated path string
+    :return: The unquoted and parsed path items
+    :rtype: tuple
+
+    Used e.g. by :meth:`~.Configuration.getvar`,
+    :meth:`~.Configuration.getvar_s` and :meth:`~.Configuration.jailed`.
+
+    The returned value is suitable as input for
+    :meth:`~.Configuration.getvarl`, :meth:`~.Configuration.getvarl_s` and
+    friends.
+
+    An empty `varname` returns an empty tuple.
+
+    """
+    #
+    # Because str.split yields a non-empty list for an empty string handle
+    # the empty string separately.
+    #
+    if varname:
+        return tuple([unquote(p) for p in varname.split(_HIER_SEPARATOR)])
+    else:
+        return tuple()
+
+
 class Configuration(CoercingMethodsMixin, _AttributeDict):
 
     """The configuration dictionary with attribute support or
@@ -497,14 +525,9 @@
         """
         varns, varname = self._split_ns(varname)
         if not varns:
-            if varname:
-                varnameparts = tuple([unquote(vp)
-                                      for vp in varname.split(_HIER_SEPARATOR)])
-            else:
-                varnameparts = tuple()
+            return self.getvarl(*pathstr2path(varname), default=default)
         else:
-            varnameparts = (varname,)
-        return self.getvarl(*varnameparts, namespace=varns, default=default)
+            return self.getvarl(varname, namespace=varns, default=default)
 
     def getkeys(self, varname):
         """Yield all the keys of a variable value.
@@ -614,17 +637,13 @@
 
         """
         varns, varname = self._split_ns(varname)
-        if not varns:
-            if varname:
-                varnameparts = tuple([unquote(vp)
-                                      for vp in varname.split(_HIER_SEPARATOR)])
+        try:
+            if not varns:
+                return self.substitute_variables_in_obj(
+                    self.getvarl(*pathstr2path(varname)))
             else:
-                varnameparts = tuple()
-        else:
-            varnameparts = (varname,)
-        try:
-            obj = self.getvarl(*varnameparts, namespace=varns)
-            return self.substitute_variables_in_obj(obj)
+                return self.substitute_variables_in_obj(
+                    self.getvarl(varname, namespace=varns))
         except KeyError:
             if default is _MARKER:
                 raise
@@ -642,17 +661,19 @@
         """
         varns, varname = self._split_ns(varname)
         if not varns:
-            cacheable = True
-            if varname:
-                varnameparts = tuple([unquote(vp)
-                                      for vp in varname.split(_HIER_SEPARATOR)])
-            else:
-                varnameparts = tuple()
+            # no namespace -> cacheable
+            return (
+                self.substitute_variables_in_obj(
+                    self.getvarl(*pathstr2path(varname))),
+                True
+            )
         else:
-            cacheable = False
-            varnameparts = (varname,)
-        obj = self.getvarl(*varnameparts, namespace=varns)
-        return (self.substitute_variables_in_obj(obj), cacheable)
+            # results from namespaced lookups are currently not cacheable
+            return (
+                self.substitute_variables_in_obj(
+                    self.getvarl(varname, namespace=varns)),
+                False
+            )
 
     def getfirstvar_s(self, *varnames, **kwds):
         """A variant of :meth:`~.getvar_s` that returns the first found
@@ -820,7 +841,7 @@
                 rest = start
                 break
             varname, filters = self._split_filters(
-                s[start+2:end])
+                s[start+2:end])     # noqa: E226
             try:
                 varvalue, cacheable = self._getvar_s_with_cache_info(varname)
             except KeyError:
@@ -905,11 +926,7 @@
             if varns:
                 raise ValueError(
                     "jailed configurations do not support namespaces")
-            if varname:
-                rootpath = tuple([unquote(p)
-                                  for p in root.split(_HIER_SEPARATOR)])
-            else:
-                rootpath = tuple()
+            rootpath = pathstr2path(root)
         jc = _JailedConfiguration(*rootpath)
         if bind_root:
             jc.rebind(self)
@@ -1071,22 +1088,11 @@
         return self._base.getfirstvarl_s(*real_paths, **kwds)
 
     def getvar(self, varname, **kwds):
-        if varname:
-            varnameparts = self._path \
-                + tuple([unquote(vp)
-                         for vp in varname.split(_HIER_SEPARATOR)])
-        else:
-            varnameparts = self._path
-        return self._base.getvarl(*varnameparts, **kwds)
+        return self._base.getvarl(*(self._path + pathstr2path(varname)),
+                                  **kwds)
 
     def getkeys(self, varname):
-        if varname:
-            varnameparts = self._path \
-                + tuple([unquote(vp)
-                         for vp in varname.split(_HIER_SEPARATOR)])
-        else:
-            varnameparts = self._path
-        for k in self._base.getkeysl(*varnameparts):
+        for k in self._base.getkeysl(*(self._path + pathstr2path(varname))):
             yield k
 
     def getfirstvar(self, *varnames, **kwds):
@@ -1094,13 +1100,8 @@
         return self._base.getfirstvar(*real_varnames, **kwds)
 
     def getvar_s(self, varname, **kwds):
-        if varname:
-            varnameparts = self._path \
-                + tuple([unquote(vp)
-                         for vp in varname.split(_HIER_SEPARATOR)])
-        else:
-            varnameparts = self._path
-        return self._base.getvarl_s(*varnameparts, **kwds)
+        return self._base.getvarl_s(*(self._path + pathstr2path(varname)),
+                                    **kwds)
 
     def getfirstvar_s(self, *varnames, **kwds):
         real_varnames = [self._pathstr + vn for vn in varnames]
@@ -1145,11 +1146,7 @@
             if varns:
                 raise ValueError(
                     "sub-jails do not support namespaces")
-            if varname:
-                rootpath = tuple([unquote(p)
-                                  for p in varname.split(_HIER_SEPARATOR)])
-            else:
-                rootpath = tuple()
+            rootpath = pathstr2path(varname)
         if self._path:
             new_rootpath = self._path + tuple(rootpath)
         else:
--- a/tests/_perf_config.py	Tue Dec 28 16:30:48 2021 +0100
+++ b/tests/_perf_config.py	Tue Dec 28 17:28:19 2021 +0100
@@ -29,20 +29,26 @@
 
 unquote = configmix.unquote
 quote = configmix.quote
+pathstr2path = configmix.pathstr2path
 
 cfg = configmix.load(os.path.join(TESTDATADIR, "conf_perf.py"))
 
+se = u""
+s1 = u"abc.def.hij"
+
 """
 
 
 num = 1000000
 num_quote = 1 * num
 
-if all or "quote" in opts or "unquote" in opts:
-    print("unquote/nothing/split: %.4f" % timeit.timeit('a = [unquote(vp) for vp in u"abc.def.hij".split(configmix.config._HIER_SEPARATOR)]', setup=setup, number=num_quote))
+if all or "quote" in opts or "unquote" in opts or "path" in opts:
+    print("unquote/nothing/split: %.4f" % timeit.timeit('a = tuple([unquote(vp) for vp in u"abc.def.hij".split(configmix.config._HIER_SEPARATOR)])', setup=setup, number=num_quote))
     print("unquote/yes/split: %.4f" % timeit.timeit('a = [unquote(vp) for vp in u"ab%x20.def.h%x2ej".split(configmix.config._HIER_SEPARATOR)]', setup=setup, number=num_quote))
     print("unquote/nothing/no-split: %.4f" % timeit.timeit('a = [unquote(vp) for vp in (u"abc," u"def", u"hij")]', setup=setup, number=num_quote))
     print("unquote/yes/no-split: %.4f" % timeit.timeit('a = [unquote(vp) for vp in (u"ab%x20", u"def", u"h%x2ej")]', setup=setup, number=num_quote))
+    print("pathstr2path/non-empty: %.4f" % timeit.timeit('a = pathstr2path(s1)', setup=setup, number=num_quote))
+    print("pathstr2path/empty: %.4f" % timeit.timeit('a = pathstr2path(se)', setup=setup, number=num_quote))    
     print("quote/nothing: %.4f" % timeit.timeit('a = [quote(vp) for vp in (u"abc", u"def", u"hij")]', setup=setup, number=num_quote))
     print("quote/yes: %.4f" % timeit.timeit('a = [quote(vp) for vp in (u"ab:c", u"def", u"h.ij")]', setup=setup, number=num_quote))
     print("="*50)