Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-cloudflare for openSUSE:Factory checked in at 2023-10-05 20:05:03 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-cloudflare (Old) and /work/SRC/openSUSE:Factory/.python-cloudflare.new.28202 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-cloudflare" Thu Oct 5 20:05:03 2023 rev:13 rq:1115792 version:2.12.4 Changes: -------- --- /work/SRC/openSUSE:Factory/python-cloudflare/python-cloudflare.changes 2023-09-06 18:58:21.987929132 +0200 +++ /work/SRC/openSUSE:Factory/.python-cloudflare.new.28202/python-cloudflare.changes 2023-10-05 20:06:17.097501612 +0200 @@ -1,0 +2,27 @@ +Thu Oct 5 09:47:35 UTC 2023 - Dirk Müller <dmuel...@suse.com> + +- update to 2.12.4: + * added ips and issue114 tests + * add importlib_resources info for older Python versions + * more api endpoints + * more twine tweaks + * Added cli4 -e option to + display example file path names + * Add `global_request_timeout` and `max_request_retries` + configuration options. Set default request timeout to 5s. Add + basic tests instantiating Cloudflare.Cloudflare + * more api endpoints + * Make RegEx string a raw string literal + * more api endpoints + * update examples and README to use == for numberic values + * more api endpoints + * remove --api option and leave --openapi in place + * handle quoted strings + * handle multipart/form-data + correctly for more than one file and with params/data + * python keywords not handled correctly at command level + * fix: remove future dependency and imports + * added after openapi review + * added deprecated processing, now shows version + +------------------------------------------------------------------- Old: ---- cloudflare-2.11.7.tar.gz New: ---- cloudflare-2.12.4.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-cloudflare.spec ++++++ --- /var/tmp/diff_new_pack.NTRkjk/_old 2023-10-05 20:06:18.201541498 +0200 +++ /var/tmp/diff_new_pack.NTRkjk/_new 2023-10-05 20:06:18.205541642 +0200 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-cloudflare -Version: 2.11.7 +Version: 2.12.4 Release: 0 Summary: Python wrapper for the Cloudflare v4 API License: MIT ++++++ cloudflare-2.11.7.tar.gz -> cloudflare-2.12.4.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/CloudFlare/__init__.py new/cloudflare-2.12.4/CloudFlare/__init__.py --- old/cloudflare-2.11.7/CloudFlare/__init__.py 2023-08-19 18:23:08.000000000 +0200 +++ new/cloudflare-2.12.4/CloudFlare/__init__.py 2023-09-22 03:45:07.000000000 +0200 @@ -1,7 +1,6 @@ """ Cloudflare v4 API""" -from __future__ import absolute_import -__version__ = '2.11.7' +__version__ = '2.12.4' from .cloudflare import CloudFlare diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/CloudFlare/api_decode_from_openapi.py new/cloudflare-2.12.4/CloudFlare/api_decode_from_openapi.py --- old/cloudflare-2.11.7/CloudFlare/api_decode_from_openapi.py 2022-11-26 23:35:22.000000000 +0100 +++ new/cloudflare-2.12.4/CloudFlare/api_decode_from_openapi.py 2023-09-21 18:52:22.000000000 +0200 @@ -7,7 +7,7 @@ API_TYPES = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] -match_identifier = re.compile('\{[A-Za-z0-9_]*\}') +match_identifier = re.compile(r'\{[A-Za-z0-9_]*\}') def do_path(cmd, values): """ do_path() """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/CloudFlare/api_v4.py new/cloudflare-2.12.4/CloudFlare/api_v4.py --- old/cloudflare-2.11.7/CloudFlare/api_v4.py 2023-08-19 18:21:25.000000000 +0200 +++ new/cloudflare-2.12.4/CloudFlare/api_v4.py 2023-09-19 21:23:01.000000000 +0200 @@ -145,6 +145,7 @@ self.add('AUTH', 'zones', 'settings/development_mode') self.add('AUTH', 'zones', 'settings/early_hints') self.add('AUTH', 'zones', 'settings/email_obfuscation') + self.add('AUTH', 'zones', 'settings/fonts') self.add('AUTH', 'zones', 'settings/h2_prioritization') self.add('AUTH', 'zones', 'settings/hotlink_protection') self.add('AUTH', 'zones', 'settings/http2') @@ -442,6 +443,11 @@ self.add('AUTH', 'accounts', 'subscriptions') self.add('AUTH', 'accounts', 'tunnels') self.add('AUTH', 'accounts', 'tunnels', 'connections') + + self.add('VOID', 'accounts', 'vectorize') + self.add('AUTH', 'accounts', 'vectorize/index') + self.add('AUTH', 'accounts', 'vectorize/indexes') + self.add('AUTH', 'accounts', 'virtual_dns') self.add('VOID', 'accounts', 'virtual_dns', 'dns_analytics') self.add('AUTH', 'accounts', 'virtual_dns', 'dns_analytics/report') @@ -466,6 +472,7 @@ self.add('AUTH', 'accounts', 'workers/queues', 'consumers') self.add('AUTH', 'accounts', 'workers/scripts') self.add('AUTH', 'accounts', 'workers/scripts', 'content') + self.add('AUTH', 'accounts', 'workers/scripts', 'content/v2') self.add('AUTH', 'accounts', 'workers/scripts', 'schedules') self.add('AUTH', 'accounts', 'workers/scripts', 'settings') self.add('AUTH', 'accounts', 'workers/scripts', 'tails') @@ -674,6 +681,7 @@ self.add('AUTH', 'accounts', 'devices/unrevoke') self.add('VOID', 'accounts', 'dex') + self.add('AUTH', 'accounts', 'dex/colos') self.add('AUTH', 'accounts', 'dex/http-tests') self.add('AUTH', 'accounts', 'dex/tests') self.add('AUTH', 'accounts', 'dex/tests/unique-devices') @@ -718,6 +726,12 @@ self.add('AUTH', 'accounts', 'intel/domain-history') self.add('AUTH', 'accounts', 'intel/domain/bulk') self.add('AUTH', 'accounts', 'intel/indicator-feeds') + self.add('AUTH', 'accounts', 'intel/indicator-feeds', 'data') + self.add('AUTH', 'accounts', 'intel/indicator-feeds', 'snapshot') + self.add('VOID', 'accounts', 'intel/indicator-feeds/permissions') + self.add('AUTH', 'accounts', 'intel/indicator-feeds/permissions/add') + self.add('AUTH', 'accounts', 'intel/indicator-feeds/permissions/remove') + self.add('AUTH', 'accounts', 'intel/indicator-feeds/permissions/view') self.add('AUTH', 'accounts', 'intel/ip') self.add('AUTH', 'accounts', 'intel/ip-list') self.add('AUTH', 'accounts', 'intel/miscategorization') @@ -738,6 +752,7 @@ self.add('AUTH', 'accounts', 'pages/projects', 'deployments', 'retry') self.add('AUTH', 'accounts', 'pages/projects', 'deployments', 'rollback') self.add('AUTH', 'accounts', 'pages/projects', 'domains') + self.add('AUTH', 'accounts', 'pages/projects', 'purge_build_cache') self.add('AUTH', 'accounts', 'pcaps') self.add('AUTH', 'accounts', 'pcaps', 'download') @@ -755,6 +770,10 @@ self.add('AUTH', 'accounts', 'urlscanner/scan', 'har') self.add('AUTH', 'accounts', 'urlscanner/scan', 'screenshot') + self.add('VOID', 'accounts', 'hyperdrive') + self.add('AUTH', 'accounts', 'hyperdrive/configs') + + def zones_extras(self): """ zones extras """ @@ -787,6 +806,15 @@ self.add('AUTH', 'zones', 'snippets', 'content') self.add('AUTH', 'zones', 'snippets/snippet_rules') + self.add('VOID', 'zones', 'speed_api') + self.add('AUTH', 'zones', 'speed_api/availabilities') + self.add('AUTH', 'zones', 'speed_api/pages') + self.add('AUTH', 'zones', 'speed_api/pages', 'tests') + self.add('AUTH', 'zones', 'speed_api/pages', 'trend') + self.add('AUTH', 'zones', 'speed_api/schedule') + + self.add('VOID', 'zones', 'dcv_delegation') + self.add('AUTH', 'zones', 'dcv_delegation/uuid') def zones_web3(self): """ zones web3 """ @@ -1068,6 +1096,12 @@ self.add('AUTH', 'radar/verified_bots/top/bots') self.add('AUTH', 'radar/verified_bots/top/categories') + self.add('VOID', 'radar/connection_tampering') + self.add('AUTH', 'radar/connection_tampering/summary') + self.add('AUTH', 'radar/connection_tampering/timeseries_groups') + self.add('AUTH', 'radar/traffic_anomalies') + self.add('AUTH', 'radar/traffic_anomalies/locations') + def from_developers(self): """ from_developers """ self.add('VOID', 'accounts', 'analytics_engine') @@ -1119,6 +1153,7 @@ self.add('VOID', 'accounts', 'stream/analytics') self.add('AUTH', 'accounts', 'stream/analytics/views') self.add('AUTH', 'accounts', 'stream/live_inputs', 'videos') + self.add('AUTH', 'accounts', 'stream/storage-usage') # self.add('AUTH', 'organizations', 'load_balancers/monitors') @@ -1157,4 +1192,3 @@ self.add('AUTH', 'accounts', 'mtls_certificates', 'associations') self.add('VOID', 'accounts', 'request-tracer') self.add('AUTH', 'accounts', 'request-tracer/trace') - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/CloudFlare/cloudflare.py new/cloudflare-2.12.4/CloudFlare/cloudflare.py --- old/cloudflare-2.11.7/CloudFlare/cloudflare.py 2023-05-21 19:26:25.000000000 +0200 +++ new/cloudflare-2.12.4/CloudFlare/cloudflare.py 2023-09-21 19:03:51.000000000 +0200 @@ -1,6 +1,4 @@ """ Cloudflare v4 API""" -from __future__ import absolute_import - import json import keyword from requests import RequestException as requests_RequestException, ConnectionError as requests_ConnectionError, exceptions as requests_exceptions, codes as requests_codes @@ -38,8 +36,14 @@ self.raw = config['raw'] self.use_sessions = config['use_sessions'] + self.global_request_timeout = config['global_request_timeout'] if 'global_request_timeout' in config else None + self.max_request_retries = config['max_request_retries'] if 'max_request_retries' in config else None self.profile = config['profile'] - self.network = CFnetwork(use_sessions=self.use_sessions) + self.network = CFnetwork( + use_sessions=self.use_sessions, + global_request_timeout=self.global_request_timeout, + max_request_retries=self.max_request_retries + ) self.user_agent = user_agent() self.logger = CFlogger(config['debug']).getLogger() if 'debug' in config and config['debug'] else None @@ -909,7 +913,7 @@ return api_decode_from_openapi(self._base.api_from_openapi(url)) - def __init__(self, email=None, key=None, token=None, certtoken=None, debug=False, raw=False, use_sessions=True, profile=None, base_url=None): + def __init__(self, email=None, key=None, token=None, certtoken=None, debug=False, raw=False, use_sessions=True, profile=None, base_url=None, global_request_timeout=5, max_request_retries=5): """ Cloudflare v4 API""" self._base = None @@ -938,6 +942,10 @@ config['profile'] = profile if base_url is not None: config['base_url'] = base_url + if global_request_timeout is not None: + config['global_request_timeout'] = global_request_timeout + if max_request_retries is not None: + config['max_request_retries'] = max_request_retries # we do not need to handle item.call values - they pass straight thru diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/CloudFlare/network.py new/cloudflare-2.12.4/CloudFlare/network.py --- old/cloudflare-2.11.7/CloudFlare/network.py 2023-05-20 02:40:13.000000000 +0200 +++ new/cloudflare-2.12.4/CloudFlare/network.py 2023-09-21 19:03:51.000000000 +0200 @@ -1,52 +1,124 @@ """ Network for Cloudflare API""" -from __future__ import absolute_import + +from urllib.parse import urlparse import requests +from requests.adapters import HTTPAdapter from .exceptions import CloudFlareAPIError -class CFnetwork(): - """ Network for Cloudflare API""" - def __init__(self, use_sessions=True): - """ Network for Cloudflare API""" +class CFnetwork: + """Network for Cloudflare API""" + + def __init__( + self, max_request_retries, use_sessions=True, global_request_timeout=5, + ): + """Network for Cloudflare API""" self.use_sessions = use_sessions + self.global_request_timeout = global_request_timeout + self.max_request_retries = max_request_retries self.session = None def __call__(self, method, url, headers=None, params=None, data=None, files=None): - """ Network for Cloudflare API""" + """Network for Cloudflare API""" if self.use_sessions: if self.session is None: - self.session = requests.Session() + s = requests.Session() + if self.max_request_retries is not None: + hostname = urlparse(url).netloc + s.mount( + f"https://{hostname}", + HTTPAdapter(max_retries=self.max_request_retries), + ) + self.session = s else: self.session = requests method = method.upper() if method == 'GET': - r = self.session.get(url, headers=headers, params=params, data=data) + r = self.session.get( + url, + headers=headers, + params=params, + data=data, + timeout=self.global_request_timeout, + ) elif method == 'POST': if isinstance(data, str): - r = self.session.post(url, headers=headers, params=params, data=data, files=files) + r = self.session.post( + url, + headers=headers, + params=params, + data=data, + files=files, + timeout=self.global_request_timeout, + ) else: - r = self.session.post(url, headers=headers, params=params, json=data, files=files) + r = self.session.post( + url, + headers=headers, + params=params, + json=data, + files=files, + timeout=self.global_request_timeout, + ) elif method == 'PUT': if isinstance(data, str): - r = self.session.put(url, headers=headers, params=params, data=data) + r = self.session.put( + url, + headers=headers, + params=params, + data=data, + timeout=self.global_request_timeout, + ) else: - r = self.session.put(url, headers=headers, params=params, json=data) + r = self.session.put( + url, + headers=headers, + params=params, + json=data, + timeout=self.global_request_timeout, + ) elif method == 'DELETE': if isinstance(data, str): - r = self.session.delete(url, headers=headers, params=params, data=data) + r = self.session.delete( + url, + headers=headers, + params=params, + data=data, + timeout=self.global_request_timeout, + ) else: - r = self.session.delete(url, headers=headers, params=params, json=data) + r = self.session.delete( + url, + headers=headers, + params=params, + json=data, + timeout=self.global_request_timeout, + ) elif method == 'PATCH': if isinstance(data, str): - r = self.session.request('PATCH', url, headers=headers, params=params, data=data) + r = self.session.request( + 'PATCH', + url, + headers=headers, + params=params, + data=data, + timeout=self.global_request_timeout, + ) else: - r = self.session.request('PATCH', url, headers=headers, params=params, json=data) + r = self.session.request( + 'PATCH', + url, + headers=headers, + params=params, + json=data, + timeout=self.global_request_timeout, + ) else: # should never happen raise CloudFlareAPIError(0, 'method not supported') @@ -54,7 +126,7 @@ return r def __del__(self): - """ Network for Cloudflare API""" + """Network for Cloudflare API""" if self.use_sessions and self.session: self.session.close() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/CloudFlare/utils.py new/cloudflare-2.12.4/CloudFlare/utils.py --- old/cloudflare-2.11.7/CloudFlare/utils.py 2023-05-20 03:00:53.000000000 +0200 +++ new/cloudflare-2.12.4/CloudFlare/utils.py 2023-09-21 19:03:51.000000000 +0200 @@ -1,6 +1,4 @@ """ misc utilities for Cloudflare API""" -from __future__ import absolute_import - import sys import json from requests import __version__ as requests__version__ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/PKG-INFO new/cloudflare-2.12.4/PKG-INFO --- old/cloudflare-2.11.7/PKG-INFO 2023-08-19 18:24:00.366338000 +0200 +++ new/cloudflare-2.12.4/PKG-INFO 2023-09-22 03:47:10.381636400 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: cloudflare -Version: 2.11.7 +Version: 2.12.4 Summary: Python wrapper for the Cloudflare v4 API Home-page: https://github.com/cloudflare/python-cloudflare Author: Martin J. Levy @@ -13,6 +13,7 @@ Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 Description-Content-Type: text/markdown +Provides-Extra: test License-File: LICENSE # cloudflare-python @@ -197,7 +198,12 @@ This parameter controls how the data is returned from a successful call (see notes below). - * `raw - An optional Raw flag (True/False) - defaults to False + * `raw` - An optional Raw flag (True/False) - defaults to False + +Timeouts (10s) and Retries (5) are configured by default. Should you wish to override them, use these settings: +* `global_request_timeout` - How long before each API call to Cloudflare should time out (in seconds) +* `max_requests_retries` - How many times to retry an API call when DNS lookups, socket connections, or connect timeouts occur. +> NOTE: `max_request_retries` is only available when `use_sessions` is not disabled. The following paramaters are for debug and/or development usage @@ -517,6 +523,48 @@ The [examples](https://github.com/cloudflare/python-cloudflare/tree/master/examples) folder contains many examples in both simple and verbose formats. +You can see the installed path of these files directly via `cli4 -e` (or `cli4 --examples`) command. + +```bash +$ cli4 -e +Python .py files: + ... + /opt/homebrew/lib/python3.11/site-packages/examples/example_always_use_https.py + ... +Bash .sh files: + ... + /opt/homebrew/lib/python3.11/site-packages/examples/example_paging_thru_zones.sh + ... +$ +``` + +The exact path will vary depending on your system. +The above example is MacOS and Python 3.9 hence the `/opt/homebrew/lib/python3.11/site-packages/` path. +One Linux, the Python pip command may install the code is a system location like `/usr/lib/python3/dist-packages` or `~/.local/lib/python3.9/site-packages/` or something different. +The `cli4 -e` command will try to decode the location and display the example files. + +If you are running release before Python 3.9 then you will be asked to install the following: + +```bash +$ pip install importlib_resources +... +$ +``` + +It will show up if you are running on an older system. For example, this is the results from running on Win7): + +```bash +U:\Users\âââââ>cli4 -e +Module "importlib_resources" missing - please "pip install importlib_resources" as your Python version is lower than 3.9 + +U:\Users\âââââ>python -V +Python 3.8.3 + +U:\Users\âââââ> +``` + +Upgrading from an older version of Python is always recommended. Upgrading from Win7 is by-default even more important! + ## A DNS zone code example ```python diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/README.md new/cloudflare-2.12.4/README.md --- old/cloudflare-2.11.7/README.md 2023-05-30 05:18:21.000000000 +0200 +++ new/cloudflare-2.12.4/README.md 2023-09-21 19:32:37.000000000 +0200 @@ -180,7 +180,12 @@ This parameter controls how the data is returned from a successful call (see notes below). - * `raw - An optional Raw flag (True/False) - defaults to False + * `raw` - An optional Raw flag (True/False) - defaults to False + +Timeouts (10s) and Retries (5) are configured by default. Should you wish to override them, use these settings: +* `global_request_timeout` - How long before each API call to Cloudflare should time out (in seconds) +* `max_requests_retries` - How many times to retry an API call when DNS lookups, socket connections, or connect timeouts occur. +> NOTE: `max_request_retries` is only available when `use_sessions` is not disabled. The following paramaters are for debug and/or development usage @@ -500,6 +505,48 @@ The [examples](https://github.com/cloudflare/python-cloudflare/tree/master/examples) folder contains many examples in both simple and verbose formats. +You can see the installed path of these files directly via `cli4 -e` (or `cli4 --examples`) command. + +```bash +$ cli4 -e +Python .py files: + ... + /opt/homebrew/lib/python3.11/site-packages/examples/example_always_use_https.py + ... +Bash .sh files: + ... + /opt/homebrew/lib/python3.11/site-packages/examples/example_paging_thru_zones.sh + ... +$ +``` + +The exact path will vary depending on your system. +The above example is MacOS and Python 3.9 hence the `/opt/homebrew/lib/python3.11/site-packages/` path. +One Linux, the Python pip command may install the code is a system location like `/usr/lib/python3/dist-packages` or `~/.local/lib/python3.9/site-packages/` or something different. +The `cli4 -e` command will try to decode the location and display the example files. + +If you are running release before Python 3.9 then you will be asked to install the following: + +```bash +$ pip install importlib_resources +... +$ +``` + +It will show up if you are running on an older system. For example, this is the results from running on Win7): + +```bash +U:\Users\âââââ>cli4 -e +Module "importlib_resources" missing - please "pip install importlib_resources" as your Python version is lower than 3.9 + +U:\Users\âââââ>python -V +Python 3.8.3 + +U:\Users\âââââ> +``` + +Upgrading from an older version of Python is always recommended. Upgrading from Win7 is by-default even more important! + ## A DNS zone code example ```python diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/cli4/__main__.py new/cloudflare-2.12.4/cli4/__main__.py --- old/cloudflare-2.11.7/cli4/__main__.py 2020-01-15 22:29:49.000000000 +0100 +++ new/cloudflare-2.12.4/cli4/__main__.py 2023-09-21 19:03:51.000000000 +0200 @@ -1,7 +1,5 @@ #!/usr/bin/env python """Cloudflare API via command line""" -from __future__ import absolute_import - import sys from .cli4 import cli4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/cli4/cli4.py new/cloudflare-2.12.4/cli4/cli4.py --- old/cloudflare-2.11.7/cli4/cli4.py 2023-05-21 19:26:25.000000000 +0200 +++ new/cloudflare-2.12.4/cli4/cli4.py 2023-09-19 18:52:10.000000000 +0200 @@ -13,6 +13,7 @@ import CloudFlare from .dump import dump_commands, dump_commands_from_web from . import converters +from . import examples def load_and_check_yaml(): """ load_and_check_yaml() """ @@ -365,6 +366,7 @@ verbose = False output = 'json' + example = False raw = False dump = False openapi_url = None @@ -374,6 +376,7 @@ usage = ('usage: cli4 ' + '[-V|--version] [-h|--help] [-v|--verbose] [-q|--quiet] ' + + '[-e|--examples] ' + '[-j|--json] [-y|--yaml] [-n|ndjson] ' + '[-r|--raw] ' + '[-d|--dump] ' @@ -386,10 +389,10 @@ try: opts, args = getopt.getopt(args, - 'VhvqjyrdA:bp:GPOUD', + 'VhvqejyrdA:bp:GPOUD', [ 'version', - 'help', 'verbose', 'quiet', 'json', 'yaml', 'ndjson', + 'help', 'verbose', 'quiet', 'examples', 'json', 'yaml', 'ndjson', 'raw', 'dump', 'openapi=', 'binary', @@ -407,6 +410,8 @@ verbose = True elif opt in ('-q', '--quiet'): output = None + elif opt in ('-e', '--examples'): + example = True elif opt in ('-j', '--json'): output = 'json' elif opt in ('-y', '--yaml'): @@ -440,6 +445,13 @@ elif opt in ('-D', '--delete'): method = 'DELETE' + if example: + try: + examples.display() + except ModuleNotFoundError as e: + sys.exit(e) + sys.exit(0) + try: cf = CloudFlare.CloudFlare(debug=verbose, raw=raw, profile=profile) except Exception as e: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/cli4/converters.py new/cloudflare-2.12.4/cli4/converters.py --- old/cloudflare-2.11.7/cli4/converters.py 2022-08-24 00:30:00.000000000 +0200 +++ new/cloudflare-2.12.4/cli4/converters.py 2023-09-21 19:03:51.000000000 +0200 @@ -1,6 +1,4 @@ """Cloudflare API via command line""" -from __future__ import absolute_import - import CloudFlare class ConverterError(Exception): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/cli4/examples.py new/cloudflare-2.12.4/cli4/examples.py --- old/cloudflare-2.11.7/cli4/examples.py 1970-01-01 01:00:00.000000000 +0100 +++ new/cloudflare-2.12.4/cli4/examples.py 2023-09-19 18:52:10.000000000 +0200 @@ -0,0 +1,39 @@ +"""Cloudflare API via command line""" + +import os +import sys + +if sys.version_info < (3, 9): + # importlib.resources either doesn't exist or lacks the files() + # function, so use the PyPI version: + try: + import importlib_resources + except ModuleNotFoundError as e: + importlib_resources = None +else: + # importlib.resources has files(), so use that: + import importlib.resources as importlib_resources + +examples_package_name = 'examples' + +def display(): + """ display() """ + + if not importlib_resources: + raise ModuleNotFoundError('Module "importlib_resources" missing - please "pip install importlib_resources" as your Python version is lower than 3.9') + + try: + pkg = importlib_resources.files(examples_package_name) + except ModuleNotFoundError as e: + raise e + + for ext,name in {'c': 'C', 'h': 'C', 'cc': 'C++', 'py':'Python', 'sh':'Bash', 'awk':'AWK'}.items(): + l = sorted(pkg.glob('**/*.' + ext)) + if len(l) == 0: + continue + print('%s .%s files:' % (name, ext)) + for f in sorted(pkg.glob('**/*.' + ext)): + if '__init__.py' in os.fspath(f): + continue + print('\t%s' % (os.fspath(f))) + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/cloudflare.egg-info/PKG-INFO new/cloudflare-2.12.4/cloudflare.egg-info/PKG-INFO --- old/cloudflare-2.11.7/cloudflare.egg-info/PKG-INFO 2023-08-19 18:24:00.000000000 +0200 +++ new/cloudflare-2.12.4/cloudflare.egg-info/PKG-INFO 2023-09-22 03:47:10.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: cloudflare -Version: 2.11.7 +Version: 2.12.4 Summary: Python wrapper for the Cloudflare v4 API Home-page: https://github.com/cloudflare/python-cloudflare Author: Martin J. Levy @@ -13,6 +13,7 @@ Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 Description-Content-Type: text/markdown +Provides-Extra: test License-File: LICENSE # cloudflare-python @@ -197,7 +198,12 @@ This parameter controls how the data is returned from a successful call (see notes below). - * `raw - An optional Raw flag (True/False) - defaults to False + * `raw` - An optional Raw flag (True/False) - defaults to False + +Timeouts (10s) and Retries (5) are configured by default. Should you wish to override them, use these settings: +* `global_request_timeout` - How long before each API call to Cloudflare should time out (in seconds) +* `max_requests_retries` - How many times to retry an API call when DNS lookups, socket connections, or connect timeouts occur. +> NOTE: `max_request_retries` is only available when `use_sessions` is not disabled. The following paramaters are for debug and/or development usage @@ -517,6 +523,48 @@ The [examples](https://github.com/cloudflare/python-cloudflare/tree/master/examples) folder contains many examples in both simple and verbose formats. +You can see the installed path of these files directly via `cli4 -e` (or `cli4 --examples`) command. + +```bash +$ cli4 -e +Python .py files: + ... + /opt/homebrew/lib/python3.11/site-packages/examples/example_always_use_https.py + ... +Bash .sh files: + ... + /opt/homebrew/lib/python3.11/site-packages/examples/example_paging_thru_zones.sh + ... +$ +``` + +The exact path will vary depending on your system. +The above example is MacOS and Python 3.9 hence the `/opt/homebrew/lib/python3.11/site-packages/` path. +One Linux, the Python pip command may install the code is a system location like `/usr/lib/python3/dist-packages` or `~/.local/lib/python3.9/site-packages/` or something different. +The `cli4 -e` command will try to decode the location and display the example files. + +If you are running release before Python 3.9 then you will be asked to install the following: + +```bash +$ pip install importlib_resources +... +$ +``` + +It will show up if you are running on an older system. For example, this is the results from running on Win7): + +```bash +U:\Users\âââââ>cli4 -e +Module "importlib_resources" missing - please "pip install importlib_resources" as your Python version is lower than 3.9 + +U:\Users\âââââ>python -V +Python 3.8.3 + +U:\Users\âââââ> +``` + +Upgrading from an older version of Python is always recommended. Upgrading from Win7 is by-default even more important! + ## A DNS zone code example ```python diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/cloudflare.egg-info/SOURCES.txt new/cloudflare-2.12.4/cloudflare.egg-info/SOURCES.txt --- old/cloudflare-2.11.7/cloudflare.egg-info/SOURCES.txt 2023-08-19 18:24:00.000000000 +0200 +++ new/cloudflare-2.12.4/cloudflare.egg-info/SOURCES.txt 2023-09-22 03:47:10.000000000 +0200 @@ -20,6 +20,7 @@ cli4/cli4.py cli4/converters.py cli4/dump.py +cli4/examples.py cli4/myjsonlines.py cli4/myyaml.py cloudflare.egg-info/PKG-INFO @@ -60,4 +61,6 @@ examples/example_with_usage.py examples/example_zone_search.sh examples/example_zones.py -tests/test1.py \ No newline at end of file +tests/test_cloudflare.py +tests/test_ips.py +tests/test_issue114.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/cloudflare.egg-info/requires.txt new/cloudflare-2.12.4/cloudflare.egg-info/requires.txt --- old/cloudflare-2.11.7/cloudflare.egg-info/requires.txt 2023-08-19 18:24:00.000000000 +0200 +++ new/cloudflare-2.12.4/cloudflare.egg-info/requires.txt 2023-09-22 03:47:10.000000000 +0200 @@ -2,3 +2,6 @@ pyyaml jsonlines beautifulsoup4 + +[test] +pytest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/setup.cfg new/cloudflare-2.12.4/setup.cfg --- old/cloudflare-2.11.7/setup.cfg 2023-08-19 18:24:00.366533500 +0200 +++ new/cloudflare-2.12.4/setup.cfg 2023-09-22 03:47:10.381838600 +0200 @@ -1,3 +1,11 @@ +[options.extras_require] +test = + pytest + +[tool:pytest] +testpaths = + tests + [egg_info] tag_build = tag_date = 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/tests/test1.py new/cloudflare-2.12.4/tests/test1.py --- old/cloudflare-2.11.7/tests/test1.py 2020-01-26 21:23:53.000000000 +0100 +++ new/cloudflare-2.12.4/tests/test1.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,16 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.abspath('..')) -import CloudFlare - -import pytest - -def test_ips(): - cf = CloudFlare.CloudFlare() - ips = cf.ips.get() - assert ips - -if __name__ == '__main__': - pytest.main([__file__]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/tests/test_cloudflare.py new/cloudflare-2.12.4/tests/test_cloudflare.py --- old/cloudflare-2.11.7/tests/test_cloudflare.py 1970-01-01 01:00:00.000000000 +0100 +++ new/cloudflare-2.12.4/tests/test_cloudflare.py 2023-09-19 21:12:22.000000000 +0200 @@ -0,0 +1,19 @@ +import os +import sys +sys.path.insert(0, os.path.abspath('..')) + +import CloudFlare + +class TestCloudflare: + def test_creating_default_client(self): + cf = CloudFlare.CloudFlare() + assert isinstance(cf, CloudFlare.CloudFlare) + + + def test_with_global_request_timeout(self): + cf = CloudFlare.CloudFlare({'global_request_timeout': 10}) + assert isinstance(cf, CloudFlare.CloudFlare) + + def test_with_max_request_retries(self): + cf = CloudFlare.CloudFlare({'max_request_retries': 2}) + assert isinstance(cf, CloudFlare.CloudFlare) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/tests/test_ips.py new/cloudflare-2.12.4/tests/test_ips.py --- old/cloudflare-2.11.7/tests/test_ips.py 1970-01-01 01:00:00.000000000 +0100 +++ new/cloudflare-2.12.4/tests/test_ips.py 2023-09-21 20:33:41.000000000 +0200 @@ -0,0 +1,20 @@ +""" ips tests """ + +import os +import sys +sys.path.insert(0, os.path.abspath('..')) + +import CloudFlare + +class TestCloudflare: + def test_ips(self): + # no auth required + cf = CloudFlare.CloudFlare() + assert isinstance(cf, CloudFlare.CloudFlare) + ips = cf.ips() + assert isinstance(ips, dict) + assert isinstance(ips['ipv4_cidrs'], list) + assert isinstance(ips['ipv6_cidrs'], list) + assert len(ips['ipv4_cidrs']) > 0 + assert len(ips['ipv6_cidrs']) > 0 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudflare-2.11.7/tests/test_issue114.py new/cloudflare-2.12.4/tests/test_issue114.py --- old/cloudflare-2.11.7/tests/test_issue114.py 1970-01-01 01:00:00.000000000 +0100 +++ new/cloudflare-2.12.4/tests/test_issue114.py 2023-09-21 21:15:42.000000000 +0200 @@ -0,0 +1,127 @@ +""" issue-114 tests """ + +import os +import sys +sys.path.insert(0, os.path.abspath('..')) + +import CloudFlare + +# CloudFlare(email=None, key=None, token=None, certtoken=None, debug=False, raw=False, use_sessions=True, profile=None, base_url=None, global_request_timeout=5, max_request_retries=5) + +class TestCloudflare: + """ TestCloudflare """ + def test_email_key_token(self): + """ test_email_key_token """ + # Always clear environment + self._setup() + + profile = self._profile + + assert self._email or self._key or self._token + + # if not self._email and not self._key and not self._token: + # assert 'EMAIL/KEY/TOKEN all needed in order to run this test' == '' + + cf = None + # loop over each combination + for email in [None, self._email, 'exam...@example.com']: + for key in [None, self._key, self._token]: + for token in [None, self._token, self._key]: + print('email = ', self._obfuscate(email), 'key = ', self._obfuscate(key), 'token = ', self._obfuscate(token)) + if cf: + del cf + cf = CloudFlare.CloudFlare(email=email, key=key, token=token, profile=profile) + assert isinstance(cf, CloudFlare.CloudFlare) + + try: + r = cf.zones(params={'per_page':1}) + except: + r = None + + if email is None and key is None and token == self._token: + assert isinstance(r, list) + assert len(r) == 1 + assert isinstance(r[0], dict) + continue + + if email is None and key == self._token and token is None: + assert isinstance(r, list) + assert len(r) == 1 + assert isinstance(r[0], dict) + continue + + if email == self._email and key == self._key and token is None: + assert isinstance(r, list) + assert len(r) == 1 + assert isinstance(r[0], dict) + continue + + if email == self._email and key is None and token == self._key: + assert isinstance(r, list) + assert len(r) == 1 + assert isinstance(r[0], dict) + continue + + # Nothing else should work! + assert r is None + + def _setup(self): + """ setup """ + # Force no profile to be picked + self._profile='' + # read in email/key/token from config file(s) + _config_files = [ + '.cloudflare.cfg', + os.path.expanduser('~/.cloudflare.cfg'), + os.path.expanduser('~/.cloudflare/cloudflare.cfg') + ] + email = None + key = None + token = None + for filename in _config_files: + try: + with open(filename, 'r') as fd: + for l in fd: + if email and key and token: + break + if l[0] == '#': + continue + a = l.split() + if len(a) < 3: + continue + if a[1] != '=': + continue + if not email and a[0] == 'email': + email = a[2] + continue + if not key and a[0] == 'key': + key = a[2] + continue + if not token and a[0] == 'token': + token = a[2] + continue + break + except FileNotFoundError: + pass + self._email = email + self._key = key + self._token = token + + # now remove all env variables! + for env in ['CLOUDFLARE_EMAIL', 'CLOUDFLARE_API_KEY', 'CLOUDFLARE_API_TOKEN']: + try: + del os.environ[env] + except KeyError: + pass + for env in ['CF_API_EMAIL', 'CF_API_KEY', 'CF_API_TOKEN']: + try: + del os.environ[env] + except KeyError: + pass + + def _obfuscate(self, s): + """ _obfuscate """ + + if s is None: + return '' + return 'â' * len(s)