Hello community,

here is the log from the commit of package python-osc-tiny for openSUSE:Factory 
checked in at 2020-01-16 18:14:25
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-osc-tiny (Old)
 and      /work/SRC/openSUSE:Factory/.python-osc-tiny.new.26092 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-osc-tiny"

Thu Jan 16 18:14:25 2020 rev:3 rq:763923 version:0.2.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-osc-tiny/python-osc-tiny.changes  
2020-01-02 14:43:20.972959410 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-osc-tiny.new.26092/python-osc-tiny.changes   
    2020-01-16 18:14:30.936746562 +0100
@@ -1,0 +2,9 @@
+Thu Jan  9 12:46:29 UTC 2020 - Chen Huang <[email protected]>
+
+- Updated to version 0.2.1
+- Allow invalid timestamps during changelog parsing
+- Removed dependency of the future package
+- Backport for Python2 compatibility
+- Bugfixes for :py:mod:`osctiny.utils.changelog`
+
+-------------------------------------------------------------------

Old:
----
  osc-tiny-0.1.11.tar.gz

New:
----
  osc-tiny-0.2.1.tar.gz

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

Other differences:
------------------
++++++ python-osc-tiny.spec ++++++
--- /var/tmp/diff_new_pack.ZYBqGs/_old  2020-01-16 18:14:32.380747379 +0100
+++ /var/tmp/diff_new_pack.ZYBqGs/_new  2020-01-16 18:14:32.380747379 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-osc-tiny
 #
-# 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,29 +17,36 @@
 
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
-%define skip_python2 1
 Name:           python-osc-tiny
-Version:        0.1.11
+Version:        0.2.1
 Release:        0
 Summary:        Client API for openSUSE BuildService
 License:        MIT
 Group:          Development/Languages/Python
 URL:            https://github.com/crazyscientist/osc-tiny
 Source:         
https://files.pythonhosted.org/packages/source/o/osc-tiny/osc-tiny-%{version}.tar.gz
-BuildRequires:  %{python_module dateutil}
 BuildRequires:  %{python_module devel}
 BuildRequires:  %{python_module lxml}
+BuildRequires:  %{python_module python-dateutil}
 BuildRequires:  %{python_module pytz}
 BuildRequires:  %{python_module requests}
 BuildRequires:  %{python_module responses}
 BuildRequires:  %{python_module setuptools}
+BuildRequires:  %{python_module six}
 BuildRequires:  fdupes
+BuildRequires:  python-mock
 BuildRequires:  python-rpm-macros
-Requires:       python-dateutil
+BuildRequires:  python-unittest2
 Requires:       python-lxml
+Requires:       python-python-dateutil
 Requires:       python-pytz
 Requires:       python-requests
+Requires:       python-six
 BuildArch:      noarch
+%ifpython2
+Requires:       python-mock
+Requires:       python-unittest2
+%endif
 %python_subpackages
 
 %description

++++++ osc-tiny-0.1.11.tar.gz -> osc-tiny-0.2.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/MANIFEST.in 
new/osc-tiny-0.2.1/MANIFEST.in
--- old/osc-tiny-0.1.11/MANIFEST.in     2019-12-30 15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/MANIFEST.in      2020-01-10 09:32:12.000000000 +0100
@@ -1,2 +1,3 @@
 include *.md
-include LICENSE
\ No newline at end of file
+include LICENSE
+include requirements*.txt
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/PKG-INFO new/osc-tiny-0.2.1/PKG-INFO
--- old/osc-tiny-0.1.11/PKG-INFO        2019-12-30 15:28:12.000000000 +0100
+++ new/osc-tiny-0.2.1/PKG-INFO 2020-01-10 09:32:27.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: osc-tiny
-Version: 0.1.11
+Version: 0.2.1
 Summary: Client API for openSUSE BuildService
 Home-page: http://github.com/crazyscientist/osc-tiny
 Author: Andreas Hasenkopf
@@ -56,7 +56,7 @@
 Classifier: Intended Audience :: System Administrators
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osc_tiny.egg-info/PKG-INFO 
new/osc-tiny-0.2.1/osc_tiny.egg-info/PKG-INFO
--- old/osc-tiny-0.1.11/osc_tiny.egg-info/PKG-INFO      2019-12-30 
15:28:12.000000000 +0100
+++ new/osc-tiny-0.2.1/osc_tiny.egg-info/PKG-INFO       2020-01-10 
09:32:27.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: osc-tiny
-Version: 0.1.11
+Version: 0.2.1
 Summary: Client API for openSUSE BuildService
 Home-page: http://github.com/crazyscientist/osc-tiny
 Author: Andreas Hasenkopf
