pyPreservica 0.9.9__py3-none-any.whl → 3.3.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,14 +10,43 @@ licence: Apache License 2.0
10
10
  """
11
11
 
12
12
  import csv
13
+ from io import BytesIO
14
+ from typing import Generator, Callable, Optional, Union
13
15
  from pyPreservica.common import *
14
16
 
15
17
  logger = logging.getLogger(__name__)
16
18
 
19
+ class SortOrder(Enum):
20
+ asc = 1
21
+ desc = 2
22
+
23
+ class Field:
24
+ name: str
25
+ value: Optional[str]
26
+ operator: Optional[str]
27
+ sort_order: Optional[SortOrder]
28
+
29
+ def __init__(self, name: str, value: str, operator: Optional[str]=None, sort_order: Optional[SortOrder]=None):
30
+ self.name = name
31
+ self.value = value
32
+ self.operator = operator
33
+ self.sort_order = sort_order
34
+
35
+
36
+
17
37
  class ContentAPI(AuthenticatedAPI):
38
+ """
39
+ The ContentAPI class provides the search interface to the Preservica repository.
40
+
41
+ """
42
+
18
43
 
19
- def __init__(self, username=None, password=None, tenant=None, server=None, use_shared_secret=False):
20
- super().__init__(username, password, tenant, server, use_shared_secret)
44
+ def __init__(self, username: str = None, password: str = None, tenant: str = None, server: str = None,
45
+ use_shared_secret: bool = False, two_fa_secret_key: str = None,
46
+ protocol: str = "https", request_hook: Callable = None, credentials_path: str = 'credentials.properties'):
47
+
48
+ super().__init__(username, password, tenant, server, use_shared_secret, two_fa_secret_key,
49
+ protocol, request_hook, credentials_path)
21
50
  self.callback = None
22
51
 
23
52
  class SearchResult:
@@ -31,10 +60,30 @@ class ContentAPI(AuthenticatedAPI):
31
60
  def search_callback(self, fn):
32
61
  self.callback = fn
33
62
 
34
- def object_details(self, entity_type, reference):
63
+ def user_security_tags(self, with_permissions: bool = False):
64
+ """
65
+ Return available security tags
66
+
67
+ :return: dict of security tags
68
+ :rtype: dict
69
+ """
70
+
71
+ return self.security_tags_base(with_permissions=with_permissions)
72
+
73
+ def object_details(self, entity_type, reference: str) -> dict:
74
+ """
75
+
76
+ :param entity_type:
77
+ :param reference:
78
+ :return: Dictionary of object attributes
79
+ """
35
80
  headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json'}
36
- params = {'id': f'sdb:{entity_type.value}|{reference}'}
37
- request = requests.get(f'https://{self.server}/api/content/object-details', params=params, headers=headers)
81
+ if type(entity_type) == EntityType:
82
+ params = {'id': f'sdb:{entity_type.value}|{reference}'}
83
+ else:
84
+ params = {'id': f'sdb:{entity_type}|{reference}'}
85
+ request = self.session.get(f'{self.protocol}://{self.server}/api/content/object-details', params=params,
86
+ headers=headers)
38
87
  if request.status_code == requests.codes.ok:
39
88
  return request.json()["value"]
40
89
  elif request.status_code == requests.codes.not_found:
@@ -47,11 +96,34 @@ class ContentAPI(AuthenticatedAPI):
47
96
  logger.error(f"object_details failed with error code: {request.status_code}")
48
97
  raise RuntimeError(request.status_code, f"object_details failed with error code: {request.status_code}")
49
98
 
99
+
100
+ def download_bytes(self, reference):
101
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/octet-stream'}
102
+ params = {'id': f'sdb:IO|{reference}'}
103
+ with self.session.get(f'{self.protocol}://{self.server}/api/content/download', params=params, headers=headers,
104
+ stream=True) as req:
105
+ if req.status_code == requests.codes.ok:
106
+ file_bytes = BytesIO()
107
+ for chunk in req.iter_content(chunk_size=CHUNK_SIZE):
108
+ file_bytes.write(chunk)
109
+ file_bytes.seek(0)
110
+ return file_bytes
111
+ elif req.status_code == requests.codes.unauthorized:
112
+ self.token = self.__token__()
113
+ return self.download_bytes(reference)
114
+ elif req.status_code == requests.codes.not_found:
115
+ logger.error(f"The requested asset reference is not found in the repository: {reference}")
116
+ raise RuntimeError(reference, "The requested reference is not found in the repository")
117
+ else:
118
+ logger.error(f"download failed with error code: {req.status_code}")
119
+ raise RuntimeError(req.status_code, f"download failed with error code: {req.status_code}")
120
+
121
+
50
122
  def download(self, reference, filename):
51
123
  headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/octet-stream'}
52
124
  params = {'id': f'sdb:IO|{reference}'}
53
- with requests.get(f'https://{self.server}/api/content/download', params=params, headers=headers,
54
- stream=True) as req:
125
+ with self.session.get(f'{self.protocol}://{self.server}/api/content/download', params=params, headers=headers,
126
+ stream=True) as req:
55
127
  if req.status_code == requests.codes.ok:
56
128
  with open(filename, 'wb') as file:
57
129
  for chunk in req.iter_content(chunk_size=CHUNK_SIZE):
@@ -69,27 +141,43 @@ class ContentAPI(AuthenticatedAPI):
69
141
  logger.error(f"download failed with error code: {req.status_code}")
70
142
  raise RuntimeError(req.status_code, f"download failed with error code: {req.status_code}")
71
143
 
144
+ def thumbnail_bytes(self, entity_type, reference: str, size: Thumbnail = Thumbnail.LARGE) -> Union[BytesIO, None]:
145
+ headers = {HEADER_TOKEN: self.token, 'accept': 'image/png'}
146
+ params = {'id': f'sdb:{entity_type}|{reference}', 'size': f'{size.value}'}
147
+ with self.session.get(f'{self.protocol}://{self.server}/api/content/thumbnail', params=params, headers=headers, stream=True) as req:
148
+ if req.status_code == requests.codes.ok:
149
+ file_bytes = BytesIO()
150
+ for chunk in req.iter_content(chunk_size=CHUNK_SIZE):
151
+ file_bytes.write(chunk)
152
+ file_bytes.seek(0)
153
+ return file_bytes
154
+ elif req.status_code == requests.codes.unauthorized:
155
+ self.token = self.__token__()
156
+ return self.thumbnail_bytes(entity_type, reference, size)
157
+ elif req.status_code == requests.codes.not_found:
158
+ logger.error(req.content.decode("utf-8"))
159
+ logger.error(f"The requested reference is not found in the repository: {reference}")
160
+ raise RuntimeError(reference, "The requested reference is not found in the repository")
161
+ else:
162
+ logger.error(f"thumbnail failed with error code: {req.status_code}")
163
+ raise RuntimeError(req.status_code, f"thumbnail failed with error code: {req.status_code}")
164
+
72
165
  def thumbnail(self, entity_type, reference, filename, size=Thumbnail.LARGE):
73
- headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/octet-stream'}
74
- if entity_type == "IO":
75
- params = {'id': f'sdb:IO|{reference}', 'size': f'{size.value}'}
76
- elif entity_type == "SO":
77
- params = {'id': f'sdb:SO|{reference}', 'size': f'{size.value}'}
78
- else:
79
- print(f"entity_type must be a folder or asset, IO or SO")
80
- raise SystemExit
81
- with requests.get(f'https://{self.server}/api/content/thumbnail', params=params, headers=headers) as req:
166
+ headers = {HEADER_TOKEN: self.token, 'accept': 'image/png'}
167
+ params = {'id': f'sdb:{entity_type}|{reference}', 'size': f'{size.value}'}
168
+ with self.session.get(f'{self.protocol}://{self.server}/api/content/thumbnail', params=params, headers=headers,
169
+ stream=True) as req:
82
170
  if req.status_code == requests.codes.ok:
83
171
  with open(filename, 'wb') as file:
84
172
  for chunk in req.iter_content(chunk_size=CHUNK_SIZE):
85
173
  file.write(chunk)
86
174
  file.flush()
87
- file.close()
88
175
  return filename
89
176
  elif req.status_code == requests.codes.unauthorized:
90
177
  self.token = self.__token__()
91
178
  return self.thumbnail(entity_type, reference, filename, size)
92
179
  elif req.status_code == requests.codes.not_found:
180
+ logger.error(req.content.decode("utf-8"))
93
181
  logger.error(f"The requested reference is not found in the repository: {reference}")
94
182
  raise RuntimeError(reference, "The requested reference is not found in the repository")
95
183
  else:
@@ -98,12 +186,12 @@ class ContentAPI(AuthenticatedAPI):
98
186
 
99
187
  def indexed_fields(self):
100
188
  headers = {HEADER_TOKEN: self.token}
101
- results = requests.get(f'https://{self.server}/api/content/indexed-fields', headers=headers)
189
+ results = self.session.get(f'{self.protocol}://{self.server}/api/content/indexed-fields', headers=headers)
102
190
  if results.status_code == requests.codes.ok:
103
- fields = list()
191
+ fields = {}
104
192
  for ob in results.json()["value"]:
105
193
  field = f'{ob["shortName"]}.{ob["index"]}'
106
- fields.append(field)
194
+ fields[field] = ob["uri"]
107
195
  return fields
108
196
  elif results.status_code == requests.codes.unauthorized:
109
197
  self.token = self.__token__()
@@ -112,52 +200,52 @@ class ContentAPI(AuthenticatedAPI):
112
200
  logger.error(f"indexed_fields failed with error code: {results.status_code}")
113
201
  raise RuntimeError(results.status_code, f"indexed_fields failed with error code: {results.status_code}")
114
202
 
115
- def simple_search_csv(self, query: str = "%", csv_file="search.csv", *args):
116
- page_size = 50
117
- if len(args) == 0:
203
+ def simple_search_csv(self, query: str = "%", page_size: int = 50, csv_file="search.csv",
204
+ list_indexes: list = None):
205
+ if list_indexes is None or len(list_indexes) == 0:
118
206
  metadata_fields = ["xip.reference", "xip.title", "xip.description", "xip.document_type",
119
207
  "xip.parent_ref", "xip.security_descriptor"]
120
208
  else:
121
- metadata_fields = list(*args)
209
+ metadata_fields = list(list_indexes)
122
210
  if "xip.reference" not in metadata_fields:
123
211
  metadata_fields.insert(0, "xip.reference")
124
212
  with open(csv_file, newline='', mode="wt", encoding="utf-8") as csv_file:
125
213
  writer = csv.DictWriter(csv_file, fieldnames=metadata_fields)
126
214
  writer.writeheader()
127
- writer.writerows(self.simple_search_list(query, page_size, *args))
215
+ writer.writerows(self.simple_search_list(query, page_size, metadata_fields))
128
216
 
129
- def simple_search_list(self, query: str = "%", page_size: int = 10, *args):
130
- search_result = self.simple_search(query, 0, page_size, *args)
217
+ def simple_search_list(self, query: str = "%", page_size: int = 50, list_indexes: list = None):
218
+ search_result = self._simple_search(query, 0, page_size, list_indexes)
131
219
  for e in search_result.results_list:
132
220
  yield e
133
221
  found = len(search_result.results_list)
134
222
  while search_result.hits > found:
135
- search_result = self.simple_search(query, found, page_size, *args)
223
+ search_result = self._simple_search(query, found, page_size, list_indexes)
136
224
  for e in search_result.results_list:
137
225
  yield e
138
226
  found = found + len(search_result.results_list)
139
227
 
140
- def simple_search(self, query: str = "%", start_index: int = 0, page_size: int = 10, *args):
228
+ def _simple_search(self, query: str = "%", start_index: int = 0, page_size: int = 10, list_indexes: list = None):
141
229
  start_from = str(start_index)
142
230
  headers = {'Content-Type': 'application/x-www-form-urlencoded', HEADER_TOKEN: self.token}
143
231
  query_term = ('{ "q": "%s" }' % query)
144
- if len(args) == 0:
232
+ if list_indexes is None or len(list_indexes) == 0:
145
233
  metadata_fields = "xip.title,xip.description,xip.document_type,xip.parent_ref,xip.security_descriptor"
146
234
  else:
147
- metadata_fields = ','.join(*args)
235
+ metadata_fields = ','.join(list_indexes)
148
236
  payload = {'start': start_from, 'max': str(page_size), 'metadata': metadata_fields, 'q': query_term}
149
- results = requests.post(f'https://{self.server}/api/content/search', data=payload, headers=headers)
150
- results_list = list()
237
+ results = self.session.post(f'{self.protocol}://{self.server}/api/content/search', data=payload,
238
+ headers=headers)
239
+ results_list = []
151
240
  if results.status_code == requests.codes.ok:
152
- json = results.json()
153
- metadata = json['value']['metadata']
154
- refs = list(json['value']['objectIds'])
241
+ json_doc = results.json()
242
+ metadata = json_doc['value']['metadata']
243
+ refs = list(json_doc['value']['objectIds'])
155
244
  refs = list(map(lambda x: content_api_identifier_to_type(x), refs))
156
- hits = int(json['value']['totalHits'])
245
+ hits = int(json_doc['value']['totalHits'])
157
246
 
158
247
  for m_row, r_row in zip(metadata, refs):
159
- results_map = dict()
160
- results_map['xip.reference'] = r_row[1]
248
+ results_map = {'xip.reference': r_row[1]}
161
249
  for li in m_row:
162
250
  results_map[li['name']] = li['value']
163
251
  results_list.append(results_map)
@@ -171,65 +259,225 @@ class ContentAPI(AuthenticatedAPI):
171
259
  return search_results
172
260
  elif results.status_code == requests.codes.unauthorized:
173
261
  self.token = self.__token__()
174
- return self.simple_search(query, start_index, page_size, *args)
262
+ return self._simple_search(query, start_index, page_size, list_indexes)
175
263
  else:
176
264
  logger.error(f"search failed with error code: {results.status_code}")
177
265
  raise RuntimeError(results.status_code, f"simple_search failed with error code: {results.status_code}")
178
266
 
179
- def search_index_filter_csv(self, query: str = "%", csv_file="search.csv", map_fields=None):
180
- page_size = 50
181
- if map_fields is None:
182
- map_fields = dict()
183
- if "xip.reference" not in map_fields:
184
- map_fields["xip.reference"] = ""
267
+ def search_index_filter_csv(self, query: str = "%", csv_file="search.csv", page_size: int = 50,
268
+ filter_values: dict = None,
269
+ sort_values: dict = None):
270
+ if filter_values is None:
271
+ filter_values = {}
272
+ if "xip.reference" not in filter_values:
273
+ filter_values["xip.reference"] = ""
185
274
 
186
- header_fields = list(map_fields.keys())
275
+ header_fields = list(filter_values.keys())
187
276
  index = header_fields.index("xip.reference")
188
277
  header_fields.insert(0, header_fields.pop(index))
189
278
  with open(csv_file, newline='', mode="wt", encoding="utf-8") as csv_file:
190
279
  writer = csv.DictWriter(csv_file, fieldnames=header_fields)
191
280
  writer.writeheader()
192
- writer.writerows(self.search_index_filter_list(query, page_size, map_fields))
281
+ writer.writerows(self.search_index_filter_list(query, page_size, filter_values, sort_values))
282
+
283
+ def search_fields(self, query: str = "%", fields: list[Field]=None, page_size: int = 25) -> Generator:
284
+ """
285
+ Run a search query with multiple fields
193
286
 
