Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-redfish for openSUSE:Factory 
checked in at 2022-01-20 00:12:10
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-redfish (Old)
 and      /work/SRC/openSUSE:Factory/.python-redfish.new.1892 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-redfish"

Thu Jan 20 00:12:10 2022 rev:8 rq:947367 version:3.1.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-redfish/python-redfish.changes    
2021-10-26 20:14:40.314040808 +0200
+++ /work/SRC/openSUSE:Factory/.python-redfish.new.1892/python-redfish.changes  
2022-01-20 00:12:55.750607969 +0100
@@ -1,0 +2,7 @@
+Wed Jan 19 09:35:39 UTC 2022 - Dirk M??ller <[email protected]>
+
+- update to 3.1.0:
+  * Updated library to leverage 'requests' in favor of 'http.client'
+- add collections-python310.patch
+
+-------------------------------------------------------------------

Old:
----
  redfish-3.0.3.tar.gz

New:
----
  collections-python310.patch
  redfish-3.1.0.tar.gz

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

Other differences:
------------------
++++++ python-redfish.spec ++++++
--- /var/tmp/diff_new_pack.2dq39E/_old  2022-01-20 00:12:56.214608344 +0100
+++ /var/tmp/diff_new_pack.2dq39E/_new  2022-01-20 00:12:56.222608350 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-redfish
 #
-# Copyright (c) 2021 SUSE LLC
+# Copyright (c) 2022 SUSE LLC
 # Copyright (c) 2020-2021, Martin Hauke <[email protected]>
 #
 # All modifications and additions to the file contributed by third parties
@@ -19,19 +19,22 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-redfish
-Version:        3.0.3
+Version:        3.1.0
 Release:        0
 Summary:        Redfish Python Library
 License:        BSD-3-Clause
 Group:          Development/Languages/Python
 URL:            https://github.com/DMTF/python-redfish-library
 Source:         
https://github.com/DMTF/python-redfish-library/archive/%{version}.tar.gz#/redfish-%{version}.tar.gz
+# submitted as gh#DMTF/python-redfish-library#118
+Patch1:         collections-python310.patch
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 Requires:       python-jsonpatch
 Requires:       python-jsonpath-rw
 Requires:       python-jsonpointer
+Requires:       python-requests
 BuildArch:      noarch
 # SECTION test requirements
 BuildRequires:  %{python_module jsonpatch}
@@ -40,6 +43,7 @@
 BuildRequires:  %{python_module mock}
 BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module requests-toolbelt}
+BuildRequires:  %{python_module requests}
 # /SECTION
 %python_subpackages
 
@@ -49,7 +53,7 @@
 the Engine of Application State) Redfish architecture.
 
 %prep
-%setup -q -n %{name}-library-%{version}
+%autosetup -p1 -n %{name}-library-%{version}
 
 %build
 %python_build

++++++ collections-python310.patch ++++++
--- python-redfish-library-3.1.0/src/redfish/ris/rmc.py 2022-01-10 
14:55:53.000000000 +0100
+++ python-redfish-library-3.1.0/src/redfish/ris/rmc.py 2022-01-16 
15:02:43.088058912 +0100
@@ -15,7 +15,8 @@
 import copy
 import shutil
 import logging
-from collections import OrderedDict, Mapping
+from collections import OrderedDict
+from collections.abc import Mapping
 
 import jsonpatch
 import jsonpath_rw

++++++ redfish-3.0.3.tar.gz -> redfish-3.1.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redfish-library-3.0.3/CHANGELOG.md 
new/python-redfish-library-3.1.0/CHANGELOG.md
--- old/python-redfish-library-3.0.3/CHANGELOG.md       2021-10-15 
22:51:22.000000000 +0200
+++ new/python-redfish-library-3.1.0/CHANGELOG.md       2022-01-10 
14:55:53.000000000 +0100
@@ -1,5 +1,8 @@
 # Change Log
 
+## [3.1.0] - 2022-01-10
+- Updated library to leverage 'requests' in favor of 'http.client'
+
 ## [3.0.3] - 2021-10-15
 - Added support for performing multi-part HTTP POST requests
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redfish-library-3.0.3/requirements.txt 
new/python-redfish-library-3.1.0/requirements.txt
--- old/python-redfish-library-3.0.3/requirements.txt   2021-10-15 
22:51:22.000000000 +0200
+++ new/python-redfish-library-3.1.0/requirements.txt   2022-01-10 
14:55:53.000000000 +0100
@@ -3,4 +3,5 @@
 jsonpath_rw
 jsonpointer
 urllib3
