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

Commit 3af9aa8

Browse files
committed
add openapi support, delay yaml and jsonlines load to speed up everything, other cleanup
1 parent dc2a6f8 commit 3af9aa8

File tree

4 files changed

+222
-136
lines changed

4 files changed

+222
-136
lines changed

cli4/cli4.py

Lines changed: 142 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,126 @@
55
import re
66
import getopt
77
import json
8-
try:
9-
import yaml
10-
except ImportError:
11-
yaml = None
12-
try:
13-
import jsonlines
14-
except ImportError:
15-
jsonlines = None
8+
9+
my_yaml = None
10+
my_jsonlines = None
1611

1712
import CloudFlare
13+
from .dump import dump_commands, dump_commands_from_web
1814
from . import converters
1915

20-
def dump_commands(cf):
21-
"""dump a tree of all the known API commands"""
22-
w = cf.api_list()
23-
sys.stdout.write('\n'.join(w) + '\n')
24-
25-
def dump_commands_from_web(cf):
26-
"""dump a tree of all the known API commands - from web"""
27-
w = cf.api_from_web()
28-
for r in w:
29-
if r['deprecated']:
30-
if r['deprecated_already']:
31-
sys.stdout.write('%-6s %s ; deprecated %s - expired!\n' % (r['action'], r['cmd'], r['deprecated_date']))
32-
else:
33-
sys.stdout.write('%-6s %s ; deprecated %s\n' % (r['action'], r['cmd'], r['deprecated_date']))
34-
else:
35-
sys.stdout.write('%-6s %s\n' % (r['action'], r['cmd']))
16+
def load_and_check_yaml():
17+
""" load_and_check_yaml() """
18+
from . import myyaml
19+
global my_yaml
20+
my_yaml = myyaml.myyaml()
21+
if not my_yaml.available():
22+
sys.exit('cli4: install yaml support')
3623

3724
def strip_multiline(s):
3825
""" remove leading/trailing tabs/spaces on each line"""
3926
# This hack is needed in order to use yaml.safe_load() on JSON text - tabs are not allowed
4027
return '\n'.join([l.strip() for l in s.splitlines()])
4128

29+
def process_params_content_files(method, binary_file, args):
30+
""" process_params_content_files() """
31+
32+
digits_only = re.compile('^-?[0-9]+$')
33+
floats_only = re.compile('^-?[0-9.]+$')
34+
35+
params = None
36+
content = None
37+
files = None
38+
# next grab the params. These are in the form of tag=value or =value or @filename
39+
while len(args) > 0 and ('=' in args[0] or args[0][0] == '@'):
40+
arg = args.pop(0)
41+
if arg[0] == '@':
42+
# a file to be uploaded - used in workers/script - only via PUT
43+
filename = arg[1:]
44+
if method not in ['PUT','POST']:
45+
sys.exit('cli4: %s - raw file upload only with PUT or POST' % (filename))
46+
try:
47+
if filename == '-':
48+
if binary_file:
49+
content = sys.stdin.buffer.read()
50+
else:
51+
content = sys.stdin.read()
52+
else:
53+
if binary_file:
54+
with open(filename, 'rb') as f:
55+
content = f.read()
56+
else:
57+
with open(filename, 'r') as f:
58+
content = f.read()
59+
except IOError:
60+
sys.exit('cli4: %s - file open failure' % (filename))
61+
continue
62+
tag_string, value_string = arg.split('=', 1)
63+
if value_string.lower() == 'true':
64+
value = True
65+
elif value_string.lower() == 'false':
66+
value = False
67+
elif value_string == '' or value_string.lower() == 'none':
68+
value = None
69+
elif value_string[0] == '=' and value_string[1:] == '':
70+
sys.exit('cli4: %s== - no number value passed' % (tag_string))
71+
elif value_string[0] == '=' and digits_only.match(value_string[1:]):
72+
value = int(value_string[1:])
73+
elif value_string[0] == '=' and floats_only.match(value_string[1:]):
74+
value = float(value_string[1:])
75+
elif value_string[0] == '=':
76+
sys.exit('cli4: %s== - invalid number value passed' % (tag_string))
77+
elif value_string[0] in '[{' and value_string[-1] in '}]':
78+
# a json structure - used in pagerules
79+
try:
80+
#value = json.loads(value) - changed to yaml code to remove unicode string issues
81+
load_and_check_yaml()
82+
# cleanup string before parsing so that yaml.safe.load does not complain about whitespace
83+
# >>> found character '\t' that cannot start any token <<<
84+
value_string = strip_multiline(value_string)
85+
try:
86+
value = my_yaml.safe_load(value_string)
87+
except my_yaml.parser.ParserError as e:
88+
raise ValueError
89+
except ValueError:
90+
sys.exit('cli4: %s="%s" - can\'t parse json value' % (tag_string, value_string))
91+
elif value_string[0] == '@':
92+
# a file to be uploaded - used in dns_records/import - only via POST
93+
filename = value_string[1:]
94+
if method != 'POST':
95+
sys.exit('cli4: %s=%s - file upload only with POST' % (tag_string, filename))
96+
files = {}
97+
try:
98+
if filename == '-':
99+
files[tag_string] = sys.stdin
100+
else:
101+
files[tag_string] = open(filename, 'rb')
102+
except IOError:
103+
sys.exit('cli4: %s=%s - file open failure' % (tag_string, filename))
104+
# no need for param code below
105+
continue
106+
else:
107+
value = value_string
108+
109+
if tag_string == '':
110+
# There's no tag; it's just an unnamed list
111+
if params is None:
112+
params = value
113+
else:
114+
sys.exit('cli4: %s=%s - param error. Can\'t mix unnamed and named list' %
115+
(tag_string, value_string))
116+
else:
117+
if params is None:
118+
params = {}
119+
tag = tag_string
120+
try:
121+
params[tag] = value
122+
except TypeError:
123+
sys.exit('cli4: %s=%s - param error. Can\'t mix unnamed and named list' %
124+
(tag_string, value_string))
125+
126+
return (params, content, files)
127+
42128
def run_command(cf, method, command, params=None, content=None, files=None):
43129
"""run the command line"""
44130
# remove leading and trailing /'s
@@ -232,8 +318,6 @@ def write_results(results, output):
232318
pass
233319
else:
234320
# anything more complex (dict, list, etc) should be dumped as JSON/YAML
235-
if output is None:
236-
results = None
237321
if output == 'json':
238322
try:
239323
results = json.dumps(results,
@@ -246,17 +330,20 @@ def write_results(results, output):
246330
indent=4,
247331
sort_keys=True,
248332
ensure_ascii=False)
249-
if output == 'yaml':
250-
results = yaml.safe_dump(results)
251-
if output == 'ndjson':
333+
elif output == 'yaml':
334+
results = my_yaml.safe_dump(results)
335+
elif output == 'ndjson':
252336
# NDJSON support seems like a hack. There has to be a better way
253337
try:
254-
writer = jsonlines.Writer(sys.stdout)
338+
writer = my_jsonlines.Writer(sys.stdout)
255339
writer.write_all(results)
256340
writer.close()
257341
except (BrokenPipeError, IOError):
258342
pass
259343
return
344+
else:
345+
# Internal error
346+
pass
260347

