-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathsearch.py
More file actions
171 lines (136 loc) · 7.24 KB
/
search.py
File metadata and controls
171 lines (136 loc) · 7.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import cloudinary
from click import command, argument, option, launch
from functools import wraps
from cloudinary_cli.defaults import logger
from cloudinary_cli.utils.json_utils import write_json_to_file, print_json
from cloudinary_cli.utils.utils import write_json_list_to_csv, confirm_action, whitelist_keys, \
normalize_list_params
DEFAULT_MAX_RESULTS = 500
def shared_options(func):
@option("-f", "--with_field", multiple=True, help="Specify which non-default asset attributes to include "
"in the result as a comma separated list.")
@option("-fi", "--fields", multiple=True, help="Specify which asset attributes to include in the result "
"(together with a subset of the default attributes) as a comma separated"
" list. This overrides any value specified for with_field.")
@option("-s", "--sort_by", nargs=2, help="Sort search results by (field, <asc|desc>).")
@option("-a", "--aggregate", nargs=1,
help="Specify the attribute for which an aggregation count should be calculated and returned.")
@option("-n", "--max_results", nargs=1, default=10,
help="The maximum number of results to return. Default: 10, maximum: 500.")
@option("-c", "--next_cursor", nargs=1, help="Continue a search using an existing cursor.")
@option("-A", "--auto_paginate", is_flag=True, help="Return all results. Will call Admin API multiple times.")
@option("-F", "--force", is_flag=True, help="Skip confirmation when running --auto-paginate.")
@option("-ff", "--filter_fields", multiple=True, help="Specify which attributes to show in the response. "
"None of the others will be shown.")
@option("-sq", "--search-query", is_flag=True, help="Show the search request query.", hidden=True)
@option("--json", nargs=1, help="Save JSON output to a file. Usage: --json <filename>")
@option("--csv", nargs=1, help="Save CSV output to a file. Usage: --csv <filename>")
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@command("search",
short_help="Run the Admin API search method.",
help="""\b
Run the Admin API search method.
Format: cld <cli options> search <command options> <Lucene query syntax string>
e.g. cld search cat AND tags:kitten -s public_id desc -f context -f tags -n 10
""")
@argument("query", nargs=-1)
@shared_options
@option("-t", "--ttl", nargs=1, default=300, help="Set the Search URL TTL in seconds. Default: 300.")
@option("-u", "--url", is_flag=True, help="Build a signed search URL.")
@option("-d", "--doc", is_flag=True, help="Open Search API documentation page.")
def search(query, with_field, fields, sort_by, aggregate, max_results, next_cursor,
auto_paginate, force, filter_fields, ttl, url, search_query, json, csv, doc):
search_instance = cloudinary.search.Search()
doc_url = "https://cloudinary.com/documentation/search_api"
result_field = 'resources'
return _perform_search(query, with_field, fields, sort_by, aggregate, max_results, next_cursor,
auto_paginate, force, filter_fields, ttl, url, search_query, json, csv, doc,
search_instance, doc_url, result_field)
@command("search_folders",
short_help="Run the Admin API search folders method.",
help="""\b
Run the Admin API search folders method.
Format: cld <cli options> search_folders <command options> <Lucene query syntax string>
e.g. cld search_folders name:folder AND path:my_parent AND created_at>4w
""")
@argument("query", nargs=-1)
@shared_options
@option("-d", "--doc", is_flag=True, help="Open Search Folders API documentation page.")
def search_folders(query, with_field, fields, sort_by, aggregate, max_results, next_cursor,
auto_paginate, force, filter_fields, search_query, json, csv, doc):
search_instance = cloudinary.search_folders.SearchFolders()
doc_url = "https://cloudinary.com/documentation/admin_api#search_folders"
result_field = 'folders'
return _perform_search(query, with_field, fields, sort_by, aggregate, max_results, next_cursor,
auto_paginate, force, filter_fields, 300, False, search_query, json, csv, doc,
search_instance, doc_url, result_field)
def _perform_search(query, with_field, fields, sort_by, aggregate, max_results, next_cursor,
auto_paginate, force, filter_fields, ttl, url, search_query, json, csv, doc,
search_instance, doc_url, result_field):
"""Shared logic for running a search."""
if doc:
return launch(doc_url)
fields_to_keep = []
if filter_fields:
fields_to_keep = tuple(normalize_list_params(filter_fields)) + tuple(normalize_list_params(with_field))
search = search_instance.expression(" ".join(query))
if auto_paginate:
max_results = DEFAULT_MAX_RESULTS
if with_field:
search.with_field(normalize_list_params(with_field))
if fields:
search.fields(normalize_list_params(fields))
if sort_by:
search.sort_by(*sort_by)
if aggregate:
search.aggregate(aggregate)
if next_cursor:
search.next_cursor(next_cursor)
if ttl:
search.ttl(ttl)
search.max_results(max_results)
if url:
print(search.to_url())
return True
if search_query:
print_json(search.as_dict())
return True
res = execute_single_request(search, fields_to_keep, result_field)
if auto_paginate:
res = handle_auto_pagination(res, search, force, fields_to_keep, result_field)
print_json(res)
if json:
write_json_to_file(res[result_field], json)
logger.info(f"Saved search JSON to '{json}' file")
if csv:
write_json_list_to_csv(res[result_field], csv, fields_to_keep)
logger.info(f"Saved search to '{csv}.csv' file")
def execute_single_request(expression, fields_to_keep, result_field='resources'):
res = expression.execute()
if fields_to_keep:
res[result_field] = whitelist_keys(res[result_field], fields_to_keep)
return res
def handle_auto_pagination(res, expression, force, fields_to_keep, result_field='resources'):
if 'next_cursor' not in res:
return res
if not force:
if not confirm_action(
f"{res['total_count']} total results. "
f"{res.rate_limit_remaining + 1} Admin API rate limit remaining.\n"
f"Running this query will use {res['total_count'] // DEFAULT_MAX_RESULTS + 1} Admin API calls. "
f"Continue? (y/N)"):
logger.info("Stopping. Please run again without -A.")
return res
else:
logger.info("Continuing. You may use the -F flag to force auto_pagination.")
all_results = res
while 'next_cursor' in res.keys():
expression.next_cursor(res['next_cursor'])
res = execute_single_request(expression, fields_to_keep, result_field)
all_results[result_field] += res[result_field]
all_results['time'] += res['time']
all_results.pop('next_cursor', None) # it is empty by now
return all_results