Hello community,

here is the log from the commit of package python-jaraco.classes for 
openSUSE:Leap:15.2 checked in at 2020-04-21 19:06:56
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Leap:15.2/python-jaraco.classes (Old)
 and      /work/SRC/openSUSE:Leap:15.2/.python-jaraco.classes.new.2738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-jaraco.classes"

Tue Apr 21 19:06:56 2020 rev:2 rq:795797 version:3.1.0

Changes:
--------
--- 
/work/SRC/openSUSE:Leap:15.2/python-jaraco.classes/python-jaraco.classes.changes
    2020-03-27 16:48:28.527945709 +0100
+++ 
/work/SRC/openSUSE:Leap:15.2/.python-jaraco.classes.new.2738/python-jaraco.classes.changes
  2020-04-21 19:08:31.284110806 +0200
@@ -1,0 +2,7 @@
+Mon Mar  9 12:42:34 UTC 2020 - Marketa Calabkova <[email protected]>
+
+- Update to 3.1.0
+  * Project now requires Python 3.6 or later.
+  * classproperty decorator now supplies a classproperty.Meta class.
+
+-------------------------------------------------------------------

Old:
----
  jaraco.classes-2.0.tar.gz

New:
----
  jaraco.classes-3.1.0.tar.gz

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

Other differences:
------------------
++++++ python-jaraco.classes.spec ++++++
--- /var/tmp/diff_new_pack.oEDy4A/_old  2020-04-21 19:08:32.152112573 +0200
+++ /var/tmp/diff_new_pack.oEDy4A/_new  2020-04-21 19:08:32.156112581 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-jaraco.classes
 #
-# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2020 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -17,8 +17,9 @@
 
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
+%define skip_python2 1
 Name:           python-jaraco.classes
-Version:        2.0
+Version:        3.1.0
 Release:        0
 Summary:        Tools to work with classes
 License:        MIT
@@ -28,11 +29,9 @@
 BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module setuptools_scm}
 BuildRequires:  %{python_module setuptools}
-BuildRequires:  %{python_module six}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 Requires:       python-jaraco.base >= 6.1
-Requires:       python-six
 BuildArch:      noarch
 %python_subpackages
 
@@ -42,6 +41,7 @@
 %prep
 %setup -q -n jaraco.classes-%{version}
 sed -i 's/--flake8//' pytest.ini
+sed -i 's/--black --cov//' pytest.ini
 
 %build
 %python_build
@@ -55,9 +55,7 @@
 }
 
 %check
-%{python_expand py.test-%{$python_bin_suffix} \
-  --ignore=_build.python3 --ignore _build.python2
-}
+%pytest
 
 %files %{python_files}
 %license LICENSE