+requests
 requests-toolbelt
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redfish-library-3.0.3/setup.py 
new/python-redfish-library-3.1.0/setup.py
--- old/python-redfish-library-3.0.3/setup.py   2021-10-15 22:51:22.000000000 
+0200
+++ new/python-redfish-library-3.1.0/setup.py   2022-01-10 14:55:53.000000000 
+0100
@@ -12,7 +12,7 @@
     long_description = f.read()
 
 setup(name='redfish',
-      version='3.0.3',
+      version='3.1.0',
       description='Redfish Python Library',
       long_description=long_description,
       long_description_content_type='text/x-rst',
@@ -31,6 +31,7 @@
       install_requires=[
           'jsonpath_rw',
           'jsonpointer',
+          "requests",
           'requests_toolbelt',
       ],
       extras_require={
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redfish-library-3.0.3/src/redfish/__init__.py 
new/python-redfish-library-3.1.0/src/redfish/__init__.py
--- old/python-redfish-library-3.0.3/src/redfish/__init__.py    2021-10-15 
22:51:22.000000000 +0200
+++ new/python-redfish-library-3.1.0/src/redfish/__init__.py    2022-01-10 
14:55:53.000000000 +0100
@@ -6,7 +6,7 @@
 """ Redfish restful library """
 
 __all__ = ['rest', 'ris', 'discovery']
-__version__ = "3.0.3"
+__version__ = "3.1.0"
 
 from redfish.rest.v1 import redfish_client
 from redfish.rest.v1 import AuthMethod
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redfish-library-3.0.3/src/redfish/rest/v1.py 
new/python-redfish-library-3.1.0/src/redfish/rest/v1.py
--- old/python-redfish-library-3.0.3/src/redfish/rest/v1.py     2021-10-15 
22:51:22.000000000 +0200
+++ new/python-redfish-library-3.1.0/src/redfish/rest/v1.py     2022-01-10 
14:55:53.000000000 +0100
@@ -8,26 +8,26 @@
 
 #---------Imports---------
 
-import os
 import sys
-import ssl
 import time
 import gzip
 import json
 import base64
 import logging
-import http.client
-import re
 import warnings
+import requests
 
 from collections import (OrderedDict)
 
 from urllib.parse import urlparse, urlencode, quote
 from io import StringIO
-from io import BytesIO
 
 from requests_toolbelt import MultipartEncoder
 
+# Many services come with self-signed certificates and will remain as such; 
need to suppress warnings for this
+from requests.packages.urllib3.exceptions import InsecureRequestWarning
+requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
+
 #---------End of imports---------
 
 #---------Debug logger---------
@@ -152,7 +152,7 @@
         :params rest_request: Holder for request information
         :type rest_request: RestRequest object
         :params http_response: Response from HTTP
-        :type http_response: HTTPResponse
+        :type http_response: requests.Response
 
         """
         self._read = None
@@ -163,14 +163,15 @@
         self._rest_request = rest_request
         self._http_response = http_response
 
-        if self._http_response:
-            self._read = self._http_response.read()
-        else:
-            self._read = None
+        if http_response is not None:
+            self._read = http_response.text
+            self._status = http_response.status_code
 
     @property
     def read(self):
         """Wrapper around httpresponse.read()"""
+
+        # Backwards compatibility: for requests, we can simply use "text"; 
need to see if there are external dependencies to continue using read
         return self._read
 
     @read.setter
@@ -188,7 +189,12 @@
 
     def getheaders(self):
         """Property for accessing the headers"""
-        return self._http_response.getheaders()
+
+        # Backwards compatibility: requests simply uses a dictionary, but 
older versions of this library returned a list of tuples
+        headers = []
+        for header in self._http_response.headers:
+            headers.append((header, self._http_response.headers[header]))
+        return headers
 
     def getheader(self, name):
         """Property for accessing an individual header
@@ -198,7 +204,7 @@
         :returns: returns a header from HTTP response
 
         """
-        return self._http_response.getheader(name, None)
+        return self._http_response.headers.get(name.lower(), None)
 
     def json(self, newdict):
         """Property for setting JSON data
@@ -212,11 +218,7 @@
     @property
     def text(self):
         """Property for accessing the data as an unparsed string"""
-        if isinstance(self.read, str):
-            value = self.read
-        else:
-            value = self.read.decode("utf-8", "ignore")
-        return value
+        return self.read
 
     @text.setter
     def text(self, value):
@@ -232,10 +234,10 @@
     def dict(self):
         """Property for accessing the data as an dict"""
         try:
-           return json.loads(self.text)
+            return json.loads(self.read)
         except:
             str = "Service responded with invalid JSON at URI {}\n{}".format(
-                self._rest_request.path, self.text)
+                self._rest_request.path, self.read)
             LOGGER.error(str)
             raise JsonDecodingError(str) from None
 
@@ -247,10 +249,7 @@
     @property
     def status(self):
         """Property for accessing the status code"""
-        if self._status:
-            return self._status
-
-        return self._http_response.status
+        return self._status
 
     @property
     def session_key(self):
@@ -258,7 +257,7 @@
         if self._session_key:
             return self._session_key
 
-        self._session_key = self._http_response.getheader('x-auth-token')
+        self._session_key = self.getheader('x-auth-token')
         return self._session_key
 
     @property
@@ -267,7 +266,7 @@
         if self._session_location:
             return self._session_location
 
-        self._session_location = self._http_response.getheader('location')
+        self._session_location = self.getheader('location')
         return self._session_location
 
     @property
@@ -276,7 +275,7 @@
         if self._task_location:
             return self._task_location
 
-        self._task_location = self._http_response.getheader('location')
+        self._task_location = self.getheader('location')
         return self._task_location
 
     @property
@@ -287,7 +286,14 @@
     @property
     def retry_after(self):
         """Retry After header"""
-        return self._http_response.getheader('retry-after')
+        retry_after = self.getheader('retry-after')
+        if retry_after is not None:
+            # Convert to int for ease of use by callers
+            try:
+                retry_after = int(retry_after)
+            except:
+                retry_after = 5
+        return retry_after
 
     def monitor(self, context):
         """Function to process Task, used on an action or POST/PATCH that 
returns 202"""
@@ -438,112 +444,17 @@
         self.__base_url = base_url.rstrip('/')
         self.__username = username
         self.__password = password
-        self.__url = urlparse(self.__base_url)
         self.__session_key = sessionkey
         self.__authorization_key = None
         self.__session_location = None
-        self._conn = None
-        self._conn_count = 0
+        self._session = requests.Session()
         self._timeout = timeout
         self._max_retry = max_retry if max_retry is not None else 10
         self.login_url = None
         self.default_prefix = default_prefix
         self.capath = capath
         self.cafile = cafile
-
-        self.__init_connection()
         self.get_root_object()
-        self.__destroy_connection()
-
-    @staticmethod
-    def _bypass_proxy(host):
-        """
-        Read NO_PROXY environment variable to determine if proxy should be
-        bypassed for host.
-
-        :param host: the host to check
-        :return: True is proxy should be bypassed, False otherwise
-        """
-        if 'NO_PROXY' in os.environ:
-            no_proxy = os.environ['NO_PROXY']
-            if no_proxy == '*':
-                return True
-            hostonly = host.rsplit(':', 1)[0]  # without port
-            no_proxy_list = [proxy.strip() for proxy in no_proxy.split(',')]
-            for name in no_proxy_list:
-                if name:
-                    name = name.lstrip('.')  # ignore leading dots
-                    name = re.escape(name)
-                    pattern = r'(.+\.)?%s$' % name
-                    if (re.match(pattern, hostonly, re.I)
-                            or re.match(pattern, host, re.I)):
-                        print('returning true (re)')
-                        return True
-        return False
-
-    def _get_connection(self, url, **kwargs):
-        """
-        Wrapper function for the HTTPSConnection/HTTPConnection constructor
-        that handles proxies set by the HTTPS_PROXY and HTTP_PROXY environment
-        variables
-
-        :param url: the target URL
-        :param kwargs: keyword arguments for the connection constructor
-        :return: the connection
-        """
-        bypass_proxy = self._bypass_proxy(url.netloc)
-        proxy = None
-        if url.scheme.upper() == "HTTPS":
-            connection = http.client.HTTPSConnection
-            if not bypass_proxy and 'HTTPS_PROXY' in os.environ:
-                host = urlparse(os.environ['HTTPS_PROXY']).netloc
-                proxy = url.netloc
-            else:
-                host = url.netloc
-        else:
-            connection = http.client.HTTPConnection
-            if not bypass_proxy and 'HTTP_PROXY' in os.environ:
-                host = urlparse(os.environ['HTTP_PROXY']).netloc
-                proxy = url.netloc
-            else:
-                host = url.netloc
-        conn = connection(host, **kwargs)
-        if proxy:
-            LOGGER.debug("Proxy %s connection to %s through %s" % (
-                url.scheme.upper(), proxy, host))
-            conn.set_tunnel(proxy)
-        return conn
-
-    def __init_connection(self, url=None):
-        """Function for initiating connection with remote server
-
-        :param url: The URL of the remote system
-        :type url: str
-
-        """
-        self.__destroy_connection()
-
-        url = url if url else self.__url
-        if url.scheme.upper() == "HTTPS":
-            if self.cafile or self.capath is not None:
-                ssl_context = ssl.create_default_context(capath=self.capath,
-                                                         cafile=self.cafile)
-            else:
-                ssl_context = ssl._create_unverified_context()
-            self._conn = self._get_connection(url, context=ssl_context,
-                                              timeout=self._timeout)
-        elif url.scheme.upper() == "HTTP":
-            self._conn = self._get_connection(url, timeout=self._timeout)
-        else:
-            pass
-
-    def __destroy_connection(self):
-        """Function for closing connection with remote server"""
-        if self._conn:
-            self._conn.close()
-
-        self._conn = None
-        self._conn_count = 0
 
     def __enter__(self):
         self.login()
@@ -551,7 +462,6 @@
 
     def __exit__(self, exc_type, exc_value, exc_traceback):
         self.logout()
-        self.__destroy_connection()
 
     def get_username(self):
         """Return used user name"""
@@ -634,7 +544,7 @@
     def get_root_object(self):
         """Perform an initial get and store the result"""
         try:
-            resp = self.get('%s%s' % (self.__url.path, self.default_prefix))
+            resp = self.get(self.default_prefix)
         except Exception as excp:
             raise excp
 
@@ -647,8 +557,8 @@
         try:
             root_data = json.loads(content)
         except:
-            str = 'Service responded with invalid JSON at URI {}{}\n{}'.format(
-                self.__url.path, self.default_prefix, content)
+            str = 'Service responded with invalid JSON at URI {}\n{}'.format(
+                self.default_prefix, content)
             LOGGER.error(str)
             raise JsonDecodingError(str) from None
 
@@ -775,12 +685,10 @@
         if 'accept' not in headers_keys:
             headers['Accept'] = '*/*'
 
-        headers['Connection'] = 'Keep-Alive'
-
         return headers
 
     def _rest_request(self, path, method='GET', args=None, body=None,
-                      headers=None, skip_redirect=False):
+                      headers=None, allow_redirects=True):
         """Rest request main function
 
         :param path: path within tree
@@ -793,8 +701,8 @@
         :type body: dict
         :param headers: provide additional headers
         :type headers: dict
-        :param skip_redirect: controls whether redirects are followed
-        :type skip_redirect: bool
+        :param allow_redirects: controls whether redirects are followed
+        :type allow_redirects: bool
         :returns: returns a RestResponse object
 
         """
