Skip to content
This repository was archived by the owner on Nov 22, 2024. It is now read-only.

Commit dc2a6f8

Browse files
committed
add openapi support, delay bs4 load to speed up everything, improve requests imports
1 parent f7b4a8c commit dc2a6f8

3 files changed

Lines changed: 121 additions & 17 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
""" API from OpenAPI for Cloudflare API"""
2+
3+
import sys
4+
import re
5+
import datetime
6+
import json
7+
8+
API_TYPES = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE']
9+
10+
match_identifier = re.compile('\{[A-Za-z0-9_]*\}')
11+
12+
def do_path(cmd, info):
13+
""" do_path() """
14+
15+
cmds = []
16+
17+
if cmd[0] != '/':
18+
cmd = '/' + cmd # make sure there's a leading /
19+
20+
cmd = match_identifier.sub(':id', cmd)
21+
if cmd[-4:] == '/:id':
22+
cmd = cmd[:-4]
23+
if cmd[-4:] == '/:id':
24+
cmd = cmd[:-4]
25+
26+
for action in info:
27+
action = action.upper()
28+
if action == '' or action not in API_TYPES:
29+
continue
30+
deprecated = False
31+
deprecated_date = ''
32+
deprecated_already = False
33+
v = {'action': action, 'cmd': cmd, 'deprecated': deprecated, 'deprecated_date': deprecated_date, 'deprecated_already': deprecated_already}
34+
cmds.append(v)
35+
return cmds
36+
37+
def api_decode_from_openapi(content):
38+
""" API decode from OpenAPI for Cloudflare API"""
39+
40+
try:
41+
j = json.loads(content)
42+
except Exception as e:
43+
sys.stderr.write("OpenAPI json format issue: %s\n" % (e))
44+
return None
45+
46+
try:
47+
components = j['components']
48+
info = j['info']
49+
openapi = j['openapi']
50+
paths = j['paths']
51+
servers = ['servers']
52+
except Exception as e:
53+
sys.stderr.write("OpenAPI json format structure: %s\n" % (e))
54+
return None
55+
56+
all_cmds = []
57+
for path in paths:
58+
if path[0] != '/':
59+
sys.stderr.write("OpenAPI invalid path: %s\n" % (path))
60+
continue
61+
all_cmds += do_path(path, paths[path])
62+
63+
return sorted(all_cmds, key=lambda v: v['cmd'])

CloudFlare/api_decode_from_web.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,37 @@
33
import sys
44
import datetime
55

6-
from bs4 import BeautifulSoup, Comment
7-
86
API_TYPES = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE']
97

8+
class mybs4():
9+
""" mybs4 """
10+
11+
_BeautifulSoup = None
12+
_Comment = None
13+
14+
def __init__(self):
15+
""" __init__ """
16+
pass
17+
18+
def _available(self):
19+
""" _available() """
20+
if not mybs4._BeautifulSoup:
21+
try:
22+
from bs4 import BeautifulSoup, Comment
23+
mybs4._BeautifulSoup = BeautifulSoup
24+
mybs4._Comment = Comment
25+
self.Comment = mybs4._Comment
26+
except ImportError:
27+
return False
28+
return True
29+
30+
def BeautifulSoup(self, content, parser):
31+
""" BeautifulSoup() """
32+
self._available()
33+
return mybs4._BeautifulSoup(content, parser)
34+
35+
my_bs4 = mybs4()
36+
1037
def do_section(section):
1138
""" API extras for Cloudflare API"""
1239

@@ -42,7 +69,7 @@ def do_section(section):
4269
for tag2 in section.find_all('pre'):
4370
cmd = []
4471
for child in tag2.children:
45-
if isinstance(child, Comment):
72+
if isinstance(child, my_bs4.Comment):
4673
# remove <!-- react-text ... -> parts
4774
continue
4875
cmd.append(str(child).strip())
@@ -61,7 +88,7 @@ def do_section(section):
6188
def api_decode_from_web(content):
6289
""" API extras for Cloudflare API"""
6390

64-
soup = BeautifulSoup(content, 'html.parser')
91+
soup = my_bs4.BeautifulSoup(content, 'html.parser')
6592

6693
for child in soup.find_all('p'):
6794
t = child.get_text()

CloudFlare/cloudflare.py

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import json
55
import keyword
6-
import requests
6+
from requests import RequestException as requests_RequestException, ConnectionError as requests_ConnectionError, exceptions as requests_exceptions, codes as requests_codes
77

88
from .network import CFnetwork
99
from .logging_helper import CFlogger
@@ -12,6 +12,7 @@
1212
from .api_v4 import api_v4
1313
from .api_extras import api_extras
1414
from .api_decode_from_web import api_decode_from_web
15+
from .api_decode_from_openapi import api_decode_from_openapi
1516
from .exceptions import CloudFlareError, CloudFlareAPIError, CloudFlareInternalError
1617