++++++ jaraco.classes-2.0.tar.gz -> jaraco.classes-3.1.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/.coveragerc 
new/jaraco.classes-3.1.0/.coveragerc
--- old/jaraco.classes-2.0/.coveragerc  1970-01-01 01:00:00.000000000 +0100
+++ new/jaraco.classes-3.1.0/.coveragerc        2019-12-19 20:32:10.000000000 
+0100
@@ -0,0 +1,5 @@
+[run]
+omit = .tox/*
+
+[report]
+show_missing = True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/.flake8 
new/jaraco.classes-3.1.0/.flake8
--- old/jaraco.classes-2.0/.flake8      2019-01-01 15:23:21.000000000 +0100
+++ new/jaraco.classes-3.1.0/.flake8    2019-12-19 20:32:10.000000000 +0100
@@ -1,8 +1,9 @@
 [flake8]
+max-line-length = 88
 ignore =
-       # Allow tabs for indentation
-       W191
        # W503 violates spec https://github.com/PyCQA/pycodestyle/issues/513
        W503
        # W504 has issues 
https://github.com/OCA/maintainer-quality-tools/issues/545
        W504
+       # Black creates whitespace before colon
+       E203
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/.pre-commit-config.yaml 
new/jaraco.classes-3.1.0/.pre-commit-config.yaml
--- old/jaraco.classes-2.0/.pre-commit-config.yaml      1970-01-01 
01:00:00.000000000 +0100
+++ new/jaraco.classes-3.1.0/.pre-commit-config.yaml    2019-12-19 
20:32:10.000000000 +0100
@@ -0,0 +1,5 @@
+repos:
+- repo: https://github.com/psf/black
+  rev: 19.3b0
+  hooks:
+  - id: black
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/.travis.yml 
new/jaraco.classes-3.1.0/.travis.yml
--- old/jaraco.classes-2.0/.travis.yml  2019-01-01 15:23:21.000000000 +0100
+++ new/jaraco.classes-3.1.0/.travis.yml        2019-12-19 20:32:10.000000000 
+0100
@@ -1,11 +1,9 @@
 dist: xenial
-sudo: false
 language: python
 
 python:
-- 2.7
 - 3.6
-- &latest_py3 3.7
+- &latest_py3 3.8
 
 jobs:
   fast_finish: true
@@ -14,11 +12,6 @@
     if: tag IS present
     python: *latest_py3
     before_script: skip
-    env:
-    - TWINE_USERNAME=jaraco
-    # TWINE_PASSWORD
-    - secure: 
t5k9gj0XMF0WBa/LnbmbCxC6A7iD+FGojE+FRGibNIjUoC0qWRnytPJcsnb0ZMEUby8NY/8LJ2yQF+TUZenLkzHfVe1091748OpmaT44m50hCQGPJZOw7AY8qzUW0UONMlHoDlbjSI59Zwv9I83Shx/tk7F8PjHrWBY+SpFvNeXbFeHC5YyoSchfZ0pc62jx37oY+yZ8c2oitpJbSRZFR1qZD8Jh3qkeTLyWQO8k0vBggBblz8YQF0Yj0X5F+a5f5QURoFeSGy35JSDo/XFcn60vdgpK24YfAOeAJP4xb4+kHBh14GthKm+Ju692NVezGGrVdgkzRo/vh6SxbxknZrRWjwZOqsUEALWjNW2LH1BOKorjawB02uIVPHyFCJM/BotBDvOc8OsungS6T7E9v6KMTOk0yYoqPUdOohU1ZqBr3JqTpEkISgUcAP5fXgdnmZOBTLuilcAjMPXKq5mvn8Erh6WmH4/ductwsi8NN1dEptM/vvNamUEIfRDp1gBid+5BGigcy6HlGj1w3sarOg2u2fjhTeUOYFWDYOwytlLsTPX4fmbXGcA3Bz4xzBnVFkx2CO2l5kti/GTzY1gsV1EpuEKSLkrSyWjC8A8mpruXrq3Qw4gt5z2LmW/PCVb1qgu5v1qK7HmoR23z2XoVm03g9wcl50BbOI3s8gnhXak=
-    - TOX_TESTENV_PASSENV="TWINE_USERNAME TWINE_PASSWORD"
     script: tox -e release
 
 cache: pip
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/CHANGES.rst 
new/jaraco.classes-3.1.0/CHANGES.rst
--- old/jaraco.classes-2.0/CHANGES.rst  2019-01-01 15:23:21.000000000 +0100
+++ new/jaraco.classes-3.1.0/CHANGES.rst        2019-12-19 20:32:10.000000000 
+0100
@@ -1,3 +1,18 @@
+v3.1.0
+======
+
+``classproperty`` decorator now supplies a
+``classproperty.Meta`` class. Classes that wish to have
+a class property should derive from that metaclass. This
+approach solves the unintended behavior of the property
+only being set on a given instance. For compatibility, the
+old behavior is retained if the metaclass is not used.
+
+v3.0.0
+======
+
+Project now requires Python 3.6 or later.
+
 2.0
 ===
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/PKG-INFO 
new/jaraco.classes-3.1.0/PKG-INFO
--- old/jaraco.classes-2.0/PKG-INFO     2019-01-01 15:23:42.000000000 +0100
+++ new/jaraco.classes-3.1.0/PKG-INFO   2019-12-19 20:32:33.492063300 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: jaraco.classes
-Version: 2.0
+Version: 3.1.0
 Summary: Utility functions for Python class constructs
 Home-page: https://github.com/jaraco/jaraco.classes
 Author: Jason R. Coombs
@@ -14,6 +14,10 @@
         .. image:: 
https://img.shields.io/travis/jaraco/jaraco.classes/master.svg
            :target: https://travis-ci.org/jaraco/jaraco.classes
         
+        .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+           :target: https://github.com/psf/black
+           :alt: Code style: Black
+        
         .. .. image:: 
https://img.shields.io/appveyor/ci/jaraco/skeleton/master.svg
         ..    :target: 
https://ci.appveyor.com/project/jaraco/skeleton/branch/master
         
@@ -24,8 +28,7 @@
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: MIT License
-Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
-Requires-Python: >=2.7
-Provides-Extra: docs
+Requires-Python: >=3.6
 Provides-Extra: testing
+Provides-Extra: docs
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/README.rst 
new/jaraco.classes-3.1.0/README.rst
--- old/jaraco.classes-2.0/README.rst   2019-01-01 15:23:21.000000000 +0100
+++ new/jaraco.classes-3.1.0/README.rst 2019-12-19 20:32:10.000000000 +0100
@@ -6,6 +6,10 @@
 .. image:: https://img.shields.io/travis/jaraco/jaraco.classes/master.svg
    :target: https://travis-ci.org/jaraco/jaraco.classes
 
+.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+   :target: https://github.com/psf/black
+   :alt: Code style: Black
+
 .. .. image:: https://img.shields.io/appveyor/ci/jaraco/skeleton/master.svg
 ..    :target: https://ci.appveyor.com/project/jaraco/skeleton/branch/master
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/appveyor.yml 
new/jaraco.classes-3.1.0/appveyor.yml
--- old/jaraco.classes-2.0/appveyor.yml 2019-01-01 15:23:21.000000000 +0100
+++ new/jaraco.classes-3.1.0/appveyor.yml       2019-12-19 20:32:10.000000000 
+0100
@@ -4,7 +4,7 @@
 
   matrix:
     - PYTHON: "C:\\Python36-x64"
-    - PYTHON: "C:\\Python27-x64"
+    - PYTHON: "C:\\Python38-x64"
 
 install:
   # symlink python from a directory with a space
@@ -18,7 +18,7 @@
   - '%LOCALAPPDATA%\pip\Cache'
 
 test_script:
-  - "python -m pip install tox tox-venv"
+  - "python -m pip install -U tox tox-venv virtualenv"
   - "tox"
 
 version: '{build}'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/docs/conf.py 
new/jaraco.classes-3.1.0/docs/conf.py
--- old/jaraco.classes-2.0/docs/conf.py 2019-01-01 15:23:21.000000000 +0100
+++ new/jaraco.classes-3.1.0/docs/conf.py       2019-12-19 20:32:10.000000000 
+0100
@@ -1,25 +1,25 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
-extensions = ["sphinx.ext.autodoc", "jaraco.packaging.sphinx", "rst.linker"]
+extensions = ['sphinx.ext.autodoc', 'jaraco.packaging.sphinx', 'rst.linker']
 
 master_doc = "index"
 
 link_files = {
-    "../CHANGES.rst": dict(
-        using=dict(GH="https://github.com";),
+    '../CHANGES.rst': dict(
+        using=dict(GH='https://github.com'),
         replace=[
             dict(
-                pattern=r"(Issue #|\B#)(?P<issue>\d+)",
-                url="{package_url}/issues/{issue}",
+                pattern=r'(Issue #|\B#)(?P<issue>\d+)',
+                url='{package_url}/issues/{issue}',
             ),
             dict(
-                pattern=r"^(?m)((?P<scm_version>v?\d+(\.\d+){1,2}))\n[-=]+\n",
-                with_scm="{text}\n{rev[timestamp]:%d %b %Y}\n",
+                pattern=r'^(?m)((?P<scm_version>v?\d+(\.\d+){1,2}))\n[-=]+\n',
+                with_scm='{text}\n{rev[timestamp]:%d %b %Y}\n',
             ),
             dict(
-                pattern=r"PEP[- ](?P<pep_number>\d+)",
-                url="https://www.python.org/dev/peps/pep-{pep_number:0>4}/",
+                pattern=r'PEP[- ](?P<pep_number>\d+)',
+                url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/',
             ),
         ],
     )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/jaraco/classes/ancestry.py 
new/jaraco.classes-3.1.0/jaraco/classes/ancestry.py
--- old/jaraco.classes-2.0/jaraco/classes/ancestry.py   2019-01-01 
15:23:21.000000000 +0100
+++ new/jaraco.classes-3.1.0/jaraco/classes/ancestry.py 2019-12-19 
20:32:10.000000000 +0100
@@ -3,73 +3,66 @@
 of an object and its parent classes.
 """
 
-from __future__ import unicode_literals
+from more_itertools import unique_everseen
 
 
 def all_bases(c):
-       """
-       return a tuple of all base classes the class c has as a parent.
-       >>> object in all_bases(list)
-       True
-       """
-       return c.mro()[1:]
+    """
+    return a tuple of all base classes the class c has as a parent.
+    >>> object in all_bases(list)
+    True
+    """
+    return c.mro()[1:]
 
 
 def all_classes(c):
-       """
-       return a tuple of all classes to which c belongs
-       >>> list in all_classes(list)
-       True
-       """
-       return c.mro()
+    """
+    return a tuple of all classes to which c belongs
+    >>> list in all_classes(list)
+    True
+    """
+    return c.mro()
+
 
 # borrowed from
 # 
http://code.activestate.com/recipes/576949-find-all-subclasses-of-a-given-class/
 
 
-def iter_subclasses(cls, _seen=None):
-       """
-       Generator over all subclasses of a given class, in depth-first order.
-
-       >>> bool in list(iter_subclasses(int))
-       True
-       >>> class A(object): pass
-       >>> class B(A): pass
-       >>> class C(A): pass
-       >>> class D(B,C): pass
-       >>> class E(D): pass
-       >>>
-       >>> for cls in iter_subclasses(A):
-       ...             print(cls.__name__)
-       B
-       D
-       E
-       C
-       >>> # get ALL (new-style) classes currently defined
-       >>> res = [cls.__name__ for cls in iter_subclasses(object)]
-       >>> 'type' in res
-       True
-       >>> 'tuple' in res
-       True
-       >>> len(res) > 100
-       True
-       """
-
-       if not isinstance(cls, type):
-               raise TypeError(
-                       'iter_subclasses must be called with '
-                       'new-style classes, not %.100r' % cls
-               )
-       if _seen is None:
-               _seen = set()
-       try:
-               subs = cls.__subclasses__()
-       except TypeError:  # fails only when cls is type
-               subs = cls.__subclasses__(cls)
-       for sub in subs:
-               if sub in _seen:
-                       continue
-               _seen.add(sub)
-               yield sub
-               for sub in iter_subclasses(sub, _seen):
-                       yield sub
+def iter_subclasses(cls):
+    """
+    Generator over all subclasses of a given class, in depth-first order.
+
+    >>> bool in list(iter_subclasses(int))
+    True
+    >>> class A(object): pass
+    >>> class B(A): pass
+    >>> class C(A): pass
+    >>> class D(B,C): pass
+    >>> class E(D): pass
+    >>>
+    >>> for cls in iter_subclasses(A):
+    ...                print(cls.__name__)
+    B
+    D
+    E
+    C
+    >>> # get ALL classes currently defined
+    >>> res = [cls.__name__ for cls in iter_subclasses(object)]
+    >>> 'type' in res
+    True
+    >>> 'tuple' in res
+    True
+    >>> len(res) > 100
+    True
+    """
+    return unique_everseen(_iter_all_subclasses(cls))
+
+
+def _iter_all_subclasses(cls):
+    try:
+        subs = cls.__subclasses__()
+    except TypeError:  # fails only when cls is type
+        subs = cls.__subclasses__(cls)
+    for sub in subs:
+        yield sub
+        yield from iter_subclasses(sub)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/jaraco/classes/meta.py 
new/jaraco.classes-3.1.0/jaraco/classes/meta.py
--- old/jaraco.classes-2.0/jaraco/classes/meta.py       2019-01-01 
15:23:21.000000000 +0100
+++ new/jaraco.classes-3.1.0/jaraco/classes/meta.py     2019-12-19 
20:32:10.000000000 +0100
@@ -4,38 +4,61 @@
 Some useful metaclasses.
 """
 
-from __future__ import unicode_literals
-
 
 class LeafClassesMeta(type):
-       """
-       A metaclass for classes that keeps track of all of them that
-       aren't base classes.
-       """
-
-       _leaf_classes = set()
-
-       def __init__(cls, name, bases, attrs):
-               if not hasattr(cls, '_leaf_classes'):
-                       cls._leaf_classes = set()
-               leaf_classes = getattr(cls, '_leaf_classes')
-               leaf_classes.add(cls)
-               # remove any base classes
-               leaf_classes -= set(bases)
+    """
+    A metaclass for classes that keeps track of all of them that
+    aren't base classes.
+
+    >>> Parent = LeafClassesMeta('MyParentClass', (), {})
+    >>> Parent in Parent._leaf_classes
+    True
+    >>> Child = LeafClassesMeta('MyChildClass', (Parent,), {})
+    >>> Child in Parent._leaf_classes
+    True
+    >>> Parent in Parent._leaf_classes
+    False
+
+    >>> Other = LeafClassesMeta('OtherClass', (), {})
+    >>> Parent in Other._leaf_classes
+    False
+    >>> len(Other._leaf_classes)
+    1
+    """
+
+    def __init__(cls, name, bases, attrs):
+        if not hasattr(cls, '_leaf_classes'):
+            cls._leaf_classes = set()
+        leaf_classes = getattr(cls, '_leaf_classes')
+        leaf_classes.add(cls)
+        # remove any base classes
+        leaf_classes -= set(bases)
 
 
 class TagRegistered(type):
-       """
-       As classes of this metaclass are created, they keep a registry in the
-       base class of all classes by a class attribute, indicated by attr_name.
-       """
-       attr_name = 'tag'
-
-       def __init__(cls, name, bases, namespace):
-               super(TagRegistered, cls).__init__(name, bases, namespace)
-               if not hasattr(cls, '_registry'):
-                       cls._registry = {}
-               meta = cls.__class__
-               attr = getattr(cls, meta.attr_name, None)
-               if attr:
-                       cls._registry[attr] = cls
+    """
+    As classes of this metaclass are created, they keep a registry in the
+    base class of all classes by a class attribute, indicated by attr_name.
+
+    >>> FooObject = TagRegistered('FooObject', (), dict(tag='foo'))
+    >>> FooObject._registry['foo'] is FooObject
+    True
+    >>> BarObject = TagRegistered('Barobject', (FooObject,), dict(tag='bar'))
+    >>> FooObject._registry is BarObject._registry
+    True
+    >>> len(FooObject._registry)
+    2
+    >>> FooObject._registry['bar']
+    <class 'jaraco.classes.meta.Barobject'>
+    """
+
+    attr_name = 'tag'
+
+    def __init__(cls, name, bases, namespace):
+        super(TagRegistered, cls).__init__(name, bases, namespace)
+        if not hasattr(cls, '_registry'):
+            cls._registry = {}
+        meta = cls.__class__
+        attr = getattr(cls, meta.attr_name, None)
+        if attr:
+            cls._registry[attr] = cls
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/jaraco/classes/properties.py 
new/jaraco.classes-3.1.0/jaraco/classes/properties.py
--- old/jaraco.classes-2.0/jaraco/classes/properties.py 2019-01-01 
15:23:21.000000000 +0100
+++ new/jaraco.classes-3.1.0/jaraco/classes/properties.py       2019-12-19 
20:32:10.000000000 +0100
@@ -1,67 +1,169 @@
-from __future__ import unicode_literals
-
-import six
-
-__metaclass__ = type
-
-
 class NonDataProperty:
-       """Much like the property builtin, but only implements __get__,
-       making it a non-data property, and can be subsequently reset.
-
-       See http://users.rcn.com/python/download/Descriptor.htm for more
-       information.
+    """Much like the property builtin, but only implements __get__,
+    making it a non-data property, and can be subsequently reset.
 
-       >>> class X(object):
-       ...   @NonDataProperty
-       ...   def foo(self):
-       ...     return 3
-       >>> x = X()
-       >>> x.foo
-       3
-       >>> x.foo = 4
-       >>> x.foo
-       4
-       """
-
-       def __init__(self, fget):
-               assert fget is not None, "fget cannot be none"
-               assert six.callable(fget), "fget must be callable"
-               self.fget = fget
-
-       def __get__(self, obj, objtype=None):
-               if obj is None:
-                       return self
-               return self.fget(obj)
-
-
-# from http://stackoverflow.com/a/5191224
-class ClassPropertyDescriptor:
-
-       def __init__(self, fget, fset=None):
-               self.fget = fget
-               self.fset = fset
-
-       def __get__(self, obj, klass=None):
-               if klass is None:
-                       klass = type(obj)
-               return self.fget.__get__(obj, klass)()
-
-       def __set__(self, obj, value):
-               if not self.fset:
-                       raise AttributeError("can't set attribute")
-               type_ = type(obj)
-               return self.fset.__get__(obj, type_)(value)
-
-       def setter(self, func):
-               if not isinstance(func, (classmethod, staticmethod)):
-                       func = classmethod(func)
-               self.fset = func
-               return self
-
-
-def classproperty(func):
-       if not isinstance(func, (classmethod, staticmethod)):
-               func = classmethod(func)
+    See http://users.rcn.com/python/download/Descriptor.htm for more
+    information.
 
-       return ClassPropertyDescriptor(func)
+    >>> class X(object):
+    ...   @NonDataProperty
+    ...   def foo(self):
+    ...     return 3
+    >>> x = X()
+    >>> x.foo
+    3
+    >>> x.foo = 4
+    >>> x.foo
+    4
+    >>> X.foo
+    <jaraco.classes.properties.NonDataProperty object at ...>
+    """
+
+    def __init__(self, fget):
+        assert fget is not None, "fget cannot be none"
+        assert callable(fget), "fget must be callable"
+        self.fget = fget
+
+    def __get__(self, obj, objtype=None):
+        if obj is None:
+            return self
+        return self.fget(obj)
+
+
+class classproperty:
+    """
+    Like @property but applies at the class level.
+
+
+    >>> class X(metaclass=classproperty.Meta):
+    ...   val = None
+    ...   @classproperty
+    ...   def foo(cls):
+    ...     return cls.val
+    ...   @foo.setter
+    ...   def foo(cls, val):
+    ...     cls.val = val
+    >>> X.foo
+    >>> X.foo = 3
+    >>> X.foo
+    3
+    >>> x = X()
+    >>> x.foo
+    3
+    >>> X.foo = 4
+    >>> x.foo
+    4
+
+    Setting the property on an instance affects the class.
+
+    >>> x.foo = 5
+    >>> x.foo
+    5
+    >>> X.foo
+    5
+    >>> vars(x)
+    {}
+    >>> X().foo
+    5
+
+    Attempting to set an attribute where no setter was defined
+    results in an AttributeError:
+
+    >>> class GetOnly(metaclass=classproperty.Meta):
+    ...   @classproperty
+    ...   def foo(cls):
+    ...     return 'bar'
+    >>> GetOnly.foo = 3
+    Traceback (most recent call last):
+    ...
+    AttributeError: can't set attribute
+
+    It is also possible to wrap a classmethod or staticmethod in
+    a classproperty.
+
+    >>> class Static(metaclass=classproperty.Meta):
+    ...   @classproperty
+    ...   @classmethod
+    ...   def foo(cls):
+    ...     return 'foo'
+    ...   @classproperty
+    ...   @staticmethod
+    ...   def bar():
+    ...     return 'bar'
+    >>> Static.foo
+    'foo'
+    >>> Static.bar
+    'bar'
+
+    *Legacy*
+
+    For compatibility, if the metaclass isn't specified, the
+    legacy behavior will be invoked.
+
+    >>> class X:
+    ...   val = None
+    ...   @classproperty
+    ...   def foo(cls):
+    ...     return cls.val
+    ...   @foo.setter
+    ...   def foo(cls, val):
+    ...     cls.val = val
+    >>> X.foo
+    >>> X.foo = 3
+    >>> X.foo
+    3
+    >>> x = X()
+    >>> x.foo
+    3
+    >>> X.foo = 4
+    >>> x.foo
+    4
+
+    Note, because the metaclass was not specified, setting
+    a value on an instance does not have the intended effect.
+
+    >>> x.foo = 5
+    >>> x.foo
+    5
+    >>> X.foo  # should be 5
+    4
+    >>> vars(x)  # should be empty
+    {'foo': 5}
+    >>> X().foo  # should be 5
+    4
+    """
+
+    class Meta(type):
+        def __setattr__(self, key, value):
+            obj = self.__dict__.get(key, None)
+            if type(obj) is classproperty:
+                return obj.__set__(self, value)
+            return super().__setattr__(key, value)
+
+    def __init__(self, fget, fset=None):
+        self.fget = self._fix_function(fget)
+        self.fset = fset
+        fset and self.setter(fset)
+
+    def __get__(self, instance, owner=None):
+        return self.fget.__get__(None, owner)()
+
+    def __set__(self, owner, value):
+        if not self.fset:
+            raise AttributeError("can't set attribute")
+        if type(owner) is not classproperty.Meta:
+            owner = type(owner)
+        return self.fset.__get__(None, owner)(value)
+
+    def setter(self, fset):
+        self.fset = self._fix_function(fset)
+        return self
+
+    @classmethod
+    def _fix_function(cls, fn):
+        """
+        Ensure fn is a classmethod or staticmethod.
+        """
+        if not isinstance(fn, (classmethod, staticmethod)):
+            return classmethod(fn)
+        return fn
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/jaraco.classes.egg-info/PKG-INFO 
new/jaraco.classes-3.1.0/jaraco.classes.egg-info/PKG-INFO
--- old/jaraco.classes-2.0/jaraco.classes.egg-info/PKG-INFO     2019-01-01 
15:23:41.000000000 +0100
+++ new/jaraco.classes-3.1.0/jaraco.classes.egg-info/PKG-INFO   2019-12-19 
20:32:33.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: jaraco.classes
-Version: 2.0
+Version: 3.1.0
 Summary: Utility functions for Python class constructs
 Home-page: https://github.com/jaraco/jaraco.classes
 Author: Jason R. Coombs
@@ -14,6 +14,10 @@
         .. image:: 
https://img.shields.io/travis/jaraco/jaraco.classes/master.svg
            :target: https://travis-ci.org/jaraco/jaraco.classes
         
+        .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+           :target: https://github.com/psf/black
+           :alt: Code style: Black
+        
         .. .. image:: 
https://img.shields.io/appveyor/ci/jaraco/skeleton/master.svg
         ..    :target: 
https://ci.appveyor.com/project/jaraco/skeleton/branch/master
         
@@ -24,8 +28,7 @@
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: MIT License
-Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
-Requires-Python: >=2.7
-Provides-Extra: docs
+Requires-Python: >=3.6
 Provides-Extra: testing
+Provides-Extra: docs
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jaraco.classes-2.0/jaraco.classes.egg-info/SOURCES.txt 
new/jaraco.classes-3.1.0/jaraco.classes.egg-info/SOURCES.txt
--- old/jaraco.classes-2.0/jaraco.classes.egg-info/SOURCES.txt  2019-01-01 
15:23:41.000000000 +0100
+++ new/jaraco.classes-3.1.0/jaraco.classes.egg-info/SOURCES.txt        
2019-12-19 20:32:33.000000000 +0100
@@ -1,4 +1,6 @@
+.coveragerc
 .flake8
+.pre-commit-config.yaml
 .readthedocs.yml
 .travis.yml
 CHANGES.rst
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jaraco.classes-2.0/jaraco.classes.egg-info/requires.txt 
new/jaraco.classes-3.1.0/jaraco.classes.egg-info/requires.txt
--- old/jaraco.classes-2.0/jaraco.classes.egg-info/requires.txt 2019-01-01 
15:23:41.000000000 +0100
+++ new/jaraco.classes-3.1.0/jaraco.classes.egg-info/requires.txt       
2019-12-19 20:32:33.000000000 +0100
@@ -1,4 +1,4 @@
-six
+more_itertools
 
 [docs]
 sphinx
@@ -7,5 +7,7 @@
 
 [testing]
 pytest!=3.7.3,>=3.5
-pytest-checkdocs
+pytest-checkdocs>=1.2.3
 pytest-flake8
+pytest-black-multipy
+pytest-cov
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/pyproject.toml 
new/jaraco.classes-3.1.0/pyproject.toml
--- old/jaraco.classes-2.0/pyproject.toml       2019-01-01 15:23:21.000000000 
+0100
+++ new/jaraco.classes-3.1.0/pyproject.toml     2019-12-19 20:32:10.000000000 
+0100
@@ -1,3 +1,6 @@
 [build-system]
 requires = ["setuptools>=34.4", "wheel", "setuptools_scm>=1.15"]
 build-backend = "setuptools.build_meta"
+
+[tool.black]
+skip-string-normalization = true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/pytest.ini 
new/jaraco.classes-3.1.0/pytest.ini
--- old/jaraco.classes-2.0/pytest.ini   2019-01-01 15:23:21.000000000 +0100
+++ new/jaraco.classes-3.1.0/pytest.ini 2019-12-19 20:32:10.000000000 +0100
@@ -1,11 +1,5 @@
 [pytest]
 norecursedirs=dist build .tox .eggs
-addopts=--doctest-modules --flake8
+addopts=--doctest-modules --flake8 --black --cov
 doctest_optionflags=ALLOW_UNICODE ELLIPSIS
 filterwarnings=
-       ignore:Possible nested set::pycodestyle:113
-       ignore:Using or importing the ABCs::flake8:410
-       # workaround for https://sourceforge.net/p/docutils/bugs/348/
-       ignore:'U' mode is deprecated::docutils.io
-       # workaround for https://gitlab.com/pycqa/flake8/issues/275
-       ignore:You passed a bytestring as `filenames`.::flake8
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/setup.cfg 
new/jaraco.classes-3.1.0/setup.cfg
--- old/jaraco.classes-2.0/setup.cfg    2019-01-01 15:23:42.000000000 +0100
+++ new/jaraco.classes-3.1.0/setup.cfg  2019-12-19 20:32:33.496063200 +0100
@@ -13,22 +13,23 @@
        Development Status :: 5 - Production/Stable
        Intended Audience :: Developers
        License :: OSI Approved :: MIT License
-       Programming Language :: Python :: 2.7
        Programming Language :: Python :: 3
 
 [options]
 packages = find:
 include_package_data = true
-python_requires = >=2.7
+python_requires = >=3.6
 install_requires = 
-       six
+       more_itertools
 setup_requires = setuptools_scm >= 1.15.0
 
 [options.extras_require]
 testing = 
        pytest >= 3.5, !=3.7.3
-       pytest-checkdocs
+       pytest-checkdocs >= 1.2.3
        pytest-flake8
+       pytest-black-multipy
+       pytest-cov
 docs = 
        sphinx
        jaraco.packaging >= 3.2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/skeleton.md 
new/jaraco.classes-3.1.0/skeleton.md
--- old/jaraco.classes-2.0/skeleton.md  2019-01-01 15:23:21.000000000 +0100
+++ new/jaraco.classes-3.1.0/skeleton.md        2019-12-19 20:32:10.000000000 
+0100
@@ -10,6 +10,8 @@
 
 The primary advantage to using an SCM for maintaining these techniques is that 
those tools help facilitate the merge between the template and its adopting 
projects.
 
+Another advantage to using an SCM-managed approach is that tools like GitHub 
recognize that a change in the skeleton is the _same change_ across all 
projects that merge with that skeleton. Without the ancestry, with a 
traditional copy/paste approach, a [commit like 
this](https://github.com/jaraco/skeleton/commit/12eed1326e1bc26ce256e7b3f8cd8d3a5beab2d5)
 would produce notifications in the upstream project issue for each and every 
application, but because it's centralized, GitHub provides just the one 
notification when the change is added to the skeleton.
+
 # Usage
 
 ## new projects
@@ -48,7 +50,8 @@
 - setuptools declarative configuration using setup.cfg
 - tox for running tests
 - A README.rst as reStructuredText with some popular badges, but with 
readthedocs and appveyor badges commented out
-- A CHANGES.rst file intended for publishing release notes about the project.
+- A CHANGES.rst file intended for publishing release notes about the project
+- Use of [black](https://black.readthedocs.io/en/stable/) for code formatting 
(disabled on unsupported Python 3.5 and earlier)
 
 ## Packaging Conventions
 
@@ -82,7 +85,7 @@
 
 Other environments (invoked with `tox -e {name}`) supplied include:
 
-  - a `build-docs` environment to build the documentation
+  - a `docs` environment to build the documentation
   - a `release` environment to publish the package to PyPI
 
 A pytest.ini is included to define common options around running tests. In 
particular:
@@ -95,18 +98,12 @@
 
 Relies a .flake8 file to correct some default behaviors:
 
-- allow tabs for indentation (legacy for jaraco projects)
-- disable mutually incompatible rules W503 and W504.
+- disable mutually incompatible rules W503 and W504
+- support for black format
 
 ## Continuous Integration
 
-The project is pre-configured to run tests in 
[Travis-CI](https://travis-ci.org) (.travis.yml). Any new project must be 
enabled either through their web site or with the `travis enable` command. In 
addition to running tests, an additional deploy stage is configured to 
automatically release tagged commits. The username and password for PyPI must 
be configured for each project using the `travis` command and only after the 
travis project is created. As releases are cut with 
[twine](https://pypi.org/project/twine), the two values are supplied through 
the `TWINE_USERNAME` and `TWINE_PASSWORD`. To configure the latter as a secret, 
run the following command:
-
-```
-echo "TWINE_PASSWORD={password}" | travis encrypt
-```
-
-Or disable it in the CI definition and configure it through the web UI.
+The project is pre-configured to run tests in 
[Travis-CI](https://travis-ci.org) (.travis.yml). Any new project must be 
enabled either through their web site or with the `travis enable` command.
 
 Features include:
 - test against Python 2 and 3
@@ -115,12 +112,26 @@
 
 Also provided is a minimal template for running under Appveyor (Windows).
 
+### Continuous Deployments
+
+In addition to running tests, an additional deploy stage is configured to 
automatically release tagged commits to PyPI using [API 
tokens](https://pypi.org/help/#apitoken). The release process expects an 
authorized token to be configured with Travis as the TWINE_PASSWORD environment 
variable. After the Travis project is created, configure the token through the 
web UI or with a command like the following (bash syntax):
+
+```
+TWINE_PASSWORD={token} travis env copy TWINE_PASSWORD
+```
+
 ## Building Documentation
 
-Documentation is automatically built by [Read the 
Docs](https://readthedocs.org) when the project is registered with it, by way 
of the .readthedocs.yml file. To test the docs build manually, a tox env may be 
invoked as `tox -e build-docs`. Both techniques rely on the dependencies 
declared in `setup.cfg/options.extras_require.docs`.
+Documentation is automatically built by [Read the 
Docs](https://readthedocs.org) when the project is registered with it, by way 
of the .readthedocs.yml file. To test the docs build manually, a tox env may be 
invoked as `tox -e docs`. Both techniques rely on the dependencies declared in 
`setup.cfg/options.extras_require.docs`.
 
 In addition to building the sphinx docs scaffolded in `docs/`, the docs build 
a `history.html` file that first injects release dates and hyperlinks into the 
CHANGES.rst before incorporating it as history in the docs.
 
 ## Cutting releases
 
 By default, tagged commits are released through the continuous integration 
deploy stage.
+
+Releases may also be cut manually by invoking the tox environment `release` 
with the PyPI token set as the TWINE_PASSWORD:
+
+```
+TWINE_PASSWORD={token} tox -e release
+```
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jaraco.classes-2.0/tox.ini 
new/jaraco.classes-3.1.0/tox.ini
--- old/jaraco.classes-2.0/tox.ini      2019-01-01 15:23:21.000000000 +0100
+++ new/jaraco.classes-3.1.0/tox.ini    2019-12-19 20:32:10.000000000 +0100
@@ -1,30 +1,41 @@
 [tox]
 envlist = python
-minversion = 2.4
+minversion = 3.2
+# https://github.com/jaraco/skeleton/issues/6
+tox_pip_extensions_ext_venv_update = true
+# Ensure that a late version of pip is used even on tox-venv.
+requires =
+       tox-pip-version>=0.0.6
+       tox-venv
+
 
 [testenv]
 deps =
        setuptools>=31.0.1
+pip_version = pip
 commands =
        pytest {posargs}
 usedevelop = True
 extras = testing
 
-[testenv:build-docs]
+[testenv:docs]
 extras =
-    docs
-    testing
+       docs
+       testing
 changedir = docs
 commands =
-    python -m sphinx . {toxinidir}/build/html
+       python -m sphinx . {toxinidir}/build/html
 
 [testenv:release]
 skip_install = True
 deps =
        pep517>=0.5
-       # workaround for https://github.com/pypa/twine/issues/423
-       git+https://github.com/pypa/twine
-       path.py
+       twine[keyring]>=1.13
+       path
+passenv =
+       TWINE_PASSWORD
+setenv =
+       TWINE_USERNAME = {env:TWINE_USERNAME:__token__}
 commands =
        python -c "import path; path.Path('dist').rmtree_p()"
        python -m pep517.build .


Reply via email to