Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-copr for openSUSE:Factory 
checked in at 2022-04-22 21:55:04
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-copr (Old)
 and      /work/SRC/openSUSE:Factory/.python-copr.new.1538 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-copr"

Fri Apr 22 21:55:04 2022 rev:3 rq:972178 version:1.119

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-copr/python-copr.changes  2022-03-07 
17:48:38.867090271 +0100
+++ /work/SRC/openSUSE:Factory/.python-copr.new.1538/python-copr.changes        
2022-04-22 21:56:18.930946921 +0200
@@ -1,0 +2,13 @@
+Fri Apr 22 12:08:48 UTC 2022 - Matej Cepl <[email protected]>
+
+- Update to 1.119:
+  - Bugfixes
+  - Kerberos/GSSAPI authentication for API and copr-cli
+  - Copr-cli re-tries connections with Copr Frontend
+  - Large queue improvements
+  - Smaller fixes
+  - More copr-cli options for editing chroots
+  - Signing packages with SHA256
+  - Remote refs in committish
+
+-------------------------------------------------------------------

Old:
----
  copr-1.115.tar.gz

New:
----
  copr-1.119.tar.gz

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

Other differences:
------------------
++++++ python-copr.spec ++++++
--- /var/tmp/diff_new_pack.H0uu3Q/_old  2022-04-22 21:56:19.342947391 +0200
+++ /var/tmp/diff_new_pack.H0uu3Q/_new  2022-04-22 21:56:19.342947391 +0200
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-copr
-Version:        1.115
+Version:        1.119
 Release:        0
 Summary:        Python client for copr service
 License:        GPL-2.0-or-later
@@ -27,9 +27,12 @@
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  python-rpm-macros
 # SECTION test requirements
+BuildRequires:  %{python_module filelock}
+BuildRequires:  %{python_module future}
 BuildRequires:  %{python_module marshmallow}
 BuildRequires:  %{python_module munch}
 BuildRequires:  %{python_module pytest}
+BuildRequires:  %{python_module requests-gssapi}
 BuildRequires:  %{python_module requests-toolbelt}
 BuildRequires:  %{python_module requests}
 BuildRequires:  %{python_module six}

++++++ copr-1.115.tar.gz -> copr-1.119.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/PKG-INFO new/copr-1.119/PKG-INFO
--- old/copr-1.115/PKG-INFO     2022-02-03 13:17:46.887011500 +0100
+++ new/copr-1.119/PKG-INFO     2022-04-05 10:50:41.658881400 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: copr
-Version: 1.115
+Version: 1.119
 Summary: Python client for copr service.
 Home-page: https://pagure.io/copr/copr
 Author: Valentin Gologuzov
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/README.rst 
new/copr-1.119/copr/README.rst
--- old/copr-1.115/copr/README.rst      2022-01-04 01:09:47.000000000 +0100
+++ new/copr-1.119/copr/README.rst      2022-04-05 10:49:51.000000000 +0200
@@ -14,7 +14,7 @@
 - Website: https://pagure.io/copr/copr
 - Python-copr documentation: http://python-copr.readthedocs.org
 - Git: https://pagure.io/copr/copr.git
-- Test instance: http://copr-fe-dev.cloud.fedoraproject.org/
+- Test instance: http://copr.stg.fedoraproject.org/
 
 Usage:
 ------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/test/client_v3/test_builds.py 
new/copr-1.119/copr/test/client_v3/test_builds.py
--- old/copr-1.115/copr/test/client_v3/test_builds.py   2022-02-01 
00:11:50.000000000 +0100
+++ new/copr-1.119/copr/test/client_v3/test_builds.py   2022-04-05 
10:49:51.000000000 +0200
@@ -7,7 +7,7 @@
 
 @mock.patch.object(Request, "send")
 class TestBuildProxy(object):
-    config = {"copr_url": "http://copr"}
+    config = {"copr_url": "http://copr";, "login": "test", "token": "test"}
 
     def test_get(self, send):
         response = mock.Mock(spec=Response)
@@ -20,14 +20,14 @@
         assert build.foo == "bar"
 
 
[email protected]("copr.v3.proxies.build.Request")
-def test_build_distgit(request):
[email protected]('copr.v3.proxies.Request.send')
+def test_build_distgit(send):
     mock_client = Client.create_from_config_file(config_location)
     mock_client.build_proxy.create_from_distgit(
         "praiskup", "ping", "mock", committish="master",
     )
-    assert len(request.call_args_list) == 1
-    call = request.call_args_list[0]
+    assert len(send.call_args_list) == 1
+    call = send.call_args_list[0]
     args = call[1]
     assert args['method'] == 'POST'
     assert args['endpoint'] == '/build/create/distgit'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/test/client_v3/test_helpers.py 
new/copr-1.119/copr/test/client_v3/test_helpers.py
--- old/copr-1.115/copr/test/client_v3/test_helpers.py  2022-02-01 
00:11:50.000000000 +0100
+++ new/copr-1.119/copr/test/client_v3/test_helpers.py  2022-04-05 
10:49:51.000000000 +0200
@@ -39,7 +39,8 @@
 
     @mock.patch("copr.v3.proxies.build.BuildProxy.get")
     def test_wait_custom_list(self, mock_get):
-        builds = List([Munch(id=1, state="succeeded"), Munch(id=2, 
state="failed")], proxy=BuildProxy({}))
+        builds = List([Munch(id=1, state="succeeded"), Munch(id=2, 
state="failed")],
+                      proxy=BuildProxy({"copr_url": "http://copr";, "login": 
"test", "token": "test"}))
         mock_get.side_effect = lambda id: builds[id-1]
         assert wait(builds)
 
@@ -65,4 +66,4 @@
 
 
 class MunchMock(Munch):
-    __proxy__ = BuildProxy({})
+    __proxy__ = BuildProxy({"copr_url": "http://copr";, "login": "test", 
"token": "test"})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/test/client_v3/test_modules.py 
new/copr-1.119/copr/test/client_v3/test_modules.py
--- old/copr-1.115/copr/test/client_v3/test_modules.py  2022-02-01 
00:11:50.000000000 +0100
+++ new/copr-1.119/copr/test/client_v3/test_modules.py  2022-04-05 
10:49:51.000000000 +0200
@@ -24,7 +24,7 @@
         shutil.rmtree(self.tmpdir)
 
     @pytest.mark.parametrize('distgit_opt', [None, 'fedora'])
