Hello community,

here is the log from the commit of package python-cloudflare for 
openSUSE:Factory checked in at 2020-06-25 15:08:32
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-cloudflare (Old)
 and      /work/SRC/openSUSE:Factory/.python-cloudflare.new.3060 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-cloudflare"

Thu Jun 25 15:08:32 2020 rev:6 rq:816914 version:2.8.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-cloudflare/python-cloudflare.changes      
2020-06-10 00:44:15.170310200 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-cloudflare.new.3060/python-cloudflare.changes
    2020-06-25 15:09:53.685829687 +0200
@@ -1,0 +2,7 @@
+Thu Jun 25 04:20:35 UTC 2020 - Steve Kowalik <[email protected]>
+
+- Update to 2.8.3:
+  * No upstream changelog
+- Add alternative for new manpage. 
+
+-------------------------------------------------------------------

Old:
----
  cloudflare-2.6.3.tar.gz

New:
----
  cloudflare-2.8.3.tar.gz

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

Other differences:
------------------
++++++ python-cloudflare.spec ++++++
--- /var/tmp/diff_new_pack.sQOCkt/_old  2020-06-25 15:09:55.949836791 +0200
+++ /var/tmp/diff_new_pack.sQOCkt/_new  2020-06-25 15:09:55.953836804 +0200
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-cloudflare
-Version:        2.6.3
+Version:        2.8.3
 Release:        0
 Summary:        Python wrapper for the Cloudflare v4 API
 License:        MIT
@@ -52,21 +52,26 @@
 
 %install
 %python_install
+mkdir -p %{buildroot}%{_mandir}/man1
+mv %{buildroot}/usr/man/man1/cli4.man %{buildroot}%{_mandir}/man1/cli4.1
+rm -rf %{buildroot}/usr/man
 %python_clone -a %{buildroot}%{_bindir}/cli4
+%python_clone -a %{buildroot}%{_mandir}/man1/cli4.1
 # remove examples from sitelib
 %python_expand rm -rf %{buildroot}%{$python_sitelib}/examples
 %python_expand %fdupes %{buildroot}%{$python_sitelib}
 
 %post
-%python_install_alternative cli4
+%python_install_alternative cli4 cli4.1
 
 %postun
-%python_uninstall_alternative cli4
+%python_uninstall_alternative cli4 cli4.1
 
 %files %{python_files}
 %doc README.rst
 %license LICENSE
 %python_alternative %{_bindir}/cli4
