Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-osc-tiny for openSUSE:Factory 
checked in at 2022-06-10 15:57:37
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-osc-tiny (Old)
 and      /work/SRC/openSUSE:Factory/.python-osc-tiny.new.1548 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-osc-tiny"

Fri Jun 10 15:57:37 2022 rev:13 rq:981539 version:0.6.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-osc-tiny/python-osc-tiny.changes  
2022-05-18 13:13:29.506678560 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-osc-tiny.new.1548/python-osc-tiny.changes    
    2022-06-10 15:57:58.868853082 +0200
@@ -1,0 +2,7 @@
+Thu Jun  9 11:53:07 UTC 2022 - Andreas Hasenkopf <ahasenk...@suse.com>
+
+- Release 0.6.0
+  * Support for the "Signature authentication scheme"
+  * Revised method to retrieve credentials from `osc`
+
+-------------------------------------------------------------------

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

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

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

Other differences:
------------------
++++++ python-osc-tiny.spec ++++++
--- /var/tmp/diff_new_pack.lGkaGZ/_old  2022-06-10 15:58:00.392854930 +0200
+++ /var/tmp/diff_new_pack.lGkaGZ/_new  2022-06-10 15:58:00.396854935 +0200
@@ -19,7 +19,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define skip_python2 1
 Name:           python-osc-tiny
-Version:        0.5.0
+Version:        0.6.0
 Release:        0
 Summary:        Client API for openSUSE BuildService
 License:        MIT
@@ -44,6 +44,7 @@
 Requires:       python-python-dateutil
 Requires:       python-pytz
 Requires:       python-requests
+Suggests:       openssh
 BuildArch:      noarch
 %python_subpackages
 

++++++ osc-tiny-0.5.0.tar.gz -> osc-tiny-0.6.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.5.0/PKG-INFO new/osc-tiny-0.6.0/PKG-INFO
--- old/osc-tiny-0.5.0/PKG-INFO 2022-05-17 14:54:34.581646200 +0200
+++ new/osc-tiny-0.6.0/PKG-INFO 2022-06-09 13:50:02.441182900 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: osc-tiny
-Version: 0.5.0
+Version: 0.6.0
 Summary: Client API for openSUSE BuildService
 Home-page: http://github.com/crazyscientist/osc-tiny
 Download-URL: http://github.com/crazyscientist/osc-tiny/tarball/master
@@ -16,6 +16,7 @@
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
 Description-Content-Type: text/markdown
 License-File: LICENSE
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.5.0/osc_tiny.egg-info/PKG-INFO 
new/osc-tiny-0.6.0/osc_tiny.egg-info/PKG-INFO
--- old/osc-tiny-0.5.0/osc_tiny.egg-info/PKG-INFO       2022-05-17 
14:54:33.000000000 +0200
+++ new/osc-tiny-0.6.0/osc_tiny.egg-info/PKG-INFO       2022-06-09 
13:50:01.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: osc-tiny
-Version: 0.5.0
+Version: 0.6.0
 Summary: Client API for openSUSE BuildService
 Home-page: http://github.com/crazyscientist/osc-tiny
 Download-URL: http://github.com/crazyscientist/osc-tiny/tarball/master
@@ -16,6 +16,7 @@
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
 Description-Content-Type: text/markdown
 License-File: LICENSE
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.5.0/osc_tiny.egg-info/SOURCES.txt 
new/osc-tiny-0.6.0/osc_tiny.egg-info/SOURCES.txt
--- old/osc-tiny-0.5.0/osc_tiny.egg-info/SOURCES.txt    2022-05-17 
14:54:34.000000000 +0200
+++ new/osc-tiny-0.6.0/osc_tiny.egg-info/SOURCES.txt    2022-06-09 
13:50:02.000000000 +0200
@@ -40,6 +40,7 @@
 osctiny/tests/osc/__init__.py
 osctiny/tests/osc/conf.py
 osctiny/utils/__init__.py