1718
BASE_URL = 'https://api.cloudflare.com/client/v4'
@@ -199,15 +200,15 @@ def _call_network(self, method, headers, parts, identifiers, params, data, files
199200

200201
try:
201202
response = self.network(method, url, headers, params, data, files)
202-
except requests.RequestException as e:
203+
except requests_RequestException as e:
203204
if self.logger:
204205
self.logger.debug('Call: requests exception! "%s"', e)
205206
raise CloudFlareAPIError(0, e)
206-
except requests.ConnectionError as e:
207+
except requests_ConnectionError as e:
207208
if self.logger:
208209
self.logger.debug('Call: requests connection exception! "%s"', e)
209210
raise CloudFlareAPIError(0, 'connection error')
210-
except requests.exceptions.Timeout as e:
211+
except requests_exceptions.Timeout as e:
211212
if self.logger:
212213
self.logger.debug('Call: requests timeout exception! "%s"', e)
213214
raise CloudFlareAPIError(0, 'connection timeout')
@@ -297,7 +298,7 @@ def _raw(self, method, headers, parts, identifiers, params, data, files):
297298
except ValueError:
298299
if response_data == '':
299300
# This should really be 'null' but it isn't. Even then, it's wrong!
300-
if response_code == requests.codes.ok:
301+
if response_code == requests_codes.ok:
301302
# 200 ok
302303
response_data = {'success': True,
303304
'result': None}
@@ -320,7 +321,7 @@ def _raw(self, method, headers, parts, identifiers, params, data, files):
320321
self.logger.debug('Response data not JSON: %r', response_data)
321322
raise CloudFlareAPIError(0, 'JSON parse failed - report to Cloudflare.')
322323

323-
if response_code == requests.codes.ok:
324+
if response_code == requests_codes.ok:
324325
# 200 ok - so nothing needs to be done
325326
pass
326327
else:
@@ -329,7 +330,7 @@ def _raw(self, method, headers, parts, identifiers, params, data, files):
329330
pass
330331
elif response_type == 'application/octet-stream' and isinstance(response_data, (int, float)):
331332
# It's binary data
332-
if response_code == requests.codes.ok:
333+
if response_code == requests_codes.ok:
333334
# 200 ok
334335
response_data = {'success': True,
335336
'result': response_data}
@@ -345,7 +346,7 @@ def _raw(self, method, headers, parts, identifiers, params, data, files):
345346
try:
346347
response_data = json.loads(response_data)
347348
if not isinstance(response_data, (dict)) or 'success' not in response_data:
348-
if response_code == requests.codes.ok:
349+
if response_code == requests_codes.ok:
349350
# 200 ok
350351
response_data = {'success': True,
351352
'result': response_data}
@@ -357,7 +358,7 @@ def _raw(self, method, headers, parts, identifiers, params, data, files):
357358
except ValueError:
358359
# So it wasn't JSON - moving on as if it's text!
359360
# A single value is returned (vs an array or object)
360-
if response_code == requests.codes.ok:
361+
if response_code == requests_codes.ok:
361362
# 200 ok
362363
response_data = {'success': True, 'result': response_data}
363364
else:
@@ -377,7 +378,7 @@ def _raw(self, method, headers, parts, identifiers, params, data, files):
377378
except ValueError:
378379
# So it wasn't JSON - moving on as if it's text!
379380
# A single value is returned (vs an array or object)
380-
if response_code == requests.codes.ok:
381+
if response_code == requests_codes.ok:
381382
# 200 ok
382383
response_data = {'success': True, 'result': response_data}
383384
else:
@@ -387,7 +388,7 @@ def _raw(self, method, headers, parts, identifiers, params, data, files):
387388
'result': response_data}
388389
elif response_type in ['text/javascript', 'application/javascript']:
389390
# used by Cloudflare workers
390-
if response_code == requests.codes.ok:
391+
if response_code == requests_codes.ok:
391392
# 200 ok
392393
response_data = {'success': True,
393394
'result': str(response_data)}
@@ -398,7 +399,7 @@ def _raw(self, method, headers, parts, identifiers, params, data, files):
398399
'result': str(response_data)}
399400
elif response_type == 'text/html':
400401
# used by media for preview
401-
if response_code == requests.codes.ok:
402+
if response_code == requests_codes.ok:
402403
# 200 ok
403404
response_data = {'success': True,
404405
'result': str(response_data)}
@@ -411,7 +412,7 @@ def _raw(self, method, headers, parts, identifiers, params, data, files):
411412
else:
412413
# Assuming nothing - but continuing anyway
413414
# A single value is returned (vs an array or object)
414-
if response_code == requests.codes.ok:
415+
if response_code == requests_codes.ok:
415416
# 200 ok
416417
response_data = {'success': True,
417418
'result': str(response_data)}
@@ -535,7 +536,15 @@ def api_from_web(self):
535536

536537
# base url isn't enough; we need less
537538
url = '/'.join(self.base_url.split('/')[0:3])
539+
return self._read_from_web(url)
538540

541+
def api_from_openapi(self, url):
542+
""" Cloudflare v4 API"""
543+
544+
return self._read_from_web(url)
545+
546+
def _read_from_web(self, url):
547+
""" Cloudflare v4 API"""
539548
try:
540549
if self.logger:
541550
self.logger.debug('Call: doit!')
@@ -894,6 +903,11 @@ def api_from_web(self):
894903

895904
return api_decode_from_web(self._base.api_from_web())
896905

906+
def api_from_openapi(self, url):
907+
""" Cloudflare v4 API"""
908+
909+
return api_decode_from_openapi(self._base.api_from_openapi(url))
910+
897911
def __init__(self, email=None, key=None, token=None, certtoken=None, debug=False, raw=False, use_sessions=True, profile=None, base_url=None):
898912
""" Cloudflare v4 API"""
899913

0 commit comments

Comments
 (0)