diff options
-rw-r--r-- | Changelog | 16 | ||||
-rw-r--r-- | PKG-INFO | 2 | ||||
-rw-r--r-- | iniparse/__init__.py | 3 | ||||
-rw-r--r-- | iniparse/compat.py | 44 | ||||
-rw-r--r-- | iniparse/config.py | 43 | ||||
-rw-r--r-- | iniparse/ini.py | 49 | ||||
-rw-r--r-- | iniparse/utils.py | 47 | ||||
-rw-r--r-- | python-iniparse.spec | 4 | ||||
-rw-r--r-- | setup.py | 2 | ||||
-rw-r--r-- | tests/__init__.py | 2 | ||||
-rw-r--r-- | tests/test_fuzz.py | 4 | ||||
-rw-r--r-- | tests/test_ini.py | 6 | ||||
-rw-r--r-- | tests/test_misc.py | 16 | ||||
-rw-r--r-- | tests/test_multiprocessing.py | 33 | ||||
-rw-r--r-- | tests/test_tidy.py | 3 |
15 files changed, 175 insertions, 99 deletions
@@ -1,3 +1,19 @@ +2010-06-12 +* released 0.4 + +2010-05-08 +* reorganize code to remove circular imports +* auto-create config settings when they are accessed via square bracket + syntax (for example, cfg[x][y]). Previously this raised KeyError. + +2010-05-07 +* Ensure that __special__ method lookups don't cause config attributes + to be added. + +2010-05-06 +* Fix problems with pickling INIConfig objects. This also fixes + multiprocessing problems. + 2010-04-17 * released 0.3.2 @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: iniparse -Version: 0.3.2 +Version: 0.4 Summary: Accessing and Modifying INI files Home-page: http://code.google.com/p/iniparse/ Author: Paramjit Oberoi diff --git a/iniparse/__init__.py b/iniparse/__init__.py index 1a267e6..8de756f 100644 --- a/iniparse/__init__.py +++ b/iniparse/__init__.py @@ -3,9 +3,10 @@ # Copyright (c) 2007 Tim Lauridsen <tla@rasmil.dk> # All Rights Reserved. See LICENSE-PSF & LICENSE for details. -from ini import INIConfig, tidy, change_comment_syntax +from ini import INIConfig, change_comment_syntax from config import BasicConfig, ConfigNamespace from compat import RawConfigParser, ConfigParser, SafeConfigParser +from utils import tidy from ConfigParser import DuplicateSectionError, \ NoSectionError, NoOptionError, \ diff --git a/iniparse/compat.py b/iniparse/compat.py index 3de6148..db89ed8 100644 --- a/iniparse/compat.py +++ b/iniparse/compat.py @@ -68,17 +68,13 @@ class RawConfigParser(object): The DEFAULT section is not acknowledged. """ - try: - self.data[section] - return True - except KeyError: - return False + return (section in self.data) def options(self, section): """Return a list of option names for the given section name.""" - try: + if section in self.data: return list(self.data[section]) - except KeyError: + else: raise NoSectionError(section) def read(self, filenames): @@ -119,19 +115,20 @@ class RawConfigParser(object): raise NoSectionError(section) if vars is not None and option in vars: value = vars[option] - try: - sec = self.data[section] + + sec = self.data[section] + if option in sec: return sec._compat_get(option) - except KeyError: + else: raise NoOptionError(option, section) def items(self, section): - try: + if section in self.data: ans = [] for opt in self.data[section]: ans.append((opt, self.get(section, opt))) return ans - except KeyError: + else: raise NoSectionError(section) def getint(self, section, option): @@ -151,21 +148,17 @@ class RawConfigParser(object): def has_option(self, section, option): """Check for the existence of a given option in a given section.""" - try: + if section in self.data: sec = self.data[section] - except KeyError: + else: raise NoSectionError(section) - try: - sec[option] - return True - except KeyError: - return False + return (option in sec) def set(self, section, option, value): """Set an option.""" - try: + if section in self.data: self.data[section][option] = value - except KeyError: + else: raise NoSectionError(section) def write(self, fp): @@ -174,15 +167,14 @@ class RawConfigParser(object): def remove_option(self, section, option): """Remove an option.""" - try: + if section in self.data: sec = self.data[section] - except KeyError: + else: raise NoSectionError(section) - try: - sec[option] + if option in sec: del sec[option] return 1 - except KeyError: + else: return 0 def remove_section(self, section): diff --git a/iniparse/config.py b/iniparse/config.py index 508dac2..5cfa2ea 100644 --- a/iniparse/config.py +++ b/iniparse/config.py @@ -17,7 +17,7 @@ class ConfigNamespace(object): # Methods that must be implemented by subclasses - def __getitem__(self, key): + def _getitem(self, key): return NotImplementedError(key) def __setitem__(self, key, value): @@ -32,7 +32,15 @@ class ConfigNamespace(object): def _new_namespace(self, name): raise NotImplementedError(name) - # Machinery for converting dotted access into contained access + def __contains__(self, key): + try: + self._getitem(key) + except KeyError: + return False + return True + + # Machinery for converting dotted access into container access, + # and automatically creating new sections/namespaces. # # To distinguish between accesses of class members and namespace # keys, we first call object.__getattribute__(). If that succeeds, @@ -43,10 +51,18 @@ class ConfigNamespace(object): # not just in the __init__() function. See BasicNamespace for # an example. + def __getitem__(self, key): + try: + return self._getitem(key) + except KeyError: + return Undefined(key, self) + def __getattr__(self, name): try: - return self.__getitem__(name) + return self._getitem(name) except KeyError: + if name.startswith('__') and name.endswith('__'): + raise AttributeError return Undefined(name, self) def __setattr__(self, name, value): @@ -63,9 +79,10 @@ class ConfigNamespace(object): except AttributeError: self.__delitem__(name) - def __getstate__(self): - return self.__dict__ - + # During unpickling, Python checks if the class has a __setstate__ + # method. But, the data dicts have not been initialised yet, which + # leads to _getitem and hence __getattr__ raising an exception. So + # we explicitly impement default __setstate__ behavior. def __setstate__(self, state): self.__dict__.update(state) @@ -85,6 +102,10 @@ class Undefined(object): obj = self.namespace._new_namespace(self.name) obj[name] = value + def __setitem__(self, name, value): + obj = self.namespace._new_namespace(self.name) + obj[name] = value + # ---- Basic implementation of a ConfigNamespace @@ -164,7 +185,7 @@ class BasicConfig(ConfigNamespace): def __init__(self): self._data = {} - def __getitem__(self, key): + def _getitem(self, key): return self._data[key] def __setitem__(self, key, value): @@ -215,11 +236,11 @@ class BasicConfig(ConfigNamespace): name_components = name.split('.') ns = self for n in name_components[:-1]: - try: + if n in ns: ns = ns[n] if not isinstance(ns, ConfigNamespace): raise TypeError('value-namespace conflict', n) - except KeyError: + else: ns = ns._new_namespace(n) ns[name_components[-1]] = value @@ -259,11 +280,11 @@ def update_config(target, source): for name in source: value = source[name] if isinstance(value, ConfigNamespace): - try: + if name in target: myns = target[name] if not isinstance(myns, ConfigNamespace): raise TypeError('value-namespace conflict') - except KeyError: + else: myns = target._new_namespace(name) update_config(myns, value) else: diff --git a/iniparse/ini.py b/iniparse/ini.py index f0e7ec2..408354d 100644 --- a/iniparse/ini.py +++ b/iniparse/ini.py @@ -45,7 +45,6 @@ import re from ConfigParser import DEFAULTSECT, ParsingError, MissingSectionHeaderError import config -import compat class LineType(object): line = None @@ -352,7 +351,7 @@ class INISection(config.ConfigNamespace): value = re.sub('\n+', '\n', value) return value - def __getitem__(self, key): + def _getitem(self, key): if key == '__name__': return self._lines[-1].name if self._optionxform: key = self._optionxform(key) @@ -474,7 +473,7 @@ class INIConfig(config.ConfigNamespace): _optionxform = _make_xform_property('_optionxform', 'optionxform') _sectionxform = _make_xform_property('_sectionxform', 'optionxform') - def __getitem__(self, key): + def _getitem(self, key): if key == DEFAULTSECT: return self._defaults if self._sectionxform: key = self._sectionxform(key) @@ -642,47 +641,3 @@ class INIConfig(config.ConfigNamespace): raise exc -def tidy(cfg): - """Clean up blank lines. - - This functions makes the configuration look clean and - handwritten - consecutive empty lines and empty lines at - the start of the file are removed, and one is guaranteed - to be at the end of the file. - """ - - if isinstance(cfg, compat.RawConfigParser): - cfg = cfg.data - cont = cfg._data.contents - i = 1 - while i < len(cont): - if isinstance(cont[i], LineContainer): - tidy_section(cont[i]) - i += 1 - elif (isinstance(cont[i-1], EmptyLine) and - isinstance(cont[i], EmptyLine)): - del cont[i] - else: - i += 1 - - # Remove empty first line - if cont and isinstance(cont[0], EmptyLine): - del cont[0] - - # Ensure a last line - if cont and not isinstance(cont[-1], EmptyLine): - cont.append(EmptyLine()) - -def tidy_section(lc): - cont = lc.contents - i = 1 - while i < len(cont): - if (isinstance(cont[i-1], EmptyLine) and - isinstance(cont[i], EmptyLine)): - del cont[i] - else: - i += 1 - - # Remove empty first line - if len(cont) > 1 and isinstance(cont[1], EmptyLine): - del cont[1] diff --git a/iniparse/utils.py b/iniparse/utils.py new file mode 100644 index 0000000..829fc28 --- /dev/null +++ b/iniparse/utils.py @@ -0,0 +1,47 @@ +import compat +from ini import LineContainer, EmptyLine + +def tidy(cfg): + """Clean up blank lines. + + This functions makes the configuration look clean and + handwritten - consecutive empty lines and empty lines at + the start of the file are removed, and one is guaranteed + to be at the end of the file. + """ + + if isinstance(cfg, compat.RawConfigParser): + cfg = cfg.data + cont = cfg._data.contents + i = 1 + while i < len(cont): + if isinstance(cont[i], LineContainer): + tidy_section(cont[i]) + i += 1 + elif (isinstance(cont[i-1], EmptyLine) and + isinstance(cont[i], EmptyLine)): + del cont[i] + else: + i += 1 + + # Remove empty first line + if cont and isinstance(cont[0], EmptyLine): + del cont[0] + + # Ensure a last line + if cont and not isinstance(cont[-1], EmptyLine): + cont.append(EmptyLine()) + +def tidy_section(lc): + cont = lc.contents + i = 1 + while i < len(cont): + if (isinstance(cont[i-1], EmptyLine) and + isinstance(cont[i], EmptyLine)): + del cont[i] + else: + i += 1 + + # Remove empty first line + if len(cont) > 1 and isinstance(cont[1], EmptyLine): + del cont[1] diff --git a/python-iniparse.spec b/python-iniparse.spec index b9dda79..4ba6bef 100644 --- a/python-iniparse.spec +++ b/python-iniparse.spec @@ -1,7 +1,7 @@ %{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} Name: python-iniparse -Version: 0.3.2 +Version: 0.4 Release: 1%{?dist} Summary: Python Module for Accessing and Modifying Configuration Data in INI files Group: Development/Libraries @@ -48,6 +48,8 @@ rm -rf $RPM_BUILD_ROOT %changelog +* Sat Jun 12 2010 Paramjit Oberoi <param@cs.wisc.edu> - 0.4-1 +- Release 0.4 * Sat Apr 17 2010 Paramjit Oberoi <param@cs.wisc.edu> - 0.3.2-1 - Release 0.3.2 * Mon Mar 2 2009 Paramjit Oberoi <param@cs.wisc.edu> - 0.3.1-1 @@ -2,7 +2,7 @@ from distutils.core import setup -VERSION = '0.3.2' +VERSION = '0.4' setup(name ='iniparse', version = VERSION, diff --git a/tests/__init__.py b/tests/__init__.py index 1269812..f1fa321 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -6,6 +6,7 @@ import test_fuzz import test_compat import test_unicode import test_tidy +import test_multiprocessing from iniparse import config from iniparse import ini @@ -20,4 +21,5 @@ class suite(unittest.TestSuite): test_compat.suite(), test_unicode.suite(), test_tidy.suite(), + test_multiprocessing.suite(), ]) diff --git a/tests/test_fuzz.py b/tests/test_fuzz.py index f63a718..5420dcc 100644 --- a/tests/test_fuzz.py +++ b/tests/test_fuzz.py @@ -4,7 +4,7 @@ import random import unittest import ConfigParser from StringIO import StringIO -from iniparse import compat, ini +from iniparse import compat, ini, tidy # TODO: # tabs @@ -101,7 +101,7 @@ class test_fuzz(unittest.TestCase): # compare the two configparsers self.assertEqualConfig(cc_py, cc) # check that tidy does not change semantics - ini.tidy(cc) + tidy(cc) cc_tidy = ConfigParser.RawConfigParser() cc_tidy.readfp(StringIO(str(cc.data))) self.assertEqualConfig(cc_py, cc_tidy) diff --git a/tests/test_ini.py b/tests/test_ini.py index bdb9080..6a76edb 100644 --- a/tests/test_ini.py +++ b/tests/test_ini.py @@ -223,8 +223,14 @@ but = also me p = ini.INIConfig(sio) p.new1.created = 1 setattr(getattr(p, 'new2'), 'created', 1) + p.new3['created'] = 1 + p['new4'].created = 1 + p['new5']['created'] = 1 self.assertEqual(p.new1.created, 1) self.assertEqual(p.new2.created, 1) + self.assertEqual(p.new3.created, 1) + self.assertEqual(p.new4.created, 1) + self.assertEqual(p.new5.created, 1) def test_order(self): sio = StringIO(self.s1) diff --git a/tests/test_misc.py b/tests/test_misc.py index 482c5c4..31cf4da 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -385,19 +385,19 @@ class test_pickle(unittest.TestCase): c = cfg_class() c.readfp(StringIO(self.s)) self.do_compat_checks(c) - p = pickle.dumps(c) - c = None - c2 = pickle.loads(p) - self.do_compat_checks(c2) + for i in range(0, pickle.HIGHEST_PROTOCOL+1): + p = pickle.dumps(c, protocol=i) + c2 = pickle.loads(p) + self.do_compat_checks(c2) def test_ini(self): c = ini.INIConfig() c._readfp(StringIO(self.s)) self.do_ini_checks(c) - p = pickle.dumps(c) - c = None - c2 = pickle.loads(p) - self.do_ini_checks(c2) + for i in range(0, pickle.HIGHEST_PROTOCOL+1): + p = pickle.dumps(c, protocol=i) + c2 = pickle.loads(p) + self.do_ini_checks(c2) class test_comment_syntax(unittest.TestCase): """Test changing comment syntax with change_comment_syntax""" diff --git a/tests/test_multiprocessing.py b/tests/test_multiprocessing.py new file mode 100644 index 0000000..f95a569 --- /dev/null +++ b/tests/test_multiprocessing.py @@ -0,0 +1,33 @@ +import unittest +try: + from multiprocessing import Process, Queue, Pipe + disabled = False +except ImportError: + disabled = True + +from iniparse import compat, ini + +class test_ini(unittest.TestCase): + """Test sending INIConfig objects.""" + + def test_queue(self): + def getxy(q, w): + cfg = q.get_nowait() + w.put(cfg.x.y) + cfg = ini.INIConfig() + cfg.x.y = '42' + q = Queue() + w = Queue() + q.put(cfg) + p = Process(target=getxy, args=(q, w)) + p.start() + self.assertEqual(w.get(timeout=1), '42') + +class suite(unittest.TestSuite): + def __init__(self): + if disabled: + unittest.TestSuite.__init__(self, []) + else: + unittest.TestSuite.__init__(self, [ + unittest.makeSuite(test_ini, 'test'), + ]) diff --git a/tests/test_tidy.py b/tests/test_tidy.py index 4990457..7304747 100644 --- a/tests/test_tidy.py +++ b/tests/test_tidy.py @@ -2,7 +2,8 @@ import unittest from textwrap import dedent from StringIO import StringIO -from iniparse.ini import INIConfig, EmptyLine, tidy +from iniparse import tidy,INIConfig +from iniparse.ini import EmptyLine from iniparse.compat import ConfigParser class test_tidy(unittest.TestCase): |