+%python_alternative %{_mandir}/man1/cli4.1%{?ext_man}
 %{python_sitelib}/*
 
 %changelog

++++++ cloudflare-2.6.3.tar.gz -> cloudflare-2.8.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/CloudFlare/__init__.py 
new/cloudflare-2.8.3/CloudFlare/__init__.py
--- old/cloudflare-2.6.3/CloudFlare/__init__.py 2020-02-09 21:37:52.000000000 
+0100
+++ new/cloudflare-2.8.3/CloudFlare/__init__.py 2020-06-23 03:44:49.000000000 
+0200
@@ -1,7 +1,7 @@
 """ Cloudflare v4 API"""
 from __future__ import absolute_import
 
-__version__ = '2.6.3'
+__version__ = '2.8.3'
 
 from .cloudflare import CloudFlare
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/CloudFlare/api_decode_from_web.py 
new/cloudflare-2.8.3/CloudFlare/api_decode_from_web.py
--- old/cloudflare-2.6.3/CloudFlare/api_decode_from_web.py      1970-01-01 
01:00:00.000000000 +0100
+++ new/cloudflare-2.8.3/CloudFlare/api_decode_from_web.py      2020-06-19 
23:59:12.000000000 +0200
@@ -0,0 +1,43 @@
+""" API extras for Cloudflare API"""
+
+from bs4 import BeautifulSoup, Comment
+
+API_TYPES = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE']
+
+def do_section(section):
+    """ API extras for Cloudflare API"""
+
+    cmds = []
+    # look for deprecated first in section
+    deprecated = False
+    for tag2 in section.find_all('h3'):
+        if 'Deprecation Warning' in str(tag2):
+            deprecated = True
+    # look for all API calls in section
+    for tag2 in section.find_all('pre'):
+        cmd = []
+        for child in tag2.children:
+            if isinstance(child, Comment):
+                # remove <!-- react-text ... -> parts
+                continue
+            cmd.append(child.strip())
+        if len(cmd) == 0:
+            continue
+        action = cmd[0]
+        cmd = '/' + ''.join(cmd[1:])
+        if action == '' or action not in API_TYPES:
+            continue
+        v = {'deprecated': deprecated, 'action': action, 'cmd': cmd}
+        cmds.append(v)
+    return cmds
+
+def api_decode_from_web(content):
+    """ API extras for Cloudflare API"""
+
+    soup = BeautifulSoup(content, 'html.parser')
+
+    all_cmds = []
+    for section in soup.find_all('section'):
+        all_cmds += do_section(section)
+
+    return sorted(all_cmds, key=lambda v: v['cmd'])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/CloudFlare/api_v4.py 
new/cloudflare-2.8.3/CloudFlare/api_v4.py
--- old/cloudflare-2.6.3/CloudFlare/api_v4.py   2020-02-09 21:23:59.000000000 
+0100
+++ new/cloudflare-2.8.3/CloudFlare/api_v4.py   2020-06-17 21:27:12.000000000 
+0200
@@ -24,6 +24,7 @@
     zones_logpush(self)
     zones_logs(self)
     zones_media(self)
+    zones_origin_tls_client_auth(self)
     zones_rate_limits(self)
     zones_secondary_dns(self)
     zones_settings(self)
@@ -48,12 +49,18 @@
     # The API commands for /accounts/
     accounts(self)
     accounts_addressing(self)
+    accounts_audit_logs(self)
     accounts_firewall(self)
+    accounts_load_balancers(self)
     accounts_secondary_dns(self)
+    accounts_stream(self)
 
     # The API commands for /memberships/
     memberships(self)
 
+    # The API commands for /graphql
+    graphql(self)
+
 def user(self):
     """ API core commands for Cloudflare API"""
 
@@ -86,12 +93,14 @@
     self.add('AUTH', "zones", "custom_certificates")
     self.add('AUTH', "zones", "custom_certificates/prioritize")
     self.add('AUTH', "zones", "custom_hostnames")
+    self.add('AUTH', "zones", "custom_hostnames/fallback_origin")
     self.add('AUTH', "zones", "custom_pages")
     self.add('AUTH', "zones", "dns_records")
     self.add('AUTH', "zones", "dns_records/export")
     self.add('AUTH', "zones", "dns_records/import")
     self.add('AUTH', "zones", "filters")
     self.add('AUTH', "zones", "healthchecks")
+    self.add('AUTH', "zones", "healthchecks/preview")
     self.add('AUTH', "zones", "keyless_certificates")
     self.add('AUTH', "zones", "pagerules")
     self.add('AUTH', "zones", "pagerules/settings")
@@ -282,10 +291,20 @@
     self.add('VOID', "zones", "ssl")
     self.add('AUTH', "zones", "ssl/analyze")
     self.add('AUTH', "zones", "ssl/certificate_packs")
+    self.add('AUTH', 'zones', 'ssl/certificate_packs/order')
+    self.add('AUTH', 'zones', 'ssl/certificate_packs/quota')
     self.add('AUTH', "zones", "ssl/verification")
     self.add('VOID', "zones", "ssl/universal")
     self.add('AUTH', "zones", "ssl/universal/settings")
 
+def zones_origin_tls_client_auth(self):
+    """ API core commands for Cloudflare API"""
+
+    self.add('AUTH', 'zones', 'origin_tls_client_auth')
+    self.add('AUTH', 'zones', 'origin_tls_client_auth/hostnames')
+    self.add('AUTH', 'zones', 'origin_tls_client_auth/hostnames/certificates')
+    self.add('AUTH', 'zones', 'origin_tls_client_auth/settings')
+
 def zones_workers(self):
     """ API core commands for Cloudflare API"""
 
@@ -311,8 +330,11 @@
 
     self.add('VOID', "user/load_balancers")
     self.add('AUTH', "user/load_balancers/monitors")
+    self.add('AUTH', "user/load_balancers/monitors", "preview")
+    self.add('AUTH', "user/load_balancers/preview")
     self.add('AUTH', "user/load_balancers/pools")
     self.add('AUTH', "user/load_balancers/pools", "health")
+    self.add('AUTH', "user/load_balancers/pools", "preview")
 
 def user_virtual_dns(self):
     """ API core commands for Cloudflare API"""
@@ -351,8 +373,10 @@
 def user_tokens_verify(self):
     """ API core commands for Cloudflare API"""
 
-    self.add('VOID', "user/tokens")
+    self.add('AUTH', "user/tokens")
+    self.add('AUTH', "user/tokens/permission_groups")
     self.add('AUTH', "user/tokens/verify")
+    self.add('AUTH', "user/tokens", "value")
 
 def organizations_audit_logs(self):
     """ API core commands for Cloudflare API"""
@@ -372,10 +396,6 @@
     self.add('VOID', "accounts", "billing")
     self.add('AUTH', "accounts", "billing/profile")
     self.add('AUTH', "accounts", "custom_pages")
-    self.add('VOID', "accounts", "load_balancers")
-    self.add('AUTH', "accounts", "load_balancers/monitors")
-    self.add('AUTH', "accounts", "load_balancers/pools")
-    self.add('AUTH', "accounts", "load_balancers/pools", "health")
     self.add('AUTH', "accounts", "members")
     self.add('AUTH', "accounts", "railguns")
     self.add('AUTH', "accounts", "railguns/connections")
@@ -390,9 +410,6 @@
     self.add('AUTH', "accounts", "storage/kv/namespaces", "bulk")
     self.add('AUTH', "accounts", "storage/kv/namespaces", "keys")
     self.add('AUTH', "accounts", "storage/kv/namespaces", "values")
-    self.add('AUTH', "accounts", "stream")
-    self.add('AUTH', "accounts", "stream/embed")
-    self.add('AUTH', "accounts", "stream/preview")
     self.add('AUTH', "accounts", "subscriptions")
     self.add('AUTH', "accounts", "virtual_dns")
     self.add('VOID', "accounts", "virtual_dns/dns_analytics")
@@ -408,6 +425,24 @@
     self.add('AUTH', "accounts", "addressing/prefixes")
     self.add('VOID', "accounts", "addressing/prefixes", "bgp")
     self.add('AUTH', "accounts", "addressing/prefixes", "bgp/status")
+    self.add('AUTH', 'accounts', 'addressing/prefixes', 'delegations')
+
+def accounts_audit_logs(self):
+    """ API core commands for Cloudflare API"""
+
+    self.add('AUTH', "accounts", "audit_logs")
+
+def accounts_load_balancers(self):
+    """ API core commands for Cloudflare API"""
+
+    self.add('VOID', "accounts", "load_balancers")
+    self.add('AUTH', 'accounts', 'load_balancers/preview')
+    self.add('AUTH', "accounts", "load_balancers/monitors")
+    self.add('AUTH', 'accounts', 'load_balancers/monitors', 'preview')
+    self.add('AUTH', "accounts", "load_balancers/pools")
+    self.add('AUTH', "accounts", "load_balancers/pools", "health")
+    self.add('AUTH', 'accounts', 'load_balancers/pools', 'preview')
+    self.add('AUTH', 'accounts', 'load_balancers/search')
 
 def accounts_firewall(self):
     """ API core commands for Cloudflare API"""
@@ -423,6 +458,17 @@
     self.add('AUTH', "accounts", "secondary_dns/masters")
     self.add('AUTH', "accounts", "secondary_dns/tsigs")
 
+def accounts_stream(self):
+    """ API core commands for Cloudflare API"""
+
+    self.add('AUTH', "accounts", "stream")
+    self.add('AUTH', "accounts", "stream/copy")
+    self.add('AUTH', "accounts", "stream/direct_upload")
+    self.add('AUTH', "accounts", "stream/embed")
+    self.add('AUTH', "accounts", "stream/keys")
+    self.add('AUTH', "accounts", "stream/preview")
+    self.add('AUTH', "accounts", "stream", "captions")
+
 def zones_media(self):
     """ API core commands for Cloudflare API"""
 
@@ -434,3 +480,8 @@
     """ API core commands for Cloudflare API"""
 
     self.add('AUTH', "memberships")
+
+def graphql(self):
+    """ API core commands for Cloudflare API"""
+
+    self.add('AUTH', "graphql")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/CloudFlare/cloudflare.py 
new/cloudflare-2.8.3/CloudFlare/cloudflare.py
--- old/cloudflare-2.6.3/CloudFlare/cloudflare.py       2020-02-06 
02:31:46.000000000 +0100
+++ new/cloudflare-2.8.3/CloudFlare/cloudflare.py       2020-06-23 
01:09:21.000000000 +0200
@@ -9,6 +9,7 @@
 from .read_configs import read_configs
 from .api_v4 import api_v4
 from .api_extras import api_extras
+from .api_decode_from_web import api_decode_from_web
 from .exceptions import CloudFlareError, CloudFlareAPIError, 
CloudFlareInternalError
 
 BASE_URL = 'https://api.cloudflare.com/client/v4'
@@ -154,6 +155,52 @@
                               identifier1, identifier2, identifier3,
                               params, data, files)
 
+        def _connection(self, method, url, headers=None, params=None, 
data=None, files=None):
+            """ Cloudflare v4 API"""
+
+            if self.use_sessions:
+                if self.session is None:
+                    self.session = requests.Session()
+            else:
+                self.session = requests
+
+            method = method.upper()
+
+            if method == 'GET':
+                return self.session.get(url,
+                                        headers=headers, params=params, 
data=data)
+            if method == 'POST':
+                if isinstance(data, str):
+                    return self.session.post(url,
+                                             headers=headers, params=params, 
data=data, files=files)
+                else:
+                    return self.session.post(url,
+                                             headers=headers, params=params, 
json=data, files=files)
+            if method == 'PUT':
+                if isinstance(data, str):
+                    return self.session.put(url,
+                                            headers=headers, params=params, 
data=data)
+                else:
+                    return self.session.put(url,
+                                            headers=headers, params=params, 
json=data)
+            if method == 'DELETE':
+                if isinstance(data, str):
+                    return self.session.delete(url,
+                                               headers=headers, params=params, 
data=data)
+                else:
+                    return self.session.delete(url,
+                                               headers=headers, params=params, 
json=data)
+            if method == 'PATCH':
+                if isinstance(data, str):
+                    return self.session.request('PATCH', url,
+                                                headers=headers, 
params=params, data=data)
+                else:
+                    return self.session.request('PATCH', url,
+                                                headers=headers, 
params=params, json=data)
+
+            # should never happen
+            raise CloudFlareAPIError(0, 'method not supported')
+
         def _network(self, method, headers, parts,
                      identifier1=None, identifier2=None, identifier3=None,
                      params=None, data=None, files=None):
@@ -208,72 +255,10 @@
                 self.logger.debug('Call: method and url %s %s', str(method), 
str(url))
                 self.logger.debug('Call: headers %s', 
str(sanitize_secrets(headers)))
 
-            method = method.upper()
-
-            if self.logger:
-                self.logger.debug('Call: doit!')
-
-            if self.use_sessions:
-                if self.session is None:
-                    self.session = requests.Session()
-            else:
-                self.session = requests
-
             try:
-                if method == 'GET':
-                    response = self.session.get(url,
-                                                headers=headers,
-                                                params=params,
-                                                data=data)
-                elif method == 'POST':
-                    if isinstance(data, str):
-                        response = self.session.post(url,
-                                                     headers=headers,
-                                                     params=params,
-                                                     data=data,
-                                                     files=files)
-                    else:
-                        response = self.session.post(url,
-                                                     headers=headers,
-                                                     params=params,
-                                                     json=data,
-                                                     files=files)
-                elif method == 'PUT':
-                    if isinstance(data, str):
-                        response = self.session.put(url,
-                                                    headers=headers,
-                                                    params=params,
-                                                    data=data)
-                    else:
-                        response = self.session.put(url,
-                                                    headers=headers,
-                                                    params=params,
-                                                    json=data)
-                elif method == 'DELETE':
-                    if isinstance(data, str):
-                        response = self.session.delete(url,
-                                                       headers=headers,
-                                                       params=params,
-                                                       data=data)
-                    else:
-                        response = self.session.delete(url,
-                                                       headers=headers,
-                                                       params=params,
-                                                       json=data)
-                elif method == 'PATCH':
-                    if isinstance(data, str):
-                        response = self.session.request('PATCH', url,
-                                                        headers=headers,
-                                                        params=params,
-                                                        data=data)
-                    else:
-                        response = self.session.request('PATCH', url,
-                                                        headers=headers,
-                                                        params=params,
-                                                        json=data)
-                else:
-                    # should never happen
-                    raise CloudFlareAPIError(0, 'method not supported')
+                if self.logger:
+                    self.logger.debug('Call: doit!')
+                response = self._connection(method, url, headers, params, 
data, files)
                 if self.logger:
                     self.logger.debug('Call: done!')
             except Exception as e:
@@ -296,7 +281,7 @@
                 response_type = 'application/octet-stream'
             response_code = response.status_code
             response_data = response.content
-            if not isinstance(response_data, str):
+            if not isinstance(response_data, (str, bytes, bytearray)):
                 response_data = response_data.decode("utf-8")
 
             if self.logger:
@@ -364,6 +349,9 @@
                     response_data = response_data.decode('utf-8')
                 try:
                     response_data = json.loads(response_data)
+                    if not isinstance(response_data, (dict)):
+                        response_data = {'success': True,
+                                         'result': response_data}
                 except ValueError:
                     if response_data == '':
                         # This should really be 'null' but it isn't. Even 
then, it's wrong!
@@ -397,23 +385,64 @@
                     # 3xx & 4xx errors - we should report that somehow - but 
not quite yet
                     # response_data['code'] = response_code
                     pass
+            elif response_type == 'application/octet-stream' and 
isinstance(response_data, (int, float)):
+                # It's binary data
+                if response_code == requests.codes.ok:
+                    # 200 ok
+                    response_data = {'success': True,
+                                     'result': response_data}
+                else:
+                    # 3xx & 4xx errors
+                    response_data = {'success': False,
+                                     'code': response_code,
+                                     'result': response_data}
+            elif response_type == 'application/octet-stream' and 
isinstance(response_data, (bytes, bytearray)):
+                # API says it's text; but maybe it's actually JSON? - should 
be fixed in API
+                if hasattr(response_data, 'decode'):
+                    response_data = response_data.decode('utf-8')
+                try:
+                    response_data = json.loads(response_data)
+                    if not isinstance(response_data, (dict)) or 'success' not 
in response_data:
+                        if response_code == requests.codes.ok:
+                            # 200 ok
+                            response_data = {'success': True,
+                                             'result': response_data}
+                        else:
+                            # 3xx & 4xx errors
+                            response_data = {'success': False,
+                                             'code': response_code,
+                                             'result': response_data}
+                except ValueError:
+                    # So it wasn't JSON - moving on as if it's text!
+                    # A single value is returned (vs an array or object)
+                    if response_code == requests.codes.ok:
+                        # 200 ok
+                        response_data = {'success': True, 'result': 
response_data}
+                    else:
+                        # 3xx & 4xx errors
+                        response_data = {'success': False,
+                                         'code': response_code,
+                                         'result': response_data}
             elif response_type == 'text/plain' or response_type == 
'application/octet-stream':
                 # API says it's text; but maybe it's actually JSON? - should 
be fixed in API
                 if hasattr(response_data, 'decode'):
                     response_data = response_data.decode('utf-8')
                 try:
                     response_data = json.loads(response_data)
+                    if not isinstance(response_data, (dict)):
+                        response_data = {'success': True,
+                                         'result': response_data}
                 except ValueError:
                     # So it wasn't JSON - moving on as if it's text!
                     # A single value is returned (vs an array or object)
                     if response_code == requests.codes.ok:
                         # 200 ok
-                        response_data = {'success': True, 'result': 
str(response_data)}
+                        response_data = {'success': True, 'result': 
response_data}
                     else:
                         # 3xx & 4xx errors
                         response_data = {'success': False,
                                          'code': response_code,
-                                         'result': str(response_data)}
+                                         'result': response_data}
             elif response_type == 'text/javascript' or response_type == 
'application/javascript':
                 # used by Cloudflare workers
                 if response_code == requests.codes.ok:
@@ -465,9 +494,29 @@
             # Sanatize the returned results - just in case API is messed up
             if 'success' not in response_data:
                 if 'errors' in response_data:
-                    if self.logger:
-                        self.logger.debug('Response: assuming success = 
"False"')
-                    response_data['success'] = False
+                    if response_data['errors'] == None:
+                        # Only happens on /graphql call
+                        if self.logger:
+                            self.logger.debug('Response: assuming success = 
"True"')
+                        response_data['success'] = True
+                    else:
+                        if self.logger:
+                            self.logger.debug('Response: assuming success = 
"False"')
+                        # The following only happens on /graphql call
+                        try:
+                            message = response_data['errors'][0]['message']
+                        except:
+                            message = ''
+                        try:
+                            location = 
str(response_data['errors'][0]['location'])
+                        except:
+                            location = ''
+                        try:
+                            path = '>'.join(response_data['errors'][0]['path'])
+                        except:
+                            path = ''
+                        response_data['errors'] = [{'code': 99999, 'message': 
message + ' - ' + location + ' - ' + path}]
+                        response_data['success'] = False
                 else:
                     if 'result' not in response_data:
                         # Only happens on /certificates call
@@ -484,8 +533,14 @@
                         response_data['success'] = True
 
             if response_data['success'] is False:
-                errors = response_data['errors'][0]
-                code = errors['code']
+                if 'errors' in response_data:
+                    errors = response_data['errors'][0]
+                else:
+                    errors = {}
+                if 'code' in errors:
+                    code = errors['code']
+                else:
+                    code = 99998
                 if 'message' in errors:
                     message = errors['message']
                 elif 'error' in errors:
@@ -509,19 +564,25 @@
                         self.logger.debug('Response: error %d %s', code, 
message)
                     raise CloudFlareAPIError(code, message)
 
-            if self.logger:
-                self.logger.debug('Response: %s', response_data['result'])
             if self.raw:
                 result = {}
-                # theres always a result value
-                result['result'] = response_data['result']
+                # theres always a result value - unless it's a graphql query
+                try:
+                    result['result'] = response_data['result']
+                except:
+                    result['result'] = response_data
                 # theres may not be a result_info on every call
                 if 'result_info' in response_data:
                     result['result_info'] = response_data['result_info']
                 # no need to return success, errors, or messages as they 
return via an exception
             else:
-                # theres always a result value
-                result = response_data['result']
+                # theres always a result value - unless it's a graphql query
+                try:
+                    result = response_data['result']
+                except:
+                    result = response_data
+            if self.logger:
+                self.logger.debug('Response: %s', result)
             return result
 
         def _call_unwrapped(self, method, headers, parts,
@@ -537,6 +598,25 @@
             result = response_data
             return result
 
+        def _api_from_web(self):
+            """ Cloudflare v4 API"""
+
+            # base url isn't enough; we need less
+            url = '/'.join(self.base_url.split('/')[0:3])
+
+            try:
+                if self.logger:
+                    self.logger.debug('Call: doit!')
+                response = self._connection("GET", url)
+                if self.logger:
+                    self.logger.debug('Call: done!')
+            except Exception as e:
+                if self.logger:
+                    self.logger.debug('Call: exception! "%s"' % (e))
+                raise CloudFlareAPIError(0, 'connection failed.')
+
+            return response.text
+
     class _AddUnused(object):
         """ Cloudflare v4 API"""
 
@@ -861,6 +941,11 @@
                 w = w + self.api_list(a, s + '/' + n)
         return w
 
+    def api_from_web(self):
+        """ Cloudflare v4 API"""
+
+        return api_decode_from_web(self._base._api_from_web())
+
     def __init__(self, email=None, token=None, certtoken=None, debug=False, 
raw=False, use_sessions=True, profile=None):
         """ Cloudflare v4 API"""
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/PKG-INFO 
new/cloudflare-2.8.3/PKG-INFO
--- old/cloudflare-2.6.3/PKG-INFO       2020-02-09 22:14:52.411620000 +0100
+++ new/cloudflare-2.8.3/PKG-INFO       2020-06-23 03:46:31.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: cloudflare
-Version: 2.6.3
+Version: 2.8.3
 Summary: Python wrapper for the Cloudflare v4 API
 Home-page: https://github.com/cloudflare/python-cloudflare
 Author: Martin J. Levy
@@ -491,6 +491,18 @@
             cli4: /zones - 9103 Unknown X-Auth-Key or X-Auth-Email
             $
         
+        More than one call can be done on the same command line. In this mode,
+        the connection is preserved between calls.
+        
+        ::
+        
+            $ cli4 /user/organizations /user/invites
+            ...
+            $
+        
+        Note that the output is presently two JSON structures one after the
+        other - so less useful that you may think.
+        
         Finally, a command that provides more than one error response. This is
         simulated by passing an invalid IPv4 address to a DNS record creation.
         
@@ -1000,7 +1012,7 @@
         
         ::
         
-            $ python3 -m cli4 /user/workers/scripts
+            $ cli4 /user/workers/scripts
             [
                 {
                     "created_on": "2018-02-15T00:00:00.000000Z",
@@ -1287,6 +1299,12 @@
         `here <https://github.com/cloudflare-api/python-cloudflare-v4>`__. It
         has been seriously expanded upon.
         
+        Changelog
+        ---------
+        
+        An automatically generated CHANGELOG is provided
+        `here <CHANGELOG.md>`__.
+        
         Copyright
         ---------
         
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/README.rst 
new/cloudflare-2.8.3/README.rst
--- old/cloudflare-2.6.3/README.rst     2020-01-18 06:59:08.000000000 +0100
+++ new/cloudflare-2.8.3/README.rst     2020-06-20 07:53:59.000000000 +0200
@@ -483,6 +483,18 @@
     cli4: /zones - 9103 Unknown X-Auth-Key or X-Auth-Email
     $
 
+More than one call can be done on the same command line. In this mode,
+the connection is preserved between calls.
+
+::
+
+    $ cli4 /user/organizations /user/invites
+    ...
+    $
+
+Note that the output is presently two JSON structures one after the
+other - so less useful that you may think.
+
 Finally, a command that provides more than one error response. This is
 simulated by passing an invalid IPv4 address to a DNS record creation.
 
@@ -992,7 +1004,7 @@
 
 ::
 
-    $ python3 -m cli4 /user/workers/scripts
+    $ cli4 /user/workers/scripts
     [
         {
             "created_on": "2018-02-15T00:00:00.000000Z",
@@ -1279,6 +1291,12 @@
 `here <https://github.com/cloudflare-api/python-cloudflare-v4>`__. It
 has been seriously expanded upon.
 
+Changelog
+---------
+
+An automatically generated CHANGELOG is provided
+`here <CHANGELOG.md>`__.
+
 Copyright
 ---------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/cli4/cli4.man 
new/cloudflare-2.8.3/cli4/cli4.man
--- old/cloudflare-2.6.3/cli4/cli4.man  1970-01-01 01:00:00.000000000 +0100
+++ new/cloudflare-2.8.3/cli4/cli4.man  2020-04-28 23:07:08.000000000 +0200
@@ -0,0 +1,111 @@
+.TH CLI4 1
+
+.SH NAME
+cli4 \- Command line access to Cloudflare v4 API
+
+.SH SYNOPSIS
+.B cli4
+[\fB\-V\fR|\fB\-\-version]
+[\fB\-h\fR|\fB\-\-help]
+[\fB\-v\fR|\fB\-\-verbose]
+[\fB\-q\fR|\fB\-\-quiet]
+[\fB\-j\fR|\fB\-\-json]
+[\fB\-y\fR|\fB\-\-yaml]
+[\fB\-n\fR|\fB\-\-ndjson]
+[\fB\-r\fR|\fB\-\-raw]
+[\fB\-d\fR|\fB\-\-dump]
+[\fB\-p profile-name\fR|\fB\-\-profile profile-name]
+[\fBitem\fR=\fIvalue\fR ...]
+[\fBitem\fR=@\fIfilename\fR ...]
+[\fB\-G\fR|\fB\-\-get]
+[\fB\-P\fR|\fB\-\-patch]
+[\fB\-O\fR|\fB\-\-post]
+[\fB\-U\fR|\fB\-\-put]
+[\fB\-D\fR|\fB\-\-delete]
+.IR /command ...
+
+.SH DESCRIPTION
+.B cli4
+provides command line access to Cloudflare v4 API
+
+.SH OPTIONS
+.TP
+.IP "[\-V, \-\-version]"
+Display program version number and exit.
+.IP "[\-h, \-\-help]"
+This information (in a terse form).
+.IP "[\-v, \-\-verbose]"
+Provide some protcol debugging information.
+.IP "[\-q, \-\-quiet]"
+Don't output any JSON/YAML responses.
+.IP "[\-j, \-\-json]"
+Output response data in JSON format (the default).
+.IP "[\-y, \-\-yaml]"
+Output response data in YAML format (if yaml package installed).
+.IP "[\-n, \-\-ndjson]"
+Output response data in NDJSON format (if jsonlines package installed).
+.IP "[\-r, \-\-raw]"
+Output JSON results in raw mode without splitting out the errors and results.
+.IP "[\-d, \-\-dump]"
+Output a list of all API calls included in the code.
+.IP "[-p \fIprofile-name\fR, \-\-profile \fIprofile-name\fR]"
+Select a \fIprofile-name\fR from the configuration file (hence select custom 
\fIemail\fR/\fItoken\fR values).
+.IP "\-\-get"
+Send HTTP request as a \fBGET\fR (the default).
+.IP "\-\-patch"
+Send HTTP request as a \fBPATCH\fR.
+.IP "\-\-post"
+Send HTTP request as a \fBPOST\fR.
+.IP "\-\-put"
+Send HTTP request as a \fBPUT\fR.
+.IP "\-\-delete"
+Send HTTP request as a \fBDELETE\fR.
+.IP "item=\fIvalue\fR"
+Set a paramater or data value to send with a \fBGET\fR, \fBPATCH\fR, 
\fBPOST\fR, \fBPUT\fR or \fBDELETE\fR command. The value is sent as a string.
+.IP item:=\fIvalue\fR
+Set a paramater or data value to send with a \fBGET\fR, \fBPATCH\fR, 
\fBPOST\fR, \fBPUT\fR or \fBDELETE\fR command. The value is sent as an interger.
+.IP item=@\fIfilename\fR
+Set a paramater or data value to send with a \fBPOST\fR or \fBPUT\fR command. 
The value is based on the content of the file.
+.IP "\fI/command ...\fR"
+The API command(s) to execute.
+
+.SH COMMAND(S)
+The command string uses slash (\fB/\fR) to seperate the verbs in the same way 
that the Cloudflare v4 API documentation does.
+Any verb starting with colon (\fB:\fR) is either converted to zone_id, 
user_id, organtization_id, or otherwise.
+
+.SH RESULTS
+The output is either JSON or YAML formatted.
+
+.SH EXAMPLES
+.B cli4 /zones
+List infomation for all zones.
+
+.B cli4 /zones/:example.com
+List specific zone info.
+
+.B cli4 /zones/:example.com/settings
+List settings for a specific zone.
+
+.B cli4 --delete purge_everything=true /zones/:example.com/purge_cache
+Purge cache for a specific zone.
+
+.B cli4 --delete files='[http://example.com/css/styles.css]' 
/zones/:example.com/purge_cache
+Purge cache for a specific zone.
+
+.B cli4 --delete 
files='[http://example.com/css/styles.css,http://example.com/js/script.js] 
/zones/:example.com/purge_cache
+Purge cache for a specific zone.
+
+.B cli4 --delete tags='[tag1,tag2,tag3]' /zones/:example.com/purge_cache
+Purge cache for a specific zone.
+
+.B cli4 /zones/:example.com/available_plans
+List available plans for a zone.
+
+.B cli4 --patch status=active /zones/:example.com/dnssec
+Make DNSSEC active for specfic zone.
+
+.B cli4 /zones/:example.com/dnssec
+List DNSSEC infomation and status for a specific zone.
+
+.SH SEE ALSO
+The Cloudflare API can be found https://api.cloudflare.com/. Each API call is 
provided via a similarly named function within the Cloudflare class.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/cli4/cli4.py 
new/cloudflare-2.8.3/cli4/cli4.py
--- old/cloudflare-2.6.3/cli4/cli4.py   2020-01-18 06:45:11.000000000 +0100
+++ new/cloudflare-2.8.3/cli4/cli4.py   2020-06-23 03:23:50.000000000 +0200
@@ -23,6 +23,15 @@
     w = cf.api_list()
     sys.stdout.write('\n'.join(w) + '\n')
 
+def dump_commands_from_web():
+    """dump a tree of all the known API commands - from web"""
+    cf = CloudFlare.CloudFlare()
+    w = cf.api_from_web()
+    for r in w:
+        if r['deprecated']:
+            continue
+        sys.stdout.write('%-6s %s\n' % (r['action'], r['cmd']))
+
 def run_command(cf, method, command, params=None, content=None, files=None):
     """run the command line"""
     # remove leading and trailing /'s
@@ -43,6 +52,7 @@
 
     hex_only = re.compile('^[0-9a-fA-F]+$')
     waf_rules = re.compile('^[0-9]+[A-Z]*$')
+    uuid_value = 
re.compile('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')
   # 8-4-4-4-12
 
     m = cf
     for element in parts:
@@ -52,6 +62,9 @@
                 if len(element) in [32, 40, 48] and hex_only.match(element):
                     # raw identifier - lets just use it as-is
                     identifier1 = element
+                if len(element) == 36  and uuid_value.match(element):
+                    # uuid identifier - lets just use it as-is
+                    identifier1 = element
                 elif element[0] == ':':
                     # raw string - used for workers script_name - use 
::script_name
                     identifier1 = element[1:]
@@ -77,12 +90,16 @@
                         else:
                             raise Exception("/%s/%s :NOT CODED YET" % 
('/'.join(cmd), element))
                     except Exception as e:
-                        sys.exit('cli4: /%s - %s' % (command, e))
+                        sys.stderr.write('cli4: /%s - %s\n' % (command, e))
+                        raise e
                 cmd.append(':' + identifier1)
             elif identifier2 is None:
                 if len(element) in [32, 40, 48] and hex_only.match(element):
                     # raw identifier - lets just use it as-is
                     identifier2 = element
+                if len(element) == 36  and uuid_value.match(element):
+                    # uuid identifier - lets just use it as-is
+                    identifier2 = element
                 elif element[0] == ':':
                     # raw string - used for workers script_names
                     identifier2 = element[1:]
@@ -92,10 +109,15 @@
                             identifier2 = 
converters.convert_dns_record_to_identifier(cf,
                                                                                
       identifier1,
                                                                                
       element)
+                        elif (cmd[0] and cmd[0] == 'zones') and (cmd[2] and 
cmd[2] == 'custom_hostnames'):
+                            identifier2 = 
converters.convert_custom_hostnames_to_identifier(cf,
+                                                                               
       identifier1,
+                                                                               
       element)
                         else:
                             raise Exception("/%s/%s :NOT CODED YET" % 
('/'.join(cmd), element))
                     except Exception as e:
-                        sys.exit('cli4: /%s - %s' % (command, e))
+                        sys.stderr.write('cli4: /%s - %s\n' % (command, e))
+                        raise e
                 # identifier2 may be an array - this needs to be dealt with 
later
                 if isinstance(identifier2, list):
                     cmd.append(':' + '[' + ','.join(identifier2) + ']')
@@ -106,10 +128,17 @@
                 if len(element) in [32, 40, 48] and hex_only.match(element):
                     # raw identifier - lets just use it as-is
                     identifier3 = element
+                if len(element) == 36  and uuid_value.match(element):
+                    # uuid identifier - lets just use it as-is
+                    identifier3 = element
                 elif waf_rules.match(element):
                     identifier3 = element
                 else:
-                    sys.exit("/%s/%s :NOT CODED YET 3" % ('/'.join(cmd), 
element))
+                    if len(cmd) >= 6 and cmd[0] == 'accounts' and cmd[2] == 
'storage' and cmd[3] == 'kv' and cmd[4] == 'namespaces' and cmd[6] == 'values':
+                        identifier3 = element
+                    else:
+                        sys.stderr.write('/%s/%s :NOT CODED YET 3\n' % 
('/'.join(cmd), element))
+                        raise e
         else:
             try:
                 m = getattr(m, element)
@@ -117,12 +146,14 @@
             except AttributeError:
                 # the verb/element was not found
                 if len(cmd) == 0:
-                    sys.exit('cli4: /%s - not found' % (element))
+                    sys.stderr.write('cli4: /%s - not found\n' % (element))
                 else:
-                    sys.exit('cli4: /%s/%s - not found' % ('/'.join(cmd), 
element))
+                    sys.stderr.write('cli4: /%s/%s - not found\n' % 
('/'.join(cmd), element))
+                raise e
 
     if content and params:
-        sys.exit('cli4: /%s - content and params not allowed together' % 
(command))
+        sys.stderr.write('cli4: /%s - content and params not allowed 
together\n' % (command))
+        raise Exception
     if content:
         params = content
 
@@ -163,11 +194,14 @@
                 # more than one error returned by the API
                 for x in e:
                     sys.stderr.write('cli4: /%s - %d %s\n' % (command, x, x))
-            sys.exit('cli4: /%s - %d %s' % (command, e, e))
+            sys.stderr.write('cli4: /%s - %d %s\n' % (command, e, e))
+            raise e
         except CloudFlare.exceptions.CloudFlareInternalError as e:
-            sys.exit('cli4: InternalError: /%s - %d %s' % (command, e, e))
+            sys.stderr.write('cli4: InternalError: /%s - %d %s\n' % (command, 
e, e))
+            raise e
         except Exception as e:
-            sys.exit('cli4: /%s - %s - api error' % (command, e))
+            sys.stderr.write('cli4: /%s - %s - api error\n' % (command, e))
+            raise e
 
         results.append(r)
     return results
@@ -228,27 +262,29 @@
     output = 'json'
     raw = False
     dump = False
+    dump_from_web = False
     profile = None
     method = 'GET'
 
     usage = ('usage: cli4 '
              + '[-V|--version] [-h|--help] [-v|--verbose] [-q|--quiet] '
-             + '[-j|--json] [-y|--yaml] [-n|ndjson]'
+             + '[-j|--json] [-y|--yaml] [-n|ndjson] '
              + '[-r|--raw] '
              + '[-d|--dump] '
+             + '[-a|--api] '
              + '[-p|--profile profile-name] '
              + '[--get|--patch|--post|--put|--delete] '
              + '[item=value|item=@filename|@filename ...] '
-             + '/command...')
+             + '/command ...')
 
     try:
         opts, args = getopt.getopt(args,
-                                   'Vhvqjyrdp:GPOUD',
+                                   'Vhvqjyrdap:GPOUD',
                                    [
                                        'version',
                                        'help', 'verbose', 'quiet', 'json', 
'yaml', 'ndjson',
                                        'raw',
-                                       'dump',
+                                       'dump', 'api',
                                        'profile=',
                                        'get', 'patch', 'post', 'put', 'delete'
                                    ])
@@ -279,6 +315,8 @@
             profile = arg
         elif opt in ('-d', '--dump'):
             dump = True
+        elif opt in ('-a', '--api'):
+            dump_from_web = True
         elif opt in ('-G', '--get'):
             method = 'GET'
         elif opt in ('-P', '--patch'):
@@ -294,6 +332,10 @@
         dump_commands()
         sys.exit(0)
 
+    if dump_from_web:
+        dump_commands_from_web()
+        sys.exit(0)
+
     digits_only = re.compile('^-?[0-9]+$')
     floats_only = re.compile('^-?[0-9.]+$')
 
@@ -306,8 +348,8 @@
         if arg[0] == '@':
             # a file to be uploaded - used in workers/script - only via PUT
             filename = arg[1:]
-            if method != 'PUT':
-                sys.exit('cli4: %s - raw file upload only with PUT' % 
(filename))
+            if method not in ['PUT','POST']:
+                sys.exit('cli4: %s - raw file upload only with PUT or POST' % 
(filename))
             try:
                 if filename == '-':
                     content = sys.stdin.read()
@@ -379,16 +421,22 @@
                          (tag_string, value_string))
 
     # what's left is the command itself
-    if len(args) != 1:
+    if len(args) < 1:
         sys.exit(usage)
-    command = args[0]
+    commands = args
 
     try:
         cf = CloudFlare.CloudFlare(debug=verbose, raw=raw, profile=profile)
     except Exception as e:
         sys.exit(e)
-    results = run_command(cf, method, command, params, content, files)
-    write_results(results, output)
+
+    for command in commands:
+        try:
+            results = run_command(cf, method, command, params, content, files)
+            write_results(results, output)
+        except Exception as e:
+            if len(commands) > 1:
+                continue
 
 def cli4(args):
     """Cloudflare API via command line"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/cli4/converters.py 
new/cloudflare-2.8.3/cli4/converters.py
--- old/cloudflare-2.6.3/cli4/converters.py     2020-01-16 19:14:19.000000000 
+0100
+++ new/cloudflare-2.8.3/cli4/converters.py     2020-06-23 03:22:51.000000000 
+0200
@@ -130,3 +130,23 @@
             return p['id']
 
     raise ConverterError('%s: not found' % (pool_name))
+
+def convert_custom_hostnames_to_identifier(cf, zone_id, custom_hostname):
+    """custom_hostnames to numbers"""
+    # this can return an array of results
+    params = {'name':custom_hostname}
+    try:
+        custom_hostnames_records = cf.zones.custom_hostnames.get(zone_id, 
params=params)
+    except CloudFlare.exceptions.CloudFlareAPIError as e:
+        raise ConverterError(int(e), '%s - %d %s' % (dns_name, e, e))
+    except Exception as e:
+        raise ConverterError(0, '%s - %s' % (dns_name, e))
+
+    r = []
+    for custom_hostnames_record in custom_hostnames_records:
+        if custom_hostname == custom_hostnames_record['hostname']:
+            r.append(custom_hostnames_record['id'])
+    if len(r) > 0:
+        return r
+
+    raise ConverterError('%s: not found' % (custom_hostname))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/cloudflare.egg-info/PKG-INFO 
new/cloudflare-2.8.3/cloudflare.egg-info/PKG-INFO
--- old/cloudflare-2.6.3/cloudflare.egg-info/PKG-INFO   2020-02-09 
22:14:52.000000000 +0100
+++ new/cloudflare-2.8.3/cloudflare.egg-info/PKG-INFO   2020-06-23 
03:46:31.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: cloudflare
-Version: 2.6.3
+Version: 2.8.3
 Summary: Python wrapper for the Cloudflare v4 API
 Home-page: https://github.com/cloudflare/python-cloudflare
 Author: Martin J. Levy
@@ -491,6 +491,18 @@
             cli4: /zones - 9103 Unknown X-Auth-Key or X-Auth-Email
             $
         
+        More than one call can be done on the same command line. In this mode,
+        the connection is preserved between calls.
+        
+        ::
+        
+            $ cli4 /user/organizations /user/invites
+            ...
+            $
+        
+        Note that the output is presently two JSON structures one after the
+        other - so less useful that you may think.
+        
         Finally, a command that provides more than one error response. This is
         simulated by passing an invalid IPv4 address to a DNS record creation.
         
@@ -1000,7 +1012,7 @@
         
         ::
         
-            $ python3 -m cli4 /user/workers/scripts
+            $ cli4 /user/workers/scripts
             [
                 {
                     "created_on": "2018-02-15T00:00:00.000000Z",
@@ -1287,6 +1299,12 @@
         `here <https://github.com/cloudflare-api/python-cloudflare-v4>`__. It
         has been seriously expanded upon.
         
+        Changelog
+        ---------
+        
+        An automatically generated CHANGELOG is provided
+        `here <CHANGELOG.md>`__.
+        
         Copyright
         ---------
         
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/cloudflare.egg-info/SOURCES.txt 
new/cloudflare-2.8.3/cloudflare.egg-info/SOURCES.txt
--- old/cloudflare-2.6.3/cloudflare.egg-info/SOURCES.txt        2020-02-09 
22:14:52.000000000 +0100
+++ new/cloudflare-2.8.3/cloudflare.egg-info/SOURCES.txt        2020-06-23 
03:46:31.000000000 +0200
@@ -4,6 +4,7 @@
 setup.cfg
 setup.py
 CloudFlare/__init__.py
+CloudFlare/api_decode_from_web.py
 CloudFlare/api_extras.py
 CloudFlare/api_v4.py
 CloudFlare/cloudflare.py
@@ -13,6 +14,7 @@
 CloudFlare/utils.py
 cli4/__init__.py
 cli4/__main__.py
+cli4/cli4.man
 cli4/cli4.py
 cli4/converters.py
 cloudflare.egg-info/PKG-INFO
@@ -27,10 +29,12 @@
 examples/example_are_zones_ipv6_simple.py
 examples/example_certificates.py
 examples/example_create_zone_and_populate.py
+examples/example_custom_hostnames.py
 examples/example_delete_zone_entry.py
 examples/example_dns_export.py
 examples/example_dnssec_settings.py
 examples/example_ips.py
+examples/example_list_api_from_web.py
 examples/example_page_rules.sh
 examples/example_paging_thru_zones.py
 examples/example_paging_thru_zones.sh
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/cloudflare.egg-info/requires.txt 
new/cloudflare-2.8.3/cloudflare.egg-info/requires.txt
--- old/cloudflare-2.6.3/cloudflare.egg-info/requires.txt       2020-02-09 
22:14:52.000000000 +0100
+++ new/cloudflare-2.8.3/cloudflare.egg-info/requires.txt       2020-06-23 
03:46:31.000000000 +0200
@@ -1,4 +1,4 @@
 requests
-future
 pyyaml
 jsonlines
+beautifulsoup4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/cloudflare-2.6.3/examples/example_always_use_https.py 
new/cloudflare-2.8.3/examples/example_always_use_https.py
--- old/cloudflare-2.6.3/examples/example_always_use_https.py   2019-11-20 
19:33:03.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_always_use_https.py   2020-06-22 
21:30:28.000000000 +0200
@@ -1,8 +1,6 @@
 #!/usr/bin/env python
 """Cloudflare API code - example"""
 
-from __future__ import print_function
-
 import os
 import sys
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/examples/example_are_zones_ipv6.py 
new/cloudflare-2.8.3/examples/example_are_zones_ipv6.py
--- old/cloudflare-2.6.3/examples/example_are_zones_ipv6.py     2019-11-20 
19:33:03.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_are_zones_ipv6.py     2020-06-22 
21:30:32.000000000 +0200
@@ -1,8 +1,6 @@
 #!/usr/bin/env python
 """Cloudflare API code - example"""
 
-from __future__ import print_function
-
 import os
 import sys
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/cloudflare-2.6.3/examples/example_are_zones_ipv6_simple.py 
new/cloudflare-2.8.3/examples/example_are_zones_ipv6_simple.py
--- old/cloudflare-2.6.3/examples/example_are_zones_ipv6_simple.py      
2019-11-20 19:33:03.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_are_zones_ipv6_simple.py      
2020-06-22 21:30:35.000000000 +0200
@@ -1,8 +1,6 @@
 #!/usr/bin/env python
 """Cloudflare API code - example"""
 
-from __future__ import print_function
-
 import os
 import sys
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/examples/example_certificates.py 
new/cloudflare-2.8.3/examples/example_certificates.py
--- old/cloudflare-2.6.3/examples/example_certificates.py       2019-11-20 
19:33:03.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_certificates.py       2020-06-22 
21:32:47.000000000 +0200
@@ -1,11 +1,8 @@
 #!/usr/bin/env python
 """Cloudflare API code - example"""
 
-from __future__ import print_function
-
 import os
 import sys
-import json
 
 sys.path.insert(0, os.path.abspath('..'))
 import CloudFlare
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/cloudflare-2.6.3/examples/example_create_zone_and_populate.py 
new/cloudflare-2.8.3/examples/example_create_zone_and_populate.py
--- old/cloudflare-2.6.3/examples/example_create_zone_and_populate.py   
2019-11-20 19:33:03.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_create_zone_and_populate.py   
2020-06-22 21:30:46.000000000 +0200
@@ -1,8 +1,6 @@
 #!/usr/bin/env python
 """Cloudflare API code - example"""
 
-from __future__ import print_function
-
 import os
 import sys
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/cloudflare-2.6.3/examples/example_custom_hostnames.py 
new/cloudflare-2.8.3/examples/example_custom_hostnames.py
--- old/cloudflare-2.6.3/examples/example_custom_hostnames.py   1970-01-01 
01:00:00.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_custom_hostnames.py   2020-06-23 
03:43:49.000000000 +0200
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+"""Cloudflare API code - example"""
+
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath('..'))
+import CloudFlare
+
+def main():
+    """Cloudflare API code - example"""
+
+    # Grab the first argument, if there is one
+    try:
+        zone_name = sys.argv[1]
+        params = {'name':zone_name, 'per_page':1}
+    except IndexError:
+        params = {'per_page':500}
+
+    cf = CloudFlare.CloudFlare()
+
+    # grab the zone identifier
+    try:
+        zones = cf.zones.get(params=params)
+    except CloudFlare.exceptions.CloudFlareAPIError as e:
+        exit('/zones.get %d %s - api call failed' % (e, e))
+    except Exception as e:
+        exit('/zones.get - %s - api call failed' % (e))
+
+    # there should only be one zone - but handle more if needed
+    for zone in sorted(zones, key=lambda v: v['name']):
+        zone_name = zone['name']
+        zone_id = zone['id']
+        zone_plan = zone['plan']['name']
+        if zone_plan != 'Enterprise Website':
+            print('%s %s %s - not Enterprise' % (zone_id, zone_name, 
zone_plan))
+            continue
+
+        print('%s %-40s %s' % (zone_id, zone_name, zone_plan))
+        try:
+            custom_info = cf.zones.custom_hostnames.get(zone_id)
+        except CloudFlare.exceptions.CloudFlareAPIError as e:
+            sys.stderr.write('/zones.custom_hostnames.get %d %s - api call 
failed\n' % (e, e))
+            continue
+
+        for hostname_info in custom_info:
+            print('\t%s %-30s %s' % (hostname_info['id'], 
hostname_info['hostname'], hostname_info['created_at']))
+
+            for s in sorted(hostname_info.keys()):
+                if s in ['id', 'hostname', 'created_at'] or hostname_info[s] 
== None:
+                    continue
+                print('\t%-15s = %s' % (s, hostname_info[s]))
+
+        try:
+            fallback_origin = 
cf.zones.custom_hostnames.fallback_origin.get(zone_id)
+        except CloudFlare.exceptions.CloudFlareAPIError as e:
+            sys.stderr.write('/zones.custom_hostnames.fallback_origin.get %d 
%s - api call failed\n' % (e, e))
+            continue
+
+        print('\t%s %-30s %s %s' % ('', fallback_origin['origin'], 
fallback_origin['created_at'], fallback_origin['status']))
+        print('')
+
+    exit(0)
+
+if __name__ == '__main__':
+    main()
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/cloudflare-2.6.3/examples/example_delete_zone_entry.py 
new/cloudflare-2.8.3/examples/example_delete_zone_entry.py
--- old/cloudflare-2.6.3/examples/example_delete_zone_entry.py  2019-11-20 
19:33:03.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_delete_zone_entry.py  2020-06-22 
21:33:41.000000000 +0200
@@ -1,13 +1,8 @@
 #!/usr/bin/env python
 """Cloudflare API code - example"""
 
-from __future__ import print_function
-
 import os
 import sys
-import re
-import json
-import requests
 
 sys.path.insert(0, os.path.abspath('..'))
 import CloudFlare
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/examples/example_dns_export.py 
new/cloudflare-2.8.3/examples/example_dns_export.py
--- old/cloudflare-2.6.3/examples/example_dns_export.py 2019-11-20 
19:33:03.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_dns_export.py 2020-06-22 
21:30:58.000000000 +0200
@@ -1,8 +1,6 @@
 #!/usr/bin/env python
 """Cloudflare API code - example"""
 
-from __future__ import print_function
-
 import os
 import sys
 sys.path.insert(0, os.path.abspath('..'))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/examples/example_dnssec_settings.py 
new/cloudflare-2.8.3/examples/example_dnssec_settings.py
--- old/cloudflare-2.6.3/examples/example_dnssec_settings.py    2019-11-20 
19:33:03.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_dnssec_settings.py    2020-06-22 
21:31:06.000000000 +0200
@@ -1,8 +1,6 @@
 #!/usr/bin/env python
 """Cloudflare API code - example"""
 
-from __future__ import print_function
-
 import os
 import sys
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/examples/example_ips.py 
new/cloudflare-2.8.3/examples/example_ips.py
--- old/cloudflare-2.6.3/examples/example_ips.py        2019-11-20 
19:33:03.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_ips.py        2020-06-22 
21:30:09.000000000 +0200
@@ -1,8 +1,6 @@
 #!/usr/bin/env python
 """Cloudflare API code - example"""
 
-from __future__ import print_function
-
 import os
 import sys
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/cloudflare-2.6.3/examples/example_list_api_from_web.py 
new/cloudflare-2.8.3/examples/example_list_api_from_web.py
--- old/cloudflare-2.6.3/examples/example_list_api_from_web.py  1970-01-01 
01:00:00.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_list_api_from_web.py  2020-06-22 
21:31:11.000000000 +0200
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+"""Cloudflare API code - example"""
+
+import os
+import sys
+import json
+
+sys.path.insert(0, os.path.abspath('..'))
+import CloudFlare
+
+def main():
+    """Cloudflare API code - example"""
+
+    cf = CloudFlare.CloudFlare()
+    try:
+        r = cf.api_from_web()
+    except Exception as e:
+        exit('api_from_web: - %s - api call connection failed' % (e))
+
+    print(json.dumps(r))
+    exit(0)
+
+if __name__ == '__main__':
+    main()
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/cloudflare-2.6.3/examples/example_paging_thru_zones.py 
new/cloudflare-2.8.3/examples/example_paging_thru_zones.py
--- old/cloudflare-2.6.3/examples/example_paging_thru_zones.py  2019-11-20 
19:33:03.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_paging_thru_zones.py  2020-06-22 
21:31:15.000000000 +0200
@@ -1,8 +1,6 @@
 #!/usr/bin/env python
 """Cloudflare API code - example"""
 
-from __future__ import print_function
-
 import os
 import sys
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/examples/example_proxied.py 
new/cloudflare-2.8.3/examples/example_proxied.py
--- old/cloudflare-2.6.3/examples/example_proxied.py    2019-11-20 
19:33:03.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_proxied.py    2020-06-22 
21:31:19.000000000 +0200
@@ -1,8 +1,6 @@
 #!/usr/bin/env python
 """Cloudflare API code - example"""
 
-from __future__ import print_function
-
 import os
 import sys
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/examples/example_settings.py 
new/cloudflare-2.8.3/examples/example_settings.py
--- old/cloudflare-2.6.3/examples/example_settings.py   2019-11-20 
19:33:03.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_settings.py   2020-06-22 
21:31:25.000000000 +0200
@@ -1,8 +1,6 @@
 #!/usr/bin/env python
 """Cloudflare API code - example"""
 
-from __future__ import print_function
-
 import os
 import sys
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/cloudflare-2.6.3/examples/example_update_dynamic_dns.py 
new/cloudflare-2.8.3/examples/example_update_dynamic_dns.py
--- old/cloudflare-2.6.3/examples/example_update_dynamic_dns.py 2020-02-09 
21:28:33.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_update_dynamic_dns.py 2020-06-22 
21:36:58.000000000 +0200
@@ -1,12 +1,8 @@
 #!/usr/bin/env python
 """Cloudflare API code - example"""
 
-from __future__ import print_function
-
 import os
 import sys
-import re
-import json
 import requests
 
 sys.path.insert(0, os.path.abspath('..'))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/examples/example_user.py 
new/cloudflare-2.8.3/examples/example_user.py
--- old/cloudflare-2.6.3/examples/example_user.py       2019-11-20 
19:33:03.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_user.py       2020-06-22 
21:37:32.000000000 +0200
@@ -1,11 +1,8 @@
 #!/usr/bin/env python
 """Cloudflare API code - example"""
 
-from __future__ import print_function
-
 import os
 import sys
-import json
 
 sys.path.insert(0, os.path.abspath('..'))
 import CloudFlare
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/examples/example_with_usage.py 
new/cloudflare-2.8.3/examples/example_with_usage.py
--- old/cloudflare-2.6.3/examples/example_with_usage.py 2019-11-20 
19:33:03.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_with_usage.py 2020-06-22 
21:31:40.000000000 +0200
@@ -1,8 +1,6 @@
 #!/usr/bin/env python
 """Cloudflare API code - example"""
 
-from __future__ import print_function
-
 import os
 import sys
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/examples/example_zone_search.sh 
new/cloudflare-2.8.3/examples/example_zone_search.sh
--- old/cloudflare-2.6.3/examples/example_zone_search.sh        2020-01-22 
01:25:22.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_zone_search.sh        2020-04-09 
02:38:19.000000000 +0200
@@ -3,23 +3,25 @@
 ZONE=${1-example.com}
 EXTRA=${2}
 
-while read search_type
+SEARCH_TYPES="
+       equal
+       not_equal
+       greater_than
+       less_than
+       starts_with
+       ends_with
+       contains
+       starts_with_case_sensitive
+       ends_with_case_sensitive
+       contains_case_sensitive
+       list_contains
+"
+
+for search_type in ${SEARCH_TYPES}
 do
        echo TRY: "name=${search_type}:${ZONE}"
        cli4 per_page=50 name="${search_type}:${ZONE}" ${EXTRA} /zones/ | jq -r 
'.[]|.id,.name' | paste - -
-done <<!!
-equal
-not_equal
-greater_than
-less_than
-starts_with
-ends_with
-contains
-starts_with_case_sensitive
-ends_with_case_sensitive
-contains_case_sensitive
-list_contains
-!!
+done
 
 exit 0
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/examples/example_zones.py 
new/cloudflare-2.8.3/examples/example_zones.py
--- old/cloudflare-2.6.3/examples/example_zones.py      2019-11-20 
19:33:03.000000000 +0100
+++ new/cloudflare-2.8.3/examples/example_zones.py      2020-06-22 
23:24:44.000000000 +0200
@@ -1,8 +1,6 @@
 #!/usr/bin/env python
 """Cloudflare API code - example"""
 
-from __future__ import print_function
-
 import os
 import sys
 import re
@@ -34,28 +32,41 @@
     for zone in sorted(zones, key=lambda v: v['name']):
         zone_name = zone['name']
         zone_id = zone['id']
+        zone_type = zone['type']
         if 'email' in zone['owner']:
             zone_owner = zone['owner']['email']
         else:
             zone_owner = '"' + zone['owner']['name'] + '"'
         zone_plan = zone['plan']['name']
 
+        print('%s %-35s %-30s %-20s %s' % (zone_id, zone_name, zone_type, 
zone_owner, zone_plan))
+
         try:
             dns_records = cf.zones.dns_records.get(zone_id)
         except CloudFlare.exceptions.CloudFlareAPIError as e:
-            exit('/zones/dns_records %d %s - api call failed' % (e, e))
-
-        print(zone_id, zone_name, zone_owner, zone_plan)
+            sys.stderr.write('/zones/dns_records %d %s - api call failed\n' % 
(e, e))
+            continue
 
         prog = re.compile('\.*'+zone_name+'$')
         dns_records = sorted(dns_records, key=lambda v: prog.sub('', 
v['name']) + '_' + v['type'])
         for dns_record in dns_records:
-            r_name = dns_record['name']
-            r_type = dns_record['type']
-            r_value = dns_record['content']
-            r_ttl = dns_record['ttl']
-            r_id = dns_record['id']
-            print('\t%s %60s %6d %-5s %s' % (r_id, r_name, r_ttl, r_type, 
r_value))
+                r_name = dns_record['name']
+                r_type = dns_record['type']
+                if 'content' in dns_record:
+                    r_value = dns_record['content']
+                else:
+                    # should not happen
+                    r_value = ''
+                if 'priority' in dns_record:
+                    r_priority = dns_record['priority']
+                else:
+                    r_priority = ''
+                r_ttl = dns_record['ttl']
+                if zone_type == 'secondary':
+                    r_id = 'secondary'
+                else:
+                    r_id = dns_record['id']
+                print('\t%s %60s %6d %-5s %4s %s' % (r_id, r_name, r_ttl, 
r_type, r_priority, r_value))
 
         print('')
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cloudflare-2.6.3/setup.py 
new/cloudflare-2.8.3/setup.py
--- old/cloudflare-2.6.3/setup.py       2020-01-14 05:22:19.000000000 +0100
+++ new/cloudflare-2.8.3/setup.py       2020-06-22 21:29:07.000000000 +0200
@@ -31,8 +31,8 @@
         #package_dir={'CloudFlare/examples': 'examples'},
         #package_data={'cloudflare-examples': ["examples/*"]},
         include_package_data=True,
-        #data_files = [('man/man1', ['cli4/cli4.man'])],
-        install_requires=['requests', 'future', 'pyyaml', 'jsonlines'],
+        data_files = [('man/man1', ['cli4/cli4.man'])],
+        install_requires=['requests', 'pyyaml', 'jsonlines', 'beautifulsoup4'],
         keywords='cloudflare',
         entry_points={
             'console_scripts': [


Reply via email to