diff options
author | grabner <pjg.github@ubergrabner.net> | 2013-09-19 16:47:52 -0400 |
---|---|---|
committer | grabner <pjg.github@ubergrabner.net> | 2013-09-19 16:47:52 -0400 |
commit | 45e64b5ae7c57ca0da3954923835c15fbbc313f6 (patch) | |
tree | b2a826287e73d2c4798fc9fe69f714218c0ca331 | |
parent | e9087b631f09975f64c12a376f162226ad2a2ef0 (diff) | |
download | iniherit-45e64b5ae7c57ca0da3954923835c15fbbc313f6.tar.gz |
moved to monkey-patching existing ConfigParser (instead of replacing)
-rw-r--r-- | README.rst | 10 | ||||
-rw-r--r-- | iniherit/mixin.py | 45 | ||||
-rw-r--r-- | iniherit/parser.py | 23 | ||||
-rw-r--r-- | iniherit/test.py | 32 |
4 files changed, 84 insertions, 26 deletions
@@ -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$ |