diff options
-rw-r--r-- | ChangeLog | 7 | ||||
-rw-r--r-- | configuration.py | 118 | ||||
-rw-r--r-- | debian.lenny/python-logilab-common.preinst | 23 | ||||
-rw-r--r-- | deprecation.py | 232 | ||||
-rw-r--r-- | optik_ext.py | 8 | ||||
-rw-r--r-- | test/unittest_configuration.py | 30 | ||||
-rw-r--r-- | test/unittest_deprecation.py | 61 |
7 files changed, 304 insertions, 175 deletions
@@ -2,6 +2,10 @@ ChangeLog for logilab.common ============================ -- + * configuration: rename option_name method into option_attrname (#140667) + + * deprecation: new DeprecationManager class (closes #108205) + * modutils: * fix typo causing name error in python3 / bad message in python2 @@ -16,6 +20,7 @@ ChangeLog for logilab.common * graph: use codecs.open avoid crash when writing utf-8 data under python3 (#155138) + 2013-04-16 -- 0.59.1 * graph: added pruning of the recursive search tree for detecting cycles in graphs (closes #2469) @@ -33,8 +38,6 @@ ChangeLog for logilab.common * fix umessages test w/ python 3 and LC_ALL=C (closes #119967, report and patch by Ian Delaney) - - 2013-01-21 -- 0.59.0 * registry: diff --git a/configuration.py b/configuration.py index 993f759..fa93a05 100644 --- a/configuration.py +++ b/configuration.py @@ -114,11 +114,11 @@ from ConfigParser import ConfigParser, NoOptionError, NoSectionError, \ from warnings import warn from logilab.common.compat import callable, raw_input, str_encode as _encode - +from logilab.common.deprecation import deprecated from logilab.common.textutils import normalize_text, unquote -from logilab.common import optik_ext as optparse +from logilab.common import optik_ext -OptionError = optparse.OptionError +OptionError = optik_ext.OptionError REQUIRED = [] @@ -136,63 +136,66 @@ def _get_encoding(encoding, stream): # validation functions ######################################################## +# validators will return the validated value or raise optparse.OptionValueError +# XXX add to documentation + def choice_validator(optdict, name, value): """validate and return a converted value for option of type 'choice' """ if not value in optdict['choices']: msg = "option %s: invalid value: %r, should be in %s" - raise optparse.OptionValueError(msg % (name, value, optdict['choices'])) + raise optik_ext.OptionValueError(msg % (name, value, optdict['choices'])) return value def multiple_choice_validator(optdict, name, value): """validate and return a converted value for option of type 'choice' """ choices = optdict['choices'] - values = optparse.check_csv(None, name, value) + values = optik_ext.check_csv(None, name, value) for value in values: if not value in choices: msg = "option %s: invalid value: %r, should be in %s" - raise optparse.OptionValueError(msg % (name, value, choices)) + raise optik_ext.OptionValueError(msg % (name, value, choices)) return values def csv_validator(optdict, name, value): """validate and return a converted value for option of type 'csv' """ - return optparse.check_csv(None, name, value) + return optik_ext.check_csv(None, name, value) def yn_validator(optdict, name, value): """validate and return a converted value for option of type 'yn' """ - return optparse.check_yn(None, name, value) + return optik_ext.check_yn(None, name, value) def named_validator(optdict, name, value): """validate and return a converted value for option of type 'named' """ - return optparse.check_named(None, name, value) + return optik_ext.check_named(None, name, value) def file_validator(optdict, name, value): """validate and return a filepath for option of type 'file'""" - return optparse.check_file(None, name, value) + return optik_ext.check_file(None, name, value) def color_validator(optdict, name, value): """validate and return a valid color for option of type 'color'""" - return optparse.check_color(None, name, value) + return optik_ext.check_color(None, name, value) def password_validator(optdict, name, value): """validate and return a string for option of type 'password'""" - return optparse.check_password(None, name, value) + return optik_ext.check_password(None, name, value) def date_validator(optdict, name, value): """validate and return a mx DateTime object for option of type 'date'""" - return optparse.check_date(None, name, value) + return optik_ext.check_date(None, name, value) def time_validator(optdict, name, value): """validate and return a time object for option of type 'time'""" - return optparse.check_time(None, name, value) + return optik_ext.check_time(None, name, value) def bytes_validator(optdict, name, value): """validate and return an integer for option of type 'bytes'""" - return optparse.check_bytes(None, name, value) + return optik_ext.check_bytes(None, name, value) VALIDATORS = {'string': unquote, @@ -222,14 +225,18 @@ def _call_validator(opttype, optdict, option, value): except TypeError: try: return VALIDATORS[opttype](value) - except optparse.OptionValueError: + except optik_ext.OptionValueError: raise except: - raise optparse.OptionValueError('%s value (%r) should be of type %s' % + raise optik_ext.OptionValueError('%s value (%r) should be of type %s' % (option, value, opttype)) # user input functions ######################################################## +# user input functions will ask the user for input on stdin then validate +# the result and return the validated value or raise optparse.OptionValueError +# XXX add to documentation + def input_password(optdict, question='password:'): from getpass import getpass while True: @@ -251,7 +258,7 @@ def _make_input_function(opttype): return None try: return _call_validator(opttype, optdict, None, value) - except optparse.OptionValueError, ex: + except optik_ext.OptionValueError, ex: msg = str(ex).split(':', 1)[-1].strip() print 'bad value: %s' % msg return input_validator @@ -264,6 +271,8 @@ INPUT_FUNCTIONS = { for opttype in VALIDATORS.keys(): INPUT_FUNCTIONS.setdefault(opttype, _make_input_function(opttype)) +# utility functions ############################################################ + def expand_default(self, option): """monkey patch OptionParser.expand_default since we have a particular way to handle defaults to avoid overriding values in the configuration @@ -278,15 +287,15 @@ def expand_default(self, option): value = None else: optdict = provider.get_option_def(optname) - optname = provider.option_name(optname, optdict) + optname = provider.option_attrname(optname, optdict) value = getattr(provider.config, optname, optdict) value = format_option_value(optdict, value) - if value is optparse.NO_DEFAULT or not value: + if value is optik_ext.NO_DEFAULT or not value: value = self.NO_DEFAULT_VALUE return option.help.replace(self.default_tag, str(value)) -def convert(value, optdict, name=''): +def _validate(value, optdict, name=''): """return a validated value for an option according to its type optional argument name is only used for error message formatting @@ -297,6 +306,9 @@ def convert(value, optdict, name=''): # FIXME return value return _call_validator(_type, optdict, name, value) +convert = deprecated('[0.60] convert() was renamed _validate()')(_validate) + +# format and output functions ################################################## def comment(string): """return string as a comment""" @@ -401,6 +413,7 @@ def rest_format_section(stream, section, options, encoding=None, doc=None): print >> stream, '' print >> stream, ' Default: ``%s``' % value.replace("`` ", "```` ``") +# Options Manager ############################################################## class OptionsManagerMixIn(object): """MixIn to handle a configuration from both a configuration file and @@ -425,7 +438,7 @@ class OptionsManagerMixIn(object): # configuration file parser self.cfgfile_parser = ConfigParser() # command line parser - self.cmdline_parser = optparse.OptionParser(usage=usage, version=version) + self.cmdline_parser = optik_ext.OptionParser(usage=usage, version=version) self.cmdline_parser.options_manager = self self._optik_option_attrs = set(self.cmdline_parser.option_class.ATTRS) @@ -461,7 +474,7 @@ class OptionsManagerMixIn(object): if group_name in self._mygroups: group = self._mygroups[group_name] else: - group = optparse.OptionGroup(self.cmdline_parser, + group = optik_ext.OptionGroup(self.cmdline_parser, title=group_name.capitalize()) self.cmdline_parser.add_option_group(group) group.level = provider.level @@ -497,9 +510,9 @@ class OptionsManagerMixIn(object): # default is handled here and *must not* be given to optik if you # want the whole machinery to work if 'default' in optdict: - if (optparse.OPTPARSE_FORMAT_DEFAULT and 'help' in optdict and - optdict.get('default') is not None and - not optdict['action'] in ('store_true', 'store_false')): + if ('help' in optdict + and optdict.get('default') is not None + and not optdict['action'] in ('store_true', 'store_false')): optdict['help'] += ' [current: %default]' del optdict['default'] args = ['--' + str(opt)] @@ -566,7 +579,7 @@ class OptionsManagerMixIn(object): """ self._monkeypatch_expand_default() try: - optparse.generate_manpage(self.cmdline_parser, pkginfo, + optik_ext.generate_manpage(self.cmdline_parser, pkginfo, section, stream=stream or sys.stdout, level=self._maxlevel) finally: @@ -686,7 +699,7 @@ class OptionsManagerMixIn(object): def add_help_section(self, title, description, level=0): """add a dummy option section for help purpose """ - group = optparse.OptionGroup(self.cmdline_parser, + group = optik_ext.OptionGroup(self.cmdline_parser, title=title.capitalize(), description=description) group.level = level @@ -694,18 +707,18 @@ class OptionsManagerMixIn(object): self.cmdline_parser.add_option_group(group) def _monkeypatch_expand_default(self): - # monkey patch optparse to deal with our default values + # monkey patch optik_ext to deal with our default values try: - self.__expand_default_backup = optparse.HelpFormatter.expand_default - optparse.HelpFormatter.expand_default = expand_default + self.__expand_default_backup = optik_ext.HelpFormatter.expand_default + optik_ext.HelpFormatter.expand_default = expand_default except AttributeError: # python < 2.4: nothing to be done pass def _unmonkeypatch_expand_default(self): # remove monkey patch - if hasattr(optparse.HelpFormatter, 'expand_default'): - # unpatch optparse to avoid side effects - optparse.HelpFormatter.expand_default = self.__expand_default_backup + if hasattr(optik_ext.HelpFormatter, 'expand_default'): + # unpatch optik_ext to avoid side effects + optik_ext.HelpFormatter.expand_default = self.__expand_default_backup def help(self, level=0): """return the usage string for available options """ @@ -734,6 +747,7 @@ class Method(object): assert self._inst, 'unbound method' return getattr(self._inst, self.method)(*args, **kwargs) +# Options Provider ############################################################# class OptionsProviderMixIn(object): """Mixin to provide options to an OptionsManager""" @@ -745,7 +759,7 @@ class OptionsProviderMixIn(object): level = 0 def __init__(self): - self.config = optparse.Values() + self.config = optik_ext.Values() for option in self.options: try: option, optdict = option @@ -777,41 +791,41 @@ class OptionsProviderMixIn(object): default = default() return default - def option_name(self, opt, optdict=None): + def option_attrname(self, opt, optdict=None): """get the config attribute corresponding to opt """ if optdict is None: optdict = self.get_option_def(opt) return optdict.get('dest', opt.replace('-', '_')) + option_name = deprecated('[0.60] OptionsProviderMixIn.option_name() was renamed to option_attrname()')(option_attrname) def option_value(self, opt): """get the current value for the given option""" - return getattr(self.config, self.option_name(opt), None) + return getattr(self.config, self.option_attrname(opt), None) def set_option(self, opt, value, action=None, optdict=None): """method called to set an option (registered in the options list) """ - # print "************ setting option", opt," to value", value if optdict is None: optdict = self.get_option_def(opt) if value is not None: - value = convert(value, optdict, opt) + value = _validate(value, optdict, opt) if action is None: action = optdict.get('action', 'store') if optdict.get('type') == 'named': # XXX need specific handling - optname = self.option_name(opt, optdict) + optname = self.option_attrname(opt, optdict) currentvalue = getattr(self.config, optname, None) if currentvalue: currentvalue.update(value) value = currentvalue if action == 'store': - setattr(self.config, self.option_name(opt, optdict), value) + setattr(self.config, self.option_attrname(opt, optdict), value) elif action in ('store_true', 'count'): - setattr(self.config, self.option_name(opt, optdict), 0) + setattr(self.config, self.option_attrname(opt, optdict), 0) elif action == 'store_false': - setattr(self.config, self.option_name(opt, optdict), 1) + setattr(self.config, self.option_attrname(opt, optdict), 1) elif action == 'append': - opt = self.option_name(opt, optdict) + opt = self.option_attrname(opt, optdict) _list = getattr(self.config, opt, None) if _list is None: if isinstance(value, (list, tuple)): @@ -893,6 +907,7 @@ class OptionsProviderMixIn(object): for optname, optdict in options: yield (optname, optdict, self.option_value(optname)) +# configuration ################################################################ class ConfigurationMixIn(OptionsManagerMixIn, OptionsProviderMixIn): """basic mixin for simple configurations which don't need the @@ -913,7 +928,7 @@ class ConfigurationMixIn(OptionsManagerMixIn, OptionsProviderMixIn): continue if not gdef in self.option_groups: self.option_groups.append(gdef) - self.register_options_provider(self, own_group=0) + self.register_options_provider(self, own_group=False) def register_options(self, options): """add some options to the configuration""" @@ -932,8 +947,8 @@ class ConfigurationMixIn(OptionsManagerMixIn, OptionsProviderMixIn): def __getitem__(self, key): try: - return getattr(self.config, self.option_name(key)) - except (optparse.OptionValueError, AttributeError): + return getattr(self.config, self.option_attrname(key)) + except (optik_ext.OptionValueError, AttributeError): raise KeyError(key) def __setitem__(self, key, value): @@ -941,7 +956,7 @@ class ConfigurationMixIn(OptionsManagerMixIn, OptionsProviderMixIn): def get(self, key, default=None): try: - return getattr(self.config, self.option_name(key)) + return getattr(self.config, self.option_attrname(key)) except (OptionError, AttributeError): return default @@ -977,20 +992,21 @@ class OptionsManager2ConfigurationAdapter(object): def __getitem__(self, key): provider = self.config._all_options[key] try: - return getattr(provider.config, provider.option_name(key)) + return getattr(provider.config, provider.option_attrname(key)) except AttributeError: raise KeyError(key) def __setitem__(self, key, value): - self.config.global_set_option(self.config.option_name(key), value) + self.config.global_set_option(self.config.option_attrname(key), value) def get(self, key, default=None): provider = self.config._all_options[key] try: - return getattr(provider.config, provider.option_name(key)) + return getattr(provider.config, provider.option_attrname(key)) except AttributeError: return default +# other functions ############################################################## def read_old_config(newconfig, changes, configfile): """initialize newconfig from a deprecated configuration file diff --git a/debian.lenny/python-logilab-common.preinst b/debian.lenny/python-logilab-common.preinst deleted file mode 100644 index 9c71641..0000000 --- a/debian.lenny/python-logilab-common.preinst +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh -e - -case "$1" in - install) - ;; - upgrade) - pycentral pkgremove python-logilab-common 2>/dev/null || true - rm -vrf /usr/lib/$(pyversions -d)/site-packages/logilab/common - if [[ $(find /usr/lib/$(pyversions -d)/site-packages/logilab/ -maxdepth 1 -type d | wc -l) = '1' ]]; then - rm -vrf /usr/lib/$(pyversions -d)/site-packages/logilab/ - fi - ;; - abort-upgrade) - ;; - *) - echo "preinst called with unknown argument '$1'" >&2 - exit 1 - ;; -esac - -#DEBHELPER# - -exit 0 diff --git a/deprecation.py b/deprecation.py index 5e2f813..c5685ec 100644 --- a/deprecation.py +++ b/deprecation.py @@ -22,93 +22,7 @@ __docformat__ = "restructuredtext en" import sys from warnings import warn -class class_deprecated(type): - """metaclass to print a warning on instantiation of a deprecated class""" - - def __call__(cls, *args, **kwargs): - msg = getattr(cls, "__deprecation_warning__", - "%(cls)s is deprecated") % {'cls': cls.__name__} - warn(msg, DeprecationWarning, stacklevel=2) - return type.__call__(cls, *args, **kwargs) - - -def class_renamed(old_name, new_class, message=None): - """automatically creates a class which fires a DeprecationWarning - when instantiated. - - >>> Set = class_renamed('Set', set, 'Set is now replaced by set') - >>> s = Set() - sample.py:57: DeprecationWarning: Set is now replaced by set - s = Set() - >>> - """ - clsdict = {} - if message is None: - message = '%s is deprecated, use %s' % (old_name, new_class.__name__) - clsdict['__deprecation_warning__'] = message - try: - # new-style class - return class_deprecated(old_name, (new_class,), clsdict) - except (NameError, TypeError): - # old-style class - class DeprecatedClass(new_class): - """FIXME: There might be a better way to handle old/new-style class - """ - def __init__(self, *args, **kwargs): - warn(message, DeprecationWarning, stacklevel=2) - new_class.__init__(self, *args, **kwargs) - return DeprecatedClass - - -def class_moved(new_class, old_name=None, message=None): - """nice wrapper around class_renamed when a class has been moved into - another module - """ - if old_name is None: - old_name = new_class.__name__ - if message is None: - message = 'class %s is now available as %s.%s' % ( - old_name, new_class.__module__, new_class.__name__) - return class_renamed(old_name, new_class, message) - -def deprecated(reason=None, stacklevel=2, name=None, doc=None): - """Decorator that raises a DeprecationWarning to print a message - when the decorated function is called. - """ - def deprecated_decorator(func): - message = reason or 'The function "%s" is deprecated' - if '%s' in message: - message = message % func.func_name - def wrapped(*args, **kwargs): - warn(message, DeprecationWarning, stacklevel=stacklevel) - return func(*args, **kwargs) - try: - wrapped.__name__ = name or func.__name__ - except TypeError: # readonly attribute in 2.3 - pass - wrapped.__doc__ = doc or func.__doc__ - return wrapped - return deprecated_decorator - -def moved(modpath, objname): - """use to tell that a callable has been moved to a new module. - - It returns a callable wrapper, so that when its called a warning is printed - telling where the object can be found, import is done (and not before) and - the actual object is called. - - NOTE: the usage is somewhat limited on classes since it will fail if the - wrapper is use in a class ancestors list, use the `class_moved` function - instead (which has no lazy import feature though). - """ - def callnew(*args, **kwargs): - from logilab.common.modutils import load_module_from_name - message = "object %s has been moved to module %s" % (objname, modpath) - warn(message, DeprecationWarning, stacklevel=2) - m = load_module_from_name(modpath) - return getattr(m, objname)(*args, **kwargs) - return callnew - +from logilab.common.changelog import Version class DeprecationWrapper(object): @@ -128,3 +42,147 @@ class DeprecationWrapper(object): else: warn(self._msg, DeprecationWarning, stacklevel=2) setattr(self._proxied, attr, value) + + +class DeprecationManager(object): + """Manage the deprecation message handling. Messages are dropped for + versions more recent than the 'compatible' version. Example:: + + deprecator = deprecation.DeprecationManager("module_name") + deprecator.compatibility('1.3') + + deprecator.warn('1.2', "message.") + + @deprecator.deprecated('1.2', 'Message') + def any_func(): + pass + + class AnyClass(object): + __metaclass__ = deprecator.class_deprecated('1.2') + """ + def __init__(self, module_name=None): + """ + """ + self.module_name = module_name + self.compatible_version = None + + def compatibility(self, compatible_version): + """Set the compatible version. + """ + self.compatible_version = Version(compatible_version) + + def deprecated(self, version=None, reason=None, stacklevel=2, name=None, doc=None): + """Display a deprecation message only if the version is older than the + compatible version. + """ + def decorator(func): + message = reason or 'The function "%s" is deprecated' + if '%s' in message: + message %= func.func_name + def wrapped(*args, **kwargs): + self.warn(version, message, stacklevel) + return func(*args, **kwargs) + return wrapped + return decorator + + def class_deprecated(self, version=None): + class metaclass(type): + """metaclass to print a warning on instantiation of a deprecated class""" + + def __call__(cls, *args, **kwargs): + msg = getattr(cls, "__deprecation_warning__", + "%(cls)s is deprecated") % {'cls': cls.__name__} + self.warn(version, msg) + return type.__call__(cls, *args, **kwargs) + return metaclass + + def moved(self, version, modpath, objname): + """use to tell that a callable has been moved to a new module. + + It returns a callable wrapper, so that when its called a warning is printed + telling where the object can be found, import is done (and not before) and + the actual object is called. + + NOTE: the usage is somewhat limited on classes since it will fail if the + wrapper is use in a class ancestors list, use the `class_moved` function + instead (which has no lazy import feature though). + """ + def callnew(*args, **kwargs): + from logilab.common.modutils import load_module_from_name + message = "object %s has been moved to module %s" % (objname, modpath) + self.warn(version, message) + m = load_module_from_name(modpath) + return getattr(m, objname)(*args, **kwargs) + return callnew + + def class_renamed(self, version, old_name, new_class, message=None): + clsdict = {} + if message is None: + message = '%s is deprecated, use %s' % (old_name, new_class.__name__) + clsdict['__deprecation_warning__'] = message + try: + # new-style class + return self.class_deprecated(version)(old_name, (new_class,), clsdict) + except (NameError, TypeError): + # old-style class + class DeprecatedClass(new_class): + """FIXME: There might be a better way to handle old/new-style class + """ + def __init__(self, *args, **kwargs): + self.warn(version, message) + new_class.__init__(self, *args, **kwargs) + return DeprecatedClass + + def class_moved(self, version, new_class, old_name=None, message=None): + """nice wrapper around class_renamed when a class has been moved into + another module + """ + if old_name is None: + old_name = new_class.__name__ + if message is None: + message = 'class %s is now available as %s.%s' % ( + old_name, new_class.__module__, new_class.__name__) + return self.class_renamed(version, old_name, new_class, message) + + def warn(self, version=None, reason="", stacklevel=2): + """Display a deprecation message only if the version is older than the + compatible version. + """ + if (self.compatible_version is None + or version is None + or Version(version) < self.compatible_version): + if self.module_name and version: + reason = '[%s %s] %s' % (self.module_name, version, reason) + elif self.module_name: + reason = '[%s] %s' % (self.module_name, reason) + elif version: + reason = '[%s] %s' % (version, reason) + warn(reason, DeprecationWarning, stacklevel=stacklevel) + +_defaultdeprecator = DeprecationManager() + +def deprecated(reason=None, stacklevel=2, name=None, doc=None): + return _defaultdeprecator.deprecated(None, reason, stacklevel, name, doc) + +class_deprecated = _defaultdeprecator.class_deprecated() + +def moved(modpath, objname): + return _defaultdeprecator.moved(None, modpath, objname) +moved.__doc__ = _defaultdeprecator.moved.__doc__ + +def class_renamed(old_name, new_class, message=None): + """automatically creates a class which fires a DeprecationWarning + when instantiated. + + >>> Set = class_renamed('Set', set, 'Set is now replaced by set') + >>> s = Set() + sample.py:57: DeprecationWarning: Set is now replaced by set + s = Set() + >>> + """ + return _defaultdeprecator.class_renamed(None, old_name, new_class, message) + +def class_moved(new_class, old_name=None, message=None): + return _defaultdeprecator.class_moved(None, new_class, old_name, message) +class_moved.__doc__ = _defaultdeprecator.class_moved.__doc__ + diff --git a/optik_ext.py b/optik_ext.py index 39bbe18..49d685b 100644 --- a/optik_ext.py +++ b/optik_ext.py @@ -65,9 +65,6 @@ try: except ImportError: HAS_MX_DATETIME = False - -OPTPARSE_FORMAT_DEFAULT = sys.version_info >= (2, 4) - from logilab.common.textutils import splitstrip def check_regexp(option, opt, value): @@ -227,10 +224,7 @@ class Option(BaseOption): def process(self, opt, value, values, parser): # First, convert the value(s) to the right type. Howl if any # value(s) are bogus. - try: - value = self.convert_value(opt, value) - except AttributeError: # py < 2.4 - value = self.check_value(opt, value) + value = self.convert_value(opt, value) if self.type == 'named': existant = getattr(values, self.dest) if existant: diff --git a/test/unittest_configuration.py b/test/unittest_configuration.py index 6798935..c95f8a1 100644 --- a/test/unittest_configuration.py +++ b/test/unittest_configuration.py @@ -25,11 +25,12 @@ from sys import version_info from logilab.common.testlib import TestCase, unittest_main from logilab.common.optik_ext import OptionValueError from logilab.common.configuration import Configuration, \ - OptionsManagerMixIn, OptionsProviderMixIn, Method, read_old_config + OptionsManagerMixIn, OptionsProviderMixIn, Method, read_old_config, \ + merge_options DATA = join(dirname(abspath(__file__)), 'data') -options = [('dothis', {'type':'yn', 'action': 'store', 'default': True, 'metavar': '<y or n>'}), +OPTIONS = [('dothis', {'type':'yn', 'action': 'store', 'default': True, 'metavar': '<y or n>'}), ('value', {'type': 'string', 'metavar': '<string>', 'short': 'v'}), ('multiple', {'type': 'csv', 'default': ('yop', 'yep'), 'metavar': '<comma separated values>', @@ -56,7 +57,7 @@ class MyConfiguration(Configuration): class ConfigurationTC(TestCase): def setUp(self): - self.cfg = MyConfiguration(name='test', options=options, usage='Just do it ! (tm)') + self.cfg = MyConfiguration(name='test', options=OPTIONS, usage='Just do it ! (tm)') def test_default(self): cfg = self.cfg @@ -201,14 +202,14 @@ named=key:val diffgroup=pouet""") - def test_loopback(self): + def test_roundtrip(self): cfg = self.cfg f = tempfile.mktemp() stream = open(f, 'w') try: cfg.generate_config(stream) stream.close() - new_cfg = MyConfiguration(name='testloop', options=options) + new_cfg = MyConfiguration(name='testloop', options=OPTIONS) new_cfg.load_file_configuration(f) self.assertEqual(cfg['dothis'], new_cfg['dothis']) self.assertEqual(cfg['multiple'], new_cfg['multiple']) @@ -329,6 +330,25 @@ class RegrTC(TestCase): self.linter.load_command_line_configuration([]) self.assertEqual(self.linter.config.profile, False) +class MergeTC(TestCase): + + def test_merge1(self): + merged = merge_options([('dothis', {'type':'yn', 'action': 'store', 'default': True, 'metavar': '<y or n>'}), + ('dothis', {'type':'yn', 'action': 'store', 'default': False, 'metavar': '<y or n>'}), + ]) + self.assertEqual(len(merged), 1) + self.assertEqual(merged[0][0], 'dothis') + self.assertEqual(merged[0][1]['default'], True) + + def test_merge2(self): + merged = merge_options([('dothis', {'type':'yn', 'action': 'store', 'default': True, 'metavar': '<y or n>'}), + ('value', {'type': 'string', 'metavar': '<string>', 'short': 'v'}), + ('dothis', {'type':'yn', 'action': 'store', 'default': False, 'metavar': '<y or n>'}), + ]) + self.assertEqual(len(merged), 2) + self.assertEqual(merged[0][0], 'value') + self.assertEqual(merged[1][0], 'dothis') + self.assertEqual(merged[1][1]['default'], True) if __name__ == '__main__': unittest_main() diff --git a/test/unittest_deprecation.py b/test/unittest_deprecation.py index 7596317..ad268e8 100644 --- a/test/unittest_deprecation.py +++ b/test/unittest_deprecation.py @@ -78,5 +78,66 @@ class RawInputTC(TestCase): self.assertEqual(self.messages, ['object moving_target has been moved to module data.deprecation']) + def test_deprecated_manager(self): + deprecator = deprecation.DeprecationManager("module_name") + deprecator.compatibility('1.3') + # This warn should be printed. + deprecator.warn('1.1', "Major deprecation message.", 1) + deprecator.warn('1.1') + + @deprecator.deprecated('1.2', 'Major deprecation message.') + def any_func(): + pass + any_func() + + @deprecator.deprecated('1.2') + def other_func(): + pass + other_func() + + self.assertListEqual(self.messages, + ['[module_name 1.1] Major deprecation message.', + '[module_name 1.1] ', + '[module_name 1.2] Major deprecation message.', + '[module_name 1.2] The function "other_func" is deprecated']) + + def test_class_deprecated_manager(self): + deprecator = deprecation.DeprecationManager("module_name") + deprecator.compatibility('1.3') + class AnyClass: + __metaclass__ = deprecator.class_deprecated('1.2') + AnyClass() + self.assertEqual(self.messages, + ['[module_name 1.2] AnyClass is deprecated']) + + + def test_deprecated_manager_noprint(self): + deprecator = deprecation.DeprecationManager("module_name") + deprecator.compatibility('1.3') + # This warn should not be printed. + deprecator.warn('1.3', "Minor deprecation message.", 1) + + @deprecator.deprecated('1.3', 'Minor deprecation message.') + def any_func(): + pass + any_func() + + @deprecator.deprecated('1.20') + def other_func(): + pass + other_func() + + @deprecator.deprecated('1.4') + def other_func(): + pass + other_func() + + class AnyClass(object): + __metaclass__ = deprecator.class_deprecated((1,5)) + AnyClass() + + self.assertFalse(self.messages) + + if __name__ == '__main__': unittest_main() |