From 8c9c625cad25045a01d4f3b9e7c9242f9ff7bc3b Mon Sep 17 00:00:00 2001 From: Dmitry Selyutin Date: Sat, 9 Sep 2017 22:54:55 +0300 Subject: [PATCH] type_assert helper (unify type error handling) --- pygnulib/config.py | 36 ++++++----- pygnulib/error.py | 18 ++++++ pygnulib/filesystem.py | 12 ++-- pygnulib/generator.py | 33 ++++------ pygnulib/module.py | 137 +++++++++++++++-------------------------- 5 files changed, 107 insertions(+), 129 deletions(-) diff --git a/pygnulib/config.py b/pygnulib/config.py index 13835278f5..7cc77ac231 100644 --- a/pygnulib/config.py +++ b/pygnulib/config.py @@ -5,10 +5,12 @@ import argparse import codecs +import collections import os import re import sys +from .error import type_assert as _type_assert_ from .error import AutoconfVersionError as _AutoconfVersionError_ @@ -185,10 +187,6 @@ class Base: @lgpl.setter def lgpl(self, value): - if value is None: - value = 0 - if value not in [0, 2, 3]: - raise TypeError("lgpl option must be either None or integral version (2 or 3)") self["lgpl"] = value @@ -355,6 +353,8 @@ class Base: key = key.replace("-", "_") if key not in Base._TABLE_: raise KeyError("unsupported option: %r" % key) + if key == "all_tests": + return self.all_tests return self.__table[key] @@ -364,15 +364,17 @@ class Base: if key not in Base._TABLE_: raise KeyError("unsupported option: %r" % key) key = key.replace("-", "_") + if key == "all_tests": + self.all_tests = value + return typeid = type(Base._TABLE_[key]) - if key == "lgpl": - if value not in [None, 2, 3]: - raise TypeError("lgpl option must be either None or integral version (2 or 3)") - elif key == "autoconf": - if value < 2.59: - raise _AutoconfVersionError_(2.59) - elif not isinstance(value, typeid): - raise TypeError("%r option must be of %r type" % (key, typeid)) + if key == "lgpl" and value is None: + value = 0 + _type_assert_(key, value, typeid) + if key == "lgpl" and value not in (0, 2, 3): + raise ValueError("lgpl: None, 2 or 3 expected") + if key == "autoconf" and value < 2.59: + raise _AutoconfVersionError_(2.59) self.__table[key] = value @@ -391,6 +393,7 @@ class Base: return self.__table.values() + class Cache(Base): """gnulib cached configuration""" _AUTOCONF_ = { @@ -438,9 +441,7 @@ class Cache(Base): def __init__(self, root, m4_base, autoconf=None, **kwargs): - if not isinstance(root, str): - raise TypeError("root must be of 'str' type") - super().__init__(m4_base=m4_base, **kwargs) + super().__init__(root=root, m4_base=m4_base, **kwargs) self.__autoconf(root, autoconf) self.__gnulib_cache(root) self.__gnulib_comp(root) @@ -460,7 +461,7 @@ class Cache(Base): if not match: continue if key == "autoconf": - self["autoconf"] = sorted(set([float(_.strip()) for _ in match if _.strip()]))[-1] + self[key] = float([_ for _ in match if match][-1]) else: self[key] = match[-1] @@ -1304,7 +1305,10 @@ class CommandLine(Base): def __init__(self, program, argv, **kwargs): + _type_assert_("program", program, str) + _type_assert_("argv", argv, collections.Iterable) super().__init__(**kwargs) + parser = argparse.ArgumentParser(prog=program, add_help=False, allow_abbrev=False) for (_, _, args) in CommandLine._SECTIONS_: for arg in args: diff --git a/pygnulib/error.py b/pygnulib/error.py index d618df0ee2..b577f0e119 100644 --- a/pygnulib/error.py +++ b/pygnulib/error.py @@ -3,6 +3,24 @@ +def type_assert(key, value, types): + typeset = [] + if isinstance(types, type): + types = [types] + types = tuple(types) + if not isinstance(value, types): + for typeid in types: + module = typeid.__module__ + name = typeid.__name__ + if module == "builtins": + typeset += [name] + else: + typeset += [module + "." + name] + typeset = "{%s}" % ", ".join(set(typeset)) + raise TypeError("{0}: {1} expected".format(key, typeset)) + + + class AutoconfVersionError(Exception): """minimum supported autoconf version mismatch""" def __init__(self, version): diff --git a/pygnulib/filesystem.py b/pygnulib/filesystem.py index 62e4d322d0..d934ec0f3f 100644 --- a/pygnulib/filesystem.py +++ b/pygnulib/filesystem.py @@ -5,6 +5,7 @@ import os +from .error import type_assert as _type_assert_ from .config import Base as _BaseConfig_ from .module import Base as _BaseModule_ from .module import File as _FileModule_ @@ -25,10 +26,8 @@ class Directory: def __init__(self, root, config): - if not isinstance(root, str): - raise TypeError("root must be of 'str' type") - if not isinstance(config, _BaseConfig_): - raise TypeError("config must be of 'Config' type") + _type_assert_("root", root, str) + _type_assert_("config", config, _BaseConfig_) if not os.path.exists(root): raise FileNotFoundError(root) if not os.path.isdir(root): @@ -39,10 +38,9 @@ class Directory: def __getitem__(self, name): """retrieve the canonical path of the specified file name""" + _type_assert_("name", name, str) parts = [] replaced = False - if not isinstance(name, str): - raise TypeError("name must be of 'str' type") path = os.path.normpath(name) if os.path.isabs(path): raise ValueError("name must be a relative path") @@ -92,6 +90,8 @@ class Git(Directory): def module(self, name, full=True): """instantiate gnulib module by its name""" + _type_assert_("name", name, str) + _type_assert_("full", full, bool) if name in Git._EXCLUDE_: raise ValueError("illegal module name") path = os.path.join(self["modules"], name) diff --git a/pygnulib/generator.py b/pygnulib/generator.py index 1a52b5848c..b9e38173e2 100644 --- a/pygnulib/generator.py +++ b/pygnulib/generator.py @@ -5,6 +5,7 @@ import os +from .error import type_assert as _type_assert_ from .config import Base as _BaseConfig_ from .module import Base as _BaseModule_ @@ -92,8 +93,7 @@ class POMakefile(Generator): "USE_MSGCTXT = no" ) def __init__(self, config): - if not isinstance(config, _BaseConfig_): - raise TypeError("config must be of pygnulib.Config type") + _type_assert_("config", config, _BaseConfig_) super().__init__() self.__config = config @@ -133,8 +133,7 @@ class POMakefile(Generator): class POTFILES(Generator): """file list to be passed to xgettext""" def __init__(self, config, files): - if not isinstance(config, _BaseConfig_): - raise TypeError("config must be of pygnulib.Config type") + _type_assert_("config", config, _BaseConfig_) super().__init__() self.__config = config self.__files = tuple(files) @@ -170,16 +169,11 @@ class AutoconfSnippet(Generator): no_libtool: disable libtool (regardless of configuration) no_gettext: disable AM_GNU_GETTEXT invocations if True """ - if not isinstance(config, _BaseConfig_): - raise TypeError("config must be of pygnulib.config.Config type") - if not isinstance(module, _BaseModule_): - raise TypeError("module must be of pygnulib.module.Module type") - if not isinstance(toplevel, bool): - raise TypeError("toplevel must be of bool type") - if not isinstance(no_libtool, bool): - raise TypeError("no_libtool must be of bool type") - if not isinstance(no_gettext, bool): - raise TypeError("no_gettext must be of bool type") + _type_assert_("config", config, _BaseConfig_) + _type_assert_("module", module, _BaseModule_) + _type_assert_("toplevel", toplevel, bool) + _type_assert_("no_libtool", no_libtool, bool) + _type_assert_("no_gettext", no_gettext, bool) super().__init__() self.__config = config self.__module = module @@ -259,12 +253,10 @@ class InitMacro(Generator): config: gnulib configuration macro_prefix: macro prefix; if None, consider configuration """ - if not isinstance(config, _BaseConfig_): - raise TypeError("config must be of pygnulib.config.Config type") + _type_assert_("config", config, _BaseConfig_) if macro_prefix is None: macro_prefix = config.macro_prefix - if not isinstance(macro_prefix, str): - raise TypeError("macro_prefix must be of str type") + _type_assert_("macro_prefix", macro_prefix, str) self.__macro_prefix = macro_prefix @@ -422,11 +414,10 @@ class InitMacroDone(InitMacro): def __init__(self, config, source_base=None, macro_prefix=None): + super().__init__(config=config, macro_prefix=macro_prefix) if source_base is None: source_base = config.source_base - if not isinstance(source_base, str): - raise TypeError("source_base must be of str type") - super().__init__(config=config, macro_prefix=macro_prefix) + _type_assert_("source_base", source_base, str) self.__source_base = source_base diff --git a/pygnulib/module.py b/pygnulib/module.py index 365914e662..4723f5a388 100644 --- a/pygnulib/module.py +++ b/pygnulib/module.py @@ -9,6 +9,8 @@ import hashlib import os import re +from .error import type_assert as _type_assert_ + class Base: @@ -34,8 +36,7 @@ class Base: def __init__(self, name, **kwargs): - if not isinstance(name, str): - raise TypeError("name must be of 'str' type") + _type_assert_("name", name, str) self.__name = name self.__table = {"maintainers": ["all"]} for key in Base._TABLE_: @@ -51,8 +52,7 @@ class Base: @name.setter def name(self, value): - if not isinstance(value, str): - raise TypeError("'str' type is expected") + _type_assert_("name", value, str) self.__name = value @@ -63,8 +63,7 @@ class Base: @description.setter def description(self, value): - if not isinstance(value, str): - raise TypeError("'str' type is expected") + _type_assert_("description", value, str) self.__table["description"] = value @@ -75,8 +74,7 @@ class Base: @comment.setter def comment(self, value): - if not isinstance(value, str): - raise TypeError("'str' type is expected") + _type_assert_("comment", value, str) self.__table["comment"] = value @@ -87,8 +85,7 @@ class Base: @status.setter def status(self, value): - if not isinstance(value, str): - raise TypeError("'str' type is expected") + _type_assert_("status", value, str) self.__table["status"] = value @@ -99,8 +96,7 @@ class Base: @notice.setter def notice(self, value): - if not isinstance(value, str): - raise TypeError("'str' type is expected") + _type_assert_("notice", value, str) self.__table["notice"] = value @@ -108,13 +104,11 @@ class Base: def applicability(self): """applicability (usually "main" or "tests")""" default = "main" if self.name.endswith("-tests") else "tests" - current = self.__table["applicability"] - return current if current.strip() else default + return self.__table.get("applicability", default) @applicability.setter def applicability(self, value): - if not isinstance(value, str): - raise TypeError("'str' type is expected") + _type_assert_("applicability", value, str) self.__table["applicability"] = value @@ -125,16 +119,13 @@ class Base: yield file @files.setter - def files(self, iterable): - if isinstance(iterable, (bytes, str)) \ - or not isinstance(iterable, collections.Iterable): - raise TypeError("iterable of strings is expected") - result = set() - for item in iterable: - if not isinstance(item, str): - raise TypeError("iterable of strings is expected") - result.update([item]) - self.__table["files"] = result + def files(self, value): + _type_assert_("files", value, collections.Iterable) + result = [] + for item in value: + _type_assert_("file", item, str) + result += [item] + self.__table["files"] = set(result) @property @@ -144,22 +135,14 @@ class Base: yield Base._PATTERN_DEPENDENCIES_.findall(entry)[0] @dependencies.setter - def dependencies(self, iterable): - error = TypeError("iterable of pairs (name, condition) is expected") - if isinstance(iterable, (bytes, str)) \ - or not isinstance(iterable, collections.Iterable): - raise error - result = set() - try: - for pair in iterable: - (name, condition) = pair - if not isinstance(name, str) \ - or not isinstance(condition, str): - raise error - result.update([(name, condition)]) - except ValueError: - raise error - self.__table["dependencies"] = result + def dependencies(self, value): + _type_assert_("files", value, collections.Iterable) + result = [] + for (name, condition) in value: + _type_assert_("name", name, str) + _type_assert_("condition", condition, str) + result += [(name, condition)] + self.__table["dependencies"] = set(result) @property @@ -169,8 +152,7 @@ class Base: @early_autoconf_snippet.setter def early_autoconf_snippet(self, value): - if not isinstance(value, str): - raise TypeError("'str' type is expected") + _type_assert_("early_autoconf_snippet", value, str) self.__table["early_autoconf_snippet"] = value @@ -181,8 +163,7 @@ class Base: @autoconf_snippet.setter def autoconf_snippet(self, value): - if not isinstance(value, str): - raise TypeError("'str' type is expected") + _type_assert_("autoconf_snippet", value, str) self.__table["autoconf_snippet"] = value @@ -193,8 +174,7 @@ class Base: @automake_snippet.setter def automake_snippet(self, value): - if not isinstance(value, str): - raise TypeError("'str' type is expected") + _type_assert_("automake_snippet", value, str) self.__table["automake_snippet"] = value @@ -206,22 +186,14 @@ class Base: yield match[0] if match else entry @include.setter - def include(self, iterable): - error = TypeError("iterable of pairs (header, comment) is expected") - if isinstance(iterable, (bytes, str)) \ - or not isinstance(iterable, collections.Iterable): - raise error - result = set() - try: - for pair in iterable: - (header, comment) = pair - if not isinstance(header, str) \ - or not isinstance(comment, str): - raise error - result.update([(header, comment)]) - except ValueError: - raise error - self.__table["include"] = result + def include(self, value): + _type_assert_("include", value, collections.Iterable) + result = [] + for (header, comment) in value: + _type_assert_("header", header, str) + _type_assert_("comment", comment, str) + result += [(header, comment)] + self.__table["include"] = set(result) @property @@ -231,16 +203,13 @@ class Base: yield entry @link.setter - def link(self, iterable): - if isinstance(iterable, (bytes, str)) \ - or not isinstance(iterable, collections.Iterable): - raise TypeError("iterable of strings is expected") - result = set() - for item in iterable: - if not isinstance(item, str): - raise TypeError("iterable of strings is expected") - result.update([item]) - self.__table["link"] = result + def link(self, value): + _type_assert_("link", value, collections.Iterable) + result = [] + for item in value: + _type_assert_("directive", item, str) + result += [item] + self.__table["link"] = set(result) @property @@ -250,8 +219,7 @@ class Base: @license.setter def license(self, value): - if not isinstance(value, str): - raise TypeError("'str' type is expected") + _type_assert_("license", value, str) self.__table["license"] = value @@ -262,16 +230,13 @@ class Base: yield entry @maintainers.setter - def maintainers(self, iterable): - if isinstance(iterable, (bytes, str)) \ - or not isinstance(iterable, collections.Iterable): - raise TypeError("iterable of strings is expected") - result = set() - for item in iterable: - if not isinstance(item, str): - raise TypeError("iterable of strings is expected") - result.update([item]) - self.__table["maintainers"] = result + def maintainers(self, value): + _type_assert_("maintainers", value, collections.Iterable) + result = [] + for item in value: + _type_assert_("maintainer", item, str) + result += [item] + self.__table["maintainers"] = set(result) def shell_variable(self, macro_prefix="gl"): -- 2.39.5