-    @mock.patch('copr.v3.requests.requests.request')
+    @mock.patch('copr.v3.requests.requests.Session.request')
     def test_module_dist_git_choice_url(self, request, distgit_opt):
         proxy = ModuleProxy(self.config_auth)
         proxy.build_from_url('owner', 'project', 'http://test.yaml',
@@ -42,7 +42,7 @@
         assert json['scmurl'] == 'http://test.yaml'
 
     @pytest.mark.parametrize('distgit_opt', [None, 'fedora'])
-    @mock.patch('copr.v3.requests.requests.request')
+    @mock.patch('copr.v3.requests.requests.Session.request')
     def test_module_dist_git_choice_upload(self, request, distgit_opt):
         proxy = ModuleProxy(self.config_auth)
         proxy.build_from_file('owner', 'project',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/test/client_v3/test_packages.py 
new/copr-1.119/copr/test/client_v3/test_packages.py
--- old/copr-1.115/copr/test/client_v3/test_packages.py 2022-02-01 
00:11:50.000000000 +0100
+++ new/copr-1.119/copr/test/client_v3/test_packages.py 2022-04-05 
10:49:51.000000000 +0200
@@ -9,7 +9,7 @@
 
 
 @pytest.mark.parametrize('method_name', ['add', 'edit'])
[email protected]("copr.v3.proxies.package.Request")
[email protected]("copr.v3.proxies.Request.send")
 def test_package_distgit(request, method_name):
     mock_client = Client.create_from_config_file(config_location)
     method = getattr(mock_client.package_proxy, method_name)
@@ -17,7 +17,7 @@
            {"committish": "master", "distgit": "fedora"})
     assert len(request.call_args_list) == 1
     call = request.call_args_list[0]
-    endpoint = call[0][0]
+    endpoint = call[1]["endpoint"]
     args = call[1]
     assert args['method'] == 'POST'
     base_url = "/package/{0}".format(method_name)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/test/client_v3/test_requests.py 
new/copr-1.119/copr/test/client_v3/test_requests.py
--- old/copr-1.115/copr/test/client_v3/test_requests.py 2022-02-01 
00:11:50.000000000 +0100
+++ new/copr-1.119/copr/test/client_v3/test_requests.py 2022-04-05 
10:49:51.000000000 +0200
@@ -1,7 +1,6 @@
-import pytest
 from requests import Response
 from copr.test import mock
-from copr.v3.requests import Request, handle_errors, CoprRequestException, 
munchify
+from copr.v3.requests import Request, munchify
 
 
 class TestResponse(object):
@@ -20,17 +19,17 @@
 
 class TestRequest(object):
     def test_endpoint_url(self):
-        r1 = Request(endpoint="foo", api_base_url="http://copr/api_3";)
-        assert r1.endpoint_url == "http://copr/api_3/foo";
+        r1 = Request(api_base_url="http://copr/api_3";)
+        assert r1.endpoint_url("foo") == "http://copr/api_3/foo";
 
         # Leading and/or trailing slash should not be a problem
-        r2 = Request(endpoint="/foo/bar", api_base_url="http://copr/api_3/";)
-        assert r2.endpoint_url == "http://copr/api_3/foo/bar";
+        r2 = Request(api_base_url="http://copr/api_3/";)
+        assert r2.endpoint_url("/foo/bar") == "http://copr/api_3/foo/bar";
 
-    @mock.patch("requests.request")
+    @mock.patch('requests.Session.request')
     def test_send(self, request):
-        req1 = Request(endpoint="foo", api_base_url="http://copr/api_3";)
-        resp1 = req1.send()
+        req1 = Request(api_base_url="http://copr/api_3";)
+        resp1 = req1.send(endpoint="foo")
 
         request.assert_called_once()
         args, kwargs = request.call_args
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/v3/__init__.py 
new/copr-1.119/copr/v3/__init__.py
--- old/copr-1.115/copr/v3/__init__.py  2022-02-01 00:11:50.000000000 +0100
+++ new/copr-1.119/copr/v3/__init__.py  2022-04-05 10:49:51.000000000 +0200
@@ -15,7 +15,8 @@
                          CoprNoResultException,
                          CoprValidationException,
                          CoprNoConfigException,
-                         CoprConfigException)
+                         CoprConfigException,
+                         CoprAuthException)
 
 
 __all__ = [
@@ -36,4 +37,5 @@
     "CoprValidationException",
     "CoprNoConfigException",
     "CoprConfigException",
+    "CoprAuthException",
 ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/v3/exceptions.py 
new/copr-1.119/copr/v3/exceptions.py
--- old/copr-1.115/copr/v3/exceptions.py        2022-02-01 00:11:50.000000000 
+0100
+++ new/copr-1.119/copr/v3/exceptions.py        2022-04-05 10:49:51.000000000 
+0200
@@ -54,6 +54,7 @@
 class CoprNoConfigException(CoprException):
     """
     Exception thrown when no config file is found
+    We left this exception in our code because someone can still catch it
     """
     pass
 
@@ -63,3 +64,9 @@
     Exception thrown when the config file is incomplete or malformed.
     """
     pass
+
+
+class CoprAuthException(CoprException):
+    """
+    Copr authentication failure
+    """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/v3/helpers.py 
new/copr-1.119/copr/v3/helpers.py
--- old/copr-1.115/copr/v3/helpers.py   2022-01-04 01:09:47.000000000 +0100
+++ new/copr-1.119/copr/v3/helpers.py   2022-04-05 10:49:51.000000000 +0200
@@ -1,9 +1,11 @@
+from __future__ import absolute_import
+
 from functools import wraps
 import os
 import time
 import configparser
 from munch import Munch
-from .exceptions import CoprNoConfigException, CoprConfigException, 
CoprException
+from .exceptions import CoprConfigException, CoprException
 
 
 class List(list):
@@ -25,12 +27,14 @@
         raise CoprConfigException(str(ex))
 
     if not exists:
-        raise CoprNoConfigException("There is no config file: {}".format(path))
+        raw_config["copr-cli"] = {"copr_url": 
"https://copr.fedorainfracloud.org"}
 
     try:
-        for field in ["username", "login", "token", "copr_url"]:
+        for field in ["username", "login", "token", "copr_url", "gssapi"]:
             config[field] = raw_config["copr-cli"].get(field, None)
         config["encrypted"] = raw_config["copr-cli"].getboolean("encrypted", 
True)
+        config["gssapi"] = raw_config["copr-cli"].getboolean("gssapi")
+
 
     except configparser.Error as err:
         raise CoprConfigException("Bad configuration file: {0}".format(err))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/v3/proxies/__init__.py 
new/copr-1.119/copr/v3/proxies/__init__.py
--- old/copr-1.115/copr/v3/proxies/__init__.py  2021-12-12 16:37:48.000000000 
+0100
+++ new/copr-1.119/copr/v3/proxies/__init__.py  2022-04-05 10:49:51.000000000 
+0200
@@ -1,7 +1,21 @@
+import errno
+import json
+import time
 import os
-from ..helpers import config_from_file
-from ..requests import Request, munchify
-from ..helpers import for_all_methods, bind_proxy
+from filelock import FileLock
+
+try:
+    from urllib.parse import urlparse
+except ImportError:
+    from urlparse import urlparse
+
+from future.utils import raise_from
+
+import requests_gssapi
+
+from ..requests import Request, munchify, requests, handle_errors
+from ..helpers import for_all_methods, bind_proxy, config_from_file
+from ..exceptions import CoprAuthException
 
 
 @for_all_methods(bind_proxy)
@@ -12,6 +26,9 @@
 
     def __init__(self, config):
         self.config = config
+        self._auth_token_cached = None
+        self._auth_username = None
+        self.request = Request(api_base_url=self.api_base_url, 
connection_attempts=config.get("connection_attempts", 1))
 
     @classmethod
     def create_from_config_file(cls, path=None):
@@ -24,7 +41,95 @@
 
     @property
     def auth(self):
-        return self.config["login"], self.config["token"]
+        if self._auth_token_cached:
+            return self._auth_token_cached
+        if self.config.get("token"):
+            self._auth_token_cached = self.config["login"], 
self.config["token"]
+            self._auth_username = self.config.get("username")
+        elif self.config.get("gssapi"):
+            session_data = self._get_session_cookie_via_gssapi()
+            self._auth_token_cached = session_data["session"]
+            self._auth_username = session_data["name"]
+        else:
+            msg = "GSSAPI disabled and login:token is invalid 
({0}:{1})".format(
+                self.config.get("login", "NOT_SET"),
+                self.config.get("token", "NOT_SET"),
+            )
+            raise CoprAuthException(msg)
+        return self._auth_token_cached
+
+    def _get_session_cookie_via_gssapi(self):
+        """
+        Return the cached session for the configured username.  If not already
+        cached, new self.get_session_via_gssapi() is performed and result is
+        cached into ~/.config/copr/<session_file>.
+        """
+        url = urlparse(self.config["copr_url"]).netloc
+        cachedir = os.path.join(os.path.expanduser("~"), ".cache", "copr")
+
+        try:
+            os.makedirs(cachedir)
+        except OSError as err:
+            if err.errno != errno.EEXIST:
+                raise
+
+        session_file = os.path.join(cachedir, url + "-session")
+        session_data = self._load_or_download_session(session_file)
+        return session_data
+
+    @staticmethod
+    def _load_session_from_file(session_file):
+        session_data = None
+        if os.path.exists(session_file):
+            with open(session_file, "r") as file:
+                session_data = json.load(file)
+
+        if session_data and session_data["expiration"] > time.time():
+            return session_data
+        return None
+
+    def _load_or_download_session(self, session_file):
+        lock = FileLock(session_file + ".lock")
+        with lock:
+            session = BaseProxy._load_session_from_file(session_file)
+            if session:
+                return session
+            # TODO: create Munch sub-class that returns serializable dict, we
+            # have something like that in Cli: 
cli/copr_cli/util.py:serializable()
+            session_data = self.get_session_via_gssapi()
+            session_data = session_data.__dict__
+            session_data.pop("__response__", None)
+            session_data.pop("__proxy__", None)
+            BaseProxy._save_session(session_file, session_data)
+            return session_data
+
+    @staticmethod
+    def _save_session(session_file, session_data):
+        with open(session_file, "w") as file:
+            session_data["expiration"] = time.time() + 10 * 3600  # +10 hours
+            file.write(json.dumps(session_data, indent=4) + "\n")
+
+    def get_session_via_gssapi(self):
+        """
+        Obtain a _new_ session using GSSAPI route
+
+        :return: Munch, provides user's "id", "name", "session" cookie, and
+            "expiration".
+        """
+        url = self.config["copr_url"] + "/api_3/gssapi_login/"
+        session = requests.Session()
+        auth = requests_gssapi.HTTPSPNEGOAuth(opportunistic_auth=True)
+        try:
+            response = session.get(url, auth=auth)
+        except requests_gssapi.exceptions.SPNEGOExchangeError as err:
+            msg = "Can not get session for {0} cookie via GSSAPI: {1}".format(
+                self.config["copr_url"], err)
+            raise_from(CoprAuthException(msg), err)
+
+        handle_errors(response)
+        retval = munchify(response)
+        retval.session = response.cookies.get("session")
+        return retval
 
     def home(self):
         """
@@ -33,8 +138,7 @@
         :return: Munch
         """
         endpoint = ""
-        request = Request(endpoint, api_base_url=self.api_base_url)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint)
         return munchify(response)
 
     def auth_check(self):
@@ -44,6 +148,15 @@
         :return: Munch
         """
         endpoint = "/auth-check"
-        request = Request(endpoint, api_base_url=self.api_base_url, 
auth=self.auth)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint, auth=self.auth)
         return munchify(response)