+osctiny/utils/auth.py
 osctiny/utils/backports.py
 osctiny/utils/base.py
 osctiny/utils/changelog.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.5.0/osctiny/osc.py 
new/osc-tiny-0.6.0/osctiny/osc.py
--- old/osc-tiny-0.5.0/osctiny/osc.py   2022-05-17 14:54:21.000000000 +0200
+++ new/osc-tiny-0.6.0/osctiny/osc.py   2022-06-09 13:49:46.000000000 +0200
@@ -3,9 +3,12 @@
 ---------------
 """
 from __future__ import unicode_literals
+
+import typing
 from io import BufferedReader, BytesIO, StringIO
 import gc
 import logging
+from pathlib import Path
 import re
 from ssl import get_default_verify_paths
 import time
@@ -16,6 +19,7 @@
 from lxml.objectify import fromstring, makeparser
 from requests import Session, Request
 from requests.auth import HTTPBasicAuth
+from requests.cookies import RequestsCookieJar, cookiejar_from_dict
 from requests.exceptions import ConnectionError as _ConnectionError
 
 from .extensions.buildresults import Build
@@ -28,6 +32,7 @@
 from .extensions.bs_requests import Request as BsRequest
 from .extensions.search import Search
 from .extensions.users import Group, Person
+from .utils.auth import HttpSignatureAuth
 from .utils.conf import get_credentials
 from .utils.errors import OscError
 
@@ -78,10 +83,12 @@
           - :py:attr:`origins`
 
     :param url: API URL of a BuildService instance
-    :param username: Credential for login
-    :param password: Password for login
+    :param username: Username
+    :param password: Password; this is either the user password or the SSH 
passphrase, if
+                     ``ssh_key_file`` is defined
     :param verify: See `SSL Cert Verification`_ for more details
     :param cache: Store API responses in a cache
+    :param ssh_key_file: Path to SSH private key file
     :raises osctiny.errors.OscError: if no credentials are provided
 
     .. versionadded:: 0.1.1
@@ -102,6 +109,10 @@
     .. versionchanged:: 0.4.0
         Raises an exception when no credentials are provided
 
+    .. versionchanged:: 0.6.0
+        Support for 2FA authentication (i.e. added the ``ssh_key_file`` 
parameter and changed the
+        meaning of the ``password`` parameter
+
     .. _SSL Cert Verification:
         http://docs.python-requests.org/en/master/user/advanced/
         #ssl-cert-verification
@@ -115,22 +126,26 @@
     default_connection_retries = 5
     default_retry_timeout = 5
 
-    def __init__(self, url=None, username=None, password=None, verify=None,
-                 cache=False):
+    def __init__(self, url: str = None, username: typing.Optional[str] = None,
+                 password: typing.Optional[str] = None, verify: 
typing.Optional[str] = None,
+                 cache: bool = False,
+                 ssh_key_file: typing.Optional[typing.Union[Path, str]] = 
None):
         # Basic URL and authentication settings
         self.url = url or self.url
         self.username = username or self.username
         self.password = password or self.password
+        self.verify = verify
+        self.cache = cache
+        self.ssh_key = ssh_key_file
+        if self.ssh_key is not None and not isinstance(self.ssh_key, Path):
+            self.ssh_key = Path(self.ssh_key)
 
-        if not self.username and not self.password:
+        if not self.username and not self.password and not self.ssh_key:
             try:
-                self.username, self.password = get_credentials(self.url)
+                self.username, self.password, self.ssh_key = 
get_credentials(self.url)
             except (ValueError, NotImplementedError, FileNotFoundError) as 
error:
                 raise OscError from error
 
-        self._session = Session()
-        self._session.verify = verify or get_default_verify_paths().capath
-        self.auth = HTTPBasicAuth(self.username, self.password)
         self.parser = makeparser(huge_tree=True)
 
         # API endpoints
@@ -146,8 +161,50 @@
         self.search = Search(osc_obj=self)
         self.users = Person(osc_obj=self)
 
+        self._session, self.session = None, None
+
+    def __del__(self):
+        # Just in case ;-)
+        gc.collect()
+
+    @property
+    def cookies(self) -> RequestsCookieJar:
+        """
+        Access session cookies
+        """
+        if self._session is None:
+            self._init_session()
+
+        return self.session.cookies
+
+    @cookies.setter
+    def cookies(self, value: RequestsCookieJar):
+        if not isinstance(value, (RequestsCookieJar, dict)):
+            raise TypeError(f"Expected a cookie jar or dict. Got instead: 
{type(value)}")
+
+        if self._session is None:
+            self._init_session()
+
+        if isinstance(value, RequestsCookieJar):
+            self._session.cookies = value
+        else:
+            self._session.cookies = cookiejar_from_dict(value)
+
+    def _init_session(self):
+        """
+        Lazy session initialization
+        """
+        self._session = Session()
+        self._session.verify = self.verify or get_default_verify_paths().capath
+
+        if self.ssh_key is not None:
+            self._session.auth = HttpSignatureAuth(username=self.username, 
password=self.password,
+                                                   ssh_key_file=self.ssh_key)
+        else:
+            self._session.auth = HTTPBasicAuth(self.username, self.password)
+
         # Cache
-        if cache:
+        if self.cache:
             # pylint: disable=broad-except
             try:
                 self.session = CacheControl(self._session)
@@ -158,10 +215,6 @@
         else:
             self.session = self._session
 
-    def __del__(self):
-        # Just in case ;-)
-        gc.collect()
-
     def request(self, url, method="GET", stream=False, data=None, params=None,
                 raise_for_status=True, timeout=None):
         """