@@ -857,11 +765,10 @@
                     LOGGER.error('Error occur while compressing body: %s', 
excp)
                     raise
 
-            headers['Content-Length'] = len(body)
-
         if args:
             if method == 'GET':
-                # Workaround for this bug: https://bugs.python.org/issue18857
+                # Workaround for this: 
https://github.com/psf/requests/issues/993
+                # Redfish supports some query parameters without using '=', 
which is apparently against HTML5
                 none_list = []
                 args_copy = {}
                 for query in args:
@@ -904,66 +811,33 @@
             LOGGER.info('Attempt %s of %s', attempts, path)
 
             try:
-                while True:
-                    if self._conn is None:
-                        self.__init_connection()
-
-                    self._conn.request(method.upper(), reqpath, body=body,
-                                                                
headers=headers)
-                    self._conn_count += 1
+                if sys.version_info < (3, 3):
+                    inittime = time.clock()
+                else:
+                    inittime = time.perf_counter()
 
-                    if sys.version_info < (3, 3):
-                        inittime = time.clock()
-                    else:
-                        inittime = time.perf_counter()
-                    resp = self._conn.getresponse()
-                    if sys.version_info < (3, 3):
-                        endtime = time.clock()
-                    else:
-                        endtime = time.perf_counter()
-                    LOGGER.info('Response Time for %s to %s: %s seconds.' %
-                                (method, reqpath, str(endtime-inittime)))
-
-                    if resp.getheader('Connection') == 'close':
-                        self.__destroy_connection()
-
-                    # redirect handling
-                    if resp.status not in list(range(300, 399)) or \
-                       resp.status == 304 or skip_redirect is True:
-                        break
-                    newloc = resp.getheader('location')
-                    newurl = urlparse(newloc)
-                    if resp.status in [301, 302, 303]:
-                        method = 'GET'
-                        body = None
-                        for h in ['Content-Type', 'Content-Length']:
-                            if h in headers:
-                                del headers[h]
+                # TODO: Migration to requests lost the "CA directory" 
capability; need to revisit
+                verify = False
+                if self.cafile:
+                    verify = self.cafile
+                resp = self._session.request(method.upper(), 
"{}{}".format(self.__base_url, reqpath), data=body,
+                                             headers=headers, 
timeout=self._timeout, allow_redirects=allow_redirects,
+                                             verify=verify)
 