+
+    def auth_username(self):
+        """
+        Return the username (string) assigned to this configuration.  May
+        contact the server and authenticate if needed.
+        """
+        if not self._auth_username:
+            # perform authentication as a side effect
+            _ = self.auth
+        return self._auth_username
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/v3/proxies/build.py 
new/copr-1.119/copr/v3/proxies/build.py
--- old/copr-1.115/copr/v3/proxies/build.py     2021-12-12 16:37:48.000000000 
+0100
+++ new/copr-1.119/copr/v3/proxies/build.py     2022-04-05 10:49:51.000000000 
+0200
@@ -2,7 +2,7 @@
 
 import os
 from . import BaseProxy
-from ..requests import Request, FileRequest, munchify, POST
+from ..requests import FileRequest, munchify, POST
 from ..exceptions import CoprValidationException
 from ..helpers import for_all_methods, bind_proxy
 
@@ -17,8 +17,7 @@
         :return: Munch
         """
         endpoint = "/build/{0}".format(build_id)
-        request = Request(endpoint, api_base_url=self.api_base_url)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint)
         return munchify(response)
 
     def get_source_chroot(self, build_id):
@@ -29,8 +28,7 @@
         :return: Munch
         """
         endpoint = "/build/source-chroot/{0}".format(build_id)
-        request = Request(endpoint, api_base_url=self.api_base_url)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint)
         return munchify(response)
 
     def get_source_build_config(self, build_id):
@@ -41,8 +39,7 @@
         :return: Munch
         """
         endpoint = "/build/source-build-config/{0}".format(build_id)
-        request = Request(endpoint, api_base_url=self.api_base_url)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint)
         return munchify(response)
 
     def get_built_packages(self, build_id):
@@ -53,8 +50,7 @@
         :return: Munch
         """
         endpoint = "/build/built-packages/{0}".format(build_id)
-        request = Request(endpoint, api_base_url=self.api_base_url)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint)
         return munchify(response)
 
     def get_list(self, ownername, projectname, packagename=None, status=None, 
pagination=None):
@@ -77,8 +73,7 @@
         }
         params.update(pagination or {})
 
