Hello community,

here is the log from the commit of package python-acme for openSUSE:Factory 
checked in at 2018-12-18 14:57:50
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-acme (Old)
 and      /work/SRC/openSUSE:Factory/.python-acme.new.28833 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-acme"

Tue Dec 18 14:57:50 2018 rev:25 rq:658303 version:0.29.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-acme/python-acme.changes  2018-11-18 
23:33:18.069401286 +0100
+++ /work/SRC/openSUSE:Factory/.python-acme.new.28833/python-acme.changes       
2018-12-18 14:58:16.590261822 +0100
@@ -1,0 +2,23 @@
+Sat Dec 15 07:02:55 UTC 2018 - Thomas Bechtold <[email protected]>
+
+- update to 0.29.1:
+  * Release 0.29.1
+  * Release 0.29.0
+  * WIP External Account Binding (#6059)
+  * Implement POST-as-GET requests (#6522)
+  * ignore erroneously no-member lint error
+  * Revert acme/acme/client.py
+  * Bump version to 0.29.0
+  * remove unused six imports
+  * Remove module-level ignore::ResourceWarnings
+  * bring requests back down to 2.4.1 in setup and oldest constraints
+  * Requests no longer vendorizes urllib3
+  * Use a newer version of requests because of the upcoming Callable import
+    Deprecation in Python 3.8 that warns in Python 3.7
+  * Cover is run on 2.7, so mark 3-only lines as no cover
+  * Ignore ResourceWarnings in various modules in a 2-compatible way.
+  * ignore ResourceWarnings in acme tests
+  * s/assertEquals/assertEqual
+- Adjust Requires
+
+-------------------------------------------------------------------

Old:
----
  acme-0.28.0.tar.gz
  acme-0.28.0.tar.gz.asc

New:
----
  acme-0.29.1.tar.gz
  acme-0.29.1.tar.gz.asc

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

Other differences:
------------------
++++++ python-acme.spec ++++++
--- /var/tmp/diff_new_pack.7DSWPl/_old  2018-12-18 14:58:17.166260952 +0100
+++ /var/tmp/diff_new_pack.7DSWPl/_new  2018-12-18 14:58:17.166260952 +0100
@@ -19,7 +19,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define libname acme
 Name:           python-%{libname}
-Version:        0.28.0
+Version:        0.29.1
 Release:        0
 Summary:        Python library for the ACME protocol
 License:        Apache-2.0
@@ -45,7 +45,6 @@
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 Requires:       python-cryptography >= 0.8
-Requires:       python-dnspython >= 1.12
 Requires:       python-josepy >= 1.0.0
 Requires:       python-ndg-httpsclient
 Requires:       python-pyOpenSSL >= 0.13

++++++ acme-0.28.0.tar.gz -> acme-0.29.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.28.0/PKG-INFO new/acme-0.29.1/PKG-INFO
--- old/acme-0.28.0/PKG-INFO    2018-11-07 22:15:05.000000000 +0100
+++ new/acme-0.29.1/PKG-INFO    2018-12-06 00:48:05.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: acme
-Version: 0.28.0
+Version: 0.29.1
 Summary: ACME protocol implementation in Python
 Home-page: https://github.com/letsencrypt/letsencrypt
 Author: Certbot Project
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.28.0/acme/client.py 
new/acme-0.29.1/acme/client.py
--- old/acme-0.28.0/acme/client.py      2018-11-07 22:14:56.000000000 +0100
+++ new/acme-0.29.1/acme/client.py      2018-12-06 00:47:58.000000000 +0100
@@ -33,6 +33,7 @@
 # 
https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning
 if sys.version_info < (2, 7, 9):  # pragma: no cover
     try:
+        # pylint: disable=no-member
         requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3()  # 
type: ignore
     except AttributeError:
         import urllib3.contrib.pyopenssl  # pylint: disable=import-error
@@ -199,22 +200,6 @@
 
         return datetime.datetime.now() + datetime.timedelta(seconds=seconds)
 
-    def poll(self, authzr):
-        """Poll Authorization Resource for status.
-
-        :param authzr: Authorization Resource
-        :type authzr: `.AuthorizationResource`
-
-        :returns: Updated Authorization Resource and HTTP response.
-
-        :rtype: (`.AuthorizationResource`, `requests.Response`)
-
-        """
-        response = self.net.get(authzr.uri)
-        updated_authzr = self._authzr_from_response(
-            response, authzr.body.identifier, authzr.uri)
-        return updated_authzr, response
-
     def _revoke(self, cert, rsn, url):
         """Revoke certificate.
 
@@ -236,6 +221,7 @@
             raise errors.ClientError(
                 'Successful revocation must return HTTP OK status')
 
+
 class Client(ClientBase):
     """ACME client for a v1 API.
 
@@ -388,6 +374,22 @@
             body=jose.ComparableX509(OpenSSL.crypto.load_certificate(
                 OpenSSL.crypto.FILETYPE_ASN1, response.content)))
 
+    def poll(self, authzr):
+        """Poll Authorization Resource for status.
+
+        :param authzr: Authorization Resource
+        :type authzr: `.AuthorizationResource`
+
+        :returns: Updated Authorization Resource and HTTP response.
+
+        :rtype: (`.AuthorizationResource`, `requests.Response`)
+
+        """
+        response = self.net.get(authzr.uri)
+        updated_authzr = self._authzr_from_response(
+            response, authzr.body.identifier, authzr.uri)
+        return updated_authzr, response
+
     def poll_and_request_issuance(
             self, csr, authzrs, mintime=5, max_attempts=10):
         """Poll and request issuance.
@@ -651,13 +653,29 @@
         body = messages.Order.from_json(response.json())
         authorizations = []
         for url in body.authorizations:
-            
authorizations.append(self._authzr_from_response(self.net.get(url), uri=url))
+            
authorizations.append(self._authzr_from_response(self._post_as_get(url), 
uri=url))
         return messages.OrderResource(
             body=body,
             uri=response.headers.get('Location'),
             authorizations=authorizations,
             csr_pem=csr_pem)
 
+    def poll(self, authzr):
+        """Poll Authorization Resource for status.
+
+        :param authzr: Authorization Resource
+        :type authzr: `.AuthorizationResource`
+
+        :returns: Updated Authorization Resource and HTTP response.
+
+        :rtype: (`.AuthorizationResource`, `requests.Response`)
+
+        """
+        response = self._post_as_get(authzr.uri)
+        updated_authzr = self._authzr_from_response(
+            response, authzr.body.identifier, authzr.uri)
+        return updated_authzr, response
+
     def poll_and_finalize(self, orderr, deadline=None):
         """Poll authorizations and finalize the order.
 
@@ -681,7 +699,7 @@
         responses = []
         for url in orderr.body.authorizations:
             while datetime.datetime.now() < deadline:
-                authzr = self._authzr_from_response(self.net.get(url), uri=url)
+                authzr = self._authzr_from_response(self._post_as_get(url), 
uri=url)
                 if authzr.body.status != messages.STATUS_PENDING:
                     responses.append(authzr)
                     break
@@ -716,12 +734,12 @@
         self._post(orderr.body.finalize, wrapped_csr)
         while datetime.datetime.now() < deadline:
             time.sleep(1)
-            response = self.net.get(orderr.uri)
+            response = self._post_as_get(orderr.uri)
             body = messages.Order.from_json(response.json())
             if body.error is not None:
                 raise errors.IssuanceError(body.error)
             if body.certificate is not None:
-                certificate_response = self.net.get(body.certificate,
+                certificate_response = self._post_as_get(body.certificate,
                                                     
content_type=DER_CONTENT_TYPE).text
                 return orderr.update(body=body, 
fullchain_pem=certificate_response)
         raise errors.TimeoutError()
@@ -739,6 +757,39 @@
         """
         return self._revoke(cert, rsn, self.directory['revokeCert'])
 
+    def external_account_required(self):
+        """Checks if ACME server requires External Account Binding 
authentication."""
+        if hasattr(self.directory, 'meta') and 
self.directory.meta.external_account_required:
+            return True
+        else:
+            return False
+
+    def _post_as_get(self, *args, **kwargs):
+        """
+        Send GET request using the POST-as-GET protocol if needed.
+        The request will be first issued using POST-as-GET for ACME v2. If the 
ACME CA servers do
+        not support this yet and return an error, request will be retried 
using GET.
+        For ACME v1, only GET request will be tried, as POST-as-GET is not 
supported.
+        :param args:
+        :param kwargs:
+        :return:
+        """
+        if self.acme_version >= 2:
+            # We add an empty payload for POST-as-GET requests
+            new_args = args[:1] + (None,) + args[1:]
+            try:
+                return self._post(*new_args, **kwargs)  # pylint: 
disable=star-args
+            except messages.Error as error:
+                if error.code == 'malformed':
+                    logger.debug('Error during a POST-as-GET request, '
+                                 'your ACME CA may not support it:\n%s', error)
+                    logger.debug('Retrying request with GET.')
+                else:  # pragma: no cover
+                    raise
+
+        # If POST-as-GET is not supported yet, we use a GET instead.
+        return self.net.get(*args, **kwargs)
+
 
 class BackwardsCompatibleClientV2(object):
     """ACME client wrapper that tends towards V2-style calls, but
@@ -768,12 +819,7 @@
             self.client = ClientV2(directory, net=net)
 
     def __getattr__(self, name):
-        if name in vars(self.client):
-            return getattr(self.client, name)
-        elif name in dir(ClientBase):
-            return getattr(self.client, name)
-        else:
-            raise AttributeError()
+        return getattr(self.client, name)
 
     def new_account_and_tos(self, regr, check_tos_cb=None):
         """Combined register and agree_tos for V1, new_account for V2
@@ -880,6 +926,15 @@
         else:
             return 1
 
+    def external_account_required(self):
+        """Checks if the server requires an external account for ACMEv2 
servers.
+
+        Always return False for ACMEv1 servers, as it doesn't use External 
Account Binding."""
+        if self.acme_version == 1:
+            return False
+        else:
+            return self.client.external_account_required()
+
 
 class ClientNetwork(object):  # pylint: disable=too-many-instance-attributes
     """Wrapper around requests that signs POSTs for authentication.
@@ -943,7 +998,7 @@
         :rtype: `josepy.JWS`
 
         """
-        jobj = obj.json_dumps(indent=2).encode()
+        jobj = obj.json_dumps(indent=2).encode() if obj else b''
         logger.debug('JWS payload:\n%s', jobj)
         kwargs = {
             "alg": self.alg,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.28.0/acme/client_test.py 
new/acme-0.29.1/acme/client_test.py
--- old/acme-0.28.0/acme/client_test.py 2018-11-07 22:14:56.000000000 +0100
+++ new/acme-0.29.1/acme/client_test.py 2018-12-06 00:47:58.000000000 +0100
@@ -1,4 +1,5 @@
 """Tests for acme.client."""
+# pylint: disable=too-many-lines
 import copy
 import datetime
 import json
@@ -283,6 +284,37 @@
             client.update_registration(mock.sentinel.regr, None)
         
mock_client().update_registration.assert_called_once_with(mock.sentinel.regr, 
None)
 
+    # newNonce present means it will pick acme_version 2
+    def test_external_account_required_true(self):
+        self.response.json.return_value = messages.Directory({
+            'newNonce': 'http://letsencrypt-test.com/acme/new-nonce',
+            'meta': messages.Directory.Meta(external_account_required=True),
+        }).to_json()
+
+        client = self._init()
+
+        self.assertTrue(client.external_account_required())
+
+    # newNonce present means it will pick acme_version 2
+    def test_external_account_required_false(self):
+        self.response.json.return_value = messages.Directory({
+            'newNonce': 'http://letsencrypt-test.com/acme/new-nonce',
+            'meta': messages.Directory.Meta(external_account_required=False),
+        }).to_json()
+
+        client = self._init()
+
+        self.assertFalse(client.external_account_required())
+
+    def test_external_account_required_false_v1(self):
+        self.response.json.return_value = messages.Directory({
+            'meta': messages.Directory.Meta(external_account_required=False),
+        }).to_json()
+
+        client = self._init()
+
+        self.assertFalse(client.external_account_required())
+
 
 class ClientTest(ClientTestBase):
     """Tests for acme.client.Client."""
@@ -665,7 +697,7 @@
     def test_revocation_payload(self):
         obj = messages.Revocation(certificate=self.certr.body, reason=self.rsn)
         self.assertTrue('reason' in obj.to_partial_json().keys())
-        self.assertEquals(self.rsn, obj.to_partial_json()['reason'])
+        self.assertEqual(self.rsn, obj.to_partial_json()['reason'])
 
     def test_revoke_bad_status_raises_error(self):
         self.response.status_code = http_client.METHOD_NOT_ALLOWED
@@ -730,9 +762,10 @@
         authz_response2 = self.response
         authz_response2.json.return_value = self.authz2.to_json()
         authz_response2.headers['Location'] = self.authzr2.uri
-        self.net.get.side_effect = (authz_response, authz_response2)
 
-        self.assertEqual(self.client.new_order(CSR_SAN_PEM), self.orderr)
+        with mock.patch('acme.client.ClientV2._post_as_get') as 
mock_post_as_get:
+            mock_post_as_get.side_effect = (authz_response, authz_response2)
+            self.assertEqual(self.client.new_order(CSR_SAN_PEM), self.orderr)
 
     @mock.patch('acme.client.datetime')
     def test_poll_and_finalize(self, mock_datetime):
@@ -821,6 +854,47 @@
         self.response.json.return_value = self.regr.body.update(
             contact=()).to_json()
 
+    def test_external_account_required_true(self):
+        self.client.directory = messages.Directory({
+            'meta': messages.Directory.Meta(external_account_required=True)
+        })
+
+        self.assertTrue(self.client.external_account_required())
+
+    def test_external_account_required_false(self):
+        self.client.directory = messages.Directory({
+            'meta': messages.Directory.Meta(external_account_required=False)
+        })
+
+        self.assertFalse(self.client.external_account_required())
+
+    def test_external_account_required_default(self):
+        self.assertFalse(self.client.external_account_required())
+
+    def test_post_as_get(self):
+        with mock.patch('acme.client.ClientV2._authzr_from_response') as 
mock_client:
+            mock_client.return_value = self.authzr2
+
+            self.client.poll(self.authzr2)  # pylint: disable=protected-access
+
+            self.client.net.post.assert_called_once_with(
+                self.authzr2.uri, None, acme_version=2,
+                
new_nonce_url='https://www.letsencrypt-demo.org/acme/new-nonce')
+            self.client.net.get.assert_not_called()
+
+            class FakeError(messages.Error):  # pylint: 
disable=too-many-ancestors
+                """Fake error to reproduce a malformed request ACME error"""
+                def __init__(self):  # pylint: disable=super-init-not-called
+                    pass
+                @property
+                def code(self):
+                    return 'malformed'
+            self.client.net.post.side_effect = FakeError()
+
+            self.client.poll(self.authzr2)  # pylint: disable=protected-access
+
+            self.client.net.get.assert_called_once_with(self.authzr2.uri)
+
 
 class MockJSONDeSerializable(jose.JSONDeSerializable):
     # pylint: disable=missing-docstring
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.28.0/acme/crypto_util_test.py 
new/acme-0.29.1/acme/crypto_util_test.py
--- old/acme-0.28.0/acme/crypto_util_test.py    2018-11-07 22:14:56.000000000 
+0100
+++ new/acme-0.29.1/acme/crypto_util_test.py    2018-12-06 00:47:58.000000000 
+0100
@@ -209,8 +209,8 @@
         # have a get_extensions() method, so we skip this test if the method
         # isn't available.
         if hasattr(csr, 'get_extensions'):
-            self.assertEquals(len(csr.get_extensions()), 1)
-            self.assertEquals(csr.get_extensions()[0].get_data(),
+            self.assertEqual(len(csr.get_extensions()), 1)
+            self.assertEqual(csr.get_extensions()[0].get_data(),
                 OpenSSL.crypto.X509Extension(
                     b'subjectAltName',
                     critical=False,
@@ -227,7 +227,7 @@
         # have a get_extensions() method, so we skip this test if the method
         # isn't available.
         if hasattr(csr, 'get_extensions'):
-            self.assertEquals(len(csr.get_extensions()), 2)
+            self.assertEqual(len(csr.get_extensions()), 2)
             # NOTE: Ideally we would filter by the TLS Feature OID, but
             # OpenSSL.crypto.X509Extension doesn't give us the extension's raw 
OID,
             # and the shortname field is just "UNDEF"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.28.0/acme/messages.py 
new/acme-0.29.1/acme/messages.py
--- old/acme-0.28.0/acme/messages.py    2018-11-07 22:14:56.000000000 +0100
+++ new/acme-0.29.1/acme/messages.py    2018-12-06 00:47:58.000000000 +0100
@@ -1,6 +1,7 @@
 """ACME protocol messages."""
 import collections
 import six
+import json
 
 import josepy as jose
 
@@ -8,6 +9,7 @@
 from acme import errors
 from acme import fields
 from acme import util
+from acme import jws
 
 OLD_ERROR_PREFIX = "urn:acme:error:"
 ERROR_PREFIX = "urn:ietf:params:acme:error:"
@@ -27,6 +29,7 @@
     'tls': 'The server experienced a TLS error during domain verification',
     'unauthorized': 'The client lacks sufficient authorization',
     'unknownHost': 'The server could not resolve a domain name',
+    'externalAccountRequired': 'The server requires external account binding',
 }
 
 ERROR_TYPE_DESCRIPTIONS = dict(
@@ -176,6 +179,7 @@
         _terms_of_service_v2 = jose.Field('termsOfService', omitempty=True)
         website = jose.Field('website', omitempty=True)
         caa_identities = jose.Field('caaIdentities', omitempty=True)
+        external_account_required = jose.Field('externalAccountRequired', 
omitempty=True)
 
         def __init__(self, **kwargs):
             kwargs = dict((self._internal_name(k), v) for k, v in 
kwargs.items())
@@ -258,6 +262,24 @@
     """ACME Resource Body."""
 
 
+class ExternalAccountBinding(object):
+    """ACME External Account Binding"""
+
+    @classmethod
+    def from_data(cls, account_public_key, kid, hmac_key, directory):
+        """Create External Account Binding Resource from contact details, kid 
and hmac."""
+
+        key_json = json.dumps(account_public_key.to_partial_json()).encode()
+        decoded_hmac_key = jose.b64.b64decode(hmac_key)
+        url = directory["newAccount"]
+
+        eab = jws.JWS.sign(key_json, jose.jwk.JWKOct(key=decoded_hmac_key),
+                           jose.jwa.HS256, None,
+                           url, kid)
+
+        return eab.to_partial_json()
+
+
 class Registration(ResourceBody):
     """Registration Resource Body.
 
@@ -275,12 +297,13 @@
     status = jose.Field('status', omitempty=True)
     terms_of_service_agreed = jose.Field('termsOfServiceAgreed', 
omitempty=True)
     only_return_existing = jose.Field('onlyReturnExisting', omitempty=True)
+    external_account_binding = jose.Field('externalAccountBinding', 
omitempty=True)
 
     phone_prefix = 'tel:'
     email_prefix = 'mailto:'
 
     @classmethod
-    def from_data(cls, phone=None, email=None, **kwargs):
+    def from_data(cls, phone=None, email=None, external_account_binding=None, 
**kwargs):
         """Create registration resource from contact details."""
         details = list(kwargs.pop('contact', ()))
         if phone is not None:
@@ -288,6 +311,10 @@
         if email is not None:
             details.extend([cls.email_prefix + mail for mail in 
email.split(',')])
         kwargs['contact'] = tuple(details)
+
+        if external_account_binding:
+            kwargs['external_account_binding'] = external_account_binding
+
         return cls(**kwargs)
 
     def _filter_contact(self, prefix):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.28.0/acme/messages_test.py 
new/acme-0.29.1/acme/messages_test.py
--- old/acme-0.28.0/acme/messages_test.py       2018-11-07 22:14:56.000000000 
+0100
+++ new/acme-0.29.1/acme/messages_test.py       2018-12-06 00:47:58.000000000 
+0100
@@ -174,6 +174,24 @@
         self.assertTrue(result)
 
 
+class ExternalAccountBindingTest(unittest.TestCase):
+    def setUp(self):
+        from acme.messages import Directory
+        self.key = jose.jwk.JWKRSA(key=KEY.public_key())
+        self.kid = "kid-for-testing"
+        self.hmac_key = "hmac-key-for-testing"
+        self.dir = Directory({
+            'newAccount': 'http://url/acme/new-account',
+        })
+
+    def test_from_data(self):
+        from acme.messages import ExternalAccountBinding
+        eab = ExternalAccountBinding.from_data(self.key, self.kid, 
self.hmac_key, self.dir)
+
+        self.assertEqual(len(eab), 3)
+        self.assertEqual(sorted(eab.keys()), sorted(['protected', 'payload', 
'signature']))
+
+
 class RegistrationTest(unittest.TestCase):
     """Tests for acme.messages.Registration."""
 
@@ -205,6 +223,22 @@
             'mailto:[email protected]',
         ))
 