@@ -191,7 +244,7 @@
         .. versionadded:: 0.1.7
             Added parameter `params`
 
-        .. versionchanged:: {{ NEXT_RELEASE }}
+        .. versionchanged:: 0.5.0
             Added logging of request/response
 
         :param url: Full URL
@@ -215,6 +268,9 @@
             https://2.python-requests.org/en/master/user/advanced/#timeouts
         """
         timeout = timeout or self.default_timeout
+        if self._session is None:
+            self._init_session()
+
         if stream:
             session = self._session
         else:
@@ -223,7 +279,6 @@
         req = Request(
             method,
             url.replace("#", quote("#")).replace("?", quote("?")),
-            auth=self.auth,
             data=self.handle_params(data),
             params=self.handle_params(params)
         )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.5.0/osctiny/tests/test_basic.py 
new/osc-tiny-0.6.0/osctiny/tests/test_basic.py
--- old/osc-tiny-0.5.0/osctiny/tests/test_basic.py      2022-05-17 
14:54:21.000000000 +0200
+++ new/osc-tiny-0.6.0/osctiny/tests/test_basic.py      2022-06-09 
13:49:46.000000000 +0200
@@ -75,7 +75,6 @@
         def callback(headers, params, request):
             match = pattern.match(request.url)
             self.assertIsNotNone(match)
-            print(match.groups())
             self.assertEqual(unquote_plus(match.group("filename")), filename)
             for special_c in special_chars:
                 self.assertNotIn(special_c, match.group("filename"))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.5.0/osctiny/tests/test_utils.py 
new/osc-tiny-0.6.0/osctiny/tests/test_utils.py
--- old/osc-tiny-0.5.0/osctiny/tests/test_utils.py      2022-05-17 
14:54:21.000000000 +0200
+++ new/osc-tiny-0.6.0/osctiny/tests/test_utils.py      2022-06-09 
13:49:46.000000000 +0200
@@ -15,6 +15,7 @@
 
 from ..utils.changelog import ChangeLog, Entry
 from ..utils.conf import get_config_path, get_credentials
+from ..utils.mapping import Mappable
 
 sys.path.append(os.path.dirname(__file__))
 
@@ -77,6 +78,19 @@
 """
 
 