-        request = Request(endpoint, api_base_url=self.api_base_url, 
params=params)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint, params=params)
         return munchify(response)
 
     def cancel(self, build_id):
@@ -89,8 +84,8 @@
         :return: Munch
         """
         endpoint = "/build/cancel/{0}".format(build_id)
-        request = Request(endpoint, api_base_url=self.api_base_url, 
method=POST, auth=self.auth)
-        response = request.send()
+        response = self.request.send(
+            endpoint=endpoint, method=POST, auth=self.auth)
         return munchify(response)
 
     def create_from_urls(self, ownername, projectname, urls, buildopts=None, 
project_dirname=None):
@@ -296,20 +291,21 @@
     def _create(self, endpoint, data, files=None, buildopts=None):
         data = data.copy()
 
-        request_class = Request
-        kwargs = {"endpoint": endpoint, "api_base_url": self.api_base_url,
-                  "data": data,"method": POST, "auth": self.auth}
-        if files:
-            request_class = FileRequest
-            kwargs["files"] = files
-
+        kwargs = {"api_base_url": self.api_base_url}
         if files and buildopts and "progress_callback" in buildopts:
             kwargs["progress_callback"] = buildopts["progress_callback"]
             del buildopts["progress_callback"]
 
         data.update(buildopts or {})
-        request = request_class(**kwargs)
-        response = request.send()
+        if not files:
+            response = self.request.send(
+                endpoint=endpoint, data=data, method=POST, auth=self.auth)
+        else:
+            kwargs["files"] = files
+            kwargs["connection_attempts"] = 
self.config.get("connection_attempts", 1)
+            request = FileRequest(**kwargs)
+            response = request.send(
+                endpoint=endpoint, data=data, method=POST, auth=self.auth)
         return munchify(response)
 
     def delete(self, build_id):
@@ -320,8 +316,8 @@
         :return: Munch
         """
         endpoint = "/build/delete/{0}".format(build_id)
-        request = Request(endpoint, api_base_url=self.api_base_url, 
method=POST, auth=self.auth)
-        response = request.send()
+        response = self.request.send(
+            endpoint=endpoint, method=POST, auth=self.auth)
         return munchify(response)
 
     def delete_list(self, build_ids):
@@ -334,6 +330,6 @@
 
         endpoint = "/build/delete/list"
         data = {"builds": build_ids}
-        request = Request(endpoint, data=data, api_base_url=self.api_base_url, 
method=POST, auth=self.auth)
-        response = request.send()
+        response = self.request.send(
+            endpoint=endpoint, data=data, method=POST, auth=self.auth)
         return munchify(response)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/v3/proxies/build_chroot.py 
new/copr-1.119/copr/v3/proxies/build_chroot.py
--- old/copr-1.115/copr/v3/proxies/build_chroot.py      2021-12-12 
16:37:48.000000000 +0100
+++ new/copr-1.119/copr/v3/proxies/build_chroot.py      2022-04-05 
10:49:51.000000000 +0200
@@ -1,7 +1,7 @@
 from __future__ import absolute_import
 
 from . import BaseProxy
-from ..requests import Request, munchify
+from ..requests import munchify
 from ..helpers import for_all_methods, bind_proxy
 
 
@@ -20,8 +20,7 @@
             "build_id": build_id,
             "chrootname": chrootname,
         }
-        request = Request(endpoint, api_base_url=self.api_base_url, 
params=params)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint, params=params)
         return munchify(response)
 
     def get_list(self, build_id, pagination=None):
@@ -38,8 +37,7 @@
             "build_id": build_id,
         }
         params.update(pagination or {})
-        request = Request(endpoint, api_base_url=self.api_base_url, 
params=params)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint, params=params)
         return munchify(response)
 
     def get_build_config(self, build_id, chrootname):
@@ -55,8 +53,7 @@
             "build_id": build_id,
             "chrootname": chrootname,
         }
-        request = Request(endpoint, api_base_url=self.api_base_url, 
params=params)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint, params=params)
         return munchify(response)
 
     def get_built_packages(self, build_id, chrootname):
@@ -72,6 +69,5 @@
             "build_id": build_id,
             "chrootname": chrootname,
         }
-        request = Request(endpoint, api_base_url=self.api_base_url, 
params=params)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint, params=params)
         return munchify(response)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/v3/proxies/mock_chroot.py 
new/copr-1.119/copr/v3/proxies/mock_chroot.py
--- old/copr-1.115/copr/v3/proxies/mock_chroot.py       2021-12-12 
16:37:48.000000000 +0100
+++ new/copr-1.119/copr/v3/proxies/mock_chroot.py       2022-04-05 
10:49:51.000000000 +0200
@@ -1,8 +1,7 @@
 from __future__ import absolute_import
 
-import os
 from . import BaseProxy
-from ..requests import Request, munchify
+from ..requests import munchify
 from ..helpers import for_all_methods, bind_proxy
 
 
@@ -10,11 +9,11 @@
 class MockChrootProxy(BaseProxy):
 
     def get_list(self, pagination=None):
+        # TODO: implement pagination
         """List all currently available chroots.
 
         :return: Munch
         """
         endpoint = "/mock-chroots/list"
-        request = Request(endpoint, api_base_url=self.api_base_url)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint)
         return munchify(response)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/v3/proxies/module.py 
new/copr-1.119/copr/v3/proxies/module.py
--- old/copr-1.115/copr/v3/proxies/module.py    2021-12-12 16:37:48.000000000 
+0100
+++ new/copr-1.119/copr/v3/proxies/module.py    2022-04-05 10:49:51.000000000 
+0200
@@ -2,7 +2,7 @@
 
 import os
 from . import BaseProxy
-from ..requests import Request, FileRequest, munchify, POST
+from ..requests import FileRequest, munchify, POST
 from ..helpers import for_all_methods, bind_proxy
 
 
@@ -31,9 +31,13 @@
         }
         if distgit is not None:
             data["distgit"] = distgit
-        request = Request(endpoint, api_base_url=self.api_base_url, 
method=POST,
-                          params=params, data=data, auth=self.auth)
-        response = request.send()
+        response = self.request.send(
+            endpoint=endpoint,
+            method=POST,
+            params=params,
+            data=data,
+            auth=self.auth,
+        )
         return munchify(response)
 
     def build_from_file(self, ownername, projectname, path, distgit=None):
@@ -57,7 +61,16 @@
         data = None
         if distgit is not None:
             data = {"distgit": distgit}
-        request = FileRequest(endpoint, api_base_url=self.api_base_url, 
method=POST,
-                              params=params, files=files, data=data, 
auth=self.auth)
-        response = request.send()
+        request = FileRequest(
+            files=files,
+            api_base_url=self.api_base_url,
+            connection_attempts=self.config.get("connection_attempts", 1)
+        )
+        response = request.send(
+            endpoint=endpoint,
+            method=POST,
+            params=params,
+            data=data,
+            auth=self.auth,
+        )
         return munchify(response)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/v3/proxies/monitor.py 
