summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgrabner <pjg.github@ubergrabner.net>2013-09-19 16:47:52 -0400
committergrabner <pjg.github@ubergrabner.net>2013-09-19 16:47:52 -0400
commit45e64b5ae7c57ca0da3954923835c15fbbc313f6 (patch)
treeb2a826287e73d2c4798fc9fe69f714218c0ca331
parente9087b631f09975f64c12a376f162226ad2a2ef0 (diff)
downloadiniherit-45e64b5ae7c57ca0da3954923835c15fbbc313f6.tar.gz
moved to monkey-patching existing ConfigParser (instead of replacing)
-rw-r--r--README.rst10
-rw-r--r--iniherit/mixin.py45
-rw-r--r--iniherit/parser.py23
-rw-r--r--iniherit/test.py32
4 files changed, 84 insertions, 26 deletions
diff --git a/README.rst b/README.rst
index d418356..fc52848 100644
--- a/README.rst
+++ b/README.rst
@@ -162,10 +162,10 @@ INI file inheritance with the `iniherit` package:
Gotchas
=======
-* Because of how the INI files are loaded and manipulated, the
- IniheritConfigParser's `write` method is disabled. This is because
- the parser cannot know in which inherited file to save any value
- changes. For writing INI files, you should use other ConfigParser
- subclasses, such as `ConfigParser.RawConfigParser`.
+* After an inherit-enabled INI file is loaded, the ConfigParser no
+ longer has knowledge of where a particular option was loaded from or
+ how it was derived. For this reason, when the `write` method is
+ called, the ConfigParser generates an INI file without inheritance.
+ In other words, it flattens the inheritance tree.
.. _ConfigParser: http://docs.python.org/2/library/configparser.html
diff --git a/iniherit/mixin.py b/iniherit/mixin.py
index 91f587c..c58fa96 100644
--- a/iniherit/mixin.py
+++ b/iniherit/mixin.py
@@ -10,24 +10,45 @@ try:
import ConfigParser as CP
except ImportError:
import configparser as CP
-from .parser import RawConfigParser, ConfigParser, SafeConfigParser
+# TODO: what is the PY3 version of 'new'?...
+import new
+from iniherit.parser import IniheritMixin
+
+attrs = [attr for attr in dir(IniheritMixin) if not attr.startswith('__')]
#------------------------------------------------------------------------------
def install_globally():
'''
- Installs :class:`iniherit.parser.RawConfigParser` as the global
- :class:`ConfigParser.RawConfigParser` (and the standard and safe
- sub-classes). Note that this is what one calls "dangerous". Please
- use with extreme caution.
+ Installs '%inherit'-enabled processing as the global default. Note
+ that this is what one calls "dangerous". Please use with extreme
+ caution.
+ '''
+ if hasattr(CP.RawConfigParser, '_iniherit_installed_'):
+ return
+ setattr(CP.RawConfigParser, '_iniherit_installed_', True)
+ for attr in attrs:
+ if hasattr(CP.RawConfigParser, attr):
+ setattr(CP.RawConfigParser,
+ '_iniherit_' + attr, getattr(CP.RawConfigParser, attr))
+ meth = getattr(IniheritMixin, attr)
+ if callable(meth):
+ meth = new.instancemethod(meth.im_func, None, CP.RawConfigParser)
+ setattr(CP.RawConfigParser, attr, meth)
+
+#------------------------------------------------------------------------------
+def uninstall_globally():
+ '''
+ Reverts the effects of :func:`install_globally`.
'''
- if CP.ConfigParser is ConfigParser:
+ if not hasattr(CP.ConfigParser, '_iniherit_installed_'):
return
- CP._real_RawConfigParser = CP.RawConfigParser
- CP._real_ConfigParser = CP.ConfigParser
- CP._real_SafeConfigParser = CP.SafeConfigParser
- CP.RawConfigParser = RawConfigParser
- CP.ConfigParser = ConfigParser
- CP.SafeConfigParser = SafeConfigParser
+ delattr(CP.RawConfigParser, '_iniherit_installed_')
+ for attr in attrs:
+ if hasattr(CP.RawConfigParser, '_iniherit_' + attr):
+ xattr = getattr(CP.RawConfigParser, '_iniherit_' + attr)
+ setattr(CP.RawConfigParser, attr, xattr)
+ else:
+ delattr(CP.RawConfigParser, attr)
#------------------------------------------------------------------------------
# end of $Id$
diff --git a/iniherit/parser.py b/iniherit/parser.py
index a91181c..7a8a3b8 100644
--- a/iniherit/parser.py
+++ b/iniherit/parser.py
@@ -35,11 +35,18 @@ _real_SafeConfigParser = CP.SafeConfigParser
DEFAULT_INHERITTAG = '%inherit'
#------------------------------------------------------------------------------
+# TODO: this would probably be *much* simpler with meta-classes...
+
+#------------------------------------------------------------------------------
class IniheritMixin(object):
+ IM_INHERITTAG = DEFAULT_INHERITTAG
+ IM_DEFAULTSECT = CP.DEFAULTSECT
+
#----------------------------------------------------------------------------
def __init__(self, *args, **kw):
self.loader = kw.get('loader', None) or Loader()
+ self.inherit = True
self.IM_INHERITTAG = DEFAULT_INHERITTAG
self.IM_DEFAULTSECT = getattr(self, 'default_section', CP.DEFAULTSECT)
@@ -60,16 +67,22 @@ class IniheritMixin(object):
#----------------------------------------------------------------------------
def _load(self, filename, encoding=None):
+ if not getattr(self, 'loader', None):
+ self.loader = Loader()
return self.loader.load(filename, encoding=encoding)
#----------------------------------------------------------------------------
def _read(self, fp, fpname, encoding=None):
- raw = self._readRecursive(fp, fpname, encoding=encoding)
- self._apply(raw, self)
+ if getattr(self, 'inherit', True) or not hasattr(self, '_iniherit__read'):
+ raw = self._readRecursive(fp, fpname, encoding=encoding)
+ self._apply(raw, self)
+ else:
+ self._iniherit__read(fp, fpname)
#----------------------------------------------------------------------------
def _makeParser(self):
ret = _real_RawConfigParser()
+ ret.inherit = False
## TODO: any other configurations that need to be copied into `ret`??...
ret.optionxform = self.optionxform
return ret
@@ -142,12 +155,6 @@ class IniheritMixin(object):
continue
_real_RawConfigParser.set(dst, dstsect, option, value)
- #----------------------------------------------------------------------------
- def write(self, *args, **kw):
- # todo: this limitation *could* be lifted by keeping track of
- # which values are inherited, and not writing those back.
- raise TypeError('IniheritMixin values cannot be written')
-
#------------------------------------------------------------------------------
# todo: i'm a little worried about the diamond inheritance here...
class RawConfigParser(IniheritMixin, _real_RawConfigParser):
diff --git a/iniherit/test.py b/iniherit/test.py
index 33f87e7..e595036 100644
--- a/iniherit/test.py
+++ b/iniherit/test.py
@@ -138,7 +138,37 @@ kw6 = extend-kw6
parser.read('config.ini')
self.assertEqual(parser.items('DEFAULT'), [])
self.assertEqual(parser.get('logger', 'timefmt', raw=True), '%H:%M:%S')
-
+
+ #----------------------------------------------------------------------------
+ def test_install_globally(self):
+ from iniherit.parser import CP
+ from iniherit.mixin import install_globally, uninstall_globally
+
+ files = [
+ ('base.ini', '[DEFAULT]\nkw = base-kw\n'),
+ ('config.ini', '[DEFAULT]\n%inherit = base.ini\n'),
+ ]
+ loader = ByteLoader(dict(files))
+
+ def do_the_test():
+ # first test that inheritance doesn't work
+ parser = CP.ConfigParser()
+ parser.loader = loader
+ parser.readfp(loader.load('config.ini'))
+ with self.assertRaises(CP.NoOptionError):
+ parser.get('DEFAULT', 'kw')
+ # then monkey-patch and test that inheritance does work
+ install_globally()
+ parser = CP.ConfigParser()
+ parser.loader = loader
+ parser.readfp(loader.load('config.ini'))
+ self.assertEqual(parser.get('DEFAULT', 'kw'), 'base-kw')
+ uninstall_globally()
+
+ do_first_test = do_second_test = do_the_test
+ do_first_test()
+ do_second_test()
+
#------------------------------------------------------------------------------
# end of $Id$