+class TestMappable(TestCase):
+    def test(self):
+        m = Mappable(a="a", b="b")
+        m["c"] = "c"
+
+        for key in ('a', 'b', 'c'):
+            with self.subTest(f"get {key}"):
+                self.assertEqual(key, m.get(key))
+
+        with self.subTest("Default"):
+            self.assertEqual("f????", m.get("d", "f????"))
+
+
 class TestEntry(TestCase):
     def test_timestamp(self):
         cet = timezone("Europe/Berlin")
@@ -317,7 +331,7 @@
         self.assertIn("Cannot parse changelog entry", wmock.call_args[0][0])
 
 
-@mock.patch("osc.conf", side_effect=ImportError, create=True)
+@mock.patch("osctiny.utils.conf._conf", new_callable=lambda: None, create=True)
 @mock.patch("pathlib.Path.is_file", return_value=True)
 class TestConfig(TestCase):
     def test_get_config_path(self, *_):
@@ -334,8 +348,8 @@
         _, path1 = mkstemp()
         _, path2 = mkstemp()
 
-        expected_insecure_credentials = ("my-dummy-user", 
"my-insecure-dummy-password")
-        expected_secure_credentials = ('my-dummy-user', 
'my-secure-dummy-password')
+        expected_insecure_credentials = ("my-dummy-user", 
"my-insecure-dummy-password", None)
+        expected_secure_credentials = ('my-dummy-user', 
'my-secure-dummy-password', None)
 
         with open(path1, "w") as handle:
             handle.write("[http://api.dummy-bs.org]\n";)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.5.0/osctiny/utils/auth.py 
