changeset 38939:3db083b5486c

type_assert helper (unify type error handling)
author Dmitry Selyutin <ghostmansd@gmail.com>
date Sat, 09 Sep 2017 22:54:55 +0300
parents a0aa248e40fc
children b790ffde5faa
files pygnulib/config.py pygnulib/error.py pygnulib/filesystem.py pygnulib/generator.py pygnulib/module.py
diffstat 5 files changed, 107 insertions(+), 129 deletions(-) [+]
line wrap: on
line diff
--- a/pygnulib/config.py	Sat Sep 09 21:58:23 2017 +0300
+++ b/pygnulib/config.py	Sat Sep 09 22:54:55 2017 +0300
@@ -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 @@
 
     @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 @@
             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 @@
             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 @@
         return self.__table.values()
 
 
+
 class Cache(Base):
     """gnulib cached configuration"""
     _AUTOCONF_ = {
@@ -438,9 +441,7 @@
 
 
     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 @@
             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 @@
 
 
     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:
--- a/pygnulib/error.py	Sat Sep 09 21:58:23 2017 +0300
+++ b/pygnulib/error.py	Sat Sep 09 22:54:55 2017 +0300
@@ -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):
--- a/pygnulib/filesystem.py	Sat Sep 09 21:58:23 2017 +0300
+++ b/pygnulib/filesystem.py	Sat Sep 09 22:54:55 2017 +0300
@@ -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 @@
 
 
     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 @@
 
     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 @@
 
     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)
--- a/pygnulib/generator.py	Sat Sep 09 21:58:23 2017 +0300
+++ b/pygnulib/generator.py	Sat Sep 09 22:54:55 2017 +0300
@@ -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 @@
         "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 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 @@
         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 @@
         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 @@
 
 
     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
 
 
--- a/pygnulib/module.py	Sat Sep 09 21:58:23 2017 +0300
+++ b/pygnulib/module.py	Sat Sep 09 22:54:55 2017 +0300
@@ -9,6 +9,8 @@
 import os
 import re
 
+from .error import type_assert as _type_assert_
+
 
 
 class Base:
@@ -34,8 +36,7 @@
 
 
     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 @@
 
     @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 @@
 
     @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 @@
 
     @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 @@
 
     @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 @@
 
     @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 @@
     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 @@
             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 @@
             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 @@
 
     @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 @@
 
     @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 @@
 
     @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 @@
             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 @@
             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 @@
 
     @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 @@
             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"):