+    def test_new_registration_from_data_with_eab(self):
+        from acme.messages import NewRegistration, ExternalAccountBinding, 
Directory
+        key = jose.jwk.JWKRSA(key=KEY.public_key())
+        kid = "kid-for-testing"
+        hmac_key = "hmac-key-for-testing"
+        directory = Directory({
+            'newAccount': 'http://url/acme/new-account',
+        })
+        eab = ExternalAccountBinding.from_data(key, kid, hmac_key, directory)
+        reg = NewRegistration.from_data(email='[email protected]', 
external_account_binding=eab)
+        self.assertEqual(reg.contact, (
+            'mailto:[email protected]',
+        ))
+        self.assertEqual(sorted(reg.external_account_binding.keys()),
+                         sorted(['protected', 'payload', 'signature']))
+
     def test_phones(self):
         self.assertEqual(('1234',), self.reg.phones)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.28.0/acme.egg-info/PKG-INFO 
new/acme-0.29.1/acme.egg-info/PKG-INFO
--- old/acme-0.28.0/acme.egg-info/PKG-INFO      2018-11-07 22:15:05.000000000 
+0100
+++ new/acme-0.29.1/acme.egg-info/PKG-INFO      2018-12-06 00:48:05.000000000 
+0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: acme
-Version: 0.28.0
+Version: 0.29.1
 Summary: ACME protocol implementation in Python
 Home-page: https://github.com/letsencrypt/letsencrypt
 Author: Certbot Project
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/acme-0.28.0/setup.py new/acme-0.29.1/setup.py
--- old/acme-0.28.0/setup.py    2018-11-07 22:14:57.000000000 +0100
+++ new/acme-0.29.1/setup.py    2018-12-06 00:47:59.000000000 +0100
@@ -3,7 +3,7 @@
 from setuptools.command.test import test as TestCommand
 import sys
 
-version = '0.28.0'
+version = '0.29.1'
 
 # Please update tox.ini when modifying dependency version requirements
 install_requires = [



Reply via email to