pyPreservica 2.8.2__tar.gz → 2.9.1__tar.gz

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.
Files changed (49) hide show
  1. {pypreservica-2.8.2 → pypreservica-2.9.1}/PKG-INFO +3 -5
  2. {pypreservica-2.8.2 → pypreservica-2.9.1}/README.md +1 -4
  3. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica/__init__.py +2 -2
  4. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica/contentAPI.py +105 -1
  5. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica/entityAPI.py +72 -0
  6. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica/mdformsAPI.py +2 -0
  7. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica/retentionAPI.py +53 -24
  8. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica/uploadAPI.py +3 -3
  9. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica/webHooksAPI.py +1 -0
  10. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica.egg-info/PKG-INFO +3 -5
  11. {pypreservica-2.8.2 → pypreservica-2.9.1}/setup.py +2 -1
  12. pypreservica-2.9.1/tests/test_authority_records.py +38 -0
  13. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_bitstream.py +25 -3
  14. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_children.py +29 -6
  15. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_content_api.py +35 -12
  16. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_identifier.py +76 -11
  17. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_par.py +1 -1
  18. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_retention.py +3 -3
  19. pypreservica-2.8.2/tests/test_authority_records.py +0 -20
  20. {pypreservica-2.8.2 → pypreservica-2.9.1}/LICENSE.txt +0 -0
  21. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica/adminAPI.py +0 -0
  22. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica/authorityAPI.py +0 -0
  23. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica/common.py +0 -0
  24. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica/monitorAPI.py +0 -0
  25. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica/opex.py +0 -0
  26. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica/parAPI.py +0 -0
  27. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica/workflowAPI.py +0 -0
  28. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica.egg-info/SOURCES.txt +0 -0
  29. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica.egg-info/dependency_links.txt +0 -0
  30. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica.egg-info/requires.txt +0 -0
  31. {pypreservica-2.8.2 → pypreservica-2.9.1}/pyPreservica.egg-info/top_level.txt +0 -0
  32. {pypreservica-2.8.2 → pypreservica-2.9.1}/setup.cfg +0 -0
  33. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_crawl_fs.py +0 -0
  34. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_delete.py +0 -0
  35. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_download.py +0 -0
  36. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_entity.py +0 -0
  37. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_export_opex.py +0 -0
  38. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_groups.py +0 -0
  39. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_ingest.py +0 -0
  40. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_integrity_check.py +0 -0
  41. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_metadata.py +0 -0
  42. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_replace.py +0 -0
  43. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_schema.py +0 -0
  44. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_security.py +0 -0
  45. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_thumbnail.py +0 -0
  46. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_upload.py +0 -0
  47. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_users.py +0 -0
  48. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_workflow.py +0 -0
  49. {pypreservica-2.8.2 → pypreservica-2.9.1}/tests/test_xml_metadata.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyPreservica
3
- Version: 2.8.2
3
+ Version: 2.9.1
4
4
  Summary: Python library for the Preservica API
5
5
  Home-page: https://pypreservica.readthedocs.io/
6
6
  Author: James Carr
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.8
15
15
  Classifier: Programming Language :: Python :: 3.9
16
16
  Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
18
19
  Classifier: License :: OSI Approved :: Apache Software License
19
20
  Classifier: Operating System :: OS Independent
20
21
  Classifier: Topic :: System :: Archiving
@@ -34,12 +35,9 @@ Requires-Dist: pyotp
34
35
  # pyPreservica
35
36
 
36
37
 