new/osc-tiny-0.6.0/osctiny/utils/auth.py
--- old/osc-tiny-0.5.0/osctiny/utils/auth.py    1970-01-01 01:00:00.000000000 
+0100
+++ new/osc-tiny-0.6.0/osctiny/utils/auth.py    2022-06-09 13:49:46.000000000 
+0200
@@ -0,0 +1,135 @@
+"""
+Authentication handlers for 2FA
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. versionadded:: 0.6.0
+"""
+import typing
+from base64 import b64decode, b64encode
+from pathlib import Path
+from subprocess import Popen, PIPE
+import re
+import sys
+from time import time
+
+from requests.auth import HTTPDigestAuth
+from requests.cookies import extract_cookies_to_jar
+from requests.utils import parse_dict_header
+from requests import Response
+
+from .errors import OscError
+
+
+class HttpSignatureAuth(HTTPDigestAuth):
+    """
+    Implementation of the "Signature authentication scheme"
+
+    .. note::
+
+        This seems to be a variation of the `HTTP Message Signatures`_ 
specification.
+
+        See also the `reference implementation for osc`_
+
+        .. _HTTP Message Signatures:
+            
https://datatracker.ietf.org/doc/draft-ietf-httpbis-message-signatures/
+
+        .. _reference implementation for osc: 
https://github.com/openSUSE/osc/pull/1032
+
+    :param username: The username
+    :param password: Passphrase for SSH key. This can be omitted, if 
``ssh-agent`` is also installed
+    :param ssh_key_file: Path of SSK key
+    """
+    def __init__(self, username: str, password: typing.Optional[str], 
ssh_key_file: Path):
+        super().__init__(username=username, password=password)
+        if not ssh_key_file.is_file():
+            raise FileNotFoundError(f"SSH key at location does not exist: 
{ssh_key_file}")
+        self.ssh_key_file = ssh_key_file
+        self.pattern = re.compile(r"(?<=\)) (?=\()")
+
+    def __eq__(self, other: 'HttpSignatureAuth') -> bool:
+        return self.ssh_key_file == getattr(other, 'ssh_key_file', None) and 
super().__eq__(other)
+
+    def split_headers(self, headers: str) -> typing.List[str]:
+        """
+        Split ``headers`` parameter from ``WWW-Authenticate Signature`` header
+
+        :param headers: Value of the ``headers`` parameter
+        """
+        parts = self.pattern.split(headers)
+        return [part.strip("()") for part in parts]
+
+    def ssh_sign(self) -> str:
+        """
+        Solve the challenge via SSH signing
+        """
+        data = "\n".join(f"({header}): {self._thread_local.chal[header]}"
+                         for header in self._thread_local.chal["headers"])
+        cmd = ['ssh-keygen', '-Y', 'sign', '-f', self.ssh_key_file.as_posix(), 
'-q',
+               '-n', self._thread_local.chal.get('realm', '')]
+        if self.password:
+            cmd += ['-P', self.password]
+
+        encoding = sys.getdefaultencoding()
+        with Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE) as proc:
+            signature, error = proc.communicate(data.encode(encoding))
+            if proc.returncode:
+                raise OscError(f"ssh-keygen returned {proc.returncode}: 
{error}")
+
+        match = re.match(br"\A-----BEGIN SSH SIGNATURE-----\n(.*)\n-----END 
SSH SIGNATURE-----",
+                         signature, re.S)
+        if not match:
+            raise OscError("Could not generate challenge response")
+        return b64encode(b64decode(match.group(1))).decode(encoding)
+
+    def build_digest_header(self, method: str, url: str) -> str:
+        """
+        Generate Authentication header
+        """
+        headers = " ".join(f"({header})" for header in 
self._thread_local.chal["headers"])
+        return f'Signature 
keyId="{self.username}",algorithm="ssh",signature={self.ssh_sign()},' \
+               
f'headers="{headers}",created={self._thread_local.chal["created"]}'
+
+    def handle_401(self, r: Response, **kwargs) -> Response:
+        """
+        Handle authentication in case of 401
+
+        Contents of method copied from 
:py:meth:`requests.auth.HTTPDigestAuth.handle_401` and edited
+        """
+        if not 400 <= r.status_code < 500:
+            self._thread_local.num_401_calls = 1
+            return r
+
+        if self._thread_local.pos is not None:
+            # Rewind the file position indicator of the body to where
+            # it was to resend the request.
+            r.request.body.seek(self._thread_local.pos)
+        s_auth = r.headers.get('www-authenticate', '')
+
+        if s_auth.lower().startswith("signature") and 
self._thread_local.num_401_calls < 2:
+            self._thread_local.num_401_calls += 1
+
+            _, challenge = s_auth.split(" ", maxsplit=1)
+            challenge = parse_dict_header(challenge)
+            challenge.setdefault("headers", ["created"])
+            challenge["created"] = int(time())
+            challenge["headers"] = self.split_headers(challenge["headers"])
+            self._thread_local.chal.update(challenge)
+
+            # The following is unchanged from 
:py:meth:`requests.auth.HTTPDigestAuth.handle_401`,
+            # so we ignore linter issues about it.
+            # pylint: disable=pointless-statement,protected-access
+            r.content
+            r.close()
+            prep = r.request.copy()
+            extract_cookies_to_jar(prep._cookies, r.request, r.raw)
+            prep.prepare_cookies(prep._cookies)
+            prep.headers['Authorization'] = self.build_digest_header(
+                prep.method, prep.url)
+            _r = r.connection.send(prep, **kwargs)
+            _r.history.append(r)
+            _r.request = prep
+
+            return _r
+
+        self._thread_local.num_401_calls = 1
+        return r
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.5.0/osctiny/utils/conf.py 
new/osc-tiny-0.6.0/osctiny/utils/conf.py
--- old/osc-tiny-0.5.0/osctiny/utils/conf.py    2022-05-17 14:54:21.000000000 
+0200
+++ new/osc-tiny-0.6.0/osctiny/utils/conf.py    2022-06-09 13:49:46.000000000 
+0200
@@ -8,6 +8,7 @@
 
 .. versionadded:: 0.4.0
 """
+import typing
 from base64 import b64decode
 from bz2 import decompress
 from configparser import ConfigParser, NoSectionError
@@ -17,11 +18,12 @@
 
 try:
     from osc import conf as _conf
+    from osc.oscerr import ConfigError, ConfigMissingApiurl
 except ImportError:
     _conf = None
 
 
-def get_config_path():
+def get_config_path() -> Path:
     """
     Return path of ``osc`` configuration file
 
@@ -43,7 +45,9 @@
     raise FileNotFoundError("No `osc` configuration file found")
 
 
-def get_credentials(url=None):
+# pylint: disable=too-many-branches
+def get_credentials(url: typing.Optional[str] = None) \
+        -> typing.Tuple[str, str, typing.Optional[Path]]:
     """
     Get credentials for Build Service instance identified by ``url``
 
@@ -56,26 +60,31 @@
 
     :param str url: URL of Build Service instance (including schema). If not 
specified, the value
                     from the ``apiurl`` parameter in the config file will be 
used.
-    :return: (username, password)
+    :return: (username, password, SSH private key path)
     :raises ValueError: if config provides no credentials
     """
     if _conf is not None:
-        # pylint: disable=protected-access
-        parser = _conf.get_configParser()
         try:
+            _conf.get_config()
             if url is None:
-                url = parser["general"].get("apiurl", url)
-            cred_mgr = _conf._get_credentials_manager(url, parser)
-            username = _conf._extract_user_compat(parser, url, cred_mgr)
-        except (KeyError, NoSectionError) as error:
-            raise ValueError("`osc` config does not provide the default API 
URL") from error
+                # get the default api url from osc's config
+                url = _conf.config["apiurl"]
+            # and now fetch the options for that particular url
+            api_config = _conf.get_apiurl_api_host_options(url)
+            username = api_config["user"]
+            password = api_config["pass"]
+            sshkey = Path(api_config["sshkey"]) if api_config["sshkey"] else 
None
+        except (ConfigError, ConfigMissingApiurl) as error:
+            if isinstance(error, ConfigError):
+                raise ValueError("`osc` config was not found.") from error
+            # this is the case of ConfigMissingApiurl
+            raise ValueError("`osc` config has no options for URL 
{}".format(url)) from error
 
         if not username:
             raise ValueError("`osc` config provides no username for URL 
{}".format(url))
-        password = cred_mgr.get_password(url, username, defer=False)
         if not password:
             raise ValueError("`osc` config provides no password for URL 
{}".format(url))
-        return username, password
+        return username, password, sshkey
 
     warnings.warn("`osc` is not installed. Not all configuration backends of 
`osc` will be "
                   "available.")
@@ -104,4 +113,8 @@
     if not password:
         raise ValueError("`osc` config provides no password for URL 
{}".format(url))
 
-    return username, password
+    sshkey = parser[url].get("sshkey", None)
+    if sshkey:
+        sshkey = Path(sshkey)
+
+    return username, password, sshkey
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.5.0/osctiny/utils/mapping.py 
new/osc-tiny-0.6.0/osctiny/utils/mapping.py
--- old/osc-tiny-0.5.0/osctiny/utils/mapping.py 2022-05-17 14:54:21.000000000 
+0200
+++ new/osc-tiny-0.6.0/osctiny/utils/mapping.py 2022-06-09 13:49:46.000000000 
+0200
@@ -48,7 +48,7 @@
 
     def get(self, key, default=None):
         try:
-            return self.__getitem__(key)
+            return self[key]
         except KeyError:
             return default
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-tiny-0.5.0/setup.py new/osc-tiny-0.6.0/setup.py
--- old/osc-tiny-0.5.0/setup.py 2022-05-17 14:54:21.000000000 +0200
+++ new/osc-tiny-0.6.0/setup.py 2022-06-09 13:49:46.000000000 +0200
@@ -19,7 +19,7 @@
 
 setup(
     name='osc-tiny',
-    version='0.5.0',
+    version='0.6.0',
     description='Client API for openSUSE BuildService',
     long_description=long_description,
     long_description_content_type="text/markdown",
@@ -40,5 +40,6 @@
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
     ]
 )

Reply via email to