261348
if results:
262349
try:
@@ -274,6 +361,7 @@ def do_it(args):
274361
raw = False
275362
dump = False
276363
dump_from_web = False
364+
openapi_url = None
277365
binary_file = False
278366
profile = None
279367
method = 'GET'
@@ -284,6 +372,7 @@ def do_it(args):
284372
+ '[-r|--raw] '
285373
+ '[-d|--dump] '
286374
+ '[-a|--api] '
375+
+ '[-A|--openapi url] '
287376
+ '[-b|--binary] '
288377
+ '[-p|--profile profile-name] '
289378
+ '[--get|--patch|--post|--put|--delete] '
@@ -292,12 +381,12 @@ def do_it(args):
292381

293382
try:
294383
opts, args = getopt.getopt(args,
295-
'Vhvqjyrdabp:GPOUD',
384+
'VhvqjyrdaA:bp:GPOUD',
296385
[
297386
'version',
298387
'help', 'verbose', 'quiet', 'json', 'yaml', 'ndjson',
299388
'raw',
300-
'dump', 'api',
389+
'dump', 'api', 'openapi=',
301390
'binary',
302391
'profile=',
303392
'get', 'patch', 'post', 'put', 'delete'
@@ -316,11 +405,13 @@ def do_it(args):
316405
elif opt in ('-j', '--json'):
317406
output = 'json'
318407
elif opt in ('-y', '--yaml'):
319-
if yaml is None:
320-
sys.exit('cli4: install yaml support')
408+
load_and_check_yaml()
321409
output = 'yaml'
322410
elif opt in ('-n', '--ndjson'):
323-
if jsonlines is None:
411+
from . import myjsonlines
412+
global my_jsonlines
413+
my_jsonlines = myjsonlines.myjsonlines()
414+
if not my_jsonlines.available():
324415
sys.exit('cli4: install jsonlines support')
325416
output = 'ndjson'
326417
elif opt in ('-r', '--raw'):
@@ -331,6 +422,8 @@ def do_it(args):
331422
dump = True
332423
elif opt in ('-a', '--api'):
333424
dump_from_web = True
425+
elif opt in ('-A', '--openapi'):
426+
openapi_url = arg
334427
elif opt in ('-b', '--binary'):
335428
binary_file = True
336429
elif opt in ('-G', '--get'):
@@ -344,121 +437,34 @@ def do_it(args):
344437
elif opt in ('-D', '--delete'):
345438
method = 'DELETE'
346439

347-
if dump:
440+
try:
348441
cf = CloudFlare.CloudFlare(debug=verbose, raw=raw, profile=profile)
349-
dump_commands(cf)
442+
except Exception as e:
443+
sys.exit(e)
444+
445+
if dump:
446+
a = dump_commands(cf)
447+
sys.stdout.write(a)
350448
sys.exit(0)
351449

352450
if dump_from_web:
353-
cf = CloudFlare.CloudFlare(debug=verbose, raw=raw, profile=profile)
354-
dump_commands_from_web(cf)
451+
a = dump_commands_from_web(cf)
452+
sys.stdout.write(a)
355453
sys.exit(0)
356454

357-
digits_only = re.compile('^-?[0-9]+$')
358-
floats_only = re.compile('^-?[0-9.]+$')
455+
if openapi_url:
456+
a = dump_commands_from_web(cf, openapi_url)
457+
sys.stdout.write(a)
458+
sys.exit(0)
359459

360460
# next grab the params. These are in the form of tag=value or =value or @filename
361-
params = None
362-
content = None
363-
files = None
364-
while len(args) > 0 and ('=' in args[0] or args[0][0] == '@'):
365-
arg = args.pop(0)
366-
if arg[0] == '@':
367-
# a file to be uploaded - used in workers/script - only via PUT
368-
filename = arg[1:]
369-
if method not in ['PUT','POST']:
370-
sys.exit('cli4: %s - raw file upload only with PUT or POST' % (filename))
371-
try:
372-
if filename == '-':
373-
if binary_file:
374-
content = sys.stdin.buffer.read()
375-
else:
376-
content = sys.stdin.read()
377-
else:
378-
if binary_file:
379-
with open(filename, 'rb') as f:
380-
content = f.read()
381-
else:
382-
with open(filename, 'r') as f:
383-
content = f.read()
384-
except IOError:
385-
sys.exit('cli4: %s - file open failure' % (filename))
386-
continue
387-
tag_string, value_string = arg.split('=', 1)
388-
if value_string.lower() == 'true':
389-
value = True
390-
elif value_string.lower() == 'false':
391-
value = False
392-
elif value_string == '' or value_string.lower() == 'none':
393-
value = None
394-
elif value_string[0] == '=' and value_string[1:] == '':
395-
sys.exit('cli4: %s== - no number value passed' % (tag_string))
396-
elif value_string[0] == '=' and digits_only.match(value_string[1:]):
397-
value = int(value_string[1:])
398-
elif value_string[0] == '=' and floats_only.match(value_string[1:]):
399-
value = float(value_string[1:])
400-
elif value_string[0] == '=':
401-
sys.exit('cli4: %s== - invalid number value passed' % (tag_string))
402-
elif value_string[0] in '[{' and value_string[-1] in '}]':
403-
# a json structure - used in pagerules
404-
try:
405-
#value = json.loads(value) - changed to yaml code to remove unicode string issues
406-
if yaml is None:
407-
sys.exit('cli4: install yaml support')
408-
# cleanup string before parsing so that yaml.safe.load does not complain about whitespace
409-
# >>> found character '\t' that cannot start any token <<<
410-
value_string = strip_multiline(value_string)
411-
try:
412-
value = yaml.safe_load(value_string)
413-
except yaml.parser.ParserError as e:
414-
raise ValueError
415-
except ValueError:
416-
sys.exit('cli4: %s="%s" - can\'t parse json value' % (tag_string, value_string))
417-
elif value_string[0] == '@':
418-
# a file to be uploaded - used in dns_records/import - only via POST
419-
filename = value_string[1:]
420-
if method != 'POST':
421-
sys.exit('cli4: %s=%s - file upload only with POST' % (tag_string, filename))
422-
files = {}
423-
try:
424-
if filename == '-':
425-
files[tag_string] = sys.stdin
426-
else:
427-
files[tag_string] = open(filename, 'rb')
428-
except IOError:
429-
sys.exit('cli4: %s=%s - file open failure' % (tag_string, filename))
430-
# no need for param code below
431-
continue
432-
else:
433-
value = value_string
434-
435-
if tag_string == '':
436-
# There's no tag; it's just an unnamed list
437-
if params is None:
438-
params = value
439-
else:
440-
sys.exit('cli4: %s=%s - param error. Can\'t mix unnamed and named list' %
441-
(tag_string, value_string))
442-
else:
443-
if params is None:
444-
params = {}
445-
tag = tag_string
446-
try:
447-
params[tag] = value
448-
except TypeError:
449-
sys.exit('cli4: %s=%s - param error. Can\'t mix unnamed and named list' %
450-
(tag_string, value_string))
461+
(params, content, files) = process_params_content_files(method, binary_file, args)
451462

452463
# what's left is the command itself
453464
if len(args) < 1:
454465
sys.exit(usage)
455466
commands = args
456467

457-
try:
458-
cf = CloudFlare.CloudFlare(debug=verbose, raw=raw, profile=profile)
459-
except Exception as e:
460-
sys.exit(e)
461-
462468
exit_with_error = False
463469
for command in commands:
464470
try:

0 commit comments

Comments
 (0)