-                    reqpath = newurl.path
-                    self.__init_connection(newurl)
+                if sys.version_info < (3, 3):
+                    endtime = time.clock()
+                else:
+                    endtime = time.perf_counter()
+                LOGGER.info('Response Time for %s to %s: %s seconds.' %
+                            (method, reqpath, str(endtime-inittime)))
 
                 restresp = RestResponse(restreq, resp)
-
-                try:
-                    if restresp.getheader('content-encoding') == "gzip":
-                        compressedfile = BytesIO(restresp.read)
-                        decompressedfile = 
gzip.GzipFile(fileobj=compressedfile)
-                        restresp.text = decompressedfile.read().decode("utf-8")
-                except Exception as excp:
-                    LOGGER.error('Error occur while decompressing body: %s',
-                                                                        excp)
-                    raise DecompressResponseError()
             except Exception as excp:
-                if isinstance(excp, DecompressResponseError):
-                    raise
-
                 if not cause_exception:
                     cause_exception = excp
                 LOGGER.info('Retrying %s [%s]'% (path, excp))
                 time.sleep(1)
 
-                self.__init_connection()
                 continue
             else:
                 break
@@ -980,7 +854,7 @@
                         LOGGER.debug('HTTP RESPONSE for %s:\nCode: 
%s\nHeaders:\n' \
                                  '%s\nBody Response of %s: %s'%\
                                  (restresp.request.path,
-                                str(restresp._http_response.status)+ ' ' + \
+                                str(restresp._http_response.status_code)+ ' ' 
+ \
                                 restresp._http_response.reason,
                                 headerstr, restresp.request.path, 
restresp.read))
                     except:
@@ -1016,8 +890,7 @@
             headers = dict()
             headers['Authorization'] = self.__authorization_key
 
-            respvalidate = self._rest_request('%s%s' % (self.__url.path,
-                                            self.login_url), headers=headers)
+            respvalidate = self._rest_request(self.login_url, headers=headers)
 
             if respvalidate.status == 401:
                 #If your REST client has a delay for fail attempts add it here
@@ -1030,7 +903,7 @@
 
             headers = dict()
             resp = self._rest_request(self.login_url, method="POST",body=data,
-                                      headers=headers, skip_redirect=True)
+                                      headers=headers, allow_redirects=False)
 
             LOGGER.info('Login returned code %s: %s', resp.status, resp.text)
 
@@ -1063,6 +936,7 @@
             self.__session_key = None
             self.__session_location = None
             self.__authorization_key = None
+        self._session.close()
 
 class HttpClient(RestClientBase):
     """A client for Rest"""
@@ -1108,7 +982,7 @@
             self.login_url = '/redfish/v1/SessionService/Sessions'
 
     def _rest_request(self, path='', method="GET", args=None, body=None,
-                      headers=None, skip_redirect=False):
+                      headers=None, allow_redirects=True):
         """Rest request for HTTP client
 
         :param path: path within tree
@@ -1121,8 +995,8 @@
         :type body: dict
         :param headers: provide additional headers
         :type headers: dict
-        :param skip_redirect: controls whether redirects are followed
-        :type skip_redirect: bool
+        :param allow_redirects: controls whether redirects are followed
+        :type allow_redirects: bool
         :returns: returns a rest request
 
         """
@@ -1134,7 +1008,7 @@
         return super(HttpClient, self)._rest_request(path=path, method=method,
                                                      args=args, body=body,
                                                      headers=headers,
-                                                     
skip_redirect=skip_redirect)
+                                                     
allow_redirects=allow_redirects)
 
     def _get_req_headers(self, headers=None, providerheader=None):
         """Get the request headers for HTTP client

Reply via email to