37
- <!---
38
- [![Downloads](https://pepy.tech/badge/pyPreservica/month)](https://pepy.tech/project/pyPreservica/month)
39
- --->
40
-
41
38
  [![Supported Versions](https://img.shields.io/pypi/pyversions/pyPreservica.svg)](https://pypi.org/project/pyPreservica)
42
39
  [![CodeQL](https://github.com/carj/pyPreservica/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/carj/pyPreservica/actions/workflows/codeql-analysis.yml)
40
+ ![PyPI Downloads](https://static.pepy.tech/badge/pypreservica)
43
41
 
44
42
  Python language binding for the Preservica API
45
43
 
@@ -2,12 +2,9 @@
2
2
  # pyPreservica
3
3
 
4
4
 
5
- <!---
6
- [![Downloads](https://pepy.tech/badge/pyPreservica/month)](https://pepy.tech/project/pyPreservica/month)
7
- --->
8
-
9
5
  [![Supported Versions](https://img.shields.io/pypi/pyversions/pyPreservica.svg)](https://pypi.org/project/pyPreservica)
10
6
  [![CodeQL](https://github.com/carj/pyPreservica/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/carj/pyPreservica/actions/workflows/codeql-analysis.yml)
7
+ ![PyPI Downloads](https://static.pepy.tech/badge/pypreservica)
11
8
 
12
9
  Python language binding for the Preservica API
13
10
 
@@ -7,7 +7,7 @@ licence: Apache License 2.0
7
7
 
8
8
  """
9
9
  from .common import *
10
- from .contentAPI import ContentAPI
10
+ from .contentAPI import ContentAPI, Field, SortOrder
11
11
  from .entityAPI import EntityAPI
12
12
  from .uploadAPI import UploadAPI, simple_asset_package, complex_asset_package, cvs_to_xsd, cvs_to_xml, \
13
13
  cvs_to_cmis_xslt, csv_to_search_xml, generic_asset_package, upload_config, multi_asset_package
@@ -23,6 +23,6 @@ from .mdformsAPI import MetadataGroupsAPI, Group, GroupField, GroupFieldType
23
23
  __author__ = "James Carr (drjamescarr@gmail.com)"
24
24
 
25
25
  # Version of the pyPreservica package
26
- __version__ = "2.8.2"
26
+ __version__ = "2.9.1"
27
27
 
28
28
  __license__ = "Apache License Version 2.0"
@@ -10,11 +10,28 @@ licence: Apache License 2.0
10
10
  """
11
11
 
12
12
  import csv
13
- from typing import Generator, Callable
13
+ from typing import Generator, Callable, Optional
14
14
  from pyPreservica.common import *
15
15
 
16
16
  logger = logging.getLogger(__name__)
17
17
 
18
+ class SortOrder(Enum):
19
+ asc = 1
20
+ desc = 2
21
+
22
+ class Field:
23
+ name: str
24
+ value: Optional[str]
25
+ operator: Optional[str]
26
+ sort_order: Optional[SortOrder]
27
+
28
+ def __init__(self, name: str, value: str, operator: Optional[str]=None, sort_order: Optional[SortOrder]=None):
29
+ self.name = name
30
+ self.value = value
31
+ self.operator = operator
32
+ self.sort_order = sort_order
33
+
34
+
18
35
 
19
36
  class ContentAPI(AuthenticatedAPI):
20
37
 
@@ -212,6 +229,93 @@ class ContentAPI(AuthenticatedAPI):
212
229
  writer.writeheader()
213
230
  writer.writerows(self.search_index_filter_list(query, page_size, filter_values, sort_values))
214
231
 
232
+ def search_fields(self, query: str = "%", fields: list[Field]=None, page_size: int = 25) -> Generator:
233
+ """
234
+ Run a search query with multiple fields
235
+
236
+ :param query: The main search query.
237
+ :param fields: List of search fields
238
+ :param page_size: The default search page size
239
+ :return: search result
240
+ """
241
+ search_result = self._search_fields(query=query, fields=fields, start_index=0, page_size=page_size)
242
+ for e in search_result.results_list:
243
+ yield e
244
+ found = len(search_result.results_list)
245
+ while search_result.hits > found:
246
+ search_result = self._search_fields(query=query, fields=fields, start_index=found, page_size=page_size)
247
+ for e in search_result.results_list:
248
+ yield e
249
+ found = found + len(search_result.results_list)
250
+
251
+ def _search_fields(self, query: str = "%", fields: list[Field]=None, start_index: int = 0, page_size: int = 25):
252
+
253
+ start_from = str(start_index)
254
+ headers = {'Content-Type': 'application/x-www-form-urlencoded', HEADER_TOKEN: self.token}
255
+
256
+ if fields is None:
257
+ fields = []
258
+
259
+ field_list = []
260
+ sort_list = []
261
+ metadata_elements = []
262
+ for field in fields:
263
+ metadata_elements.append(field.name)
264
+ if field.value is None or field.value == "":
265
+ field_list.append('{' f' "name": "{field.name}", "values": [] ' + '}')
266
+ elif field.operator == "NOT":
267
+ field_list.append('{' f' "name": "{field.name}", "values": ["{field.value}"], "operator": "NOT" ' + '}')
268
+ else:
269
+ field_list.append('{' f' "name": "{field.name}", "values": ["{field.value}"] ' + '}')
270
+
271
+ if field.sort_order is not None:
272
+ sort_list.append(f'{{"sortFields": ["{field.name}"], "sortOrder": "{field.sort_order.name}"}}')
273
+
274
+
275
+ filter_terms = ','.join(field_list)
276
+
277
+ if len(sort_list) == 0:
278
+ query_term = ('{ "q": "%s", "fields": [ %s ] }' % (query, filter_terms))
279
+ else:
280
+ sort_terms = ','.join(sort_list)
281
+ query_term = ('{ "q": "%s", "fields": [ %s ], "sort": [ %s ]}' % (query, filter_terms, sort_terms))
282
+
283
+ if len(metadata_elements) == 0:
284
+ metadata_elements.append("xip.title")
285
+
286
+
287
+ payload = {'start': start_from, 'max': str(page_size), 'metadata': list(metadata_elements), 'q': query_term}
288
+ logger.debug(payload)
289
+ results = self.session.post(f'{self.protocol}://{self.server}/api/content/search', data=payload,
290
+ headers=headers)
291
+ results_list = []
292
+ if results.status_code == requests.codes.ok:
293
+ json_doc = results.json()
294
+ metadata = json_doc['value']['metadata']
295
+ refs = list(json_doc['value']['objectIds'])
296
+ refs = list(map(lambda x: content_api_identifier_to_type(x), refs))
297
+ hits = int(json_doc['value']['totalHits'])
298
+
299
+ for m_row, r_row in zip(metadata, refs):
300
+ results_map = {'xip.reference': r_row[1]}
301
+ for li in m_row:
302
+ results_map[li['name']] = li['value']
303
+ results_list.append(results_map)
304
+ next_start = start_index + page_size
305
+
306
+ if self.callback is not None:
307
+ value = str(f'{len(results_list) + start_index}:{hits}')
308
+ self.callback(value)
309
+
310
+ search_results = self.SearchResult(metadata, refs, hits, results_list, next_start)
311
+ return search_results
312
+ elif results.status_code == requests.codes.unauthorized:
313
+ self.token = self.__token__()
314
+ return self._search_predicates(query, predicates, start_index, page_size)
315
+ else:
316
+ logger.error(f"search failed with error code: {results.status_code}")
317
+ raise RuntimeError(results.status_code, f"search_index_filter failed")
318
+
215
319
  def search_index_filter_list(self, query: str = "%", page_size: int = 25, filter_values: dict = None,
216
320
  sort_values: dict = None) -> Generator:
217
321
  """
@@ -572,6 +572,76 @@ class EntityAPI(AuthenticatedAPI):
572
572
  logger.error(exception)
573
573
  raise exception
574
574
 
575
+ def update_identifiers(self, entity: Entity, identifier_type: str = None, identifier_value: str = None):
576
+ """
577
+ Update external identifiers based on Entity and Type
578
+
579
+ Returns the internal identifier DB Key
580
+
581
+ :param entity: The entity to delete identifiers from
582
+ :param identifier_type: The type of the identifier to delete.
583
+ :param identifier_value: The value of the identifier to delete.
584
+ """
585
+
586
+ if (self.major_version < 7) and (self.minor_version < 1):
587
+ raise RuntimeError("update_identifiers API call is not available when connected to a v6.0 System")
588
+
589
+ headers = {HEADER_TOKEN: self.token}
590
+ response = self.session.get(
591
+ f'{self.protocol}://{self.server}/api/entity/{entity.path}/{entity.reference}/identifiers',
592
+ headers=headers)
593
+
594
+ if response.status_code == requests.codes.ok:
595
+ xml_response = str(response.content.decode('utf-8'))
596
+ entity_response = xml.etree.ElementTree.fromstring(xml_response)
597
+ identifier_list = entity_response.findall(f'.//{{{self.xip_ns}}}Identifier')
598
+ for identifier_element in identifier_list:
599
+ _ref = _type = _value = _aipid = None
600
+ for identifier in identifier_element:
601
+ if identifier.tag.endswith("Entity"):
602
+ _ref = identifier.text
603
+ if identifier.tag.endswith("Type") and identifier_type is not None:
604
+ _type = identifier.text
605
+ if identifier.tag.endswith("Value") and identifier_value is not None:
606
+ _value = identifier.text
607
+ if identifier.tag.endswith("ApiId"):
608
+ _aipid = identifier.text
609
+ if _ref == entity.reference and _type == identifier_type:
610
+
611
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/xml;charset=UTF-8'}
612
+
613
+ xml_object = xml.etree.ElementTree.Element('Identifier', {"xmlns": self.xip_ns})
614
+ xml.etree.ElementTree.SubElement(xml_object, "Type").text = identifier_type
615
+ xml.etree.ElementTree.SubElement(xml_object, "Value").text = identifier_value
616
+ xml.etree.ElementTree.SubElement(xml_object, "Entity").text = entity.reference
617
+ xml_request = xml.etree.ElementTree.tostring(xml_object, encoding='utf-8')
618
+
619
+ put_response = self.session.put(
620
+ f'{self.protocol}://{self.server}/api/entity/{entity.path}/{entity.reference}/identifiers/{_aipid}',
621
+ headers=headers, data=xml_request)
622
+ if put_response.status_code == requests.codes.ok:
623
+ xml_string = str(put_response.content.decode("utf-8"))
624
+ identifier_response = xml.etree.ElementTree.fromstring(xml_string)
625
+ aip_id = identifier_response.find(f'.//{{{self.xip_ns}}}ApiId')
626
+ if hasattr(aip_id, 'text'):
627
+ return aip_id.text
628
+ else:
629
+ return None
630
+ if put_response.status_code == requests.codes.unauthorized:
631
+ self.token = self.__token__()
632
+ return self.update_identifiers(entity, identifier_type, identifier_value)
633
+ if put_response.status_code == requests.codes.no_content:
634
+ pass
635
+ else:
636
+ return None
637
+ return entity
638
+ elif response.status_code == requests.codes.unauthorized:
639
+ self.token = self.__token__()
640
+ return self.update_identifiers(entity, identifier_type, identifier_value)
641
+ else:
642
+ logger.error(response)
643
+ raise RuntimeError(response.status_code, "update_identifiers failed")
644
+
575
645
  def delete_relationships(self, entity: Entity, relationship_type: str = None):
576
646
  """
577
647
  Delete a relationship between two entities by its internal id
@@ -2275,6 +2345,8 @@ class EntityAPI(AuthenticatedAPI):
2275
2345
  entity_response = xml.etree.ElementTree.fromstring(req.content.decode("utf-8"))
2276
2346
  status = entity_response.find(".//{http://status.preservica.com}Status")
2277
2347
  if hasattr(status, 'text'):
2348
+ if status.text == "COMPLETED":
2349
+ return entity.reference
2278
2350
  if status.text == "PENDING":
2279
2351
  headers = {HEADER_TOKEN: self.manager_token(manager_username, manager_password),
2280
2352
  'Content-Type': 'application/xml;charset=UTF-8'}
@@ -96,6 +96,8 @@ def _object_from_json_(json_doc: dict) -> Group:
96
96
  gf.editable = bool(field['editable'])
97
97
  if 'values' in field:
98
98
  for v in field['values']:
99
+ if gf.values is None:
100
+ gf.values = []
99
101
  gf.values.append(str(v))
100
102
  if 'defaultValue' in field:
101
103
  gf.default = str(field['defaultValue'])
@@ -9,7 +9,6 @@ licence: Apache License 2.0
9
9
 
10
10
  """
11
11
 
12
-
13
12
  import xml.etree.ElementTree
14
13
  from typing import Set, Callable
15
14
 
@@ -60,7 +59,6 @@ class RetentionAPI(AuthenticatedAPI):
60
59
 
61
60
  def __init__(self, username=None, password=None, tenant=None, server=None, use_shared_secret=False,
62
61
  two_fa_secret_key: str = None, protocol: str = "https", request_hook: Callable = None):
63
-
64
62
  super().__init__(username, password, tenant, server, use_shared_secret, two_fa_secret_key,
65
63
  protocol, request_hook)
66
64
  if self.major_version < 7 and self.minor_version < 2:
@@ -78,7 +76,8 @@ class RetentionAPI(AuthenticatedAPI):
78
76
 
79
77
  """
80
78
  headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/xml;charset=UTF-8'}
81
- request = self.session.get(f'{self.protocol}://{self.server}/api/entity/retention-policies/{reference}', headers=headers)
79
+ request = self.session.get(f'{self.protocol}://{self.server}/api/entity/retention-policies/{reference}',
80
+ headers=headers)
82
81
  if request.status_code == requests.codes.ok:
83
82
  xml_response = str(request.content.decode('utf-8'))
84
83
  logger.debug(xml_response)
@@ -92,14 +91,22 @@ class RetentionAPI(AuthenticatedAPI):
92
91
  security_tag = entity_response.find(f'.//{{{self.rm_ns}}}RetentionPolicy/{{{self.rm_ns}}}SecurityTag').text
93
92
  rp.security_tag = security_tag
94
93
  start_date_field = entity_response.find(
95
- f'.//{{{self.rm_ns}}}RetentionPolicy/{{{self.rm_ns}}}StartDateField').text
96
- rp.start_date_field = start_date_field
97
- period = entity_response.find(f'.//{{{self.rm_ns}}}RetentionPolicy/{{{self.rm_ns}}}Period').text
98
- rp.period = period
99
- period_unit = entity_response.find(f'.//{{{self.rm_ns}}}RetentionPolicy/{{{self.rm_ns}}}PeriodUnit').text
100
- rp.period_unit = period_unit
101
- expiry_action = entity_response.find(
102
- f'.//{{{self.rm_ns}}}RetentionPolicy/{{{self.rm_ns}}}ExpiryAction')
94
+ f'.//{{{self.rm_ns}}}RetentionPolicy/{{{self.rm_ns}}}StartDateField')
95
+ if start_date_field is not None:
96
+ rp.start_date_field = start_date_field.text
97
+ else:
98
+ start_date_field = None
99
+ period = entity_response.find(f'.//{{{self.rm_ns}}}RetentionPolicy/{{{self.rm_ns}}}Period')
100
+ if period is not None:
101
+ rp.period = period.text
102
+ else:
103
+ rp.period = None
104
+ period_unit = entity_response.find(f'.//{{{self.rm_ns}}}RetentionPolicy/{{{self.rm_ns}}}PeriodUnit')
105
+ if period_unit is not None:
106
+ rp.period_unit = period_unit.text
107
+ else:
108
+ rp.period_unit = None
109
+ expiry_action = entity_response.find(f'.//{{{self.rm_ns}}}RetentionPolicy/{{{self.rm_ns}}}ExpiryAction')
103
110
  if expiry_action is not None:
104
111
  rp.expiry_action = expiry_action.text
105
112
  else:
@@ -134,8 +141,9 @@ class RetentionAPI(AuthenticatedAPI):
134
141
  """
135
142
  headers = {HEADER_TOKEN: self.token, 'Content-Type': 'text/plain;charset=UTF-8'}
136
143
  data = str(status)
137
- request = self.session.put(f'{self.protocol}://{self.server}/api/entity/retention-policies/{reference}/assignable',
138
- headers=headers, data=data)
144
+ request = self.session.put(
145
+ f'{self.protocol}://{self.server}/api/entity/retention-policies/{reference}/assignable',
146
+ headers=headers, data=data)
139
147
  if request.status_code == requests.codes.ok:
140
148
  pass
141
149
  elif request.status_code == requests.codes.unauthorized:
@@ -230,7 +238,8 @@ class RetentionAPI(AuthenticatedAPI):
230
238
 
231
239
  xml_request = xml.etree.ElementTree.tostring(retention_policy, encoding='utf-8')
232
240
 
233
- request = self.session.put(f'{self.protocol}://{self.server}/api/entity/retention-policies/{reference}', data=xml_request,
241
+ request = self.session.put(f'{self.protocol}://{self.server}/api/entity/retention-policies/{reference}',
242
+ data=xml_request,
234
243
  headers=headers)
235
244
  if request.status_code == requests.codes.ok:
236
245
  return self.policy(reference)
@@ -374,7 +383,8 @@ class RetentionAPI(AuthenticatedAPI):
374
383
  """
375
384
  headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/xml;charset=UTF-8'}
376
385
  data = {'start': str(0), 'max': "250"}
377
- request = self.session.get(f'{self.protocol}://{self.server}/api/entity/retention-policies', data=data, headers=headers)
386
+ request = self.session.get(f'{self.protocol}://{self.server}/api/entity/retention-policies', data=data,
387
+ headers=headers)
378
388
  if request.status_code == requests.codes.ok:
379
389
  xml_response = str(request.content.decode('utf-8'))
380
390
  logger.debug(xml_response)
@@ -390,10 +400,10 @@ class RetentionAPI(AuthenticatedAPI):
390
400
  else:
391
401
  raise RuntimeError(request.status_code, "policies failed")
392
402
 
393
- def policies(self) -> Set[RetentionPolicy]:
403
+ def policies(self, maximum: int = 250, next_page: str = None) -> PagedSet:
394
404
  """
395
405
  Return a list of all retention policies
396
- Only returns the first 250 policies in the system
406
+ Returns a maxmium of 250 policies by default
397
407
 
398
408
 
399
409
  :return: Set of retention policies
@@ -401,22 +411,33 @@ class RetentionAPI(AuthenticatedAPI):
401
411
 
402
412
  """
403
413
  headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/xml;charset=UTF-8'}
404
- data = {'start': str(0), 'max': "250"}
405
- request = self.session.get(f'{self.protocol}://{self.server}/api/entity/retention-policies', data=data, headers=headers)
414
+
415
+ if next_page is None:
416
+ params = {'start': '0', 'max': str(maximum)}
417
+ request = self.session.get(f'{self.protocol}://{self.server}/api/entity/retention-policies', params=params,
418
+ headers=headers)
419
+ else:
420
+ request = self.session.get(next_page, headers=headers)
421
+
406
422
  if request.status_code == requests.codes.ok:
407
423
  xml_response = str(request.content.decode('utf-8'))
408
424
  entity_response = xml.etree.ElementTree.fromstring(xml_response)
409
425
  logger.debug(xml_response)
410
426
  result = set()
427
+ next_url = entity_response.find(f'.//{{{self.entity_ns}}}Paging/{{{self.entity_ns}}}Next')
411
428
  total_results = int(entity_response.find(
412
429
  f'.//{{{self.entity_ns}}}TotalResults').text)
413
- if total_results > 250:
414
- logger.error("Not all retention policies have been returned.")
415
430
  for assignment in entity_response.findall(f'.//{{{self.entity_ns}}}RetentionPolicy'):
416
431
  ref = assignment.attrib['ref']
417
432
  name = assignment.attrib['name']
418
433
  result.add(self.policy(reference=ref))
419
- return result
434
+ has_more = True
435
+ url = None
436
+ if next_url is None:
437
+ has_more = False
438
+ else:
439
+ url = next_url.text
440
+ return PagedSet(result, has_more, total_results, url)
420
441
  elif request.status_code == requests.codes.unauthorized:
421
442
  self.token = self.__token__()
422
443
  return self.policies()
@@ -455,7 +476,11 @@ class RetentionAPI(AuthenticatedAPI):
455
476
  api_id = entity_response.find(f'.//{{{self.rm_ns}}}ApiId').text
456
477
  policy_ref = entity_response.find(f'.//{{{self.rm_ns}}}RetentionPolicy').text
457
478
  entity_ref = entity_response.find(f'.//{{{self.rm_ns}}}Entity').text
458
- start_date = entity_response.find(f'.//{{{self.rm_ns}}}StartDate').text
479
+ start_date = entity_response.find(f'.//{{{self.rm_ns}}}StartDate')
480
+ if start_date is not None:
481
+ start_date = start_date.text
482
+ else:
483
+ start_date = None
459
484
  assert entity_ref == entity.reference
460
485
  assert policy_ref == policy.reference
461
486
  return RetentionAssignment(entity_ref, policy_ref, api_id, start_date)
@@ -516,7 +541,11 @@ class RetentionAPI(AuthenticatedAPI):
516
541
  entity_ref = assignment.find(f'.//{{{self.rm_ns}}}Entity').text
517
542
  assert entity_ref == entity.reference
518
543
  policy = assignment.find(f'.//{{{self.rm_ns}}}RetentionPolicy').text
519
- start_date = assignment.find(f'.//{{{self.rm_ns}}}StartDate').text
544
+ start_date = assignment.find(f'.//{{{self.rm_ns}}}StartDate')
545
+ if start_date is not None:
546
+ start_date = start_date.text
547
+ else:
548
+ start_date = None
520
549
  expired = bool(assignment.find(f'.//{{{self.rm_ns}}}Expired').text == 'true')
521
550
  api_id = assignment.find(f'.//{{{self.rm_ns}}}ApiId').text
522
551
  ra = RetentionAssignment(entity_ref, policy, api_id, start_date, expired)
@@ -1421,7 +1421,7 @@ class UploadAPI(AuthenticatedAPI):
1421
1421
  try:
1422
1422
  auth = tweepy.AppAuthHandler(twitter_consumer_key, twitter_secret_key)
1423
1423
  api = tweepy.API(auth, wait_on_rate_limit=True)
1424
- except TweepError:
1424
+ except RuntimeError:
1425
1425
  logger.error("No valid Twitter API keys. Could not authenticate")
1426
1426
  raise RuntimeError("No valid Twitter API keys. Could not authenticate")
1427
1427
  if api is not None:
@@ -1734,8 +1734,8 @@ class UploadAPI(AuthenticatedAPI):
1734
1734
  self.upload_zip_package(path_to_zip_package=package, callback=progress_display,
1735
1735
  delete_after_upload=delete_after_upload)
1736
1736
  else:
1737
- self.upload_zip_package_to_S3(path_to_zip_package=package, bucket_name=bucket_name,
1738
- callback=progress_display,
1737
+ self.upload_zip_to_Source(path_to_zip_package=package, container_name=bucket_name,
1738
+ show_progress= bool(progress_display is not None),
1739
1739
  delete_after_upload=delete_after_upload)
1740
1740
 
1741
1741
  logger.info(f"Uploaded " + "{:.1f}".format(bytes_ingested / (1024 * 1024)) + " MB")
@@ -74,6 +74,7 @@ class TriggerType(Enum):
74
74
  MOVED = "MOVED"
75
75
  INDEXED = "FULL_TEXT_INDEXED"
76
76
  SECURITY_CHANGED = "CHANGED_SECURITY_DESCRIPTOR"
77
+ INGEST_FAILED = "INGEST_FAILED"
77
78
 
78
79
 
79
80
  class WebHooksAPI(AuthenticatedAPI):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyPreservica
3
- Version: 2.8.2
3
+ Version: 2.9.1
4
4
  Summary: Python library for the Preservica API
5
5
  Home-page: https://pypreservica.readthedocs.io/
6
6
  Author: James Carr
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.8
15
15
  Classifier: Programming Language :: Python :: 3.9
16
16
  Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
18
19
  Classifier: License :: OSI Approved :: Apache Software License
19
20
  Classifier: Operating System :: OS Independent
20
21
  Classifier: Topic :: System :: Archiving
@@ -34,12 +35,9 @@ Requires-Dist: pyotp
34
35
  # pyPreservica
35
36
 
36
37
 
37
- <!---
38
- [![Downloads](https://pepy.tech/badge/pyPreservica/month)](https://pepy.tech/project/pyPreservica/month)
39
- --->
40
-
41
38
  [![Supported Versions](https://img.shields.io/pypi/pyversions/pyPreservica.svg)](https://pypi.org/project/pyPreservica)
42
39
  [![CodeQL](https://github.com/carj/pyPreservica/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/carj/pyPreservica/actions/workflows/codeql-analysis.yml)
40
+ ![PyPI Downloads](https://static.pepy.tech/badge/pypreservica)
43
41
 
44
42
  Python language binding for the Preservica API
45
43
 
@@ -21,7 +21,7 @@ if sys.argv[-1] == 'publish':
21
21
  # This call to setup() does all the work
22
22
  setup(
23
23
  name=PKG,
24
- version="2.8.2",
24
+ version="2.9.1",
25
25
  description="Python library for the Preservica API",
26
26
  long_description=README,
27
27
  long_description_content_type="text/markdown",
@@ -36,6 +36,7 @@ setup(
36
36
  'Programming Language :: Python :: 3.9',
37
37
  'Programming Language :: Python :: 3.10',
38
38
  'Programming Language :: Python :: 3.11',
39
+ 'Programming Language :: Python :: 3.12',
39
40
  "License :: OSI Approved :: Apache Software License",
40
41
  "Operating System :: OS Independent",
41
42
  "Topic :: System :: Archiving",
@@ -0,0 +1,38 @@
1
+ import pytest
2
+ from pyPreservica import *
3
+
4
+
5
+ def setup():
6
+ pass
7
+
8
+ def tear_down():
9
+ pass
10
+
11
+
12
+ @pytest.fixture
13
+ def setup_data():
14
+ print("\nSetting up resources...")
15
+
16
+ setup()
17
+
18
+ yield
19
+
20
+ print("\nTearing down resources...")
21
+
22
+ tear_down()
23
+
24
+
25
+ def test_get_tables(setup_data):
26
+ client = AuthorityAPI()
27
+ results = client.tables()
28
+ assert isinstance(results, set)
29
+ for table in results:
30
+ assert isinstance(table, Table)
31
+ assert table.name in ["Countries", 'Physical Storage']
32
+
33
+
34
+ def test_get_records(setup_data):
35
+ client = AuthorityAPI()
36
+ tables = client.tables()
37
+ for tab in tables:
38
+ records = client.records(tab)
@@ -3,10 +3,32 @@ from pathlib import Path
3
3
  import pytest
4
4
  from pyPreservica import *
5
5
 
6
+
7
+ def setup():
8
+ pass
9
+
10
+
11
+ def tear_down():
12
+ pass
13
+
14
+
15
+ @pytest.fixture
16
+ def setup_data():
17
+ print("\nSetting up resources...")
18
+
19
+ setup()
20
+
21
+ yield
22
+
23
+ print("\nTearing down resources...")
24
+
25
+ tear_down()
26
+
27
+
6
28
  ASSET_ID = "b14848b5-4c4d-4d8a-b394-3b764069ee93"
7
29
 
8
30
 
9
- def test_get_representations():
31
+ def test_get_representations(setup_data):
10
32
  client = EntityAPI()
11
33
  asset = client.asset(ASSET_ID)
12
34
  assert asset is not None
@@ -20,7 +42,7 @@ def test_get_representations():
20
42
  assert representation.name == "Preservation-1"
21
43
 
22
44
 
23
- def test_get_generations():
45
+ def test_get_generations(setup_data):
24
46
  client = EntityAPI()
25
47
  asset = client.asset(ASSET_ID)
26
48
  assert asset is not None
@@ -60,7 +82,7 @@ def test_get_generations():
60
82
  assert generation.format_group == "jpeg"
61
83
 
62
84
 
63
- def test_get_bitstream_content():
85
+ def test_get_bitstream_content(setup_data):
64
86
  client = EntityAPI()
65
87
  asset = client.asset(ASSET_ID)
66
88
  preservation_representations = list(filter(lambda x: x.rep_type == "Preservation", client.representations(asset)))
@@ -8,7 +8,30 @@ ASSET_ID = "683f9db7-ff81-4859-9c03-f68cfa5d9c3d"
8
8
  CO_ID = "0f2997f7-728c-4e55-9f92-381ed1260d70"
9
9
 
10
10
 
11
- def test_get_root_folders():
11
+
12
+ def setup():
13
+ pass
14
+
15
+
16
+ def tear_down():
17
+ pass
18
+
19
+
20
+ @pytest.fixture
21
+ def setup_data():
22
+ print("\nSetting up resources...")
23
+
24
+ setup()
25
+
26
+ yield
27
+
28
+ print("\nTearing down resources...")
29
+
30
+ tear_down()
31
+
32
+
33
+
34
+ def test_get_root_folders(setup_data):
12
35
  client = EntityAPI()
13
36
  paged_set = client.children(None)
14
37
  assert paged_set.total > 0
@@ -20,14 +43,14 @@ def test_get_root_folders():
20
43
  assert len(objs) == paged_set.total
21
44
 
22
45
 
23
- def test_get_root_folders_descendants():
46
+ def test_get_root_folders_descendants(setup_data):
24
47
  client = EntityAPI()
25
48
  for f in client.descendants(None):
26
49
  assert f.entity_type == EntityType.FOLDER
27
50
  assert f.parent is None
28
51
 
29
52
 
30
- def test_get_root_folder1():
53
+ def test_get_root_folder1(setup_data):
31
54
  client = EntityAPI()
32
55
  paged_set = client.children()
33
56
  assert paged_set.total > 0
@@ -39,7 +62,7 @@ def test_get_root_folder1():
39
62
  assert len(objs) == paged_set.total
40
63
 
41
64
 
42
- def test_get_root_folders_paged():
65
+ def test_get_root_folders_paged(setup_data):
43
66
  client = EntityAPI()
44
67
  objs = set()
45
68
  url = None
@@ -56,7 +79,7 @@ def test_get_root_folders_paged():
56
79
  assert len(objs) == paged_set.total
57
80
 
58
81
 
59
- def test_get_children_of_folder():
82
+ def test_get_children_of_folder(setup_data):
60
83
  client = EntityAPI()
61
84
  paged_set = client.children(FOLDER_ID)
62
85
  assert paged_set.total == 171
@@ -65,7 +88,7 @@ def test_get_children_of_folder():
65
88
  assert f.parent == FOLDER_ID
66
89
 
67
90
 
68
- def test_get_children_of_folder_descendants():
91
+ def test_get_children_of_folder_descendants(setup_data):
69
92
  client = EntityAPI()
70
93
  objs = set()
71
94
  for f in client.descendants(FOLDER_ID):
@@ -7,7 +7,30 @@ ASSET_ID = "683f9db7-ff81-4859-9c03-f68cfa5d9c3d"
7
7
  CO_ID = "0f2997f7-728c-4e55-9f92-381ed1260d70"
8
8
 
9
9
 
10
- def test_get_asset_details():
10
+
11
+
12
+ def setup():
13
+ pass
14
+
15
+
16
+ def tear_down():
17
+ pass
18
+
19
+
20
+ @pytest.fixture
21
+ def setup_data():
22
+ print("\nSetting up resources...")
23
+
24
+ setup()
25
+
26
+ yield
27
+
28
+ print("\nTearing down resources...")
29
+
30
+ tear_down()
31
+
32
+
33
+ def test_get_asset_details(setup_data):
11
34
  client = ContentAPI()
12
35
  json_dict = client.object_details(EntityType.ASSET, ASSET_ID)
13
36
  assert json_dict is not None
@@ -15,41 +38,41 @@ def test_get_asset_details():
15
38
  assert json_dict['name'] == 'LC-USZ62-20901'
16
39
 
17
40
 
18
- def test_download_asset():
41
+ def test_download_asset(setup_data):
19
42
  client = ContentAPI()
20
43
  filename = client.download(ASSET_ID, "filename.img")
21
44
  assert os.path.exists(filename)
22
45
  os.remove(filename)
23
46
 
24
47
 
25
- def test_download_folder():
48
+ def test_download_folder(setup_data):
26
49
  client = ContentAPI()
27
50
  with pytest.raises(RuntimeError):
28
51
  filename = client.download(FOLDER_ID, "filename.img")
29
52
 
30
53
 
31
- def test_get_thumbnail_small():
54
+ def test_get_thumbnail_small(setup_data):
32
55
  client = ContentAPI()
33
56
  small = client.thumbnail("IO", "464444f7-8a6e-40f3-86c3-1dd2a51cfeeb", "filename.img", Thumbnail.SMALL)
34
57
  assert os.path.exists(small)
35
58
  os.remove(small)
36
59
 
37
60
 
38
- def test_get_thumbnail_med():
61
+ def test_get_thumbnail_med(setup_data):
39
62
  client = ContentAPI()
40
63
  med = client.thumbnail("IO", "8d268bed-93d7-449b-91e4-2e7e86562a07", "filename.img", Thumbnail.MEDIUM)
41
64
  assert os.path.exists(med)
42
65
  os.remove(med)
43
66
 
44
67
 
45
- def test_get_thumbnail_large():
68
+ def test_get_thumbnail_large(setup_data):
46
69
  client = ContentAPI()
47
70
  large = client.thumbnail("IO", "d5048e76-79c5-4ca7-99c9-a202c8f6dc8b", "filename.img", Thumbnail.LARGE)
48
71
  assert os.path.exists(large)
49
72
  os.remove(large)
50
73
 
51
74
 
52
- def test_get_indexed_fields():
75
+ def test_get_indexed_fields(setup_data):
53
76
  client = ContentAPI()
54
77
  fields = client.indexed_fields()
55
78
  assert fields is not None
@@ -57,24 +80,24 @@ def test_get_indexed_fields():
57
80
  assert 'xip.description' in fields
58
81
 
59
82
 
60
- def test_simple_search_list():
83
+ def test_simple_search_list(setup_data):
61
84
  client = ContentAPI()
62
85
  results = list(client.simple_search_list(query="pyPreservica"))
63
- assert len(results) == 2
86
+ assert len(results) == 5
64
87
  assert results.pop()['xip.reference'] == '9fd239eb-19a3-4a46-9495-40fd9a5d8f93'
65
88
 
66
89
 
67
- def test_simple_search_list2():
90
+ def test_simple_search_list2(setup_data):
68
91
  client = ContentAPI()
69
92
 
70
93
  columns = ["xip.reference", "xip.title", "xip.description", "xip.document_type"]
71
94
 
72
95
  results = list(client.simple_search_list("pyPreservica", 25, columns))
73
- assert len(results) == 2
96
+ assert len(results) == 5
74
97
  assert results.pop()['xip.reference'] == '9fd239eb-19a3-4a46-9495-40fd9a5d8f93'
75
98
 
76
99
 
77
- def test_field_search():
100
+ def test_field_search(setup_data):
78
101
  search = ContentAPI()
79
102
  for result in search.search_index_filter_list(query="%", filter_values={"xip.security_descriptor": "open",
80
103
  "xip.document_type": "IO",
@@ -6,7 +6,40 @@ ASSET_ID = "683f9db7-ff81-4859-9c03-f68cfa5d9c3d"
6
6
  CO_ID = "0f2997f7-728c-4e55-9f92-381ed1260d70"
7
7
 
8
8
 
9
- def test_get_entity_by_identifier():
9
+ def delete_identifiers():
10
+ client = EntityAPI()
11
+ folder = client.folder(FOLDER_ID)
12
+ for i in client.identifiers_for_entity(folder):
13
+ client.delete_identifiers(folder, i[0], i[1])
14
+
15
+ asset = client.asset(ASSET_ID)
16
+ for i in client.identifiers_for_entity(asset):
17
+ client.delete_identifiers(asset, i[0], i[1])
18
+
19
+ co = client.content_object(CO_ID)
20
+ for i in client.identifiers_for_entity(co):
21
+ client.delete_identifiers(co, i[0], i[1])
22
+
23
+
24
+ @pytest.fixture
25
+ def setup_data():
26
+ print("\nSetting up resources...")
27
+
28
+ client = EntityAPI()
29
+ folder = client.folder(FOLDER_ID)
30
+
31
+ delete_identifiers()
32
+
33
+ client.add_identifier(folder, "code", "Amelia Earhart")
34
+
35
+ yield
36
+
37
+ print("\nTearing down resources...")
38
+
39
+ delete_identifiers()
40
+
41
+
42
+ def test_get_entity_by_identifier(setup_data):
10
43
  client = EntityAPI()
11
44
  list_of_objects = client.identifier("code", "Amelia Earhart")
12
45
  assert len(list_of_objects) == 1
@@ -16,7 +49,7 @@ def test_get_entity_by_identifier():
16
49
  assert entity.reference == FOLDER_ID
17
50
 
18
51
 
19
- def test_add_identifier_folder():
52
+ def test_add_identifier_folder(setup_data):
20
53
  client = EntityAPI()
21
54
  folder = client.folder(FOLDER_ID)
22
55
  client.add_identifier(folder, "ISBN", "1234567890")
@@ -26,7 +59,7 @@ def test_add_identifier_folder():
26
59
  assert entity.reference == folder.reference
27
60
 
28
61
 
29
- def test_add_identifier_asset():
62
+ def test_add_identifier_asset(setup_data):
30
63
  client = EntityAPI()
31
64
  asset = client.asset(ASSET_ID)
32
65
  client.add_identifier(asset, "ARK", "1234567890")
@@ -37,7 +70,7 @@ def test_add_identifier_asset():
37
70
  assert len(client.identifiers_for_entity(asset)) == 1
38
71
 
39
72
 
40
- def test_add_identifier_content_obj():
73
+ def test_add_identifier_content_obj(setup_data):
41
74
  client = EntityAPI()
42
75
  content_object = client.content_object(CO_ID)
43
76
  client.add_identifier(content_object, "CO", "12333")
@@ -48,11 +81,15 @@ def test_add_identifier_content_obj():
48
81
  assert len(client.identifiers_for_entity(content_object)) == 1
49
82
 
50
83
 
51
- def test_get_identifier_folder():
84
+ def test_get_identifier_folder(setup_data):
52
85
  client = EntityAPI()
53
86
  folder = client.folder(FOLDER_ID)
87
+
88
+ client.add_identifier(folder, "ISBN", "1234567890")
89
+ client.add_identifier(folder, "ARK", "1234567890")
90
+
54
91
  identifiers = client.identifiers_for_entity(folder)
55
- assert len(identifiers) == 2
92
+ assert len(identifiers) == 3
56
93
  test_pass = False
57
94
  for identifier in identifiers:
58
95
  if "ISBN" in identifier:
@@ -61,9 +98,12 @@ def test_get_identifier_folder():
61
98
  assert test_pass
62
99
 
63
100
 
64
- def test_get_identifier_asset():
101
+ def test_get_identifier_asset(setup_data):
65
102
  client = EntityAPI()
66
103
  asset = client.asset(ASSET_ID)
104
+
105
+ client.add_identifier(asset, "ARK", "1234567890")
106
+
67
107
  identifiers = client.identifiers_for_entity(asset)
68
108
  assert len(identifiers) == 1
69
109
  test_pass = False
@@ -74,9 +114,12 @@ def test_get_identifier_asset():
74
114
  assert test_pass
75
115
 
76
116
 
77
- def test_delete_identifier_folder():
117
+ def test_delete_identifier_folder(setup_data):
78
118
  client = EntityAPI()
79
119
  folder = client.folder(FOLDER_ID)
120
+
121
+ client.add_identifier(folder, "ISBN", "1234567890")
122
+
80
123
  entity_set = client.identifier("ISBN", "1234567890")
81
124
  assert len(entity_set) == 1
82
125
  entity = entity_set.pop()
@@ -89,9 +132,12 @@ def test_delete_identifier_folder():
89
132
  assert len(identifiers) == 1
90
133
 
91
134
 
92
- def test_delete_identifier_asset():
135
+ def test_delete_identifier_asset(setup_data):
93
136
  client = EntityAPI()
94
137
  asset = client.asset(ASSET_ID)
138
+
139
+ client.add_identifier(asset, "ARK", "1234567890")
140
+
95
141
  entity_set = client.identifier("ARK", "1234567890")
96
142
  assert len(entity_set) == 1
97
143
  entity = entity_set.pop()
@@ -101,9 +147,12 @@ def test_delete_identifier_asset():
101
147
  assert len(entity_set) == 0
102
148
 
103
149
 
104
- def test_delete_identifier_co():
150
+ def test_delete_identifier_co(setup_data):
105
151
  client = EntityAPI()
106
152
  content_object = client.content_object(CO_ID)
153
+
154
+ client.add_identifier(content_object, "CO", "12333")
155
+
107
156
  entity_set = client.identifier("CO", "12333")
108
157
  assert len(entity_set) == 1
109
158
  entity = entity_set.pop()
@@ -113,7 +162,7 @@ def test_delete_identifier_co():
113
162
  assert len(entity_set) == 0
114
163
 
115
164
 
116
- def test_add_same_id_to_asset_folder():
165
+ def test_add_same_id_to_asset_folder(setup_data):
117
166
  client = EntityAPI()
118
167
  asset = client.asset(ASSET_ID)
119
168
  folder = client.folder(FOLDER_ID)
@@ -129,3 +178,19 @@ def test_add_same_id_to_asset_folder():
129
178
  client.delete_identifiers(folder, "DOI", "123456:98776")
130
179
  entity_set = client.identifier("DOI", "123456:98776")
131
180
  assert len(entity_set) == 0
181
+
182
+
183
+ def test_can_update_identifier(setup_data):
184
+ client = EntityAPI()
185
+ folder = client.folder(FOLDER_ID)
186
+ client.add_identifier(folder, "ISBN", "ISBN_0001")
187
+ entity_set = client.identifier("ISBN", "ISBN_0001")
188
+ assert len(entity_set) == 1
189
+ entity = entity_set.pop()
190
+ assert entity.reference == folder.reference
191
+ client.update_identifiers(folder, "ISBN", "ISBN_0002")
192
+ entity_set = client.identifier("ISBN", "ISBN_0002")
193
+ assert len(entity_set) == 1
194
+ entity = entity_set.pop()
195
+ assert entity.reference == folder.reference
196
+ client.delete_identifiers(folder, "ISBN", "ISBN_0002")
@@ -6,7 +6,7 @@ from pyPreservica import *
6
6
  def test_format_families():
7
7
  par = PreservationActionRegistry()
8
8
  document = par.format_families()
9
- assert len(json.loads(document)['formatFamilies']) == 185
9
+ assert len(json.loads(document)['formatFamilies']) == 190
10
10
 
11
11
 
12
12
  def test_format():
@@ -14,7 +14,7 @@ def test_get_policy_by_name():
14
14
 
15
15
  def test_get_policies():
16
16
  retention = RetentionAPI()
17
- for p in retention.policies():
17
+ for p in retention.policies().results:
18
18
  print(p.name)
19
19
  print(p.reference)
20
20
  print(p.description)
@@ -106,7 +106,7 @@ def test_add_assignments():
106
106
 
107
107
  assert len(retention_assignments) == 0
108
108
 
109
- policy = retention.policies().pop()
109
+ policy = retention.policies().results.pop()
110
110
 
111
111
  if not policy.assignable:
112
112
  retention.assignable_policy(policy.reference, True)
@@ -126,7 +126,7 @@ def test_add_assignments():
126
126
 
127
127
  def test_zdelete_policy():
128
128
  retention = RetentionAPI()
129
- for policy in retention.policies():
129
+ for policy in retention.policies().results:
130
130
  if policy.name == "API Created Policy1":
131
131
  retention.delete_policy(policy.reference)
132
132
 
@@ -1,20 +0,0 @@
1
- import pytest
2
- from pyPreservica import *
3
-
4
-
5
- def test_get_tables():
6
- client = AuthorityAPI()
7
- results = client.tables()
8
- assert isinstance(results, set)
9
- for table in results:
10
- assert isinstance(table, Table)
11
- assert table.name in ["Countries", 'Physical Storage']
12
-
13
-
14
- def test_get_records():
15
- client = AuthorityAPI()
16
- tables = client.tables()
17
- for tab in tables:
18
- records = client.records(tab)
19
-
20
-
File without changes
File without changes