@@ -56,7 +56,7 @@
 Classifier: Intended Audience :: System Administrators
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osc_tiny.egg-info/SOURCES.txt 
new/osc-tiny-0.2.1/osc_tiny.egg-info/SOURCES.txt
--- old/osc-tiny-0.1.11/osc_tiny.egg-info/SOURCES.txt   2019-12-30 
15:28:12.000000000 +0100
+++ new/osc-tiny-0.2.1/osc_tiny.egg-info/SOURCES.txt    2020-01-10 
09:32:27.000000000 +0100
@@ -1,6 +1,10 @@
 LICENSE
 MANIFEST.in
 README.md
+requirements.txt
+requirements27.txt
+requirements34.txt
+requirements_devel.txt
 setup.py
 osc_tiny.egg-info/PKG-INFO
 osc_tiny.egg-info/SOURCES.txt
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osc_tiny.egg-info/requires.txt 
new/osc-tiny-0.2.1/osc_tiny.egg-info/requires.txt
--- old/osc-tiny-0.1.11/osc_tiny.egg-info/requires.txt  2019-12-30 
15:28:12.000000000 +0100
+++ new/osc-tiny-0.2.1/osc_tiny.egg-info/requires.txt   2020-01-10 
09:32:27.000000000 +0100
@@ -3,3 +3,4 @@
 responses
 python-dateutil
 pytz
+six
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/__init__.py 
new/osc-tiny-0.2.1/osctiny/__init__.py
--- old/osc-tiny-0.1.11/osctiny/__init__.py     2019-12-30 15:27:53.000000000 
+0100
+++ new/osc-tiny-0.2.1/osctiny/__init__.py      2020-01-10 09:32:12.000000000 
+0100
@@ -6,4 +6,4 @@
 
 __all__ = ['Osc', 'bs_requests', 'buildresults', 'comments', 'packages',
            'projects', 'search', 'users']
-__version__ = "0.1.11"
+__version__ = "0.2.1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/extensions/bs_requests.py 
new/osc-tiny-0.2.1/osctiny/extensions/bs_requests.py
--- old/osc-tiny-0.1.11/osctiny/extensions/bs_requests.py       2019-12-30 
15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/extensions/bs_requests.py        2020-01-10 
09:32:12.000000000 +0100
@@ -2,7 +2,9 @@
 Requests extension
 ------------------
 """
-from urllib.parse import urljoin
+from __future__ import unicode_literals
+from six.moves.urllib.parse import urljoin
+from six import text_type
 
 from ..utils.base import ExtensionBase
 
@@ -15,7 +17,7 @@
 
     @staticmethod
     def _validate_id(request_id):
-        request_id = str(request_id)
+        request_id = text_type(request_id)
         if not request_id.isnumeric():
             raise ValueError(
                 "Request ID must be numeric! Got instead: 
{}".format(request_id)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/extensions/buildresults.py 
new/osc-tiny-0.2.1/osctiny/extensions/buildresults.py
--- old/osc-tiny-0.1.11/osctiny/extensions/buildresults.py      2019-12-30 
15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/extensions/buildresults.py       2020-01-10 
09:32:12.000000000 +0100
@@ -3,7 +3,8 @@
 ----------------------
 """
 # pylint: disable=too-few-public-methods
-from urllib.parse import urljoin
+from __future__ import unicode_literals
+from six.moves.urllib.parse import urljoin
 
 from ..utils.base import ExtensionBase
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/extensions/comments.py 
new/osc-tiny-0.2.1/osctiny/extensions/comments.py
--- old/osc-tiny-0.1.11/osctiny/extensions/comments.py  2019-12-30 
15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/extensions/comments.py   2020-01-10 
09:32:12.000000000 +0100
@@ -2,8 +2,10 @@
 Comments extension
 ------------------
 """
+from __future__ import unicode_literals
 import os
-from urllib.parse import urljoin
+from six.moves.urllib.parse import urljoin
+from six import text_type
 
 from ..utils.base import ExtensionBase
 
@@ -51,7 +53,7 @@
         response = self.osc.request(
             url=urljoin(self.osc.url,
                         os.path.join(*([self.base_path, obj_type]
-                                       + [str(x) for x in ids]))),
+                                       + [text_type(x) for x in ids]))),
             method="GET",
         )
         return self.osc.get_objectified_xml(response)
@@ -74,9 +76,9 @@
         self._validate(obj_type, ids)
         url = urljoin(self.osc.url,
                       os.path.join(*([self.base_path, obj_type]
-                                     + [str(x) for x in ids])))
+                                     + [text_type(x) for x in ids])))
         params = {}
-        if parent_id and str(parent_id).isnumeric():
+        if parent_id and text_type(parent_id).isnumeric():
             params["parent_id"] = parent_id
 
         response = self.osc.request(
@@ -102,7 +104,7 @@
         :return: ``True``, if successful. Otherwise API response
         :rtype: bool or lxml.objectify.ObjectifiedElement
         """