new/copr-1.119/copr/v3/proxies/monitor.py
--- old/copr-1.115/copr/v3/proxies/monitor.py   2022-01-04 01:09:47.000000000 
+0100
+++ new/copr-1.119/copr/v3/proxies/monitor.py   2022-04-05 10:49:51.000000000 
+0200
@@ -3,7 +3,7 @@
 """
 
 from copr.v3 import proxies
-from copr.v3.requests import Request, munchify
+from copr.v3.requests import munchify
 from copr.v3.helpers import for_all_methods, bind_proxy
 
 
@@ -47,6 +47,5 @@
             "project_dirname": project_dirname,
             "additional_fields[]": additional_fields,
         }
-        request = Request(endpoint, api_base_url=self.api_base_url, 
params=params)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint, params=params)
         return munchify(response)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/v3/proxies/package.py 
new/copr-1.119/copr/v3/proxies/package.py
--- old/copr-1.115/copr/v3/proxies/package.py   2022-01-04 01:09:47.000000000 
+0100
+++ new/copr-1.119/copr/v3/proxies/package.py   2022-04-05 10:49:51.000000000 
+0200
@@ -1,7 +1,7 @@
 from __future__ import absolute_import
 from . import BaseProxy
 from .build import BuildProxy
-from ..requests import Request, munchify, POST
+from ..requests import munchify, POST
 from ..helpers import for_all_methods, bind_proxy
 
 
@@ -31,8 +31,7 @@
             "with_latest_build": with_latest_build,
             "with_latest_succeeded_build": with_latest_succeeded_build,
         }
-        request = Request(endpoint, api_base_url=self.api_base_url, 
params=params)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint, params=params)
         return munchify(response)
 
     def get_list(self, ownername, projectname, pagination=None,
@@ -59,8 +58,7 @@
         }
         params.update(pagination or {})
 
-        request = Request(endpoint, api_base_url=self.api_base_url, 
params=params)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint, params=params)
         return munchify(response)
 
     def add(self, ownername, projectname, packagename, source_type, 
source_dict):
@@ -85,9 +83,13 @@
             "package_name": packagename,
         }
         data.update(source_dict)
-        request = Request(endpoint, api_base_url=self.api_base_url, 
method=POST,
-                          params=params, data=data, auth=self.auth)
-        response = request.send()
+        response = self.request.send(
+            endpoint=endpoint,
+            method=POST,
+            params=params,
+            data=data,
+            auth=self.auth,
+        )
         return munchify(response)
 
     def edit(self, ownername, projectname, packagename, source_type=None, 
source_dict=None):
@@ -112,9 +114,13 @@
             "package_name": packagename,
         }
         data.update(source_dict or {})
-        request = Request(endpoint, api_base_url=self.api_base_url, 
method=POST,
-                          params=params, data=data, auth=self.auth)
-        response = request.send()
+        response = self.request.send(
+            endpoint=endpoint,
+            method=POST,
+            params=params,
+            data=data,
+            auth=self.auth,
+        )
         return munchify(response)
 
     def reset(self, ownername, projectname, packagename):
@@ -134,8 +140,8 @@
             "projectname": projectname,
             "package_name": packagename,
         }
-        request = Request(endpoint, api_base_url=self.api_base_url, data=data, 
method=POST, auth=self.auth)
-        response = request.send()
+        response = self.request.send(
+            endpoint=endpoint, data=data, method=POST, auth=self.auth)
         return munchify(response)
 
     def build(self, ownername, projectname, packagename, buildopts=None, 
project_dirname=None):
@@ -174,6 +180,6 @@
             "projectname": projectname,
             "package_name": packagename,
         }
-        request = Request(endpoint, api_base_url=self.api_base_url, data=data, 
method=POST, auth=self.auth)
-        response = request.send()
+        response = self.request.send(
+            endpoint=endpoint, data=data, method=POST, auth=self.auth)
         return munchify(response)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/v3/proxies/project.py 
new/copr-1.119/copr/v3/proxies/project.py
--- old/copr-1.115/copr/v3/proxies/project.py   2022-01-04 01:09:47.000000000 
+0100
+++ new/copr-1.119/copr/v3/proxies/project.py   2022-04-05 10:49:51.000000000 
+0200
@@ -3,7 +3,7 @@
 import warnings
 
 from . import BaseProxy
-from ..requests import Request, munchify, POST, GET, PUT
+from ..requests import munchify, POST, GET, PUT
 from ..helpers import for_all_methods, bind_proxy
 
 
@@ -30,8 +30,7 @@
             "ownername": ownername,
             "projectname": projectname,
         }
-        request = Request(endpoint, api_base_url=self.api_base_url, 
params=params)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint, params=params)
         return munchify(response)
 
     def get_list(self, ownername=None, pagination=None):
@@ -47,8 +46,7 @@
             "ownername": ownername,
         }
         params.update(pagination or {})
-        request = Request(endpoint, api_base_url=self.api_base_url, 
params=params)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint, params=params)
         return munchify(response)
 
     def search(self, query, pagination=None):
@@ -64,8 +62,7 @@
             "query": query,
         }
         params.update(pagination or {})
-        request = Request(endpoint, api_base_url=self.api_base_url, 
params=params)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint, params=params)
         return munchify(response)
 
     def add(self, ownername, projectname, chroots, description=None, 
instructions=None, homepage=None,
@@ -136,9 +133,13 @@
 
         _compat_use_bootstrap_container(data, use_bootstrap_container)
 
-        request = Request(endpoint, api_base_url=self.api_base_url, 
method=POST,
-                          params=params, data=data, auth=self.auth)
-        response = request.send()
+        response = self.request.send(
+            endpoint=endpoint,
+            method=POST,
+            params=params,
+            data=data,
+            auth=self.auth,
+        )
         return munchify(response)
 
     def edit(self, ownername, projectname, chroots=None, description=None, 
instructions=None, homepage=None,
@@ -207,9 +208,13 @@
 
         _compat_use_bootstrap_container(data, use_bootstrap_container)
 
-        request = Request(endpoint, api_base_url=self.api_base_url, 
method=POST,
-                          params=params, data=data, auth=self.auth)
-        response = request.send()
+        response = self.request.send(
+            endpoint=endpoint,
+            method=POST,
+            params=params,
+            data=data,
+            auth=self.auth,
+        )
         return munchify(response)
 
     def delete(self, ownername, projectname):
@@ -228,9 +233,13 @@
         data = {
             "verify": True,
         }
-        request = Request(endpoint, api_base_url=self.api_base_url, 
method=POST,
-                          params=params, data=data, auth=self.auth)
-        response = request.send()
+        response = self.request.send(
+            endpoint=endpoint,
+            method=POST,
+            params=params,
+            data=data,
+            auth=self.auth,
+        )
         return munchify(response)
 
     def fork(self, ownername, projectname, dstownername, dstprojectname, 
confirm=False):
@@ -255,9 +264,13 @@
             "ownername": dstownername,
             "confirm": confirm,
         }
-        request = Request(endpoint, api_base_url=self.api_base_url, 
method=POST,
-                          params=params, data=data, auth=self.auth)
-        response = request.send()
+        response = self.request.send(
+            endpoint=endpoint,
+            method=POST,
+            params=params,
+            data=data,
+            auth=self.auth,
+        )
         return munchify(response)
 
     def get_permissions(self, ownername, projectname):
@@ -277,14 +290,8 @@
             "ownername": ownername,
             "projectname": projectname,
         }
-        request = Request(
-                endpoint,
-                api_base_url=self.api_base_url,
-                auth=self.auth,
-                method=GET,
-                params=params)
-
-        response = request.send()
+        response = self.request.send(
+            endpoint=endpoint, params=params, auth=self.auth)
         return munchify(response)
 
     def set_permissions(self, ownername, projectname, permissions):
@@ -312,15 +319,13 @@
             "ownername": ownername,
             "projectname": projectname,
         }
-        request = Request(
-                endpoint,
-                api_base_url=self.api_base_url,
-                auth=self.auth,
-                method=PUT,
-                params=params,
-                data=permissions)
-
-        request.send()
+        self.request.send(
+            endpoint=endpoint,
+            method=PUT,
+            params=params,
+            data=permissions,
+            auth=self.auth,
+        )
 
     def request_permissions(self, ownername, projectname, permissions):
         """
