Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-certbot-dns-cloudflare for 
openSUSE:Factory checked in at 2026-05-15 23:52:58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-certbot-dns-cloudflare (Old)
 and      /work/SRC/openSUSE:Factory/.python-certbot-dns-cloudflare.new.1966 
(New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-certbot-dns-cloudflare"

Fri May 15 23:52:58 2026 rev:53 rq:1353145 version:5.6.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-certbot-dns-cloudflare/python-certbot-dns-cloudflare.changes
      2026-03-16 15:49:19.654291265 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-certbot-dns-cloudflare.new.1966/python-certbot-dns-cloudflare.changes
    2026-05-15 23:53:04.544036029 +0200
@@ -1,0 +2,24 @@
+Thu May 14 09:15:03 UTC 2026 - Markéta Machová <[email protected]>
+
+- update to version 5.6.0
+  * certbot-dns-cloudflare requires 4.0+ of the Cloudflare Python 
+    library
+
+-------------------------------------------------------------------
+Wed May  6 20:44:59 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 5.5.0:
+  * Moved nearly all code for the certbot-nginx and certbot-
+    apache plugins into private modules in the certbot package
+    which now offers "apache" and "nginx" extras. Users should
+    notice no major changes. certbot-apache and certbot-nginx
+    will continue to exist as simple packages that depend on
+    certbot and also help register the plugin with certbot so it
+    knows the functionality is available. Unit tests for these
+    plugins are now also part of the certbot package.
+  * The certbot.ocsp module has been deprecated and will be
+    removed in the next major release. This is not a change to
+    Certbot's OCSP functionality. The code is just being removed
+    from Certbot's public API.
+
+-------------------------------------------------------------------

Old:
----
  certbot_dns_cloudflare-5.4.0.tar.gz

New:
----
  certbot_dns_cloudflare-5.6.0.tar.gz

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

Other differences:
------------------
++++++ python-certbot-dns-cloudflare.spec ++++++
--- /var/tmp/diff_new_pack.m5d38k/_old  2026-05-15 23:53:05.292066821 +0200
+++ /var/tmp/diff_new_pack.m5d38k/_new  2026-05-15 23:53:05.296066986 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-certbot-dns-cloudflare
-Version:        5.4.0
+Version:        5.6.0
 Release:        0
 Summary:        Cloudflare Authenticator plugin for Certbot
 License:        Apache-2.0
@@ -26,7 +26,7 @@
 Source:         
https://files.pythonhosted.org/packages/source/c/certbot-dns-cloudflare/certbot_dns_cloudflare-%{version}.tar.gz
 BuildRequires:  %{python_module acme >= %{version}}
 BuildRequires:  %{python_module certbot >= %{version}}
-BuildRequires:  %{python_module cloudflare >= 2.19}
+BuildRequires:  %{python_module cloudflare >= 4.0}
 BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module setuptools}
@@ -34,7 +34,7 @@
 BuildRequires:  python-rpm-macros
 Requires:       python-acme >= %{version}
 Requires:       python-certbot >= %{version}
-Requires:       python-cloudflare >= 2.19
+Requires:       python-cloudflare >= 4.0
 BuildArch:      noarch
 %python_subpackages
 

++++++ certbot_dns_cloudflare-5.4.0.tar.gz -> 
certbot_dns_cloudflare-5.6.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot_dns_cloudflare-5.4.0/PKG-INFO 
new/certbot_dns_cloudflare-5.6.0/PKG-INFO
--- old/certbot_dns_cloudflare-5.4.0/PKG-INFO   2026-03-10 18:47:02.259142600 
+0100
+++ new/certbot_dns_cloudflare-5.6.0/PKG-INFO   2026-05-11 17:56:24.594847700 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: certbot-dns-cloudflare
-Version: 5.4.0
+Version: 5.6.0
 Summary: Cloudflare DNS Authenticator plugin for Certbot
 Author: Certbot Project
 License-Expression: Apache-2.0
@@ -25,9 +25,9 @@
 Requires-Python: >=3.10
 Description-Content-Type: text/x-rst
 License-File: LICENSE.txt