194
- def search_index_filter_list(self, query: str = "%", page_size: int = 10, map_fields=None):
195
- search_result = self.search_index_filter(query, 0, page_size, map_fields)
287
+ :param query: The main search query.
288
+ :param fields: List of search fields
289
+ :param page_size: The default search page size
290
+ :return: search result
291
+ """
292
+
293
+ if self.major_version < 7 and self.minor_version < 5:
294
+ raise RuntimeError("search_fields API call is not available when connected to a v7.5 System")
295
+
296
+ search_result = self._search_fields(query=query, fields=fields, start_index=0, page_size=page_size)
196
297
  for e in search_result.results_list:
197
298
  yield e
198
299
  found = len(search_result.results_list)
199
300
  while search_result.hits > found:
200
- search_result = self.search_index_filter(query, found, page_size, map_fields)
301
+ search_result = self._search_fields(query=query, fields=fields, start_index=found, page_size=page_size)
201
302
  for e in search_result.results_list:
202
303
  yield e
203
304
  found = found + len(search_result.results_list)
204
305
 
205
- def search_index_filter(self, query: str = "%", start_index: int = 0, page_size: int = 10, map_fields=None):
306
+ def _search_fields(self, query: str = "%", fields: list[Field]=None, start_index: int = 0, page_size: int = 25):
307
+
206
308
  start_from = str(start_index)
207
309
  headers = {'Content-Type': 'application/x-www-form-urlencoded', HEADER_TOKEN: self.token}
208
310
 
209
- field_list = list()
210
- for key, value in map_fields.items():
311
+ if fields is None:
312
+ fields = []
313
+
314
+ field_list = []
315
+ sort_list = []
316
+ metadata_elements = []
317
+ for field in fields:
318
+ metadata_elements.append(field.name)
319
+ if field.value is None or field.value == "":
320
+ field_list.append('{' f' "name": "{field.name}", "values": [] ' + '}')
321
+ elif field.operator == "NOT":
322
+ field_list.append('{' f' "name": "{field.name}", "values": ["{field.value}"], "operator": "NOT" ' + '}')
323
+ else:
324
+ field_list.append('{' f' "name": "{field.name}", "values": ["{field.value}"] ' + '}')
325
+
326
+ if field.sort_order is not None:
327
+ sort_list.append(f'{{"sortFields": ["{field.name}"], "sortOrder": "{field.sort_order.name}"}}')
328
+
329
+
330
+ filter_terms = ','.join(field_list)
331
+
332
+ if len(sort_list) == 0:
333
+ query_term = ('{ "q": "%s", "fields": [ %s ] }' % (query, filter_terms))
334
+ else:
335
+ sort_terms = ','.join(sort_list)
336
+ query_term = ('{ "q": "%s", "fields": [ %s ], "sort": [ %s ]}' % (query, filter_terms, sort_terms))
337
+
338
+ if len(metadata_elements) == 0:
339
+ metadata_elements.append("xip.title")
340
+
341
+
342
+ payload = {'start': start_from, 'max': str(page_size), 'metadata': list(metadata_elements), 'q': query_term}
343
+ logger.debug(payload)
344
+ results = self.session.post(f'{self.protocol}://{self.server}/api/content/search', data=payload,
345
+ headers=headers)
346
+ results_list = []
347
+ if results.status_code == requests.codes.ok:
348
+ json_doc = results.json()
349
+ metadata = json_doc['value']['metadata']
350
+ refs = list(json_doc['value']['objectIds'])
351
+ refs = list(map(lambda x: content_api_identifier_to_type(x), refs))
352
+ hits = int(json_doc['value']['totalHits'])
353
+
354
+ for m_row, r_row in zip(metadata, refs):
355
+ results_map = {'xip.reference': r_row[1]}
356
+ for li in m_row:
357
+ results_map[li['name']] = li['value']
358
+ results_list.append(results_map)
359
+ next_start = start_index + page_size
360
+
361
+ if self.callback is not None:
362
+ value = str(f'{len(results_list) + start_index}:{hits}')
363
+ self.callback(value)
364
+
365
+ search_results = self.SearchResult(metadata, refs, hits, results_list, next_start)
366
+ return search_results
367
+ elif results.status_code == requests.codes.unauthorized:
368
+ self.token = self.__token__()
369
+ return self._search_fields(query, fields, start_index, page_size)
370
+ else:
371
+ logger.error(f"search failed with error code: {results.status_code}")
372
+ raise RuntimeError(results.status_code, f"search_index_filter failed")
373
+
374
+ def search_index_filter_list(self, query: str = "%", page_size: int = 25, filter_values: dict = None,
375
+ sort_values: dict = None) -> Generator:
376
+ """
377
+ Run a search query with optional filters
378
+
379
+ :param query: The main search query.
380
+ :param page_size: The default search page size
381
+ :param filter_values: Dictionary of index names and values
382
+ :param sort_values: Dictionary of sort index names and values
383
+ :return: search result
384
+ """
385
+ search_result = self._search_index_filter(query, 0, page_size, filter_values, sort_values)
386
+ for e in search_result.results_list:
387
+ yield e
388
+ found = len(search_result.results_list)
389
+ while search_result.hits > found:
390
+ search_result = self._search_index_filter(query, found, page_size, filter_values, sort_values)
391
+ for e in search_result.results_list:
392
+ yield e
393
+ found = found + len(search_result.results_list)
394
+
395
+ def search_index_filter_hits(self, query: str = "%", filter_values: dict = None) -> int:
396
+ """
397
+ Run a search query with filters and return the number of hits only
398
+
399
+ :param query: The main search query.
400
+ :param filter_values: Dictionary of index names and values
401
+ :return: Number of search results
402
+ """
403
+ start_from = str(0)
404
+ headers = {'Content-Type': 'application/x-www-form-urlencoded', HEADER_TOKEN: self.token}
405
+
406
+ field_list = []
407
+ for key, value in filter_values.items():
211
408
  if value == "":
212
409
  field_list.append('{' f' "name": "{key}", "values": [] ' + '}')
213
410
  else:
214
- field_list.append('{' f' "name": "{key}", "values": ["{value}"] ' + '}')
411
+ if isinstance(value, str):
412
+ field_list.append('{' f' "name": "{key}", "values": ["{value}"] ' + '}')
413
+ if isinstance(value, list):
414
+ v = f' {",".join(f'"{w}"' for w in value)} '
415
+ field_list.append('{' f' "name": "{key}", "values":[ {v} ]' '}')
215
416
 
216
417
  filter_terms = ','.join(field_list)
217
418
 
218
419
  query_term = ('{ "q": "%s", "fields": [ %s ] }' % (query, filter_terms))
219
420
 
220
- payload = {'start': start_from, 'max': str(page_size), 'metadata': list(map_fields.keys()), 'q': query_term}
221
- results = requests.post(f'https://{self.server}/api/content/search', data=payload, headers=headers)
222
- results_list = list()
421
+ payload = {'start': start_from, 'max': str(10), 'metadata': list(filter_values.keys()), 'q': query_term}
422
+ results = self.session.post(f'{self.protocol}://{self.server}/api/content/search', data=payload,
423
+ headers=headers)
223
424
  if results.status_code == requests.codes.ok:
224
- json = results.json()
225
- metadata = json['value']['metadata']
226
- refs = list(json['value']['objectIds'])
425
+ json_doc = results.json()
426
+ return int(json_doc['value']['totalHits'])
427
+ elif results.status_code == requests.codes.unauthorized:
428
+ self.token = self.__token__()
429
+ return self.search_index_filter_hits(query, filter_values)
430
+ else:
431
+ logger.error(f"search failed with error code: {results.status_code}")
432
+ raise RuntimeError(results.status_code, f"_search_index_filter_hits failed")
433
+
434
+ def _search_index_filter(self, query: str = "%", start_index: int = 0, page_size: int = 25,
435
+ filter_values: dict = None, sort_values: dict = None):
436
+ start_from = str(start_index)
437
+ headers = {'Content-Type': 'application/x-www-form-urlencoded', HEADER_TOKEN: self.token}
438
+
439
+ if filter_values is None:
440
+ filter_values = {}
441
+
442
+ field_list = []
443
+ for key, value in filter_values.items():
444
+ if value == "":
445
+ field_list.append('{' f' "name": "{key}", "values": [] ' + '}')
446
+ else:
447
+ if isinstance(value, str):
448
+ field_list.append('{' f' "name": "{key}", "values": ["{value}"] ' + '}')
449
+ if isinstance(value, list):
450
+ v = f' {",".join(f'"{w}"' for w in value)} '
451
+ field_list.append('{' f' "name": "{key}", "values":[ {v} ]' '}')
452
+
453
+ filter_terms = ','.join(field_list)
454
+
455
+ if sort_values is None:
456
+ query_term = ('{ "q": "%s", "fields": [ %s ] }' % (query, filter_terms))
457
+ else:
458
+ sort_list = []
459
+ for key, value in sort_values.items():
460
+ direction = "asc"
461
+ if str(value).lower().startswith("d"):
462
+ direction = "desc"
463
+ sort_list.append(f'{{"sortFields": ["{key}"], "sortOrder": "{direction}"}}')
464
+ sort_terms = ','.join(sort_list)
465
+ query_term = ('{ "q": "%s", "fields": [ %s ], "sort": [ %s ]}' % (query, filter_terms, sort_terms))
466
+
467
+ payload = {'start': start_from, 'max': str(page_size), 'metadata': list(filter_values.keys()), 'q': query_term}
468
+ logger.debug(payload)
469
+ results = self.session.post(f'{self.protocol}://{self.server}/api/content/search', data=payload,
470
+ headers=headers)
471
+ results_list = []
472
+ if results.status_code == requests.codes.ok:
473
+ json_doc = results.json()
474
+ metadata = json_doc['value']['metadata']
475
+ refs = list(json_doc['value']['objectIds'])
227
476
  refs = list(map(lambda x: content_api_identifier_to_type(x), refs))
228
- hits = int(json['value']['totalHits'])
477
+ hits = int(json_doc['value']['totalHits'])
229
478
 
230
479
  for m_row, r_row in zip(metadata, refs):
231
- results_map = dict()
232
- results_map['xip.reference'] = r_row[1]
480
+ results_map = {'xip.reference': r_row[1]}
233
481
  for li in m_row:
234
482
  results_map[li['name']] = li['value']
235
483
  results_list.append(results_map)
@@ -243,7 +491,25 @@ class ContentAPI(AuthenticatedAPI):
243
491
  return search_results
244
492
  elif results.status_code == requests.codes.unauthorized:
245
493
  self.token = self.__token__()
246
- return self.search_index_filter(query, start_index, page_size, map_fields)
494
+ return self._search_index_filter(query, start_index, page_size, filter_values)
247
495
  else:
248
496
  logger.error(f"search failed with error code: {results.status_code}")
249
497
  raise RuntimeError(results.status_code, f"search_index_filter failed")
498
+
499
+ class ReportProgressCallBack:
500
+ def __init__(self):
501
+ self.current = 0
502
+ self.total = 0
503
+ self._lock = threading.Lock()
504
+
505
+ def __call__(self, value):
506
+ with self._lock:
507
+ values = value.split(":")
508
+ self.total = int(values[1])
509
+ self.current = int(values[0])
510
+ if self.total == 0:
511
+ percentage = 100.0
512
+ else:
513
+ percentage = (self.current / self.total) * 100
514
+ sys.stdout.write("\rProcessing Hits %s from %s (%.2f%%)" % (self.current, self.total, percentage))
515
+ sys.stdout.flush()