Hello community,

here is the log from the commit of package python-zetup for openSUSE:Factory 
checked in at 2019-03-14 15:00:09
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-zetup (Old)
 and      /work/SRC/openSUSE:Factory/.python-zetup.new.28833 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-zetup"

Thu Mar 14 15:00:09 2019 rev:3 rq:684699 version:0.2.45

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-zetup/python-zetup.changes        
2018-08-31 10:46:23.159343211 +0200
+++ /work/SRC/openSUSE:Factory/.python-zetup.new.28833/python-zetup.changes     
2019-03-14 15:01:07.555698179 +0100
@@ -1,0 +2,8 @@
+Wed Mar 13 13:47:55 UTC 2019 - Tomáš Chvátal <tchva...@suse.com>
+
+- Update to 0.2.45:
+  * Cleanup whitespace/formatting/docstrings
+  * Print install hints
+  * Fix various py3 compats
+
+-------------------------------------------------------------------

Old:
----
  zetup-0.2.43.tar.gz

New:
----
  zetup-0.2.45.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-zetup.spec ++++++
--- /var/tmp/diff_new_pack.HkPm0H/_old  2019-03-14 15:01:09.507697794 +0100
+++ /var/tmp/diff_new_pack.HkPm0H/_new  2019-03-14 15:01:09.511697794 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-zetup
 #
-# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -12,31 +12,32 @@
 # license that conforms to the Open Source Definition (Version 1.9)
 # published by the Open Source Initiative.
 
-# Please submit bugfixes or comments via http://bugs.opensuse.org/
+# Please submit bugfixes or comments via https://bugs.opensuse.org/
 #
 
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-zetup
-Version:        0.2.43
+Version:        0.2.45
 Release:        0
 Summary:        Project setups tools
 License:        LGPL-3.0-only
 Group:          Development/Languages/Python
 URL:            https://github.com/zimmermanncode/zetup
 Source:         
https://files.pythonhosted.org/packages/source/z/zetup/zetup-%{version}.tar.gz
-BuildRequires:  %{python_module jupyter_nbconvert >= 5.2}
-BuildRequires:  %{python_module path.py >= 10.3}
-BuildRequires:  %{python_module pytest}
-BuildRequires:  %{python_module setuptools_scm >= 2.0}
+BuildRequires:  %{python_module jupyter_nbconvert >= 5.4}
+BuildRequires:  %{python_module path.py >= 11.1}
+BuildRequires:  %{python_module pytest >= 3.8}
+BuildRequires:  %{python_module setuptools_scm >= 3.1}
 BuildRequires:  %{python_module setuptools}
+BuildRequires:  dos2unix
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 Requires(post): update-alternatives
 Requires(postun): update-alternatives
 Recommends:     python-jinjatools >= 0.1.7
-Recommends:     python-jupyter_nbconvert >= 5.2
-Recommends:     python-path.py >= 10.3
+Recommends:     python-jupyter_nbconvert >= 5.4
+Recommends:     python-path.py >= 11.1
 Recommends:     python-pytest
 BuildArch:      noarch
 %python_subpackages
@@ -46,8 +47,8 @@
 
 %prep
 %setup -q -n zetup-%{version}
-sed -i 's/\r$//' README.rst
-sed -i 's/\r$//' zetup/script.py
+dos2unix README.rst
+dos2unix zetup/script.py
 
 %build
 %python_build