-        url = urljoin(self.osc.url, '/comment/' + str(comment_id))
+        url = urljoin(self.osc.url, '/comment/' + text_type(comment_id))
 
         response = self.osc.request(
             url=url,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/extensions/packages.py 
new/osc-tiny-0.2.1/osctiny/extensions/packages.py
--- old/osc-tiny-0.1.11/osctiny/extensions/packages.py  2019-12-30 
15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/extensions/packages.py   2020-01-10 
09:32:12.000000000 +0100
@@ -2,9 +2,11 @@
 Packages extension
 ------------------
 """
+from __future__ import unicode_literals
 import errno
 import os
-from urllib.parse import urljoin
+from six.moves.urllib.parse import urljoin
+from six import text_type
 
 from lxml.etree import tounicode, SubElement, Element
 from lxml.objectify import fromstring
@@ -100,7 +102,7 @@
         :type meta: str or lxml.objectify.ObjectifiedElement
         :return:
         """
-        if isinstance(meta, str):
+        if isinstance(meta, text_type):
             meta = fromstring(meta)
 
         if meta is not None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/extensions/projects.py 
new/osc-tiny-0.2.1/osctiny/extensions/projects.py
--- old/osc-tiny-0.1.11/osctiny/extensions/projects.py  2019-12-30 
15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/extensions/projects.py   2020-01-10 
09:32:12.000000000 +0100
@@ -2,8 +2,10 @@
 Projects extension
 ------------------
 """
+from __future__ import unicode_literals
 import re
-from urllib.parse import urljoin
+from six.moves.urllib.parse import urljoin
+from six import text_type
 
 from lxml.etree import tounicode
 from lxml.objectify import fromstring
@@ -85,7 +87,7 @@
         if metafile is None:
             metafile = TEMPLATE_META
 
-        if isinstance(metafile, str):
+        if isinstance(metafile, text_type):
             metafile = fromstring(metafile)
 
         metafile.set("name", project)
@@ -134,7 +136,7 @@
         if meta:
             kwargs["meta"] = '1'
         if rev:
-            kwargs["rev"] = str(rev)
+            kwargs["rev"] = text_type(rev)
         response = self.osc.request(
             url=urljoin(
                 self.osc.url,
@@ -201,7 +203,7 @@
         attr_xml.attribute.set('namespace', match.group("prefix"))
         attr_xml.attribute.set('name', match.group("name"))
         # pylint: disable=protected-access
-        attr_xml.attribute.value._setText(str(value))
+        attr_xml.attribute.value._setText(text_type(value))
 
         response = self.osc.request(
             url=url,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/extensions/search.py 
new/osc-tiny-0.2.1/osctiny/extensions/search.py
--- old/osc-tiny-0.1.11/osctiny/extensions/search.py    2019-12-30 
15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/extensions/search.py     2020-01-10 
09:32:12.000000000 +0100
@@ -2,7 +2,8 @@
 Search extension
 ----------------
 """
-from urllib.parse import urljoin
+from __future__ import unicode_literals
+from six.moves.urllib.parse import urljoin
 
 from ..utils.base import ExtensionBase
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/extensions/users.py 
new/osc-tiny-0.2.1/osctiny/extensions/users.py
--- old/osc-tiny-0.1.11/osctiny/extensions/users.py     2019-12-30 
15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/extensions/users.py      2020-01-10 
09:32:12.000000000 +0100
@@ -2,7 +2,8 @@
 Persons and groups extension
 ----------------------------
 """
-from urllib.parse import urljoin
+from __future__ import unicode_literals
+from six.moves.urllib.parse import urljoin
 
 from ..utils.base import ExtensionBase
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/osc.py 
new/osc-tiny-0.2.1/osctiny/osc.py
--- old/osc-tiny-0.1.11/osctiny/osc.py  2019-12-30 15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/osc.py   2020-01-10 09:32:12.000000000 +0100
@@ -2,6 +2,7 @@
 Main API access
 ---------------
 """
+from __future__ import unicode_literals
 from io import BufferedReader, BytesIO, StringIO
 import gc
 import re
@@ -14,6 +15,7 @@
 from requests import Session, Request
 from requests.auth import HTTPBasicAuth
 from requests.exceptions import ConnectionError as _ConnectionError
+from six import text_type
 
 from .extensions.buildresults import Build
 from .extensions.comments import Comment
@@ -191,6 +193,7 @@
         )
         prepped_req = session.prepare_request(req)
         prepped_req.headers['Content-Type'] = "application/octet-stream"