@@ -341,15 +346,13 @@
             "ownername": ownername,
             "projectname": projectname,
         }
-        request = Request(
-                endpoint,
-                api_base_url=self.api_base_url,
-                auth=self.auth,
-                method=PUT,
-                params=params,
-                data=permissions)
-
-        request.send()
+        self.request.send(
+            endpoint=endpoint,
+            method=PUT,
+            params=params,
+            data=permissions,
+            auth=self.auth,
+        )
 
     def regenerate_repos(self, ownername, projectname):
         """
@@ -363,13 +366,6 @@
             "ownername": ownername,
             "projectname": projectname
         }
-        request = Request(
-                endpoint,
-                api_base_url=self.api_base_url,
-                auth=self.auth,
-                method=PUT,
-                params=params)
-
-        request.send()
-        response = request.send()
+        response = self.request.send(
+            endpoint=endpoint, method=PUT, params=params, auth=self.auth)
         return munchify(response)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/v3/proxies/project_chroot.py 
new/copr-1.119/copr/v3/proxies/project_chroot.py
--- old/copr-1.115/copr/v3/proxies/project_chroot.py    2021-12-12 
16:37:48.000000000 +0100
+++ new/copr-1.119/copr/v3/proxies/project_chroot.py    2022-04-05 
10:49:51.000000000 +0200
@@ -2,7 +2,7 @@
 
 import os
 from . import BaseProxy
-from ..requests import Request, FileRequest, munchify, POST
+from ..requests import FileRequest, munchify, POST
 from ..helpers import for_all_methods, bind_proxy
 
 
@@ -24,8 +24,7 @@
             "projectname": projectname,
             "chrootname": chrootname,
         }
-        request = Request(endpoint, api_base_url=self.api_base_url, 
params=params)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint, params=params)
         return munchify(response)
 
     def get_build_config(self, ownername, projectname, chrootname):
@@ -43,14 +42,14 @@
             "projectname": projectname,
             "chrootname": chrootname,
         }
-        request = Request(endpoint, api_base_url=self.api_base_url, 
params=params)
-        response = request.send()
+        response = self.request.send(endpoint=endpoint, params=params)
         return munchify(response)
 
     # pylint: disable=too-many-arguments
     def edit(self, ownername, projectname, chrootname, 
additional_packages=None, additional_repos=None,
-             comps=None, delete_comps=False, with_opts=None, without_opts=None,
-             bootstrap=None, bootstrap_image=None, isolation=None):
+             additional_modules=None, comps=None, delete_comps=False, 
with_opts=None, without_opts=None,
+             bootstrap=None, bootstrap_image=None, isolation=None,
+             reset_fields=None):
         """
         Edit a chroot configuration in a project
 
@@ -59,6 +58,7 @@
         :param str chrootname:
         :param list additional_packages: buildroot packages for the chroot
         :param list additional_repos: buildroot additional additional_repos
+        :param list additional_modules: additional modules for the chroot
         :param str comps: file path to the comps.xml file
         :param bool delete_comps: if True, current comps.xml will be removed
         :param list with_opts: Mock --with option
@@ -68,6 +68,10 @@
         :param str bootstrap_image: Implies 'bootstrap=image'.
         :param str isolation: Mock isolation feature setup.
             Possible values are 'default', 'simple', 'nspawn'.