++++++ zetup-0.2.43.tar.gz -> zetup-0.2.45.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/PKG-INFO new/zetup-0.2.45/PKG-INFO
--- old/zetup-0.2.43/PKG-INFO   2018-04-18 17:16:50.000000000 +0200
+++ new/zetup-0.2.45/PKG-INFO   2018-09-17 21:35:10.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: zetup
-Version: 0.2.43
+Version: 0.2.45
 Summary: Zimmermann's Extensible Tools for Unified Project_setups
 Home-page: https://github.com/zimmermanncode/zetup
 Author: Stefan Zimmermann
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/VERSION new/zetup-0.2.45/VERSION
--- old/zetup-0.2.43/VERSION    2018-04-18 17:16:50.000000000 +0200
+++ new/zetup-0.2.45/VERSION    2018-09-17 21:35:09.000000000 +0200
@@ -1 +1 @@
-0.2.43
\ No newline at end of file
+0.2.45
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/requirements.commands.txt 
new/zetup-0.2.45/requirements.commands.txt
--- old/zetup-0.2.43/requirements.commands.txt  2018-04-18 17:15:38.000000000 
+0200
+++ new/zetup-0.2.45/requirements.commands.txt  2018-09-17 21:33:58.000000000 
+0200
@@ -1,2 +1,2 @@
-path.py >= 10.3 #import path
+path.py >= 11.1 #import path
 jinjatools >= 0.1.7
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/requirements.notebook.txt 
new/zetup-0.2.45/requirements.notebook.txt
--- old/zetup-0.2.43/requirements.notebook.txt  2018-04-18 17:15:38.000000000 
+0200
+++ new/zetup-0.2.45/requirements.notebook.txt  2018-09-17 21:33:58.000000000 
+0200
@@ -1,2 +1,2 @@
-path.py >= 10.3 #import path
-nbconvert >= 5.2
+path.py >= 11.1 #import path
+nbconvert >= 5.4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/requirements.pytest.txt 
new/zetup-0.2.45/requirements.pytest.txt
--- old/zetup-0.2.43/requirements.pytest.txt    2018-04-18 17:15:38.000000000 
+0200
+++ new/zetup-0.2.45/requirements.pytest.txt    2018-09-17 21:33:58.000000000 
+0200
@@ -1 +1 @@
-pytest
+pytest >= 3.8
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/requirements.txt 
new/zetup-0.2.45/requirements.txt
--- old/zetup-0.2.43/requirements.txt   2018-04-18 17:15:38.000000000 +0200
+++ new/zetup-0.2.45/requirements.txt   2018-09-17 21:33:58.000000000 +0200
@@ -1 +1 @@
-setuptools_scm >= 2.0
+setuptools_scm >= 3.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/tox.ini new/zetup-0.2.45/tox.ini
--- old/zetup-0.2.43/tox.ini    2018-04-18 17:15:38.000000000 +0200
+++ new/zetup-0.2.45/tox.ini    2018-09-17 21:33:58.000000000 +0200
@@ -1,5 +1,5 @@
 [tox]
-envlist = py27,py33,py34,py35,py36,pypy
+envlist = py27,py34,py35,py36,pypy
 
 [testenv]
 deps =
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/zetup/__init__.py 
new/zetup-0.2.45/zetup/__init__.py
--- old/zetup-0.2.43/zetup/__init__.py  2018-04-18 17:15:38.000000000 +0200
+++ new/zetup-0.2.45/zetup/__init__.py  2018-09-17 21:33:58.000000000 +0200
@@ -41,6 +41,7 @@
 from .annotate import annotate
 from .modules import package, toplevel, extra_toplevel
 from .classpackage import classpackage
+from .pip import ZetupPipError, pip
 # import notebook subpackage for defining extra_toplevel below
 from . import notebook
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/zetup/__main__.py 
new/zetup-0.2.45/zetup/__main__.py
--- old/zetup-0.2.43/zetup/__main__.py  2018-04-18 17:15:38.000000000 +0200
+++ new/zetup-0.2.45/zetup/__main__.py  2018-09-17 21:33:58.000000000 +0200
@@ -25,5 +25,5 @@
 
 import zetup.script
 
-
-zetup.script.run()
+if __name__ == '__main__':
+    zetup.script.run()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/zetup/commands/del_.py 
new/zetup-0.2.45/zetup/commands/del_.py
--- old/zetup-0.2.43/zetup/commands/del_.py     2018-04-18 17:15:38.000000000 
+0200
+++ new/zetup-0.2.45/zetup/commands/del_.py     2018-09-17 21:33:58.000000000 
+0200
@@ -27,30 +27,30 @@
 import os
 
 import pkg_resources
-import pip
 
 from path import Path
 
 from zetup.zetup import Zetup
 from zetup.commands.command import command
 from zetup.conda import conda
+from zetup.pip import pip
 
-__all__ = ['del_']
+__all__ = ('del_', )
 
 
 @Zetup.command(name='del')
 @command(name='del')
 def del_(zfg, args=None):
-    """Delete project from python environment.
-    """
+    """Delete project from python environment."""
     try:  # check for conda
         conda_info = conda.info()
     except OSError:  # ==> no conda
         pass
     else:
         # are we in a conda environment?
