comparison configmix/config.py @ 495:3f0c932588fc

Performance: module-level variable lookup is much faster (similar to local) than class-level (either via CLASS.VARIABLE or self.VARIABLE). See tests/_perf_lookups.py
author Franz Glasner <f.glasner@feldmann-mg.com>
date Fri, 17 Dec 2021 19:34:38 +0100
parents 6a0f761ff35b
children 8e516f17cf95
comparison
equal deleted inserted replaced
494:60683361ebed 495:3f0c932588fc
65 """Get a (possibly substituted) variable and coerce text to a 65 """Get a (possibly substituted) variable and coerce text to a
66 number. 66 number.
67 67
68 """ 68 """
69 s = self.getvarl_s(*path, **kwds) 69 s = self.getvarl_s(*path, **kwds)
70 if isinstance(s, Configuration._TEXTTYPE): 70 if isinstance(s, _TEXTTYPE):
71 return int(s, 0) 71 return int(s, 0)
72 else: 72 else:
73 return s 73 return s
74 74
75 def getfirstintvarl_s(self, *paths, **kwds): 75 def getfirstintvarl_s(self, *paths, **kwds):
76 """Get a (possibly substituted) variable and coerce text to a 76 """Get a (possibly substituted) variable and coerce text to a
77 number. 77 number.
78 78
79 """ 79 """
80 s = self.getfirstvarl_s(*paths, **kwds) 80 s = self.getfirstvarl_s(*paths, **kwds)
81 if isinstance(s, Configuration._TEXTTYPE): 81 if isinstance(s, _TEXTTYPE):
82 return int(s, 0) 82 return int(s, 0)
83 else: 83 else:
84 return s 84 return s
85 85
86 def getintvar_s(self, varname, default=_MARKER): 86 def getintvar_s(self, varname, default=_MARKER):
87 """Get a (possibly substituted) variable and coerce text to a 87 """Get a (possibly substituted) variable and coerce text to a
88 number. 88 number.
89 89
90 """ 90 """
91 s = self.getvar_s(varname, default=default) 91 s = self.getvar_s(varname, default=default)
92 if isinstance(s, Configuration._TEXTTYPE): 92 if isinstance(s, _TEXTTYPE):
93 return int(s, 0) 93 return int(s, 0)
94 else: 94 else:
95 return s 95 return s
96 96
97 def getfirstintvar_s(self, *varnames, **kwds): 97 def getfirstintvar_s(self, *varnames, **kwds):
98 """A variant of :meth:`~.getintvar_s` that returns the first found 98 """A variant of :meth:`~.getintvar_s` that returns the first found
99 variable in the list of given variables in `varnames`. 99 variable in the list of given variables in `varnames`.
100 100
101 """ 101 """
102 s = self.getfirstvar_s(*varnames, **kwds) 102 s = self.getfirstvar_s(*varnames, **kwds)
103 if isinstance(s, Configuration._TEXTTYPE): 103 if isinstance(s, _TEXTTYPE):
104 return int(s, 0) 104 return int(s, 0)
105 else: 105 else:
106 return s 106 return s
107 107
108 def getboolvarl_s(self, *path, **kwds): 108 def getboolvarl_s(self, *path, **kwds):
109 """Get a (possibly substituted) variable and convert text to a 109 """Get a (possibly substituted) variable and convert text to a
110 boolean 110 boolean
111 111
112 """ 112 """
113 s = self.getvarl_s(*path, **kwds) 113 s = self.getvarl_s(*path, **kwds)
114 if isinstance(s, Configuration._TEXTTYPE): 114 if isinstance(s, _TEXTTYPE):
115 sl = s.strip().lower() 115 sl = s.strip().lower()
116 if sl not in self._BOOL_CVT: 116 if sl not in self._BOOL_CVT:
117 raise ValueError("Not a boolean: %r" % (s, )) 117 raise ValueError("Not a boolean: %r" % (s, ))
118 return self._BOOL_CVT[sl] 118 return self._BOOL_CVT[sl]
119 else: 119 else:
123 """Get a (possibly substituted) variable and convert text to a 123 """Get a (possibly substituted) variable and convert text to a
124 boolean 124 boolean
125 125
126 """ 126 """
127 s = self.getfirstvarl_s(*paths, **kwds) 127 s = self.getfirstvarl_s(*paths, **kwds)
128 if isinstance(s, Configuration._TEXTTYPE): 128 if isinstance(s, _TEXTTYPE):
129 sl = s.strip().lower() 129 sl = s.strip().lower()
130 if sl not in self._BOOL_CVT: 130 if sl not in self._BOOL_CVT:
131 raise ValueError("Not a boolean: %r" % (s, )) 131 raise ValueError("Not a boolean: %r" % (s, ))
132 return self._BOOL_CVT[sl] 132 return self._BOOL_CVT[sl]
133 else: 133 else:
137 """Get a (possibly substituted) variable and convert text to a 137 """Get a (possibly substituted) variable and convert text to a
138 boolean 138 boolean
139 139
140 """ 140 """
141 s = self.getvar_s(varname, default=default) 141 s = self.getvar_s(varname, default=default)
142 if isinstance(s, Configuration._TEXTTYPE): 142 if isinstance(s, _TEXTTYPE):
143 sl = s.strip().lower() 143 sl = s.strip().lower()
144 if sl not in self._BOOL_CVT: 144 if sl not in self._BOOL_CVT:
145 raise ValueError("Not a boolean: %r" % (s, )) 145 raise ValueError("Not a boolean: %r" % (s, ))
146 return self._BOOL_CVT[sl] 146 return self._BOOL_CVT[sl]
147 else: 147 else:
151 """A variant of :meth:`~.getboolvar_s` that returns the first found 151 """A variant of :meth:`~.getboolvar_s` that returns the first found
152 variable in the list of given variables in `varnames`. 152 variable in the list of given variables in `varnames`.
153 153
154 """ 154 """
155 s = self.getfirstvar_s(*varnames, **kwds) 155 s = self.getfirstvar_s(*varnames, **kwds)
156 if isinstance(s, Configuration._TEXTTYPE): 156 if isinstance(s, _TEXTTYPE):
157 sl = s.strip().lower() 157 sl = s.strip().lower()
158 if sl not in self._BOOL_CVT: 158 if sl not in self._BOOL_CVT:
159 raise ValueError("Not a boolean: %r" % (s, )) 159 raise ValueError("Not a boolean: %r" % (s, ))
160 return self._BOOL_CVT[sl] 160 return self._BOOL_CVT[sl]
161 else: 161 else:
171 """Get a (possibly substituted) variable and convert text to a 171 """Get a (possibly substituted) variable and convert text to a
172 float 172 float
173 173
174 """ 174 """
175 s = self.getvarl_s(*path, **kwds) 175 s = self.getvarl_s(*path, **kwds)
176 if isinstance(s, Configuration._TEXTTYPE): 176 if isinstance(s, _TEXTTYPE):
177 return float(s) 177 return float(s)
178 else: 178 else:
179 return s 179 return s
180 180
181 def getfirstfloatvarl_s(self, *path, **kwds): 181 def getfirstfloatvarl_s(self, *path, **kwds):
182 """Get a (possibly substituted) variable and convert text to a 182 """Get a (possibly substituted) variable and convert text to a
183 float 183 float
184 184
185 """ 185 """
186 s = self.getfirstvarl_s(*path, **kwds) 186 s = self.getfirstvarl_s(*path, **kwds)
187 if isinstance(s, Configuration._TEXTTYPE): 187 if isinstance(s, _TEXTTYPE):
188 return float(s) 188 return float(s)
189 else: 189 else:
190 return s 190 return s
191 191
192 def getfloatvar_s(self, varname, default=_MARKER): 192 def getfloatvar_s(self, varname, default=_MARKER):
193 """Get a (possibly substituted) variable and convert text to a 193 """Get a (possibly substituted) variable and convert text to a
194 float 194 float
195 195
196 """ 196 """
197 s = self.getvar_s(varname, default) 197 s = self.getvar_s(varname, default)
198 if isinstance(s, Configuration._TEXTTYPE): 198 if isinstance(s, _TEXTTYPE):
199 return float(s) 199 return float(s)
200 else: 200 else:
201 return s 201 return s
202 202
203 def getfirstfloatvar_s(self, varname, default=_MARKER): 203 def getfirstfloatvar_s(self, varname, default=_MARKER):
204 """Get a (possibly substituted) variable and convert text to a 204 """Get a (possibly substituted) variable and convert text to a
205 float 205 float
206 206
207 """ 207 """
208 s = self.getfirstvar_s(varname, default) 208 s = self.getfirstvar_s(varname, default)
209 if isinstance(s, Configuration._TEXTTYPE): 209 if isinstance(s, _TEXTTYPE):
210 return float(s) 210 return float(s)
211 else: 211 else:
212 return s 212 return s
213
214
215 # Speed
216 _EMPTY_STR = u("")
217 _TEXTTYPE = type(_EMPTY_STR)
218 _STARTTOK = u(b"{{")
219 _ENDTOK = u(b"}}")
220 _HIER_SEPARATOR = u(b'.')
221 _NS_SEPARATOR = u(b':')
222 _FILTER_SEPARATOR = u(b'|')
223 _STARTTOK_REF = _STARTTOK + REF_NAMESPACE + _NS_SEPARATOR
224 _ENDTOK_REF = _ENDTOK
225 _DOT = u(b'.')
226 _QUOTE = u(b'%')
227 _QUOTE_x = u(b'x')
228 _QUOTE_u = u(b'u')
229 _QUOTE_U = u(b'U')
230 _COMMENT = u(b'#')
231 _QUOTE_MAP = {
232 0x25: u(b'%x25'), # _QUOTE
233 0x2e: u(b'%x2e'), # _DOT
234 0x3a: u(b'%x3a'), # _NS_SEPARATOR
235 0x23: u(b'%x23'), # _COMMENT / anchor
236 0x7c: u(b'%x7c'), # _FILTER_SEPARATOR
237 0x22: u(b'%x22'),
238 0x27: u(b'%x27'),
239 0x7b: u(b'%x7b'),
240 0x7d: u(b'%x7d'),
241 0x5b: u(b'%x5b'),
242 0x5d: u(b'%x5d'),
243 }
244 _QUOTE_SAFE = u(b'abcdefghijklmnopqrstuvwxyz'
245 b'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
246 b'0123456789'
247 b'-_@!$&/\\()=?*+~;,<>^')
248 """Mostly used configuration key characters that do not need any quoting
249
250 """
213 251
214 252
215 class Configuration(CoercingMethodsMixin, _AttributeDict): 253 class Configuration(CoercingMethodsMixin, _AttributeDict):
216 254
217 """The configuration dictionary with attribute support or 255 """The configuration dictionary with attribute support or
218 variable substitution. 256 variable substitution.
219 257
220 .. note:: When retrieving by attribute names variables will *not* 258 .. note:: When retrieving by attribute names variables will *not*
221 substituted. 259 substituted.
222
223 """
224
225 # Speed
226 _EMPTY_STR = u("")
227 _TEXTTYPE = type(_EMPTY_STR)
228 _STARTTOK = u(b"{{")
229 _ENDTOK = u(b"}}")
230 _HIER_SEPARATOR = u(b'.')
231 _NS_SEPARATOR = u(b':')
232 _FILTER_SEPARATOR = u(b'|')
233 _STARTTOK_REF = _STARTTOK + REF_NAMESPACE + _NS_SEPARATOR
234 _ENDTOK_REF = _ENDTOK
235 _DOT = u(b'.')
236 _QUOTE = u(b'%')
237 _QUOTE_x = u(b'x')
238 _QUOTE_u = u(b'u')
239 _QUOTE_U = u(b'U')
240 _COMMENT = u(b'#')
241 _QUOTE_MAP = {
242 0x25: u(b'%x25'), # _QUOTE
243 0x2e: u(b'%x2e'), # _DOT
244 0x3a: u(b'%x3a'), # _NS_SEPARATOR
245 0x23: u(b'%x23'), # _COMMENT / anchor
246 0x7c: u(b'%x7c'), # _FILTER_SEPARATOR
247 0x22: u(b'%x22'),
248 0x27: u(b'%x27'),
249 0x7b: u(b'%x7b'),
250 0x7d: u(b'%x7d'),
251 0x5b: u(b'%x5b'),
252 0x5d: u(b'%x5d'),
253 }
254 _QUOTE_SAFE = u(b'abcdefghijklmnopqrstuvwxyz'
255 b'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
256 b'0123456789'
257 b'-_@!$&/\\()=?*+~;,<>^')
258 """Mostly used configuration key characters that do not need any quoting
259 260
260 """ 261 """
261 262
262 is_jail = False 263 is_jail = False
263 """Flag to show that this is not a jail for another configuration""" 264 """Flag to show that this is not a jail for another configuration"""
407 varns, varname = self._split_ns(varname) 408 varns, varname = self._split_ns(varname)
408 if not varns: 409 if not varns:
409 if varname: 410 if varname:
410 varnameparts = [ 411 varnameparts = [
411 self.unquote(vp) 412 self.unquote(vp)
412 for vp in varname.split(self._HIER_SEPARATOR) 413 for vp in varname.split(_HIER_SEPARATOR)
413 ] 414 ]
414 else: 415 else:
415 varnameparts = tuple() 416 varnameparts = tuple()
416 else: 417 else:
417 varnameparts = (varname,) 418 varnameparts = (varname,)
551 "none of the given variables found: %r" % (varnames,)) 552 "none of the given variables found: %r" % (varnames,))
552 else: 553 else:
553 return default 554 return default
554 555
555 def _split_ns(self, s): 556 def _split_ns(self, s):
556 ns, sep, rest = s.partition(self._NS_SEPARATOR) 557 ns, sep, rest = s.partition(_NS_SEPARATOR)
557 if sep: 558 if sep:
558 return (self.unquote(ns), rest) 559 return (self.unquote(ns), rest)
559 else: 560 else:
560 return (None, ns) 561 return (None, ns)
561 562
562 def _split_filters(self, s): 563 def _split_filters(self, s):
563 name, sep, filters = s.partition(self._FILTER_SEPARATOR) 564 name, sep, filters = s.partition(_FILTER_SEPARATOR)
564 if sep: 565 if sep:
565 filters = filters.strip() 566 filters = filters.strip()
566 if filters: 567 if filters:
567 return (name.rstrip(), 568 return (name.rstrip(),
568 filters.split(self._FILTER_SEPARATOR)) 569 filters.split(_FILTER_SEPARATOR))
569 else: 570 else:
570 return (name.rstrip(), []) 571 return (name.rstrip(), [])
571 else: 572 else:
572 return (name, []) 573 return (name, [])
573 574
609 unchanged. 610 unchanged.
610 611
611 :raise KeyError: If the reverence cannot found 612 :raise KeyError: If the reverence cannot found
612 613
613 """ 614 """
614 if not isinstance(v, self._TEXTTYPE): 615 if not isinstance(v, _TEXTTYPE):
615 return v 616 return v
616 if v.startswith(self._STARTTOK_REF) and v.endswith(self._ENDTOK_REF): 617 if v.startswith(_STARTTOK_REF) and v.endswith(_ENDTOK_REF):
617 return self.expand_ref_uri( 618 return self.expand_ref_uri(
618 v[len(self._STARTTOK_REF):-len(self._ENDTOK_REF)]) 619 v[len(_STARTTOK_REF):-len(_ENDTOK_REF)])
619 else: 620 else:
620 return v 621 return v
621 622
622 def expand_ref_uri(self, uri, default=_MARKER): 623 def expand_ref_uri(self, uri, default=_MARKER):
623 pu = urlsplit(uri) 624 pu = urlsplit(uri)
624 if pu.scheme or pu.netloc or pu.path or pu.query: 625 if pu.scheme or pu.netloc or pu.path or pu.query:
625 raise ValueError("only fragment-only URIs are supported") 626 raise ValueError("only fragment-only URIs are supported")
626 if not pu.fragment: 627 if not pu.fragment:
627 return self 628 return self
628 if pu.fragment.startswith(self._DOT): 629 if pu.fragment.startswith(_DOT):
629 raise ValueError("relative refs not supported") 630 raise ValueError("relative refs not supported")
630 return self.getvar(pu.fragment, default=default) 631 return self.getvar(pu.fragment, default=default)
631 632
632 def substitute_variables_in_obj(self, obj): 633 def substitute_variables_in_obj(self, obj):
633 """Recursively expand variables in the object tree `obj`.""" 634 """Recursively expand variables in the object tree `obj`."""
634 ty = type(obj) 635 ty = type(obj)
635 if issubclass(ty, self._TEXTTYPE): 636 if issubclass(ty, _TEXTTYPE):
636 # a string - really replace the value 637 # a string - really replace the value
637 return self.expand_variable(obj) 638 return self.expand_variable(obj)
638 elif issubclass(ty, dict): 639 elif issubclass(ty, dict):
639 newdict = ty() 640 newdict = ty()
640 for k in obj: 641 for k in obj:
652 else: 653 else:
653 return obj 654 return obj
654 655
655 def expand_variable(self, s): 656 def expand_variable(self, s):
656 """Expand variables in the single string `s`""" 657 """Expand variables in the single string `s`"""
657 start = s.find(self._STARTTOK, 0) 658 start = s.find(_STARTTOK, 0)
658 if start < 0: 659 if start < 0:
659 return s 660 return s
660 res = [] 661 res = []
661 res_append = res.append 662 res_append = res.append
662 rest = 0 663 rest = 0
663 while start != -1: 664 while start != -1:
664 res_append(s[rest:start]) 665 res_append(s[rest:start])
665 end = s.find(self._ENDTOK, start) 666 end = s.find(_ENDTOK, start)
666 if end < 0: 667 if end < 0:
667 rest = start 668 rest = start
668 break 669 break
669 varname, filters = self._split_filters(s[start+2:end]) 670 varname, filters = self._split_filters(s[start+2:end])
670 try: 671 try:
672 varvalue = self._apply_filters( 673 varvalue = self._apply_filters(
673 filters, self.getvar_s(varname, default=None)) 674 filters, self.getvar_s(varname, default=None))
674 elif EMPTY_FILTER in filters: 675 elif EMPTY_FILTER in filters:
675 varvalue = self._apply_filters( 676 varvalue = self._apply_filters(
676 filters, self.getvar_s(varname, 677 filters, self.getvar_s(varname,
677 default=self._EMPTY_STR)) 678 default=_EMPTY_STR))
678 else: 679 else:
679 varvalue = self._apply_filters( 680 varvalue = self._apply_filters(
680 filters, self.getvar_s(varname)) 681 filters, self.getvar_s(varname))
681 except KeyError: 682 except KeyError:
682 warnings.warn("Cannot expand variable %r in string " 683 warnings.warn("Cannot expand variable %r in string "
695 pass 696 pass
696 else: 697 else:
697 res_append(str_and_u(varvalue)) 698 res_append(str_and_u(varvalue))
698 # don't re-evaluate because `self.getvar_s()` expands already 699 # don't re-evaluate because `self.getvar_s()` expands already
699 rest = end + 2 700 rest = end + 2
700 start = s.find(self._STARTTOK, rest) 701 start = s.find(_STARTTOK, rest)
701 res_append(s[rest:]) 702 res_append(s[rest:])
702 return self._EMPTY_STR.join(res) 703 return _EMPTY_STR.join(res)
703 704
704 def _apply_filters(self, filters, value): 705 def _apply_filters(self, filters, value):
705 for name in filters: 706 for name in filters:
706 try: 707 try:
707 filterfn = lookup_filter(name) 708 filterfn = lookup_filter(name)
726 727
727 See also the :ref:`quoting` section. 728 See also the :ref:`quoting` section.
728 729
729 """ 730 """
730 # Quick check whether all of the chars are in _QUOTE_SAFE 731 # Quick check whether all of the chars are in _QUOTE_SAFE
731 if not s.rstrip(klass._QUOTE_SAFE): 732 if not s.rstrip(_QUOTE_SAFE):
732 return s 733 return s
733 734
734 # Slow path 735 # Slow path
735 re_encode = False 736 re_encode = False
736 if PY2: 737 if PY2:
737 # Use the Unicode translation variant in PY2 738 # Use the Unicode translation variant in PY2
738 if isinstance(s, str): 739 if isinstance(s, str):
739 s = s.decode("latin1") 740 s = s.decode("latin1")
740 re_encode = True 741 re_encode = True
741 s = s.translate(klass._QUOTE_MAP) 742 s = s.translate(_QUOTE_MAP)
742 if re_encode: 743 if re_encode:
743 return s.encode("latin1") 744 return s.encode("latin1")
744 else: 745 else:
745 return s 746 return s
746 747
750 ``%uNNNN`` or ``%UNNNNNNNN``. 751 ``%uNNNN`` or ``%UNNNNNNNN``.
751 752
752 This is the inverse of :meth:`~.quote`. 753 This is the inverse of :meth:`~.quote`.
753 754
754 """ 755 """
755 if klass._QUOTE not in s: 756 if _QUOTE not in s:
756 return s 757 return s
757 parts = s.split(klass._QUOTE) 758 parts = s.split(_QUOTE)
758 res = [parts[0]] 759 res = [parts[0]]
759 res_append = res.append 760 res_append = res.append
760 for p in parts[1:]: 761 for p in parts[1:]:
761 try: 762 try:
762 qc = p[0] 763 qc = p[0]
763 except IndexError: 764 except IndexError:
764 raise ValueError("unknown quote syntax string: {}".format(s)) 765 raise ValueError("unknown quote syntax string: {}".format(s))
765 if qc == klass._QUOTE_x: 766 if qc == _QUOTE_x:
766 if len(p) < 3: 767 if len(p) < 3:
767 raise ValueError("quote syntax: length too small") 768 raise ValueError("quote syntax: length too small")
768 res_append(uchr(int(p[1:3], 16))) 769 res_append(uchr(int(p[1:3], 16)))
769 res_append(p[3:]) 770 res_append(p[3:])
770 elif qc == klass._QUOTE_u: 771 elif qc == _QUOTE_u:
771 if len(p) < 5: 772 if len(p) < 5:
772 raise ValueError("quote syntax: length too small") 773 raise ValueError("quote syntax: length too small")
773 res_append(uchr(int(p[1:5], 16))) 774 res_append(uchr(int(p[1:5], 16)))
774 res_append(p[5:]) 775 res_append(p[5:])
775 elif qc == klass._QUOTE_U: 776 elif qc == _QUOTE_U:
776 if len(p) < 9: 777 if len(p) < 9:
777 raise ValueError("quote syntax: length too small") 778 raise ValueError("quote syntax: length too small")
778 res_append(uchr(int(p[1:9], 16))) 779 res_append(uchr(int(p[1:9], 16)))
779 res_append(p[9:]) 780 res_append(p[9:])
780 else: 781 else:
781 raise ValueError("unknown quote syntax string: {}".format(s)) 782 raise ValueError("unknown quote syntax string: {}".format(s))
782 return klass._EMPTY_STR.join(res) 783 return _EMPTY_STR.join(res)
783 784
784 def jailed(self, rootpath=None, root=None, bind_root=True): 785 def jailed(self, rootpath=None, root=None, bind_root=True):
785 """Return a "jailed" configuration of the current configuration. 786 """Return a "jailed" configuration of the current configuration.
786 787
787 :param rootpath: a sequence of strings that shall emcompass 788 :param rootpath: a sequence of strings that shall emcompass
813 raise ValueError( 814 raise ValueError(
814 "jailed configurations do not support namespaces") 815 "jailed configurations do not support namespaces")
815 if varname: 816 if varname:
816 rootpath = [ 817 rootpath = [
817 self.unquote(p) for p in root.split( 818 self.unquote(p) for p in root.split(
818 self._HIER_SEPARATOR) 819 _HIER_SEPARATOR)
819 ] 820 ]
820 else: 821 else:
821 rootpath = tuple() 822 rootpath = tuple()
822 jc = _JailedConfiguration(*rootpath) 823 jc = _JailedConfiguration(*rootpath)
823 if bind_root: 824 if bind_root:
855 def __init__(self, *path): 856 def __init__(self, *path):
856 super(_JailedConfiguration, self).__init__() 857 super(_JailedConfiguration, self).__init__()
857 self._path = path 858 self._path = path
858 if path: 859 if path:
859 self._pathstr = \ 860 self._pathstr = \
860 Configuration._HIER_SEPARATOR.join( 861 _HIER_SEPARATOR.join(
861 [Configuration.quote(p) for p in path]) \ 862 [Configuration.quote(p) for p in path]) \
862 + Configuration._HIER_SEPARATOR 863 + _HIER_SEPARATOR
863 else: 864 else:
864 self._pathstr = Configuration._EMPTY_STR 865 self._pathstr = _EMPTY_STR
865 866
866 @property 867 @property
867 def base(self): 868 def base(self):
868 """Ask for the base (aka parent) configuration". 869 """Ask for the base (aka parent) configuration".
869 870
1031 raise ValueError( 1032 raise ValueError(
1032 "sub-jails do not support namespaces") 1033 "sub-jails do not support namespaces")
1033 if varname: 1034 if varname:
1034 rootpath = [ 1035 rootpath = [
1035 self._base.unquote(p) for p in varname.split( 1036 self._base.unquote(p) for p in varname.split(
1036 self._base._HIER_SEPARATOR) 1037 _HIER_SEPARATOR)
1037 ] 1038 ]
1038 else: 1039 else:
1039 rootpath = tuple() 1040 rootpath = tuple()
1040 if self._path: 1041 if self._path:
1041 new_rootpath = self._path + tuple(rootpath) 1042 new_rootpath = self._path + tuple(rootpath)