+        :param list reset_fields: list of chroot attributes, that should be
+            reseted to their respective defaults. Possible values are
+            `additional_packages`, `additional_modules`, `isolation`, etc. See
+            the output of `ProjectProxy.get` for all the possible field names.
         :return: Munch
         """
         endpoint = 
"/project-chroot/edit/{ownername}/{projectname}/{chrootname}"
@@ -83,19 +87,30 @@
         data = {
             "additional_repos": additional_repos,
             "additional_packages": additional_packages,
+            "additional_modules": additional_modules,
             "delete_comps": delete_comps,
             "with_opts": with_opts,
             "without_opts": without_opts,
             "bootstrap": bootstrap,
             "bootstrap_image": bootstrap_image,
             "isolation": isolation,
+            "reset_fields": reset_fields,
         }
         files = {}
         if comps:
             comps_f = open(comps, "rb")
             files["upload_comps"] = (os.path.basename(comps_f.name), comps_f, 
"application/text")
 
-        request = FileRequest(endpoint, api_base_url=self.api_base_url, 
method=POST,
-                              params=params, data=data, files=files, 
auth=self.auth)
-        response = request.send()
+        request = FileRequest(
+            api_base_url=self.api_base_url,
+            files=files,
+            connection_attempts=self.config.get("connection_attempts", 1)
+        )
+        response = request.send(
+            endpoint=endpoint,
+            method=POST,
+            params=params,
+            data=data,
+            auth=self.auth,
+        )
         return munchify(response)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/v3/proxies/webhook.py 
new/copr-1.119/copr/v3/proxies/webhook.py
--- old/copr-1.115/copr/v3/proxies/webhook.py   2021-12-12 16:37:48.000000000 
+0100
+++ new/copr-1.119/copr/v3/proxies/webhook.py   2022-04-05 10:49:51.000000000 
+0200
@@ -5,7 +5,7 @@
 from __future__ import absolute_import
 
 from . import BaseProxy
-from ..requests import Request, munchify, POST
+from ..requests import munchify, POST
 from ..helpers import for_all_methods, bind_proxy
 
 
@@ -29,7 +29,6 @@
             "ownername": ownername,
             "projectname": projectname,
         }
-        request = Request(endpoint, api_base_url=self.api_base_url, 
method=POST,
-                          params=params, auth=self.auth)
-        response = request.send()
+        response = self.request.send(
+            endpoint=endpoint, method=POST, params=params, auth=self.auth)
         return munchify(response)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr/v3/requests.py 
new/copr-1.119/copr/v3/requests.py
--- old/copr-1.115/copr/v3/requests.py  2022-02-01 00:11:50.000000000 +0100
+++ new/copr-1.119/copr/v3/requests.py  2022-04-05 10:49:51.000000000 +0200
@@ -2,11 +2,14 @@
 
 import os
 import json
+import time
 import requests
+import requests_gssapi
+from copr.v3.helpers import List
 from munch import Munch
+from future.utils import raise_from
 from requests_toolbelt.multipart.encoder import MultipartEncoder, 
MultipartEncoderMonitor
-from .helpers import List
-from .exceptions import CoprRequestException, CoprNoResultException, 
CoprTimeoutException
+from .exceptions import CoprRequestException, CoprNoResultException, 
CoprTimeoutException, CoprAuthException
 
 
 GET = "GET"
@@ -18,67 +21,80 @@
     # This should be a replacement of the _fetch method from APIv1
     # We can have Request, FileRequest, AuthRequest/UnAuthRequest, ...
 
-    def __init__(self, endpoint, api_base_url=None, method=None, data=None, 
params=None, auth=None):
+    def __init__(self, api_base_url=None, connection_attempts=1):
         """
-        :param endpoint:
         :param api_base_url:
-        :param method:
-        :param data: dict
-        :param params: dict for constructing query params in URL (e.g. 
?key1=val1)
-        :param auth: tuple (login, token)
+        :param connection_attempts:
 
         @TODO maybe don't have both params and data, but rather only one 