+        prepped_req.headers['Accept'] = "application/xml"
         settings = session.merge_environment_settings(
             prepped_req.url, {}, None, None, None
         )
@@ -227,12 +230,12 @@
         if isinstance(params, bytes):
             return params
 
-        if isinstance(params, str):
-            return params.encode()
+        if isinstance(params, text_type):
+            return params.encode('utf-8')
 
         if isinstance(params, StringIO):
             params.seek(0)
-            return params.read().encode()
+            return params.read().encode('utf-8')
 
         if isinstance(params, (BufferedReader, BytesIO)):
             params.seek(0)
@@ -265,7 +268,8 @@
         except ValueError:
             # Just in case OBS returns a Unicode string with encoding
             # declaration
-            if isinstance(response.text, str) and "encoding=" in response.text:
+            if isinstance(response.text, text_type) and \
+                    "encoding=" in response.text:
                 return fromstring(
                     re.sub(r'encoding="[^"]+"', "", response.text)
                 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/tests/base.py 
new/osc-tiny-0.2.1/osctiny/tests/base.py
--- old/osc-tiny-0.1.11/osctiny/tests/base.py   2019-12-30 15:27:53.000000000 
+0100
+++ new/osc-tiny-0.2.1/osctiny/tests/base.py    2020-01-10 09:32:12.000000000 
+0100
@@ -1,8 +1,14 @@
+from __future__ import unicode_literals
+import sys
+if sys.version_info.major < 3:
+    from unittest2 import TestCase
+else:
+    from unittest import TestCase
+
 from io import IOBase
-from unittest import TestCase
-from urllib.parse import parse_qs
 
 import responses
+from six.moves.urllib_parse import parse_qs
 
 from osctiny import Osc
 
@@ -34,8 +40,8 @@
             body.seek(0)
             body = body.read()
         if hasattr(body, "decode"):
-            body = body.decode()
-        params = parse_qs(body)
+            body = body.decode('utf-8')
+        params = parse_qs(body) if body else {}
         headers = {
             "Cache-Control": "max-age=0, private, must-revalidate",
             "Connection": "Keep-Alive",
@@ -53,7 +59,7 @@
 class OscTest(TestCase):
     @classmethod
     def setUpClass(cls):
-        super().setUpClass()
+        super(OscTest, cls).setUpClass()
         cls.osc = Osc(
             url="http://api.example.com";,
             username="foobar",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/tests/test_basic.py 
new/osc-tiny-0.2.1/osctiny/tests/test_basic.py
--- old/osc-tiny-0.1.11/osctiny/tests/test_basic.py     2019-12-30 
15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/tests/test_basic.py      2020-01-10 
09:32:12.000000000 +0100
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
 from ..extensions import projects
 from .base import OscTest
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/tests/test_build.py 
new/osc-tiny-0.2.1/osctiny/tests/test_build.py
--- old/osc-tiny-0.1.11/osctiny/tests/test_build.py     2019-12-30 
15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/tests/test_build.py      2020-01-10 
09:32:12.000000000 +0100
@@ -1,5 +1,6 @@
+from __future__ import unicode_literals
 import re
-from urllib.parse import urlparse, parse_qs
+from six.moves.urllib_parse import urlparse, parse_qs
 
 from requests.exceptions import HTTPError
 import responses
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/tests/test_comments.py 
new/osc-tiny-0.2.1/osctiny/tests/test_comments.py
--- old/osc-tiny-0.1.11/osctiny/tests/test_comments.py  2019-12-30 
15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/tests/test_comments.py   2020-01-10 
09:32:12.000000000 +0100
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
 import re
 
 from lxml.objectify import ObjectifiedElement
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/tests/test_datadir.py 
new/osc-tiny-0.2.1/osctiny/tests/test_datadir.py
--- old/osc-tiny-0.1.11/osctiny/tests/test_datadir.py   2019-12-30 
15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/tests/test_datadir.py    2020-01-10 
09:32:12.000000000 +0100
@@ -1,4 +1,9 @@
-from unittest import mock
+from __future__ import unicode_literals
+
+try:
+    from unittest import mock
+except ImportError:
+    import mock
 
 # Absolute import needed for mocking ;)
 from ..utils.base import DataDir
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/tests/test_packages.py 
new/osc-tiny-0.2.1/osctiny/tests/test_packages.py
--- old/osc-tiny-0.1.11/osctiny/tests/test_packages.py  2019-12-30 
15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/tests/test_packages.py   2020-01-10 
09:32:12.000000000 +0100
@@ -1,7 +1,10 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
 from io import StringIO, BytesIO, IOBase
 import re
 from unittest import skip
 
+from six import text_type
 import responses
 
 from .base import OscTest, CallbackFactory
@@ -156,7 +159,7 @@
             response = self.osc.packages.get_meta(
                 "SUSE:SLE-12-SP1:Update", "python.8549", blame=True
             )
-            self.assertTrue(isinstance(response, str))
+            self.assertTrue(isinstance(response, text_type))
 
     @skip("No test data available")
     @responses.activate
@@ -245,7 +248,7 @@
         response = self.osc.packages.cmd(
             "SUSE:SLE-12-SP1:Update", "python.8549", "diff"
         )
-        self.assertTrue(isinstance(response, str))
+        self.assertTrue(isinstance(response, text_type))
         self.assertIn(
             "++#if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE", response
         )
@@ -368,22 +371,22 @@
 
         with self.subTest("as unicode"):
             self.osc.packages.push_file("prj", "pkg", "readme.txt", content)
-            self.assertEqual(bodies[-1], content.encode())
+            self.assertEqual(bodies[-1], content.encode('utf-8'))
 
         with self.subTest("as bytes"):
             self.osc.packages.push_file("prj", "pkg", "readme.txt",
-                                        content.encode())
-            self.assertEqual(bodies[-1], content.encode())
+                                        content.encode('utf-8'))
+            self.assertEqual(bodies[-1], content.encode('utf-8'))
 
         with self.subTest("as StringIO"):
             self.osc.packages.push_file("prj", "pkg", "readme.txt",
                                         StringIO(content))
-            self.assertEqual(bodies[-1], content.encode())
+            self.assertEqual(bodies[-1], content.encode('utf-8'))
 
         with self.subTest("as BytesIO"):
             self.osc.packages.push_file("prj", "pkg", "readme.txt",
-                                        BytesIO(content.encode()))
-            self.assertEqual(bodies[-1], content.encode())
+                                        BytesIO(content.encode('utf-8')))
+            self.assertEqual(bodies[-1], content.encode('utf-8'))
 
     @responses.activate
     def test_aggregate(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/tests/test_projects.py 
new/osc-tiny-0.2.1/osctiny/tests/test_projects.py
--- old/osc-tiny-0.1.11/osctiny/tests/test_projects.py  2019-12-30 
15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/tests/test_projects.py   2020-01-10 
09:32:12.000000000 +0100
@@ -1,5 +1,6 @@
+from __future__ import unicode_literals
 import re
-from urllib.parse import urlparse, parse_qs
+from six.moves.urllib_parse import urlparse, parse_qs
 
 from lxml.objectify import fromstring
 from requests.exceptions import HTTPError
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/tests/test_requests.py 
new/osc-tiny-0.2.1/osctiny/tests/test_requests.py
--- old/osc-tiny-0.1.11/osctiny/tests/test_requests.py  2019-12-30 
15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/tests/test_requests.py   2020-01-10 
09:32:12.000000000 +0100
@@ -1,5 +1,9 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
 import re
-from urllib.parse import urlparse, parse_qs
+
+from six.moves.urllib_parse import urlparse, parse_qs
+from six import text_type
 
 from lxml.objectify import ObjectifiedElement
 import responses
@@ -318,7 +322,7 @@
 
 class TestRequest(OscTest):
     def setUp(self):
-        super().setUp()
+        super(TestRequest, self).setUp()
 
         self.mock_request(
             method=responses.GET,
@@ -374,7 +378,7 @@
     def test_cmd(self):
         with self.subTest("plain diff"):
             response = self.osc.requests.cmd(30902, "diff")
-            self.assertTrue(isinstance(response, str))
+            self.assertTrue(isinstance(response, text_type))
             self.assertIn("changes files:", response)
             self.assertIn("+++ perl-XML-DOM-XPath.changes", response)
         with self.subTest("xml diff"):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/tests/test_search.py 
new/osc-tiny-0.2.1/osctiny/tests/test_search.py
--- old/osc-tiny-0.1.11/osctiny/tests/test_search.py    2019-12-30 
15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/tests/test_search.py     2020-01-10 
09:32:12.000000000 +0100
@@ -1,6 +1,8 @@
+from __future__ import unicode_literals
+
 import re
-from urllib.parse import urlparse, parse_qs
 
+from six.moves.urllib_parse import urlparse, parse_qs
 import responses
 
 from .base import OscTest, CallbackFactory
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/tests/test_utils.py 
new/osc-tiny-0.2.1/osctiny/tests/test_utils.py
--- old/osc-tiny-0.1.11/osctiny/tests/test_utils.py     2019-12-30 
15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/tests/test_utils.py      2020-01-10 
09:32:12.000000000 +0100
@@ -1,10 +1,24 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import sys
+
+if sys.version_info.major < 3:
+    from unittest2 import TestCase
+    import mock
+else:
+    from unittest import TestCase, mock
+
 from datetime import datetime
 from io import StringIO
+from os import remove
+from tempfile import mkstemp
 from types import GeneratorType
-from unittest import TestCase, mock
+from unittest import skipIf
 
 from dateutil.parser import parse
 from pytz import _UTC, timezone
+from six import text_type
 
 from ..utils.changelog import ChangeLog, Entry
 
@@ -56,6 +70,17 @@
 
 """
 
+SAMPLE_CHANGES_3 = """
+Tue Dec  3 10:38:41 UTC 2019 - Andreas Hasenkopf <[email protected]>
+
+- Version 0.1.10
+
+-------------------------------------------------------------------
+Tue Dec  3 10:13:08 UTC 2019 - Andreas Hasenkopf <[email protected]>
+
+- New package osc-tiny (version 0.1.9)
+"""
+
 
 class TestEntry(TestCase):
     def test_timestamp(self):
@@ -111,6 +136,7 @@
                 timestamp = parse(match.group("timestamp"))
                 self.assertIsInstance(timestamp, datetime)
 
+    @skipIf(sys.version_info.major < 3, "Python2 does not support this")
     def test_parse_non_generative(self):
         with mock.patch("osctiny.utils.changelog.open",
                         mock.mock_open(read_data=SAMPLE_CHANGES),
@@ -135,6 +161,27 @@
         self.assertIsInstance(cl.entries, list)
         self.assertEqual(len(cl.entries), 2)
 
+    def test_parse_path(self):
+        _, path = mkstemp()
+        with open(path, "w") as handle:
+            handle.write(SAMPLE_CHANGES)
+
+        try:
+            with self.subTest("generative"):
+                cl = ChangeLog.parse(path, generative=True)
+
+                self.assertNotIsInstance(cl.entries, list)
+                self.assertEqual(len(list(cl.entries)), 2)
+
+            with self.subTest("non-generative"):
+                cl = ChangeLog.parse(path, generative=False)
+
+                self.assertIsInstance(cl.entries, list)
+                self.assertEqual(len(cl.entries), 2)
+        finally:
+            remove(path)
+
+    @skipIf(sys.version_info.major < 3, "Python2 does not support this")
     def test_parse_generative(self):
         with mock.patch("osctiny.utils.changelog.open",
                         mock.mock_open(read_data=SAMPLE_CHANGES),
@@ -179,7 +226,7 @@
             cl.write(path="/who/cares/test.changes")
 
             self.assertEqual(len(omock.mock_calls), 6)
-            content = "".join(str(omock.mock_calls[x][1][0])
+            content = "".join(text_type(omock.mock_calls[x][1][0])
                               for x in range(2, 5))
 
             self.assertEqual(
@@ -214,6 +261,7 @@
                 "Føø Bar\n\n"
             )
 
+    @skipIf(sys.version_info.major < 3, "Python2 does not support this")
     def test_write_append(self):
         with mock.patch("osctiny.utils.changelog.open",
                         mock.mock_open(read_data=SAMPLE_CHANGES_2),
@@ -232,7 +280,7 @@
             write_calls = [x for x in omock.mock_calls if x[0] == "().write"]
             self.assertEqual(len(write_calls), 5)
             self.assertEqual(
-                str(write_calls[0][1][0]),
+                text_type(write_calls[0][1][0]),
                 
"-------------------------------------------------------------------\n"
                 "Sat Oct 31 00:00:00 UTC 2020 - Andreas Hasenkopf 
<[email protected]>\n\n"
                 "New entry at the top\n\n"
@@ -254,4 +302,24 @@
         self.assertEqual(
             len([x for x in outbuff.readlines() if x.startswith("-" * 67)]),
             5
-        )
\ No newline at end of file
+        )
+
+    def test_parse_missing_sep(self):
+        buffer = StringIO(SAMPLE_CHANGES_3)
+        cl = ChangeLog.parse(buffer, generative=False)
+
+        self.assertIsInstance(cl.entries, list)
+        self.assertEqual(len(cl.entries), 2)
+
+    @mock.patch("warnings.warn")
+    def test_parse_invalid_timestamp(self, wmock):
+        buffer = StringIO(SAMPLE_CHANGES.replace("10:38:41", "10::41"))
+        cl = ChangeLog.parse(buffer, generative=False)
+
+        self.assertIsInstance(cl.entries, list)
+        self.assertEqual(len(cl.entries), 2)
+        self.assertIsInstance(cl.entries[0].timestamp, text_type)
+        self.assertIsInstance(cl.entries[1].timestamp, datetime)
+
+        self.assertEqual(wmock.call_count, 1)
+        self.assertIn("Cannot parse changelog entry", wmock.call_args[0][0])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/osctiny/utils/changelog.py 
new/osc-tiny-0.2.1/osctiny/utils/changelog.py
--- old/osc-tiny-0.1.11/osctiny/utils/changelog.py      2019-12-30 
15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/osctiny/utils/changelog.py       2020-01-10 
09:32:12.000000000 +0100
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 """
 Changelog
 ^^^^^^^^^
@@ -10,12 +11,15 @@
 
 .. versionadded:: 0.1.11
 """
+from __future__ import unicode_literals
 from datetime import datetime
 from io import TextIOBase
 import re
+import warnings
 
 from dateutil.parser import parse
 from pytz import _UTC
+from six import text_type, binary_type
 
 
 def is_aware(timestamp):
@@ -69,6 +73,9 @@
     def __bool__(self):
         return bool(self.timestamp and self.packager and self.content)
 
+    def __len__(self):
+        return 1 if self.timestamp and self.packager and self.content else 0
+
     def now(self):
         """
         Return current UTC timestamp
@@ -84,6 +91,9 @@
 
         :return: str
         """
+        if not isinstance(self.timestamp, datetime):
+            return self.timestamp
+
         return self.timestamp\
             .astimezone(self.default_tz)\
             .strftime("%a %b %d %H:%M:%S %Z %Y")
@@ -92,6 +102,9 @@
         return "{sep}\n{self.formatted_timestamp} - {self.packager}\n\n" \
                "{self.content}\n\n".format(sep="-" * 67, self=self)
 
+    def __unicode__(self):
+        return self.__str__()
+
 
 class ChangeLog:
     """
@@ -138,44 +151,62 @@
         :param handle: An open and iterable (file) handle
         :type handle: Any derived object of :py:class:`io.IOBase`
         """
-        entry = None
+        # pylint: disable=too-many-branches
+        entry = self.entry_factory()
+
+        if isinstance(handle, TextIOBase):
+            handle.seek(0)
+        elif isinstance(handle, (text_type, binary_type)):
+            handle = open(handle, "r")
+        else:
+            raise TypeError("Unexpected type for 'path': {}".format(
+                type(handle)))
 
-        handle.seek(0)
-        for line in handle:
-            match = self.patterns["init"].match(line)
-            if match:
-                if entry:
-                    # We are at the beginning of a new entry. Time to emit
-                    # the finished one.
-                    yield entry
-                entry = self.entry_factory()
-                continue
-
-            match = self.patterns["header"].match(line)
-            if match:
-                entry.timestamp = parse(match.group("timestamp"),
-                                        ignoretz=False,
-                                        tzinfos=self.additional_tzinfos)
-                # Assuming UTC may not be correct, but beats dealing with
-                # a mix of tz-aware and naive datetime objects
-                if not is_aware(entry.timestamp):
-                    entry.timestamp = entry.default_tz.localize(
-                        parse(match.group("timestamp"), ignoretz=True,)
-                    )
-                entry.packager = match.group("packager")
-                continue
-
-            if not line.strip():
-                continue
-
-            if entry.content:
-                entry.content += "\n"
-
-            entry.content += line.rstrip()
-
-        if entry:
-            # The last entry of the file is emitted explicitly
-            yield entry
+        try:
+            for line in handle:
+                match = self.patterns["init"].match(line)
+                if match:
+                    if entry:
+                        # We are at the beginning of a new entry. Time to emit
+                        # the finished one.
+                        yield entry
+                    entry = self.entry_factory()
+                    continue
+
+                match = self.patterns["header"].match(line)
+                if match:
+                    try:
+                        entry.timestamp = parse(match.group("timestamp"),
+                                                ignoretz=False,
+                                                
tzinfos=self.additional_tzinfos)
+                    except ValueError as error:
+                        warnings.warn(
+                            "Cannot parse changelog entry's timestamp: "
+                            "'{}'".format(error))
+                        entry.timestamp = match.group("timestamp")
+                    else:
+                        # Assuming UTC may not be correct, but beats dealing
+                        # with a mix of tz-aware and naive datetime objects
+                        if not is_aware(entry.timestamp):
+                            entry.timestamp = entry.default_tz.localize(
+                                parse(match.group("timestamp"), ignoretz=True,)
+                            )
+                    entry.packager = match.group("packager")
+                    continue
+
+                if not line.strip():
+                    continue
+
+                if entry.content:
+                    entry.content += "\n"
+
+                entry.content += line.rstrip()
+
+            if entry:
+                # The last entry of the file is emitted explicitly
+                yield entry
+        finally:
+            handle.close()
 
     @classmethod
     def parse(cls, path, generative=True):
@@ -207,22 +238,13 @@
         :raises TypeError: if ``path`` is not a string or a subclass of
                            :py:class:`io.TextIOBase`
         """
-        def _wrapped(handle):
-            # pylint: disable=protected-access
-            if generative:
-                new.entries = new._parse(handle)
-            else:
-                new.entries = list(new._parse(handle))
-
         new = cls()
 
-        if isinstance(path, TextIOBase):
-            _wrapped(path)
-        elif isinstance(path, str):
-            with open(path, "r") as handle:
-                _wrapped(handle)
+        # pylint: disable=protected-access
+        if generative:
+            new.entries = new._parse(path)
         else:
-            raise TypeError("Unexpected type for 'path': 
{}".format(type(path)))
+            new.entries = list(new._parse(path))
 
         return new
 
@@ -239,11 +261,11 @@
         def _wrapped(handle):
             for entry in sorted(self.entries, key=lambda x: x.timestamp,
                                 reverse=True):
-                handle.write(str(entry))
+                handle.write(text_type(entry))
 
         if isinstance(path, TextIOBase):
             _wrapped(path)
-        elif isinstance(path, str):
+        elif isinstance(path, (text_type, binary_type)):
             with open(path, "w") as handle:
                 _wrapped(handle)
         else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/requirements.txt 
new/osc-tiny-0.2.1/requirements.txt
--- old/osc-tiny-0.1.11/requirements.txt        1970-01-01 01:00:00.000000000 
+0100
+++ new/osc-tiny-0.2.1/requirements.txt 2020-01-10 09:32:12.000000000 +0100
@@ -0,0 +1,6 @@
+lxml
+requests
+responses
+python-dateutil
+pytz
+six
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/requirements27.txt 
new/osc-tiny-0.2.1/requirements27.txt
--- old/osc-tiny-0.1.11/requirements27.txt      1970-01-01 01:00:00.000000000 
+0100
+++ new/osc-tiny-0.2.1/requirements27.txt       2020-01-10 09:32:12.000000000 
+0100
@@ -0,0 +1,8 @@
+lxml
+mock
+requests
+responses
+python-dateutil
+pytz
+unittest2
+six
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/requirements34.txt 
new/osc-tiny-0.2.1/requirements34.txt
--- old/osc-tiny-0.1.11/requirements34.txt      1970-01-01 01:00:00.000000000 
+0100
+++ new/osc-tiny-0.2.1/requirements34.txt       2020-01-10 09:32:12.000000000 
+0100
@@ -0,0 +1,6 @@
+lxml<4.4
+requests
+responses
+python-dateutil
+pytz
+six
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/requirements_devel.txt 
new/osc-tiny-0.2.1/requirements_devel.txt
--- old/osc-tiny-0.1.11/requirements_devel.txt  1970-01-01 01:00:00.000000000 
+0100
+++ new/osc-tiny-0.2.1/requirements_devel.txt   2020-01-10 09:32:12.000000000 
+0100
@@ -0,0 +1,3 @@
+pylint
+Sphinx
+sphinx_rtd_theme
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.1.11/setup.py new/osc-tiny-0.2.1/setup.py
--- old/osc-tiny-0.1.11/setup.py        2019-12-30 15:27:53.000000000 +0100
+++ new/osc-tiny-0.2.1/setup.py 2020-01-10 09:32:12.000000000 +0100
@@ -1,16 +1,28 @@
-#!/usr/bin/env python3
+#!/usr/bin/env python
 # -*- coding: utf-8 -*-
-
+from __future__ import unicode_literals
+import sys
 from setuptools import setup, find_packages
 
 
+def get_requires():
+    def _filter(requires):
+        return [req.strip() for req in requires if req.strip()]
+
+    filename = "requirements.txt" if sys.version_info.major >= 3 \
+                                  else "requirements27.txt"
+
+    with open(filename, "r") as fh:
+        return _filter(fh.readlines())
+
+
 with open("README.md") as fh:
     long_description = fh.read()
 
 
 setup(
     name='osc-tiny',
-    version='0.1.11',
+    version='0.2.1',
     description='Client API for openSUSE BuildService',
     long_description=long_description,
     long_description_content_type="text/markdown",
@@ -20,20 +32,14 @@
     download_url='http://github.com/crazyscientist/osc-tiny/tarball/master',
     packages=find_packages(),
     license='MIT',
-    install_requires=[
-        "lxml",
-        "requests",
-        "responses",
-        "python-dateutil",
-        "pytz"
-    ],
+    install_requires=get_requires(),
     classifiers=[
         "Development Status :: 5 - Production/Stable",
         "Intended Audience :: Developers",
         "Intended Audience :: System Administrators",
         "License :: OSI Approved :: MIT License",
         "Operating System :: OS Independent",
-        "Programming Language :: Python :: 3 :: Only",
+        "Programming Language :: Python :: 2.7",
         "Programming Language :: Python :: 3.4",
         "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.6",


Reply via email to