-        if any(Path(conda_info[key]).samefile(sys.prefix)
-               for key in ['root_prefix', 'default_prefix']
+        if any(
+                Path(conda_info[key]).samefile(sys.prefix)
+                for key in ['root_prefix', 'default_prefix']
         # and is project installed via conda?
         ) and conda.list('--no-pip', '--full-name', zfg.NAME):
             # then also remove it via conda
@@ -65,7 +65,7 @@
             dist = pkg_resources.WorkingSet().by_key[zfg.NAME]
         except KeyError:  # ==> nothing left to uninstall
             break
-        status = pip.main(['uninstall', zfg.NAME, '--yes'])
+        status = pip(['uninstall', zfg.NAME, '--yes'], raise_=False)
         if status:  # ==> error
             return status
         root = Path(dist.location)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/zetup/commands/dev.py 
new/zetup-0.2.45/zetup/commands/dev.py
--- old/zetup-0.2.43/zetup/commands/dev.py      2018-04-18 17:15:38.000000000 
+0200
+++ new/zetup-0.2.45/zetup/commands/dev.py      2018-09-17 21:33:58.000000000 
+0200
@@ -26,6 +26,7 @@
 import pip
 
 from zetup.zetup import Zetup
+from zetup.pip import pip
 
 from zetup.commands.del_ import del_
 
@@ -34,12 +35,11 @@
 
 @Zetup.command(depends=['setup.py'])
 def dev(zfg, args=None):
-    """Install project in develop mode.
-    """
+    """Install project in develop mode."""
     # first remove any current project installation
     del_(zfg)
     # then (re)install project in develop mode (and return pip status code)
     source = str(zfg.ZETUP_DIR)
     if zfg.EXTRAS:
         source += '[all]'
-    return pip.main(['install', '--editable', source])
+    return pip(['install', '--editable', source], raise_=False)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/zetup/object.py 
new/zetup-0.2.45/zetup/object.py
--- old/zetup-0.2.43/zetup/object.py    2018-04-18 17:15:38.000000000 +0200
+++ new/zetup-0.2.45/zetup/object.py    2018-09-17 21:33:58.000000000 +0200
@@ -1,71 +1,107 @@
-# zetup.py
+# ZETUP | Zimmermann's Extensible Tools for Unified Projects
 #
-# Zimmermann's Python package setup.
+# Copyright (C) 2014-2018 Stefan Zimmermann <u...@zimmermann.co>
 #
-# Copyright (C) 2014-2015 Stefan Zimmermann <zimmermann.c...@gmail.com>
-#
-# zetup.py is free software: you can redistribute it and/or modify
+# ZETUP is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
-# zetup.py is distributed in the hope that it will be useful,
+# ZETUP is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
-# along with zetup.py. If not, see <http://www.gnu.org/licenses/>.
+# along with ZETUP. If not, see <http://www.gnu.org/licenses/>.
 
-"""zetup.object
+"""
+Define basic :class:`zetup.object` and its :class:`zetup.meta`.
 
-Basic ``object``-derived class and ``type``-derived ``meta`` class
-with some added features.
+For extending builtin ``object`` and ``type`` with useful extra features
 
-.. moduleauthor:: Stefan Zimmermann <zimmermann.c...@gmail.com>
+.. moduleauthor:: Stefan Zimmermann <u...@zimmermann.co>
 """
+from __future__ import absolute_import
+
 from itertools import chain
 
-__all__ = ['object', 'meta']
+__all__ = ('object', 'meta')
 
 
 class meta(type):
-    """Basic metaclass derived from builtin ``type``,
-       which adds a unified basic ``__dir__`` method for Python 2 and 3,
-       which always returns all member names from metaclass and class level.
+    """
+    Basic metaclass that extends builtin ``type`` with useful extra features.
+
+    Adds a unified basic ``__dir__`` method for Python 2 and 3, which always
+    returns all member names from metaclass and class level
+
+    Adds :meth:`.metamethod` and :meth:`.method` decorators for adding new
+    members to classes and their metaclasses outside of the class definition
+    scopes
     """
     if hasattr(type, '__dir__'):  # PY3
         def __dir__(cls):
             """Get all member names from class and metaclass level.
             """
-            return list(set(chain(type.__dir__(cls), dir(type(cls)))))
+            return sorted(set(chain(type.__dir__(cls), dir(type(cls)))))
 
     else:  # PY2
         def __dir__(cls):
             """Get all member names from class and metaclass level.
             """
-            return list(set(chain(
-                dir(type(cls)), *(c.__dict__ for c in cls.mro()))))
+            return sorted(set(chain(
+                dir(type(cls)),
+                *(c.__dict__ for c in cls.mro()))))
 
     @classmethod
     def metamember(mcs, obj):
-        """Decorator for adding `obj` as a member
-        to the metaclass of this class ::
+        """
+        Decorator for adding `obj` as a member to the metaclass of this class.
+
+        >>> import six  # if you want compatibility with Python 2 and 3
+        >>> import zetup
 
-           class Meta(zetup.meta):
-               ...
+        This is of course only useful when you have a custom metaclass:
 
-           class Class(zetup.object, metaclass=Meta):
-               ...
+        >>> class Meta(zetup.meta):
+        ...     pass
 
-           @Class.metamember
-           def method(cls, ...):
-               ...
+        Now you can add new members to your custom metaclass outside of the
+        class definition scope:
+
+        >>> @Meta.metamember
+        ... def method(cls, arg):
+        ...     pass
 
         >>> Meta.method
-        <function Meta.method>
+        <... Meta.method...>
+
+        This also works from a new class based on this metaclass. In Python 3
+        syntax you create such a class like::
+
+            class Class(zetup.object, metaclass=Meta):
+                pass
+
+        But if you want to be compatible with Python 2 and 3:
+
+        >>> class Class(six.with_metaclass(Meta, zetup.object)):
+        ...     pass
+
+        >>> @Class.metamember
+        ... def another_method(cls, arg):
+        ...     pass
+
+        >>> Meta.another_method
+        <... Meta.another_method...>
+
+        >>> Class.another_method
+        <bound method Meta.another_method of ...Class...>
+
+        And the formerly defined ``@Meta.metamember`` is of course also there:
+
         >>> Class.method
-        <bound method Meta.method of Class>
+        <bound method Meta.method of ...Class...>
         """
         if isinstance(obj, property):
             name = obj.fget.__name__
@@ -82,17 +118,26 @@
         return obj
 
     def member(cls, obj):
-        """Decorator for adding `obj` as a member to this class ::
+        """
+        Decorator for adding `obj` as a member to this class.
 
-           class Class(zetup.object):
-               ...
+        >>> import zetup
 
-           @Class.member
-           def method(self, ...):
-               ...
+        >>> class Class(zetup.object):
+        ...     pass
+
+        Now you can add new members to your class outside of the class
+        definition scope:
+
+        >>> @Class.member
+        ... def method(self, arg):
+        ...     pass
 
         >>> Class.method
-        <function Class.method>
+        <... Class.method...>
+
+        >>> Class().method
+        <bound method Class.method of ...Class...>
         """
         if isinstance(obj, property):
             name = obj.fget.__name__
@@ -109,18 +154,28 @@
         return obj
 
 
-# PY2/3 compatible way to create class `object` with metaclass `meta`
+# PY2/3 compatible way to create class `object` with metaclass `meta`...
+
 clsattrs = {
     '__doc__':
-    """Basic class derived from builtin ``object``,
-       which adds a basic ``__dir__`` method for Python 2.
+    """
+    Basic class that extends builtin ``object`` with useful extra features.
+
+    Adds a basic ``__dir__`` method for Python 2
+
+    Has :class:`zetup.meta` as metaclass and therefore provides
+    :meth:`zetup.meta.metamethod` and :meth:`zetup.meta.method` decorators for
+    adding new members to classes and their metaclasses outside of the class
+    definition scopes
     """}
+
 if not hasattr(object, '__dir__'):
+
     def __dir__(self):
-        """Get all member names from instance and class level.
-        """
-        return list(set(chain(
-            self.__dict__, *(c.__dict__ for c in type(self).mro()))))
+        """Get all member names from instance and class level."""
+        return sorted(set(chain(
+            self.__dict__,
+            *(c.__dict__ for c in type(self).mro()))))
 
     clsattrs['__dir__'] = __dir__
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/zetup/pip/__init__.py 
new/zetup-0.2.45/zetup/pip/__init__.py
--- old/zetup-0.2.43/zetup/pip/__init__.py      1970-01-01 01:00:00.000000000 
+0100
+++ new/zetup-0.2.45/zetup/pip/__init__.py      2018-09-17 21:33:58.000000000 
+0200
@@ -0,0 +1,53 @@
+"""Define :func:`zetup.pip` command runner and :exc:`zetup.ZetupPipError`."""
+
+import logging
+
+from zetup.requires import requires
+
+from .errors import ZetupPipError
+
+__all__ = ('ZetupPipError', 'pip')
+
+
+@requires("pip >= 10.0")
+def pip(command, raise_=True):
+    """
+    Run a pip `command`.
+
+    :param command: The list of pip arguments
+
+    >>> import zetup
+
+    >>> zetup.pip(['list', '-v'])
+    Package...Version...Location...Installer
+    -------...-------...--------...---------
+    ...
+    0
+
+    :return:
+        ``0`` on success or, if called with ``raise_=False``, also any
+        non-zero pip exit code
+
+    >>> zetup.pip(['invalid'], raise_=False)
+    1
+
+    :raises zetup.ZetupPipError:
+        on non-zero pip exit codes, if not called with ``raise_=False``
+
+    >>> zetup.pip(['invalid'])
+    Traceback (most recent call last):
+    ...
+    ZetupPipError: pip command ['invalid'] failed with exit code 1
+    """
+    from pip._internal import configuration, main
+
+    # HACK: Avoid unnecessary debug messages (which also destroy doctests)
+    configuration.logger.level = logging.INFO
+    try:
+        exit_code = main(command)
+    except SystemExit as exc:
+        exit_code = exc.code
+    if raise_ and exit_code != 0:
+        raise ZetupPipError(command, exit_code)
+
+    return exit_code
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/zetup/pip/errors.py 
new/zetup-0.2.45/zetup/pip/errors.py
--- old/zetup-0.2.43/zetup/pip/errors.py        1970-01-01 01:00:00.000000000 
+0100
+++ new/zetup-0.2.45/zetup/pip/errors.py        2018-09-17 21:33:58.000000000 
+0200
@@ -0,0 +1,32 @@
+"""Define exceptions for failing ``zetup.pip`` calls."""
+
+from zetup.error import ZetupError
+
+
+class ZetupPipError(ZetupError):
+    """
+    ``zetup.pip([command...])`` failed.
+    
+    ``.command`` argument list and ``.exit_code`` can be retrieved from the
+    exception object:
+
+    >>> import zetup
+
+    >>> try:
+    ...     zetup.pip(['invalid'])
+    ... except zetup.ZetupPipError as exc:
+    ...     exc.command, exc.exit_code
+    (['invalid'], 1)
+    """
+
+    def __init__(self, command, exit_code):
+        """
+        Compose error message from `command` list and `exit_code`.
+
+        Also store the parameters as exception object attributes
+        """
+        ZetupError.__init__(
+            self, "pip command {!r} failed with exit code {:d}".format(
+                command, exit_code))
+        self.command = command
+        self.exit_code = exit_code
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/zetup/pip/test_errors.py 
new/zetup-0.2.45/zetup/pip/test_errors.py
--- old/zetup-0.2.43/zetup/pip/test_errors.py   1970-01-01 01:00:00.000000000 
+0100
+++ new/zetup-0.2.45/zetup/pip/test_errors.py   2018-09-17 21:33:58.000000000 
+0200
@@ -0,0 +1,27 @@
+"""Test :mod:`zetup.pip.errors`."""
+
+import zetup
+from zetup.pip.errors import ZetupPipError
+
+
+class TestZetupPipError:
+    """Test :exc:`zetup.pip.errors.ZetupPipError`."""
+
+    def test_toplevel_export(self):
+        """Should be properly provided by :mod:`zetup` API."""
+        assert zetup.ZetupPipError is ZetupPipError
+
+    def test_superclass(self):
+        """Should be derived from :exc:`zetup.ZetupError`."""
+        assert issubclass(ZetupPipError, zetup.ZetupError)
+
+    def test_attributes(self):
+        """Should store ``.command`` and ``.exit_code``."""
+        exc = ZetupPipError(['invalid'], 1)
+        assert exc.command == ['invalid']
+        assert exc.exit_code is 1
+
+    def test_message(self):
+        """Should provide correct exception message."""
+        exc = ZetupPipError(['invalid'], 1)
+        assert str(exc) == "pip command ['invalid'] failed with exit code 1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/zetup/requires.py 
new/zetup-0.2.45/zetup/requires.py
--- old/zetup-0.2.43/zetup/requires.py  2018-04-18 17:15:38.000000000 +0200
+++ new/zetup-0.2.45/zetup/requires.py  2018-09-17 21:33:58.000000000 +0200
@@ -17,19 +17,37 @@
 # You should have received a copy of the GNU Lesser General Public License
 # along with zetup.py. If not, see <http://www.gnu.org/licenses/>.
 
-__all__ = ['Requirements', 'DistributionNotFound', 'VersionConflict']
-
+import re
 import sys
+from textwrap import dedent
+
+import pkg_resources
+from pkg_resources import (
+    parse_requirements, Requirement, get_distribution)
+
+__all__ = (
+    'DistributionNotFound',
+    'Requirements',
+    'VersionConflict',
+    'requires',
+)
+
+
 if sys.version_info[0] == 3:
     unicode = str
-import re
 
-import pkg_resources
-from pkg_resources import \
-    parse_requirements, Requirement, get_distribution
+
+def howto_install_requirement(req):
+    """Create a message explaining how to install `req`"""
+    return dedent("""
+    You can install the needed requirement with the following shell command:
+
+        pip install "{}"
+    """.format(req)).strip()
 
 
 class DistributionNotFound(pkg_resources.DistributionNotFound):
+
     def __init__(self, req, requirer, reason=None):
         super(DistributionNotFound, self).__init__(req, [requirer])
         self.requirer = requirer
@@ -39,38 +57,43 @@
         text = "%s needs %s" % (self.requirer, self.req)
         if self.reason:
             text += " (%s)" % self.reason
-        return text
+        return "{}\n\n{}".format(text, howto_install_requirement(self.req))
 
 
 class VersionConflict(pkg_resources.VersionConflict):
+
     def __init__(self, req, found_version, requirer, reason=None):
         super(VersionConflict, self).__init__(
-          '%s-%s' % (req.key, found_version), req)
+            '%s-%s' % (req.key, found_version), req)
         self.requirer = requirer
         self.reason = reason
 
     def __str__(self):
         text = "%s needs %s but found %s" % (
-          self.requirer, self.req, self.dist)
+            self.requirer, self.req, self.dist)
         if self.reason:
             text += " (%s)" % self.reason
-        return text
+        return "{}\n\n{}".format(text, howto_install_requirement(self.req))
 
 
 class Requirements(object):
-    """Package requirements manager.
-    """
+    """Package requirements manager."""
+
     @staticmethod
     def _parse(text):
-        """ Generate parsed requirements from `text`,
-            which should contain newline separated requirement specs.
+        """
+        Generate parsed requirements from `text`.
+        
+        :param text:
+            Newline-separated requirement specs
+
+        Additionally looks for ``#import name`` comments after requirement
+        lines (the actual root module name of the required package to use for
+        runtime dependency checks) and stores them as .impname attrs on the
+        Requirement instances.
 
-        - Additionally looks for "#import name" comments
-          after requirement lines (the actual root module name
-          of the required package to use for runtime dependency checks)
-          and stores them as .impname attrs on the Requirement instances.
-        - Supports #py.. tags at the beginning of lines,
-          specifying a python version the requirement applies to.
+        Supports ``#py..`` tags at the beginning of lines, specifying a python
+        version the requirement applies to.
         """
         for line in text.split('\n'):
             line = line.strip()
@@ -96,16 +119,17 @@
             yield req
 
     def __init__(self, reqs, zfg=None):
-        """Store a list of :class:`pkg_resources.Requirement` instances
-           from the given requirement specs
-           and additionally store them newline separated
-           in the :class:`str` base.
-
-        :param reqs: Either a single string of requirement specs
-          or a sequence of strings and/or
-          :class:`pkg_resources.Requirement` instances.
-        :param zfg: Optional zetup config object
-          the requirements are related to.
+        """
+        Generate a list of :class:`pkg_resources.Requirement` instances.
+
+        And additionally store all requirement specs as text with newline
+        separators in the ``str`` base
+
+        :param reqs:
+            Either a single string of requirement specs or a sequence of
+            strings and/or :class:`pkg_resources.Requirement` instances
+        :param zfg:
+            Optional zetup config object the requirements are related to
         """
         if isinstance(reqs, Requirements):
             txt = reqs.txt
@@ -130,57 +154,80 @@
         self.zfg = zfg
 
     def __eq__(self, other):
-        return isinstance(other, Requirements) \
-          and self._list == other._list
+        return isinstance(other, Requirements) and (
+            self._list == other._list)
 
     def __str__(self):
         return '\n'.join(map(str, self))
 
-    def check(self, raise_=True):
-        """Check that all requirements are available (importable)
-           and their versions match (using modules' __version__ attributes).
-
-        - Fallback to pkg_resources.get_distribution().version
-          if no __version__ or is None.
-
-        :param raise_: Raise DistributionNotFound if ImportError
-          or VersionConflict if version doesn't match?
-          If False just return False in that case.
+    def check(self, requirer=None, raise_=True):
+        """
+        Check that all requirements are satisfied.
+        
+        They must be importable and their versions (from their top-level
+        package modules' ``.__version__`` attributes) must match the required
+        specs
+
+        Falls back to ``pkg_resources.get_distribution().version`` if
+        ``.__version__`` is missing or ``None``
+
+        :param requirer:
+            Optional identification string of the requirer
+        :param raise_:
+            If set to ``False`` then also just return ``False`` on unsatisfied
+            requirements instead of raising exceptions
+
+        :raises zetup.DistributionNotFound:
+            If a requirement can't be imported
+        :raises zetup.VersionConflict:
+            If a required version doesn't match
         """
-        requirer = self.zfg and '%s-%s' % (
-          self.zfg.NAME, self.zfg.VERSION or '(none)')
+        if requirer is None:
+            requirer = self.zfg and '%s-%s' % (
+                self.zfg.NAME, self.zfg.VERSION or '(none)')
         for req in self:
             try:
                 mod = __import__(req.impname)
             except ImportError as e:
                 if raise_:
-                    raise DistributionNotFound(req, requirer,
-                      reason="%s: %s" % (type(e).__name__, e))
+                    raise DistributionNotFound(
+                        req, requirer,
+                        reason="%s: %s" % (type(e).__name__, e))
+
                 return False
-            if not req.specs: # No version constraints
+
+            if not req.specs:  # No version constraints
                 continue
+
             try:
                 version = mod.__version__
-                if version is None:
+                if version is None or version == 'unknown':
                     # treat the same as if .__version__ not exists
                     # (handle in following except block)
                     # ==> same exception, just different msg
                     raise AttributeError(
-                      "module's '__version__' attribute is None")
+                        "module's '__version__' attribute is {!r}"
+                        .format(version))
+
             except AttributeError as e_no__version__attr:
-                try: # try to get version from distribution
+                try:  # try to get version from distribution
                     dist = get_distribution(req.key)
                 except pkg_resources.DistributionNotFound as e:
                     if raise_:
-                        raise VersionConflict(req, None, requirer,
-                          reason="%s: %s. %s: %s" % (
-                            e_no__version__attr, mod, type(e).__name__, e))
+                        raise VersionConflict(
+                            req, None, requirer,
+                            reason="%s: %s. %s: %s" % (
+                                e_no__version__attr, mod, type(e).__name__, e))
+
                     return False
+
                 version = dist.version
             if version not in req:
                 if raise_:
                     raise VersionConflict(req, version, requirer)
+
                 return False
+
         return True
 
     @property
@@ -192,25 +239,28 @@
         return iter(self._list)
 
     def __getitem__(self, name):
-        """Get a requirement by its distribution name.
-        """
+        """Get a requirement by its distribution name."""
         for req in self._list:
             if name in [req.key, req.unsafe_name]:
                 return req
+
         raise KeyError(name)
 
     def __delitem__(self, name):
-        """Delete a requirement by its distribution name.
-        """
+        """Delete a requirement by its distribution name."""
         for req in list(self._list):
             if name in [req.key, req.unsafe_name]:
                 return self._list.remove(req)
+
         raise KeyError(name)
 
     def __add__(self, reqs):
-        """Return a new :class:`Requirements` instance
-           with additional `reqs` from string
-           or another :class:`Requirements` instance.
+        """
+        Create a new :class:`Requirements` instance.
+
+        :param reqs:
+            Additional requirements as string or another :class:`Requirements`
+            instance
         """
         if isinstance(reqs, Requirements):
             reqs = reqs.txt
@@ -223,8 +273,44 @@
     @property
     def py(self):
         return '%s("""\n%s\n""", zfg=zfg)' % (
-          type(self).__name__, self.txt) ## '\n'.join(
-            ## '%s #import %s' % (req, req.impname) for req in self))
+            type(self).__name__, self.txt)  # '\n'.join(
+            # '%s #import %s' % (req, req.impname) for req in self))
 
     def __repr__(self):
         return str(self)
+
+
+def requires(reqs):
+    """
+    Create a function decorator for defining extra requirements.
+
+    :param reqs:
+        Requirement specs compatible with :class:`zetup.Requirements` 
+
+    >>> @requires("not-installed")
+    ... def func():
+    ...     pass
+
+    The actual requirements check happens only on calling the function:
+
+    >>> func()  # doctest: +IGNORE_EXCEPTION_DETAIL
+    Traceback (most recent call last):
+    ...
+    DistributionNotFound: ...func() needs not-installed ...
+    <BLANKLINE>
+    You can install the needed requirement with the following shell command:
+    <BLANKLINE>
+        pip install "not-installed"
+    """
+    reqs = Requirements(reqs)
+
+    def deco(func):
+
+        def caller(*args, **kwargs):
+            reqs.check(requirer='{}.{}()'.format(
+                func.__module__, func.__name__))
+            return func(*args, **kwargs)
+
+        return caller
+
+    return deco
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/zetup/zetup_config.py 
new/zetup-0.2.45/zetup/zetup_config.py
--- old/zetup-0.2.43/zetup/zetup_config.py      2018-04-18 17:16:50.000000000 
+0200
+++ new/zetup-0.2.45/zetup/zetup_config.py      2018-09-17 21:35:09.000000000 
+0200
@@ -110,6 +110,18 @@
   )
 ,
     
+Package('zetup.pip',
+  sources=[
+    '__init__.py',
+    'errors.py',
+    'test_errors.py'
+    ],
+  subpackages=[
+    
+    ],
+  )
+,
+    
 Package('zetup.process',
   sources=[
     '__init__.py',
@@ -131,23 +143,23 @@
 
 EXTRAS = Extras([
 ('commands', Requirements("""
-path.py >= 10.3 #import path
+path.py >= 11.1 #import path
 jinjatools >= 0.1.7
 
 """)),
 ('notebook', Requirements("""
-path.py >= 10.3 #import path
-nbconvert >= 5.2
+path.py >= 11.1 #import path
+nbconvert >= 5.4
 
 """)),
 ('pytest', Requirements("""
-pytest
+pytest >= 3.8
 
 """))
 ], zfg=zfg)
 
 REQUIRES = Requirements("""
-setuptools_scm >= 2.0
+setuptools_scm >= 3.1
 
 """, zfg=zfg)
 
@@ -155,7 +167,7 @@
 
 SETUP_HOOKS = []
 
-VERSION = Version('0.2.43')
+VERSION = Version('0.2.45')
 
 KEEP_MADE = []
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/zetup.egg-info/PKG-INFO 
new/zetup-0.2.45/zetup.egg-info/PKG-INFO
--- old/zetup-0.2.43/zetup.egg-info/PKG-INFO    2018-04-18 17:16:50.000000000 
+0200
+++ new/zetup-0.2.45/zetup.egg-info/PKG-INFO    2018-09-17 21:35:09.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: zetup
-Version: 0.2.43
+Version: 0.2.45
 Summary: Zimmermann's Extensible Tools for Unified Project_setups
 Home-page: https://github.com/zimmermanncode/zetup
 Author: Stefan Zimmermann
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/zetup.egg-info/SOURCES.txt 
new/zetup-0.2.45/zetup.egg-info/SOURCES.txt
--- old/zetup-0.2.43/zetup.egg-info/SOURCES.txt 2018-04-18 17:16:50.000000000 
+0200
+++ new/zetup-0.2.45/zetup.egg-info/SOURCES.txt 2018-09-17 21:35:10.000000000 
+0200
@@ -62,5 +62,8 @@
 zetup/doc/__init__.py
 zetup/notebook/__init__.py
 zetup/notebook/jinja.py
+zetup/pip/__init__.py
+zetup/pip/errors.py
+zetup/pip/test_errors.py
 zetup/process/__init__.py
 zetup/process/scons.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zetup-0.2.43/zetup.egg-info/requires.txt 
new/zetup-0.2.45/zetup.egg-info/requires.txt
--- old/zetup-0.2.43/zetup.egg-info/requires.txt        2018-04-18 
17:16:50.000000000 +0200
+++ new/zetup-0.2.45/zetup.egg-info/requires.txt        2018-09-17 
21:35:09.000000000 +0200
@@ -1,19 +1,19 @@
-setuptools_scm>=2.0
+setuptools_scm>=3.1
 
 [all]
-path.py>=10.3
+path.py>=11.1
 jinjatools>=0.1.7
-path.py>=10.3
-nbconvert>=5.2
-pytest
+path.py>=11.1
+nbconvert>=5.4
+pytest>=3.8
 
 [commands]
-path.py>=10.3
+path.py>=11.1
 jinjatools>=0.1.7
 
 [notebook]
-path.py>=10.3
-nbconvert>=5.2
+path.py>=11.1
+nbconvert>=5.4
 
 [pytest]
-pytest
+pytest>=3.8


Reply via email to