variable
         @TODO and send it as data on POST and as params on GET
         """
-        self.endpoint = endpoint
         self.api_base_url = api_base_url
-        self._method = method or GET
-        self.data = data
-        self.params = params or {}
-        self.auth = auth
-        self.headers = None
-
-    @property
-    def endpoint_url(self):
-        endpoint = self.endpoint.strip("/").format(**self.params)
+        self.connection_attempts = connection_attempts
+
+    def endpoint_url(self, endpoint, params=None):
+        params = params or {}
+        endpoint = endpoint.strip("/").format(**params)
         return os.path.join(self.api_base_url, endpoint)
 
-    @property
-    def method(self):
-        return self._method.upper()
-
-    def send(self):
-        try:
-            response = requests.request(**self._request_params)
-        except requests.exceptions.ConnectionError:
-            raise CoprRequestException("Unable to connect to 
{0}.".format(self.api_base_url))
+    def send(self, endpoint, method=GET, data=None, params=None, headers=None,
+             auth=None):
+        session = requests.Session()
+        if not isinstance(auth, tuple):
+            # api token not available, set session cookie obtained via gssapi
+            session.cookies.set("session", auth)
+
+        request_params = self._request_params(
+            endpoint, method, data, params, headers, auth)
+
+        response = self._send_request_repeatedly(session, request_params)
         handle_errors(response)
         return response
 
-    @property
-    def _request_params(self):
-        return {
-            "url": self.endpoint_url,
-            "auth": self.auth,
-            "json": self.data,
-            "method": self.method,
-            "params": self.params,
-            "headers": self.headers,
+    def _send_request_repeatedly(self, session, request_params):
+        """
+        Repeat the request until it succeeds, or connection retry reaches its 
limit.
+        """
+        sleep = 5
+        for i in range(1, self.connection_attempts + 1):
+            try:
+                response = session.request(**request_params)
+            except requests_gssapi.exceptions.SPNEGOExchangeError as e:
+                raise_from(CoprAuthException("GSSAPI authentication failed."), 
e)
+            except requests.exceptions.ConnectionError:
+                if i < self.connection_attempts:
+                    time.sleep(sleep)
+            else:
+                return response
+        raise CoprRequestException("Unable to connect to 
{0}.".format(self.api_base_url))
+
+    def _request_params(self, endpoint, method=GET, data=None, params=None,
+                        headers=None, auth=None):
+        params = {
+            "url": self.endpoint_url(endpoint, params),
+            "json": data,
+            "method": method.upper(),
+            "params": params,
+            "headers": headers,
         }
+        # We usually use a tuple (login, token). If this is not available,
+        # we use gssapi auth, which works with cookies.
+        if isinstance(auth, tuple):
+            params["auth"] = auth
+        return params
 
 
 class FileRequest(Request):
-    def __init__(self, endpoint, files=None, progress_callback=None, **kwargs):
-        super(FileRequest, self).__init__(endpoint, **kwargs)
+    def __init__(self, files=None, progress_callback=None, **kwargs):
+        super(FileRequest, self).__init__(**kwargs)
         self.files = files
         self.progress_callback = progress_callback
 
-    @property
-    def _request_params(self):
-        params = super(FileRequest, self)._request_params
+    def _request_params(self, *args, **kwargs):
+        params = super(FileRequest, self)._request_params(*args, **kwargs)
 
         data = self.files or {}
-        data["json"] = ("json", json.dumps(self.data), "application/json")
+        data["json"] = ("json", json.dumps(params["json"]), "application/json")
 
         callback = self.progress_callback or (lambda x: x)
         m = MultipartEncoder(data)
@@ -102,6 +118,9 @@
         if "error" not in response_json:
             return
 
+        if response.status_code == 403:
+            raise CoprAuthException(response_json["error"], response=response)
+
         if response.status_code == 404:
             raise CoprNoResultException(response_json["error"], 
response=response)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr.egg-info/PKG-INFO 
new/copr-1.119/copr.egg-info/PKG-INFO
--- old/copr-1.115/copr.egg-info/PKG-INFO       2022-02-03 13:17:46.000000000 
+0100
+++ new/copr-1.119/copr.egg-info/PKG-INFO       2022-04-05 10:50:41.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: copr
-Version: 1.115
+Version: 1.119
 Summary: Python client for copr service.
 Home-page: https://pagure.io/copr/copr
 Author: Valentin Gologuzov
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/copr.egg-info/requires.txt 
new/copr-1.119/copr.egg-info/requires.txt
--- old/copr-1.115/copr.egg-info/requires.txt   2022-02-03 13:17:46.000000000 
+0100
+++ new/copr-1.119/copr.egg-info/requires.txt   2022-04-05 10:50:41.000000000 
+0200
@@ -1,6 +1,9 @@
+filelock
 marshmallow
 requests
 requests-toolbelt
 setuptools
 six
 munch
+requests-gssapi
+future
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/docs/client_v2/resources_usage.rst 
new/copr-1.119/docs/client_v2/resources_usage.rst
--- old/copr-1.115/docs/client_v2/resources_usage.rst   2022-01-04 
01:09:47.000000000 +0100
+++ new/copr-1.119/docs/client_v2/resources_usage.rst   2022-04-05 
10:49:51.000000000 +0200
@@ -12,7 +12,7 @@
 
         >>> from copr import create_client2_from_params
         # using dev server for test
-        >>> cl = 
create_client2_from_params(root_url="http://copr-fe-dev.cloud.fedoraproject.org/";)
+        >>> cl = 
create_client2_from_params(root_url="http://copr.stg.fedoraproject.org/";)
 
         >>> projects = cl.projects.get_list(name="copr", limit=3)
         >>> for p in projects:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/python-copr.spec 
new/copr-1.119/python-copr.spec
--- old/copr-1.115/python-copr.spec     2022-02-02 22:55:33.000000000 +0100
+++ new/copr-1.119/python-copr.spec     2022-04-05 10:49:51.000000000 +0200
@@ -9,7 +9,7 @@
 %endif
 
 Name:       python-copr
-Version:    1.115
+Version:    1.119
 Release:    1%{?dist}
 Summary:    Python interface for Copr
 
@@ -35,9 +35,12 @@
 BuildRequires: python-six >= 1.9.0
 BuildRequires: python-mock
 BuildRequires: python-munch
+BuildRequires: python-filelock
 BuildRequires: python-configparser
 BuildRequires: pytest
 BuildRequires: python2-devel
+BuildRequires: python2-requests-gssapi
+BuildRequires: python-future
 # for doc package
 BuildRequires: python-sphinx
 BuildRequires: python-docutils
@@ -47,11 +50,13 @@
 BuildRequires: python2-requests-toolbelt
 BuildRequires: python2-marshmallow
 BuildRequires: python2-six >= 1.9.0
-BuildRequires: python2-mock
 BuildRequires: python2-pytest
 BuildRequires: python2-devel
 BuildRequires: python-munch
+BuildRequires: python2-filelock
 BuildRequires: python2-configparser
+BuildRequires: python2-requests-gssapi
+BuildRequires: python-future
 # for doc package
 BuildRequires: python2-sphinx
 BuildRequires: python2-docutils
@@ -78,18 +83,23 @@
 Requires: python-configparser
 Requires: python-marshmallow
 Requires: python-munch
+Requires: python-filelock
 Requires: python-requests
 Requires: python-requests-toolbelt
 Requires: python-setuptools
 Requires: python-six >= 1.9.0
+Requires: python-future
 %else
 Requires: python2-configparser
 Requires: python2-marshmallow
 Requires: python2-munch
+Requires: python2-filelock
 Requires: python2-requests
 Requires: python2-requests-toolbelt
 Requires: python2-setuptools
+Requires: python2-requests-gssapi
 Requires: python2-six >= 1.9.0
+Requires: python-future
 %endif
 
 %{?python_provide:%python_provide python2-copr}
@@ -108,6 +118,7 @@
 BuildRequires: python3-devel
 BuildRequires: python3-docutils
 BuildRequires: python3-munch
+BuildRequires: python3-filelock
 BuildRequires: python3-marshmallow
 BuildRequires: python3-pytest
 BuildRequires: python3-setuptools
@@ -115,13 +126,18 @@
 BuildRequires: python3-requests-toolbelt
 BuildRequires: python3-six
 BuildRequires: python3-sphinx
+BuildRequires: python3-requests-gssapi
+BuildRequires: python3-future
 
 Requires: python3-marshmallow
 Requires: python3-munch
+Requires: python3-filelock
 Requires: python3-requests
 Requires: python3-requests-toolbelt
 Requires: python3-setuptools
 Requires: python3-six
+Requires: python3-requests-gssapi
+Requires: python3-future
 %endif
 
 %{?python_provide:%python_provide python3-copr}
@@ -130,6 +146,9 @@
 BuildRequires: python3-devel
 BuildRequires: python3-sphinx
 BuildRequires: python3-pytest
+BuildRequires: python3-requests-gssapi
+BuildRequires: python3-future
+BuildRequires: python3-filelock
 
 %generate_buildrequires
 %pyproject_buildrequires -r
@@ -221,6 +240,21 @@
 %doc %{_pkgdocdir}
 
 %changelog
+* Mon Apr 04 2022 Pavel Raiskup <[email protected]> 1.119-1
+- really depend on filelock component
+
+* Mon Apr 04 2022 Pavel Raiskup <[email protected]> 1.118-1
+- drop the python-mock build-requires again
+
+* Mon Apr 04 2022 Pavel Raiskup <[email protected]> 1.117-1
+- support for GSSAPI added (gssapi=true config option)
+- better error message when authentication fails
+- add a connection_attempts, retries connection to Frontend upon failure
+
+* Fri Mar 18 2022 Pavel Raiskup <[email protected]> 1.116-1
+- add support for resetting fields
+- allow setting modules in 'edit-chroot' methods
+
 * Wed Feb 02 2022 Silvie Chlupova <[email protected]> 1.115-1
 - don't BuildRequires pyproject-rpm-macros directly
 - fix exception caused by default msg value
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/requirements.txt 
new/copr-1.119/requirements.txt
--- old/copr-1.115/requirements.txt     2021-12-12 16:37:48.000000000 +0100
+++ new/copr-1.119/requirements.txt     2022-04-05 10:49:51.000000000 +0200
@@ -2,5 +2,4 @@
 # Use this file by running "$ pip install -r requirements.txt"
 
 pytest
-mock
 sphinx
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/copr-1.115/setup.py new/copr-1.119/setup.py
--- old/copr-1.115/setup.py     2022-02-02 22:55:33.000000000 +0100
+++ new/copr-1.119/setup.py     2022-04-05 10:49:51.000000000 +0200
@@ -14,12 +14,15 @@
 This part is a python client to the copr service."""
 
 requires = [
+    'filelock',
     'marshmallow',
     'requests',
     'requests-toolbelt',
     'setuptools',
     'six',
     'munch',
+    'requests-gssapi',
+    'future',
 ]
 
 __description__ = "Python client for copr service."
@@ -30,7 +33,7 @@
 
 setup(
     name='copr',
-    version="1.115",
+    version="1.119",
     description=__description__,
     long_description=long_description,
     author=__author__,

Reply via email to