-Requires-Dist: cloudflare<2.20,>=2.19
-Requires-Dist: acme>=5.4.0
-Requires-Dist: certbot>=5.4.0
+Requires-Dist: cloudflare>=4.0
+Requires-Dist: acme>=5.6.0
+Requires-Dist: certbot>=5.6.0
 Provides-Extra: docs
 Requires-Dist: Sphinx>=1.0; extra == "docs"
 Requires-Dist: sphinx_rtd_theme; extra == "docs"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/certbot_dns_cloudflare-5.4.0/setup.py 
new/certbot_dns_cloudflare-5.6.0/setup.py
--- old/certbot_dns_cloudflare-5.4.0/setup.py   2026-03-10 18:46:56.000000000 
+0100
+++ new/certbot_dns_cloudflare-5.6.0/setup.py   2026-05-11 17:56:17.000000000 
+0200
@@ -2,12 +2,10 @@
 
 from setuptools import setup
 
-version = '5.4.0'
+version = '5.6.0'
 
 install_requires = [
-    # for now, do not upgrade to cloudflare>=2.20 to avoid deprecation 
warnings and the breaking
-    # changes in version 3.0. see 
https://github.com/certbot/certbot/issues/9938
-    'cloudflare>=2.19, <2.20',
+    'cloudflare>=4.0',
 ]
 
 if os.environ.get('SNAP_BUILD'):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/certbot_dns_cloudflare-5.4.0/src/certbot_dns_cloudflare/_internal/dns_cloudflare.py
 
new/certbot_dns_cloudflare-5.6.0/src/certbot_dns_cloudflare/_internal/dns_cloudflare.py
--- 
old/certbot_dns_cloudflare-5.4.0/src/certbot_dns_cloudflare/_internal/dns_cloudflare.py
     2026-03-10 18:46:56.000000000 +0100
+++ 
new/certbot_dns_cloudflare-5.6.0/src/certbot_dns_cloudflare/_internal/dns_cloudflare.py
     2026-05-11 17:56:16.000000000 +0200
@@ -1,11 +1,21 @@
 """DNS Authenticator for Cloudflare."""
 import logging
+import warnings
 from typing import Any
 from typing import Callable
+from typing import Literal
 from typing import Optional
-from typing import cast
+from typing import TypedDict
 
-import CloudFlare
+# cloudflare 4.x includes a pydantic v1 compatibility shim that emits a
+# UserWarning on Python 3.14+.  Suppress it here so that this internal-detail
+# warning is not shown to users during plugin discovery.  In our test suite it
+# is filtered out via pytest.ini so it does not affect filterwarnings=error.
+with warnings.catch_warnings():
+    warnings.filterwarnings('ignore', message='Core Pydantic V1 functionality',
+                            category=UserWarning)
+    import cloudflare
+    from cloudflare.types.zones import Zone
 
 from certbot import errors
 from certbot.plugins import dns_common
@@ -95,15 +105,15 @@
                  api_token: Optional[str] = None) -> None:
         if email:
             # If an email was specified, we're using an email/key combination 
and not a token.
-            # We can't use named arguments in this case, as it would break 
compatibility with
-            # the Cloudflare library since version 2.10.1, as the `token` 
argument was used for
-            # tokens and keys alike and the `key` argument did not exist in 
earlier versions.
-            self.cf = CloudFlare.CloudFlare(email, api_key)
+            # We use named arguments here to match the cloudflare 4.x SDK's 
explicit parameter
+            # names (api_email and api_key), which correspond to the Global 
API Key credentials
+            # found in the Cloudflare dashboard under My Profile > API Tokens.
+            self.cf = cloudflare.Cloudflare(api_email=email, api_key=api_key)
         else:
-            # If no email was specified, we're using just a token. Let's use 
the named argument
-            # for simplicity, which is compatible with all (current) versions 
of the Cloudflare
-            # library.
-            self.cf = CloudFlare.CloudFlare(token=api_token)
+            # If no email was specified, we're using just an API token. We use 
the named argument
+            # for clarity. API Tokens are the recommended authentication 
method as they support
+            # fine-grained permissions scoped to specific zones and operations.
+            self.cf = cloudflare.Cloudflare(api_token=api_token)
 
     def add_txt_record(self, domain: str, record_name: str, record_content: 
str,
                        record_ttl: int) -> None:
@@ -119,24 +129,30 @@
 
         zone_id = self._find_zone_id(domain)
 
-        data = {'type': 'TXT',
-                'name': record_name,
-                'content': record_content,
-                'ttl': record_ttl}
+        data: _RecordData = {
+            'type': 'TXT',
+            'name': record_name,
+            'content': record_content,
+            'ttl': record_ttl,
+        }
 
         try:
             logger.debug('Attempting to add record to zone %s: %s', zone_id, 
data)
-            self.cf.zones.dns_records.post(zone_id, data=data)  # zones | 
pylint: disable=no-member
-        except CloudFlare.exceptions.CloudFlareAPIError as e:
-            code = int(e)
+            self.cf.dns.records.create(zone_id=zone_id, **data)
+        except cloudflare.APIStatusError as e:
+            code = _cf_error_code(e)
             hint = None
 
             if code == 1009:
                 hint = 'Does your API token have "Zone:DNS:Edit" permissions?'
 
-            logger.error('Encountered CloudFlareAPIError adding TXT record: %d 
%s', e, e)
+            logger.error('Encountered Cloudflare API error adding TXT record: 
%s', e)
             raise errors.PluginError('Error communicating with the Cloudflare 
API: {0}{1}'
                                      .format(e, ' ({0})'.format(hint) if hint 
else ''))
+        except cloudflare.APIConnectionError as e:
+            logger.error('Network error talking to the Cloudflare API: %s', e)
+            raise errors.PluginError('Network error communicating with the 
Cloudflare API '
+                                     'while adding a TXT record: 
{0}'.format(e))
 
         record_id = self._find_txt_record_id(zone_id, record_name, 
record_content)
         logger.debug('Successfully added TXT record with record_id: %s', 
record_id)
@@ -159,52 +175,48 @@
             zone_id = self._find_zone_id(domain)
         except errors.PluginError as e:
             logger.debug('Encountered error finding zone_id during deletion: 
%s', e)
+            logger.debug('Zone not found; no cleanup needed.')
             return
 
-        if zone_id:
-            record_id = self._find_txt_record_id(zone_id, record_name, 
record_content)
-            if record_id:
-                try:
-                    # zones | pylint: disable=no-member
-                    self.cf.zones.dns_records.delete(zone_id, record_id)
-                    logger.debug('Successfully deleted TXT record.')
-                except CloudFlare.exceptions.CloudFlareAPIError as e:
-                    logger.warning('Encountered CloudFlareAPIError deleting 
TXT record: %s', e)
-            else:
-                logger.debug('TXT record not found; no cleanup needed.')
+        record_id = self._find_txt_record_id(zone_id, record_name, 
record_content)
+        if record_id:
+            try:
+                self.cf.dns.records.delete(dns_record_id=record_id, 
zone_id=zone_id)
+                logger.debug('Successfully deleted TXT record.')
+            except cloudflare.APIStatusError as e:
+                logger.warning('Encountered Cloudflare API error deleting TXT 
record: %s', e)
+            except cloudflare.APIConnectionError as e:
+                logger.warning('Network error deleting TXT record from 
Cloudflare: %s', e)
         else:
-            logger.debug('Zone not found; no cleanup needed.')
+            logger.debug('TXT record not found; no cleanup needed.')
 
     def _find_zone_id(self, domain: str) -> str:
         """
         Find the zone_id for a given domain.
 
         :param str domain: The domain for which to find the zone_id.
-        :returns: The zone_id, if found.
+        :returns: The zone_id for the first matching zone that has a non-empty
+            identifier. A zone with an empty/invalid id is treated as if no 
zone
+            were found, so this method never returns an empty string.
         :rtype: str
         :raises certbot.errors.PluginError: if no zone_id is found.
         """
 
         zone_name_guesses = dns_common.base_domain_name_guesses(domain)
-        zones: list[dict[str, Any]] = []
+        zone: Zone | None = None
         code = msg = None
 
         for zone_name in zone_name_guesses:
-            params = {'name': zone_name,
-                      'per_page': 1}
-
             try:
-                zones = self.cf.zones.get(params=params)  # zones | pylint: 
disable=no-member
-            except CloudFlare.exceptions.CloudFlareAPIError as e:
-                code = int(e)
+                zone = next(iter(self.cf.zones.list(name=zone_name, 
per_page=1)), None)
+            except cloudflare.APIStatusError as e:
+                code = _cf_error_code(e)
                 msg = str(e)
                 hint = None
 
                 if code == 6003:
-                    hint = ('Did you copy your entire API token/key? To use 
Cloudflare tokens, '
-                            'you\'ll need the python package 
cloudflare>=2.3.1.{}'
-                    .format(' This certbot is running cloudflare ' + 
str(CloudFlare.__version__)
-                    if hasattr(CloudFlare, '__version__') else ''))
+                    hint = ('Did you copy your entire API token/key? '
+                            'See {} to manage your API 
tokens.'.format(ACCOUNT_URL))
                 elif code == 9103:
                     hint = 'Did you enter the correct email address and Global 
key?'
                 elif code == 9109:
@@ -215,13 +227,19 @@
                                   'that you have supplied valid Cloudflare API 
credentials. ({2})'
                                                                          
.format(code, msg, hint))
                 else:
-                    logger.debug('Unrecognised CloudFlareAPIError while 
finding zone_id: %d %s. '
-                                 'Continuing with next zone guess...', e, e)
-
-            if zones:
-                zone_id: str = zones[0]['id']
-                logger.debug('Found zone_id of %s for %s using name %s', 
zone_id, domain, zone_name)
-                return zone_id
+                    logger.debug('Unrecognised Cloudflare API error while 
finding zone_id: %s. '
+                                 'Continuing with next zone guess...', e)
+            except cloudflare.APIConnectionError as e:
+                raise errors.PluginError('Network error contacting the 
Cloudflare API while '
+                                         'looking up the zone for {0}: 
{1}'.format(domain, e))
+
+            if zone:
+                zone_id = zone.id
+                if zone_id:
+                    logger.debug('Found zone_id of %s for %s using name %s',
+                                 zone_id, domain, zone_name)
+                    return zone_id
+                break  # Found a zone but it has no usable ID; stop searching
 
         if msg is not None:
             if 'com.cloudflare.api.account.zone.list' in msg:
@@ -252,20 +270,38 @@
         :rtype: str
         """
 
-        params = {'type': 'TXT',
-                  'name': record_name,
-                  'content': record_content,
-                  'per_page': 1}
         try:
-            # zones | pylint: disable=no-member
-            records = self.cf.zones.dns_records.get(zone_id, params=params)
-        except CloudFlare.exceptions.CloudFlareAPIError as e:
-            logger.debug('Encountered CloudFlareAPIError getting TXT 
record_id: %s', e)
+            records = list(self.cf.dns.records.list(
+                zone_id=zone_id, type='TXT', name={'exact': record_name},
+                content={'exact': record_content}, per_page=1))
+        except cloudflare.APIStatusError as e:
+            logger.debug('Encountered Cloudflare API error getting TXT 
record_id: %s', e)
+            records = []
+        except cloudflare.APIConnectionError as e:
+            logger.debug('Network error getting TXT record_id from Cloudflare: 
%s', e)
             records = []
 
         if records:
             # Cleanup is returning the system to the state we found it. If, 
for some reason,
             # there are multiple matching records, we only delete one because 
we only added one.
-            return cast(str, records[0]['id'])
+            return records[0].id
         logger.debug('Unable to find TXT record.')
         return None
+
+
+class _RecordData(TypedDict):
+    """Offers type hints for dictionaries of Cloudflare API parameters."""
+
+    type: Literal['TXT']
+    name: str
+    content: str
+    ttl: int
+
+
+def _cf_error_code(e: cloudflare.APIStatusError) -> int | None:
+    """Extract the first Cloudflare error code from an API error response."""
+    try:
+        body = e.response.json()
+        return int(body['errors'][0]['code'])
+    except (ValueError, KeyError, IndexError, TypeError):  # pragma: no cover
+        return None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/certbot_dns_cloudflare-5.4.0/src/certbot_dns_cloudflare/_internal/tests/dns_cloudflare_test.py
 
new/certbot_dns_cloudflare-5.6.0/src/certbot_dns_cloudflare/_internal/tests/dns_cloudflare_test.py
--- 
old/certbot_dns_cloudflare-5.4.0/src/certbot_dns_cloudflare/_internal/tests/dns_cloudflare_test.py
  2026-03-10 18:46:56.000000000 +0100
+++ 
new/certbot_dns_cloudflare-5.6.0/src/certbot_dns_cloudflare/_internal/tests/dns_cloudflare_test.py
  2026-05-11 17:56:16.000000000 +0200
@@ -2,9 +2,10 @@
 
 import sys
 import unittest
+from typing import Any
 from unittest import mock
 
-import CloudFlare
+import cloudflare
 import pytest
 
 from certbot import errors
@@ -13,7 +14,23 @@
 from certbot.plugins.dns_test_common import DOMAIN
 from certbot.tests import util as test_util
 
-API_ERROR = CloudFlare.exceptions.CloudFlareAPIError(1000, '', '')
+
+def _make_api_error(cf_code: int, msg: str = '', http_status: int = 400
+                    ) -> cloudflare.APIStatusError:
+    """Build a cloudflare.APIStatusError with a Cloudflare error code in the 
body."""
+    body: Any = {'success': False, 'errors': [{'code': cf_code, 'message': 
msg}]}
+    response = mock.Mock(status_code=http_status, request=mock.Mock())
+    response.json.return_value = body
+    return cloudflare.APIStatusError(message=msg or str(cf_code), 
response=response, body=body)
+
+
+def _make_connection_error(msg: str = 'Connection error.') -> 
cloudflare.APIConnectionError:
+    """Build a cloudflare.APIConnectionError (e.g. transient network 
failure)."""
+    return cloudflare.APIConnectionError(message=msg, request=mock.Mock())
+
+
+API_ERROR = _make_api_error(1000)
+CONNECTION_ERROR = _make_connection_error()
 
 API_TOKEN = 'an-api-token'
 
@@ -96,13 +113,43 @@
         with pytest.raises(errors.PluginError):
             self.auth.perform([self.achall])
 
+    def test_get_cloudflare_client_with_api_token(self):
+        from certbot_dns_cloudflare._internal.dns_cloudflare import 
Authenticator
+        from certbot_dns_cloudflare._internal.dns_cloudflare import 
_CloudflareClient
+        mock_auth = mock.MagicMock()
+        mock_auth.credentials.conf.return_value = API_TOKEN
+        client = Authenticator._get_cloudflare_client(mock_auth)
+        self.assertIsInstance(client, _CloudflareClient)
+
+    def test_get_cloudflare_client_with_email_key(self):
+        from certbot_dns_cloudflare._internal.dns_cloudflare import 
Authenticator
+        from certbot_dns_cloudflare._internal.dns_cloudflare import 
_CloudflareClient
+        mock_auth = mock.MagicMock()
+        mock_auth.credentials.conf.side_effect = lambda k: None if k == 
'api-token' else 'some_value'
+        client = Authenticator._get_cloudflare_client(mock_auth)
+        self.assertIsInstance(client, _CloudflareClient)
+
+
+def _mock_zone(zone_id: str | None) -> mock.MagicMock:
+    """Create a mock zone object with an .id attribute."""
+    zone = mock.MagicMock()
+    zone.id = zone_id
+    return zone
+
+
+def _mock_record(record_id: str | None) -> mock.MagicMock:
+    """Create a mock DNS record object with an .id attribute."""
+    record = mock.MagicMock()
+    record.id = record_id
+    return record
+
 
 class CloudflareClientTest(unittest.TestCase):
     record_name = "foo"
     record_content = "bar"
     record_ttl = 42
-    zone_id = 1
-    record_id = 2
+    zone_id = "zone-id-1"
+    record_id = "record-id-2"
 
     def setUp(self):
         from certbot_dns_cloudflare._internal.dns_cloudflare import 
_CloudflareClient
@@ -113,119 +160,145 @@
         self.cloudflare_client.cf = self.cf
 
     def test_add_txt_record(self):
-        self.cf.zones.get.return_value = [{'id': self.zone_id}]
+        self.cf.zones.list.return_value = [_mock_zone(self.zone_id)]
 
         self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, 
self.record_content,
                                               self.record_ttl)
 
-        self.cf.zones.dns_records.post.assert_called_with(self.zone_id, 
data=mock.ANY)
-
-        post_data = self.cf.zones.dns_records.post.call_args[1]['data']
-
-        assert 'TXT' == post_data['type']
-        assert self.record_name == post_data['name']
-        assert self.record_content == post_data['content']
-        assert self.record_ttl == post_data['ttl']
+        self.cf.dns.records.create.assert_called_with(
+            zone_id=self.zone_id, type='TXT', name=self.record_name,
+            content=self.record_content, ttl=self.record_ttl)
 
     def test_add_txt_record_error(self):
-        self.cf.zones.get.return_value = [{'id': self.zone_id}]
+        self.cf.zones.list.return_value = [_mock_zone(self.zone_id)]
 
-        self.cf.zones.dns_records.post.side_effect = 
CloudFlare.exceptions.CloudFlareAPIError(1009, '', '')
+        self.cf.dns.records.create.side_effect = _make_api_error(1009)
 
         with pytest.raises(errors.PluginError):
-            self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, 
self.record_content, self.record_ttl)
+            self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, 
self.record_content,
+                                                  self.record_ttl)
 
     def test_add_txt_record_error_during_zone_lookup(self):
-        self.cf.zones.get.side_effect = API_ERROR
+        self.cf.zones.list.side_effect = API_ERROR
 
         with pytest.raises(errors.PluginError):
-            self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, 
self.record_content, self.record_ttl)
+            self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, 
self.record_content,
+                                                  self.record_ttl)
 
     def test_add_txt_record_zone_not_found(self):
-        self.cf.zones.get.return_value = []
+        self.cf.zones.list.return_value = []
 
         with pytest.raises(errors.PluginError):
-            self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, 
self.record_content, self.record_ttl)
+            self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, 
self.record_content,
+                                                  self.record_ttl)
+
+    def test_add_txt_record_connection_error_on_create(self):
+        self.cf.zones.list.return_value = [_mock_zone(self.zone_id)]
+        self.cf.dns.records.create.side_effect = CONNECTION_ERROR
+
+        with pytest.raises(errors.PluginError, match='Network error'):
+            self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, 
self.record_content,
+                                                  self.record_ttl)
+
+    def test_add_txt_record_connection_error_during_zone_lookup(self):
+        self.cf.zones.list.side_effect = CONNECTION_ERROR
+
+        with pytest.raises(errors.PluginError, match='Network error'):
+            self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, 
self.record_content,
+                                                  self.record_ttl)
 
     def test_add_txt_record_bad_creds(self):
-        self.cf.zones.get.side_effect = 
CloudFlare.exceptions.CloudFlareAPIError(6003, '', '')
+        self.cf.zones.list.side_effect = _make_api_error(6003)
         with pytest.raises(errors.PluginError):
-            self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, 
self.record_content, self.record_ttl)
+            self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, 
self.record_content,
+                                                  self.record_ttl)
 
-        self.cf.zones.get.side_effect = 
CloudFlare.exceptions.CloudFlareAPIError(9103, '', '')
+        self.cf.zones.list.side_effect = _make_api_error(9103)
         with pytest.raises(errors.PluginError):
-            self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, 
self.record_content, self.record_ttl)
+            self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, 
self.record_content,
+                                                  self.record_ttl)
 
-        self.cf.zones.get.side_effect = 
CloudFlare.exceptions.CloudFlareAPIError(9109, '', '')
+        self.cf.zones.list.side_effect = _make_api_error(9109)
         with pytest.raises(errors.PluginError):
-            self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, 
self.record_content, self.record_ttl)
+            self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, 
self.record_content,
+                                                  self.record_ttl)
 
-        self.cf.zones.get.side_effect = 
CloudFlare.exceptions.CloudFlareAPIError(0, 
'com.cloudflare.api.account.zone.list', '')
+        self.cf.zones.list.side_effect = _make_api_error(0, 
'com.cloudflare.api.account.zone.list')
         with pytest.raises(errors.PluginError):
-            self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, 
self.record_content, self.record_ttl)
+            self.cloudflare_client.add_txt_record(DOMAIN, self.record_name, 
self.record_content,
+                                                  self.record_ttl)
 
     def test_del_txt_record(self):
-        self.cf.zones.get.return_value = [{'id': self.zone_id}]
-        self.cf.zones.dns_records.get.return_value = [{'id': self.record_id}]
+        self.cf.zones.list.return_value = [_mock_zone(self.zone_id)]
+        self.cf.dns.records.list.return_value = [_mock_record(self.record_id)]
 
         self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, 
self.record_content)
 
-        expected = [mock.call.zones.get(params=mock.ANY),
-                    mock.call.zones.dns_records.get(self.zone_id, 
params=mock.ANY),
-                    mock.call.zones.dns_records.delete(self.zone_id, 
self.record_id)]
-
-        assert expected == self.cf.mock_calls
-
-        get_data = self.cf.zones.dns_records.get.call_args[1]['params']
-
-        assert 'TXT' == get_data['type']
-        assert self.record_name == get_data['name']
-        assert self.record_content == get_data['content']
+        self.cf.zones.list.assert_called_once()
+        self.cf.dns.records.list.assert_called_once_with(
+            zone_id=self.zone_id, type='TXT', name={'exact': self.record_name},
+            content={'exact': self.record_content}, per_page=1)
+        self.cf.dns.records.delete.assert_called_once_with(
+            dns_record_id=self.record_id, zone_id=self.zone_id)
 
     def test_del_txt_record_error_during_zone_lookup(self):
-        self.cf.zones.get.side_effect = API_ERROR
+        self.cf.zones.list.side_effect = API_ERROR
 
         self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, 
self.record_content)
 
     def test_del_txt_record_error_during_delete(self):
-        self.cf.zones.get.return_value = [{'id': self.zone_id}]
-        self.cf.zones.dns_records.get.return_value = [{'id': self.record_id}]
-        self.cf.zones.dns_records.delete.side_effect = API_ERROR
+        self.cf.zones.list.return_value = [_mock_zone(self.zone_id)]
+        self.cf.dns.records.list.return_value = [_mock_record(self.record_id)]
+        self.cf.dns.records.delete.side_effect = API_ERROR
 
         self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, 
self.record_content)
-        expected = [mock.call.zones.get(params=mock.ANY),
-                    mock.call.zones.dns_records.get(self.zone_id, 
params=mock.ANY),
-                    mock.call.zones.dns_records.delete(self.zone_id, 
self.record_id)]
 
-        assert expected == self.cf.mock_calls
+        self.cf.dns.records.delete.assert_called_once_with(
+            dns_record_id=self.record_id, zone_id=self.zone_id)
 
     def test_del_txt_record_error_during_get(self):
-        self.cf.zones.get.return_value = [{'id': self.zone_id}]
-        self.cf.zones.dns_records.get.side_effect = API_ERROR
+        self.cf.zones.list.return_value = [_mock_zone(self.zone_id)]
+        self.cf.dns.records.list.side_effect = API_ERROR
 
         self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, 
self.record_content)
-        expected = [mock.call.zones.get(params=mock.ANY),
-                    mock.call.zones.dns_records.get(self.zone_id, 
params=mock.ANY)]
 
-        assert expected == self.cf.mock_calls
+        self.cf.dns.records.list.assert_called_once()
+        self.cf.dns.records.delete.assert_not_called()
 
     def test_del_txt_record_no_record(self):
-        self.cf.zones.get.return_value = [{'id': self.zone_id}]
-        self.cf.zones.dns_records.get.return_value = []
+        self.cf.zones.list.return_value = [_mock_zone(self.zone_id)]
+        self.cf.dns.records.list.return_value = []
 
         self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, 
self.record_content)
-        expected = [mock.call.zones.get(params=mock.ANY),
-                    mock.call.zones.dns_records.get(self.zone_id, 
params=mock.ANY)]
 
-        assert expected == self.cf.mock_calls
+        self.cf.dns.records.list.assert_called_once()
+        self.cf.dns.records.delete.assert_not_called()
 
     def test_del_txt_record_no_zone(self):
-        self.cf.zones.get.return_value = [{'id': None}]
+        self.cf.zones.list.return_value = [_mock_zone(None)]
+
+        self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, 
self.record_content)
+
+        self.cf.zones.list.assert_called_once()
+
+    def test_del_txt_record_connection_error_on_delete(self):
+        self.cf.zones.list.return_value = [_mock_zone(self.zone_id)]
+        self.cf.dns.records.list.return_value = [_mock_record(self.record_id)]
+        self.cf.dns.records.delete.side_effect = CONNECTION_ERROR
+
+        self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, 
self.record_content)
+
+        self.cf.dns.records.delete.assert_called_once_with(
+            dns_record_id=self.record_id, zone_id=self.zone_id)
+
+    def test_del_txt_record_connection_error_on_get(self):
+        self.cf.zones.list.return_value = [_mock_zone(self.zone_id)]
+        self.cf.dns.records.list.side_effect = CONNECTION_ERROR
 
         self.cloudflare_client.del_txt_record(DOMAIN, self.record_name, 
self.record_content)
-        expected = [mock.call.zones.get(params=mock.ANY)]
 
-        assert expected == self.cf.mock_calls
+        self.cf.dns.records.list.assert_called_once()
+        self.cf.dns.records.delete.assert_not_called()
 
 
 if __name__ == "__main__":
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/certbot_dns_cloudflare-5.4.0/src/certbot_dns_cloudflare.egg-info/PKG-INFO 
new/certbot_dns_cloudflare-5.6.0/src/certbot_dns_cloudflare.egg-info/PKG-INFO
--- 
old/certbot_dns_cloudflare-5.4.0/src/certbot_dns_cloudflare.egg-info/PKG-INFO   
    2026-03-10 18:47:02.000000000 +0100
+++ 
new/certbot_dns_cloudflare-5.6.0/src/certbot_dns_cloudflare.egg-info/PKG-INFO   
    2026-05-11 17:56:24.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: certbot-dns-cloudflare
-Version: 5.4.0
+Version: 5.6.0
 Summary: Cloudflare DNS Authenticator plugin for Certbot
 Author: Certbot Project
 License-Expression: Apache-2.0
@@ -25,9 +25,9 @@
 Requires-Python: >=3.10
 Description-Content-Type: text/x-rst
 License-File: LICENSE.txt
-Requires-Dist: cloudflare<2.20,>=2.19
-Requires-Dist: acme>=5.4.0
-Requires-Dist: certbot>=5.4.0
+Requires-Dist: cloudflare>=4.0
+Requires-Dist: acme>=5.6.0
+Requires-Dist: certbot>=5.6.0
 Provides-Extra: docs
 Requires-Dist: Sphinx>=1.0; extra == "docs"
 Requires-Dist: sphinx_rtd_theme; extra == "docs"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/certbot_dns_cloudflare-5.4.0/src/certbot_dns_cloudflare.egg-info/requires.txt
 
new/certbot_dns_cloudflare-5.6.0/src/certbot_dns_cloudflare.egg-info/requires.txt
--- 
old/certbot_dns_cloudflare-5.4.0/src/certbot_dns_cloudflare.egg-info/requires.txt
   2026-03-10 18:47:02.000000000 +0100
+++ 
new/certbot_dns_cloudflare-5.6.0/src/certbot_dns_cloudflare.egg-info/requires.txt
   2026-05-11 17:56:24.000000000 +0200
@@ -1,6 +1,6 @@
-cloudflare<2.20,>=2.19
-acme>=5.4.0
-certbot>=5.4.0
+cloudflare>=4.0
+acme>=5.6.0
+certbot>=5.6.0
 
 [docs]
 Sphinx>=1.0

Reply via email to