pyPreservica 3.2.2__tar.gz → 3.2.3__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.

Potentially problematic release.


This version of pyPreservica might be problematic. Click here for more details.

Files changed (50) hide show
  1. {pypreservica-3.2.2 → pypreservica-3.2.3}/PKG-INFO +1 -1
  2. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica/__init__.py +1 -1
  3. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica/adminAPI.py +19 -13
  4. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica/authorityAPI.py +1 -1
  5. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica/common.py +10 -5
  6. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica/entityAPI.py +85 -29
  7. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica/mdformsAPI.py +20 -0
  8. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica/webHooksAPI.py +1 -0
  9. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica.egg-info/PKG-INFO +1 -1
  10. {pypreservica-3.2.2 → pypreservica-3.2.3}/setup.py +1 -1
  11. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_export_opex.py +29 -4
  12. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_schema.py +11 -11
  13. {pypreservica-3.2.2 → pypreservica-3.2.3}/LICENSE.txt +0 -0
  14. {pypreservica-3.2.2 → pypreservica-3.2.3}/README.md +0 -0
  15. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica/contentAPI.py +0 -0
  16. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica/monitorAPI.py +0 -0
  17. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica/opex.py +0 -0
  18. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica/parAPI.py +0 -0
  19. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica/retentionAPI.py +0 -0
  20. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica/settingsAPI.py +0 -0
  21. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica/uploadAPI.py +0 -0
  22. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica/workflowAPI.py +0 -0
  23. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica.egg-info/SOURCES.txt +0 -0
  24. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica.egg-info/dependency_links.txt +0 -0
  25. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica.egg-info/requires.txt +0 -0
  26. {pypreservica-3.2.2 → pypreservica-3.2.3}/pyPreservica.egg-info/top_level.txt +0 -0
  27. {pypreservica-3.2.2 → pypreservica-3.2.3}/setup.cfg +0 -0
  28. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_authority_records.py +0 -0
  29. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_bitstream.py +0 -0
  30. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_children.py +0 -0
  31. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_content_api.py +0 -0
  32. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_crawl_fs.py +0 -0
  33. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_delete.py +0 -0
  34. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_download.py +0 -0
  35. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_entity.py +0 -0
  36. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_groups.py +0 -0
  37. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_identifier.py +0 -0
  38. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_ingest.py +0 -0
  39. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_integrity_check.py +0 -0
  40. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_metadata.py +0 -0
  41. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_par.py +0 -0
  42. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_replace.py +0 -0
  43. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_retention.py +0 -0
  44. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_security.py +0 -0
  45. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_settings.py +0 -0
  46. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_thumbnail.py +0 -0
  47. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_upload.py +0 -0
  48. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_users.py +0 -0
  49. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_workflow.py +0 -0
  50. {pypreservica-3.2.2 → pypreservica-3.2.3}/tests/test_xml_metadata.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyPreservica
3
- Version: 3.2.2
3
+ Version: 3.2.3
4
4
  Summary: Python library for the Preservica API
5
5
  Home-page: https://pypreservica.readthedocs.io/
6
6
  Author: James Carr
@@ -35,6 +35,6 @@ from .settingsAPI import SettingsAPI
35
35
  __author__ = "James Carr (drjamescarr@gmail.com)"
36
36
 
37
37
  # Version of the pyPreservica package
38
- __version__ = "3.2.2"
38
+ __version__ = "3.2.3"
39
39
 
40
40
  __license__ = "Apache License Version 2.0"
@@ -10,7 +10,7 @@ licence: Apache License 2.0
10
10
  """
11
11
  import csv
12
12
  import xml.etree.ElementTree
13
- from typing import List, Any
13
+ from typing import List, Any, Union
14
14
 
15
15
  from pyPreservica.common import *
16
16
 
@@ -36,7 +36,7 @@ class AdminAPI(AuthenticatedAPI):
36
36
  request = self.session.delete(f'{self.protocol}://{self.server}/api/admin/security/roles/{role_name}',
37
37
  headers=headers)
38
38
  if request.status_code == requests.codes.no_content:
39
- return
39
+ return None
40
40
  elif request.status_code == requests.codes.unauthorized:
41
41
  self.token = self.__token__()
42
42
  return self.delete_system_role(role_name)
@@ -61,7 +61,7 @@ class AdminAPI(AuthenticatedAPI):
61
61
  request = self.session.delete(f'{self.protocol}://{self.server}/api/admin/security/tags/{tag_name}',
62
62
  headers=headers)
63
63
  if request.status_code == requests.codes.no_content:
64
- return
64
+ return None
65
65
  elif request.status_code == requests.codes.unauthorized:
66
66
  self.token = self.__token__()
67
67
  return self.delete_security_tag(tag_name)
@@ -211,7 +211,7 @@ class AdminAPI(AuthenticatedAPI):
211
211
  headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/xml;charset=UTF-8'}
212
212
  request = self.session.delete(f'{self.protocol}://{self.server}/api/admin/users/{username}', headers=headers)
213
213
  if request.status_code == requests.codes.no_content:
214
- return
214
+ return None
215
215
  elif request.status_code == requests.codes.unauthorized:
216
216
  self.token = self.__token__()
217
217
  return self.delete_user(username)
@@ -463,7 +463,7 @@ class AdminAPI(AuthenticatedAPI):
463
463
  params=params,
464
464
  data=xml_data)
465
465
  if request.status_code == requests.codes.created:
466
- return
466
+ return None
467
467
  elif request.status_code == requests.codes.unauthorized:
468
468
  self.token = self.__token__()
469
469
  return self.add_xml_schema(name, description, originalName, xml_data)
@@ -513,7 +513,7 @@ class AdminAPI(AuthenticatedAPI):
513
513
  params=params,
514
514
  data=xml_data)
515
515
  if request.status_code == requests.codes.created:
516
- return
516
+ return None
517
517
  elif request.status_code == requests.codes.unauthorized:
518
518
  self.token = self.__token__()
519
519
  return self.add_xml_document(name, xml_data, document_type)
@@ -543,13 +543,14 @@ class AdminAPI(AuthenticatedAPI):
543
543
  f"{self.protocol}://{self.server}/api/admin/documents/{document['ApiId']}",
544
544
  headers=headers)
545
545
  if request.status_code == requests.codes.no_content:
546
- return
546
+ return None
547
547
  elif request.status_code == requests.codes.unauthorized:
548
548
  self.token = self.__token__()
549
549
  return self.delete_xml_document(uri)
550
550
  else:
551
551
  logger.error(request.content.decode('utf-8'))
552
552
  raise RuntimeError(request.status_code, "delete_xml_document failed")
553
+ return None
553
554
 
554
555
  def delete_xml_schema(self, uri: str):
555
556
  """
@@ -572,15 +573,16 @@ class AdminAPI(AuthenticatedAPI):
572
573
  request = self.session.delete(f"{self.protocol}://{self.server}/api/admin/schemas/{schema['ApiId']}",
573
574
  headers=headers)
574
575
  if request.status_code == requests.codes.no_content:
575
- return
576
+ return None
576
577
  elif request.status_code == requests.codes.unauthorized:
577
578
  self.token = self.__token__()
578
579
  return self.delete_xml_schema(uri)
579
580
  else:
580
581
  logger.error(request.content.decode('utf-8'))
581
582
  raise RuntimeError(request.status_code, "delete_xml_schema failed")
583
+ return None
582
584
 
583
- def xml_schema(self, uri: str) -> str:
585
+ def xml_schema(self, uri: str) -> Union[str, None]:
584
586
  """
585
587
  Fetch the metadata schema XSD document as a string by its URI
586
588
 
@@ -607,8 +609,9 @@ class AdminAPI(AuthenticatedAPI):
607
609
  else:
608
610
  logger.error(request.content.decode('utf-8'))
609
611
  raise RuntimeError(request.status_code, "xml_schema failed")
612
+ return None
610
613
 
611
- def xml_document(self, uri: str) -> str:
614
+ def xml_document(self, uri: str) -> Union[str, None]:
612
615
  """
613
616
  fetch the metadata XML document as a string by its URI
614
617
 
@@ -634,6 +637,7 @@ class AdminAPI(AuthenticatedAPI):
634
637
  else:
635
638
  logger.error(request.content.decode('utf-8'))
636
639
  raise RuntimeError(request.status_code, "xml_document failed")
640
+ return None
637
641
 
638
642
  def xml_documents(self) -> List:
639
643
  """
@@ -754,7 +758,7 @@ class AdminAPI(AuthenticatedAPI):
754
758
  logger.error(request.content.decode('utf-8'))
755
759
  raise RuntimeError(request.status_code, "xml_transforms failed")
756
760
 
757
- def xml_transform(self, input_uri: str, output_uri: str) -> str:
761
+ def xml_transform(self, input_uri: str, output_uri: str) -> Union[str, None]:
758
762
  """
759
763
  fetch the XML transform as a string by its URIs
760
764
 
@@ -782,6 +786,7 @@ class AdminAPI(AuthenticatedAPI):
782
786
  else:
783
787
  logger.error(request.content.decode('utf-8'))
784
788
  raise RuntimeError(request.status_code, "xml_transform failed")
789
+ return None
785
790
 
786
791
  def delete_xml_transform(self, input_uri: str, output_uri: str):
787
792
  """
@@ -808,13 +813,14 @@ class AdminAPI(AuthenticatedAPI):
808
813
  f"{self.protocol}://{self.server}/api/admin/transforms/{transform['ApiId']}",
809
814
  headers=headers)
810
815
  if request.status_code == requests.codes.no_content:
811
- return
816
+ return None
812
817
  elif request.status_code == requests.codes.unauthorized:
813
818
  self.token = self.__token__()
814
819
  return self.delete_xml_transform(input_uri, output_uri)
815
820
  else:
816
821
  logger.error(request.content.decode('utf-8'))
817
822
  raise RuntimeError(request.status_code, "delete_xml_transform failed")
823
+ return None
818
824
 
819
825
  def add_xml_transform(self, name: str, input_uri: str, output_uri: str, purpose: str, originalName: str,
820
826
  xml_data: Any):
@@ -860,7 +866,7 @@ class AdminAPI(AuthenticatedAPI):
860
866
  params=params,
861
867
  data=xml_data)
862
868
  if request.status_code == requests.codes.created:
863
- return
869
+ return None
864
870
 
865
871
  if request.status_code == requests.codes.unauthorized:
866
872
  self.token = self.__token__()
@@ -54,7 +54,7 @@ class AuthorityAPI(AuthenticatedAPI):
54
54
  self.token = self.__token__()
55
55
  return self.delete_record(reference)
56
56
  if response.status_code == requests.codes.no_content:
57
- return
57
+ return None
58
58
  else:
59
59
  exception = HTTPException("", response.status_code, response.url, "delete_record",
60
60
  response.content.decode('utf-8'))
@@ -673,19 +673,24 @@ class AuthenticatedAPI:
673
673
  logger.error(f"The AdminAPI requires the user to have ROLE_SDB_MANAGER_USER")
674
674
  raise RuntimeError(f"The API requires the user to have at least the ROLE_SDB_MANAGER_USER")
675
675
 
676
- def _find_user_roles_(self) -> list:
676
+ def _find_user_roles_(self) -> list[str]:
677
677
  """
678
678
  Get a list of roles for the user
679
679
  :return list of roles:
680
680
  """
681
- headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/xml;charset=UTF-8'}
681
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json'}
682
682
  request = self.session.get(f"{self.protocol}://{self.server}/api/user/details", headers=headers)
683
+ logger.debug(request.headers)
683
684
  if request.status_code == requests.codes.ok:
684
- roles = json.loads(str(request.content.decode('utf-8')))['roles']
685
+ json_document = str(request.content.decode('utf-8'))
686
+ logger.debug(json_document)
687
+ roles: list[str] = json.loads(json_document)['roles']
685
688
  return roles
686
689
  elif request.status_code == requests.codes.unauthorized:
687
690
  self.token = self.__token__()
688
691
  return self._find_user_roles_()
692
+ return []
693
+
689
694
 
690
695
  def security_tags_base(self, with_permissions: bool = False) -> dict:
691
696
  """
@@ -847,7 +852,7 @@ class AuthenticatedAPI:
847
852
  with open('credentials.properties', 'wt', encoding="utf-8") as configfile:
848
853
  config.write(configfile)
849
854
 
850
- def manager_token(self, username: str, password: str):
855
+ def manager_token(self, username: str, password: str) -> str:
851
856
  data = {'username': username, 'password': password, 'tenant': self.tenant}
852
857
  response = self.session.post(f'{self.protocol}://{self.server}/api/accesstoken/login', data=data)
853
858
  if response.status_code == requests.codes.ok:
@@ -859,7 +864,7 @@ class AuthenticatedAPI:
859
864
  logger.error(str(response.content))
860
865
  RuntimeError(response.status_code, "Could not generate valid manager approval token")
861
866
 
862
- def __token__(self):
867
+ def __token__(self) -> str:
863
868
  """
864
869
  Generate am API token to use to authenticate calls
865
870
  :return: API Token
@@ -82,28 +82,28 @@ class EntityAPI(AuthenticatedAPI):
82
82
 
83
83
  def bitstream_bytes(self, bitstream: Bitstream, chunk_size: int = CHUNK_SIZE) -> Union[BytesIO, None]:
84
84
  """
85
- Download a file represented as a Bitstream to a byteIO array
85
+ Download a file represented as a Bitstream to a byteIO array
86
86
 
87
- Returns the byteIO
88
- Returns None if the file does not contain the correct number of bytes (default 2k)
87
+ Returns the byteIO
88
+ Returns None if the file does not contain the correct number of bytes (default 2k)
89
89
 
90
- :param chunk_size: The buffer copy chunk size in bytes default
91
- :param bitstream: A Bitstream object
92
- :type bitstream: Bitstream
90
+ :param chunk_size: The buffer copy chunk size in bytes default
91
+ :param bitstream: A Bitstream object
92
+ :type bitstream: Bitstream
93
93
 
94
- :return: The file in bytes
95
- :rtype: byteIO
94
+ :return: The file in bytes
95
+ :rtype: byteIO
96
96
  """
97
97
  if not isinstance(bitstream, Bitstream):
98
98
  logger.error("bitstream_content argument is not a Bitstream object")
99
99
  raise RuntimeError("bitstream_bytes argument is not a Bitstream object")
100
- with self.session.get(bitstream.content_url, headers={HEADER_TOKEN: self.token}, stream=True) as request:
101
- if request.status_code == requests.codes.unauthorized:
100
+ with self.session.get(bitstream.content_url, headers={HEADER_TOKEN: self.token}, stream=True) as response:
101
+ if response.status_code == requests.codes.unauthorized:
102
102
  self.token = self.__token__()
103
103
  return self.bitstream_bytes(bitstream)
104
- elif request.status_code == requests.codes.ok:
104
+ elif response.status_code == requests.codes.ok:
105
105
  file_bytes = BytesIO()
106
- for chunk in request.iter_content(chunk_size=chunk_size):
106
+ for chunk in response.iter_content(chunk_size=chunk_size):
107
107
  file_bytes.write(chunk)
108
108
  file_bytes.seek(0)
109
109
  if file_bytes.getbuffer().nbytes == bitstream.length:
@@ -113,8 +113,8 @@ class EntityAPI(AuthenticatedAPI):
113
113
  logger.error("Downloaded file size did not match the Preservica held value")
114
114
  return None
115
115
  else:
116
- exception = HTTPException(bitstream.filename, request.status_code, request.url, "bitstream_content",
117
- request.content.decode('utf-8'))
116
+ exception = HTTPException(bitstream.filename, response.status_code, response.url, "bitstream_content",
117
+ response.content.decode('utf-8'))
118
118
  logger.error(exception)
119
119
  raise exception
120
120
 
@@ -131,23 +131,25 @@ class EntityAPI(AuthenticatedAPI):
131
131
  url: str = f'{self.protocol}://{self.server}/api/entity/content-objects/{bitstream.co_ref}/generations/{bitstream.gen_index}/bitstreams/{bitstream.bs_index}/storage-locations'
132
132
 
133
133
  with self.session.get(url, headers={HEADER_TOKEN: self.token}, stream=True) as request:
134
- if request.status_code == requests.codes.unauthorized:
135
- self.token = self.__token__()
136
- return self.bitstream_location(bitstream)
137
- elif request.status_code == requests.codes.ok:
134
+ if request.status_code == requests.codes.ok:
138
135
  xml_response = str(request.content.decode('utf-8'))
139
136
  entity_response = xml.etree.ElementTree.fromstring(xml_response)
140
137
  logger.debug(xml_response)
141
138
  locations = entity_response.find(f'.//{{{self.entity_ns}}}StorageLocation')
142
139
  for adapter in locations:
143
140
  storage_locations.append(adapter.attrib['name'])
141
+ return storage_locations
142
+
143
+ if request.status_code == requests.codes.unauthorized:
144
+ self.token = self.__token__()
145
+ return self.bitstream_location(bitstream)
144
146
  else:
145
147
  exception = HTTPException(bitstream.filename, request.status_code, request.url, "bitstream_location",
146
148
  request.content.decode('utf-8'))
147
149
  logger.error(exception)
148
150
  raise exception
149
151
 
150
- return storage_locations
152
+
151
153
 
152
154
  def bitstream_content(self, bitstream: Bitstream, filename: str, chunk_size: int = CHUNK_SIZE) -> Union[int, None]:
153
155
  """
@@ -760,7 +762,7 @@ class EntityAPI(AuthenticatedAPI):
760
762
  end_point = f"{entity.path}/{entity.reference}/links/{relationship.api_id}"
761
763
  request = self.session.delete(f'{self.protocol}://{self.server}/api/entity/{end_point}', headers=headers)
762
764
  if request.status_code == requests.codes.no_content:
763
- print(relationship)
765
+ return None
764
766
  elif request.status_code == requests.codes.unauthorized:
765
767
  self.token = self.__token__()
766
768
  return self.__delete_relationship(relationship)
@@ -779,7 +781,7 @@ class EntityAPI(AuthenticatedAPI):
779
781
  :type: page_size: int
780
782
 
781
783
  :param entity: The Source Entity
782
- :type: entity: Entity
784
+ :type: entity: An Entity type such as Asset, Folder etc
783
785
 
784
786
  :return: Generator
785
787
  :rtype: Relationship
@@ -847,7 +849,7 @@ class EntityAPI(AuthenticatedAPI):
847
849
 
848
850
  return PagedSet(results, has_more, int(total_hits.text), url)
849
851
  elif request.status_code == requests.codes.unauthorized:
850
- self.__relationships__(entity=entity, maximum=maximum, next_page=next_page)
852
+ return self.__relationships__(entity=entity, maximum=maximum, next_page=next_page)
851
853
  else:
852
854
  exception = HTTPException(entity.reference, request.status_code, request.url, "relationships",
853
855
  request.content.decode('utf-8'))
@@ -1108,6 +1110,7 @@ class EntityAPI(AuthenticatedAPI):
1108
1110
  if 'CustomType' in response:
1109
1111
  content_object.custom_type = response['CustomType']
1110
1112
  return content_object
1113
+ return None
1111
1114
  elif request.status_code == requests.codes.unauthorized:
1112
1115
  self.token = self.__token__()
1113
1116
  return self.save(entity)
@@ -1296,9 +1299,9 @@ class EntityAPI(AuthenticatedAPI):
1296
1299
  for uri, schema_name in entity.metadata.items():
1297
1300
  if schema == schema_name:
1298
1301
  return self.metadata(uri)
1299
- return
1302
+ return None
1300
1303
 
1301
- def metadata_tag_for_entity(self, entity: Entity, schema: str, tag: str, isXpath: bool = False) -> str:
1304
+ def metadata_tag_for_entity(self, entity: Entity, schema: str, tag: str, isXpath: bool = False) -> Union[str, None]:
1302
1305
  """
1303
1306
  Retrieve the first value of the tag from a metadata template given by schema
1304
1307
 
@@ -1313,10 +1316,11 @@ class EntityAPI(AuthenticatedAPI):
1313
1316
  xml_doc = self.metadata_for_entity(entity, schema)
1314
1317
  if xml_doc:
1315
1318
  xml_object = xml.etree.ElementTree.fromstring(xml_doc)
1316
- if isXpath is False:
1319
+ if not isXpath:
1317
1320
  return xml_object.find(f'.//{{*}}{tag}').text
1318
1321
  else:
1319
1322
  return xml_object.find(tag).text
1323
+ return None
1320
1324
 
1321
1325
  def security_tag_sync(self, entity: EntityT, new_tag: str) -> EntityT:
1322
1326
  """
@@ -1413,6 +1417,7 @@ class EntityAPI(AuthenticatedAPI):
1413
1417
  return self.folder(reference)
1414
1418
  if entity_type is EntityType.ASSET:
1415
1419
  return self.asset(reference)
1420
+ return None
1416
1421
 
1417
1422
  def add_physical_asset(self, title: str, description: str, parent: Folder, security_tag: str = "open") -> Asset:
1418
1423
  """
@@ -1460,11 +1465,62 @@ class EntityAPI(AuthenticatedAPI):
1460
1465
  logger.error(exception)
1461
1466
  raise exception
1462
1467
 
1463
- def merge_folder(self, folder: Folder)-> str:
1468
+ def merge_assets(self, assets: list[Asset], title: str, description: str) -> str:
1469
+ """
1470
+ Create a new Asset with the content from each Asset in supplied list
1471
+ This call will create a new multipart Asset which contains all the content from list of Assets.
1472
+
1473
+ The return value is the progress status of the merge operation.
1474
+ """
1475
+
1476
+ headers = {
1477
+ HEADER_TOKEN: self.token,
1478
+ "Content-Type": "application/xml;charset=UTF-8",
1479
+ "accept": "text/plain;charset=UTF-8",
1480
+ }
1481
+
1482
+ merge_object = xml.etree.ElementTree.Element("MergeAction", {"xmlns": self.entity_ns, "xmlns:xip": self.xip_ns})
1483
+ xml.etree.ElementTree.SubElement(merge_object, "Title").text = str(title)
1484
+ xml.etree.ElementTree.SubElement(merge_object, "Description").text = str(description)
1485
+ for a in assets:
1486
+ xml.etree.ElementTree.SubElement(merge_object, "Entity", {
1487
+ "excludeIdentifiers": "true",
1488
+ "excludeLinks": "true",
1489
+ "excludeMetadata": "true",
1490
+ "ref": a.reference,
1491
+ "type": EntityType.ASSET.value}
1492
+ )
1493
+ # order_object = xml.etree.ElementTree.SubElement(merge_object, "Order")
1494
+ # for a in assets:
1495
+ # xml.etree.ElementTree.SubElement(order_object, "Entity", {
1496
+ # "ref": a.reference,
1497
+ # "type": EntityType.CONTENT_OBJECT.value}
1498
+ # )
1499
+ xml_request = xml.etree.ElementTree.tostring(merge_object, encoding="utf-8")
1500
+ print(xml_request)
1501
+ request = self.session.post(
1502
+ f"{self.protocol}://{self.server}/api/entity/actions/merges", data=xml_request, headers=headers)
1503
+ if request.status_code == requests.codes.accepted:
1504
+ return request.content.decode('utf-8')
1505
+ elif request.status_code == requests.codes.unauthorized:
1506
+ self.token = self.__token__()
1507
+ return self.merge_assets(assets, title, description)
1508
+ else:
1509
+ exception = HTTPException(
1510
+ "",
1511
+ request.status_code,
1512
+ request.url,
1513
+ "merge_assets",
1514
+ request.content.decode("utf-8"),
1515
+ )
1516
+ logger.error(exception)
1517
+ raise exception
1518
+
1519
+ def merge_folder(self, folder: Folder) -> str:
1464
1520
  """
1465
1521
  Create a new Asset with the content from each Asset in the Folder
1466
1522
 
1467
- This call will create a new multi-part Asset which contains all the content from the Folder.
1523
+ This call will create a new multipart Asset which contains all the content from the Folder.
1468
1524
 
1469
1525
  The new Asset which is created will have the same title, description and parent as the Folder.
1470
1526
 
@@ -1495,7 +1551,6 @@ class EntityAPI(AuthenticatedAPI):
1495
1551
  logger.error(exception)
1496
1552
  raise exception
1497
1553
 
1498
-
1499
1554
  def asset(self, reference: str) -> Asset:
1500
1555
  """
1501
1556
  Retrieve an Asset by its reference
@@ -2118,7 +2173,7 @@ class EntityAPI(AuthenticatedAPI):
2118
2173
  params=params, headers=headers)
2119
2174
 
2120
2175
  if request.status_code == requests.codes.ok:
2121
- pass
2176
+ return None
2122
2177
  elif request.status_code == requests.codes.unauthorized:
2123
2178
  self.token = self.__token__()
2124
2179
  return self._event_actions(entity, maximum=maximum)
@@ -2262,6 +2317,7 @@ class EntityAPI(AuthenticatedAPI):
2262
2317
  else:
2263
2318
  url = next_url.text
2264
2319
  return PagedSet(result_list, has_more, int(total_hits.text), url)
2320
+ return None
2265
2321
 
2266
2322
  def entity_from_event(self, event_id: str) -> Generator:
2267
2323
  self.token = self.__token__()
@@ -451,6 +451,26 @@ class MetadataGroupsAPI(AuthenticatedAPI):
451
451
  raise exception
452
452
 
453
453
 
454
+ def delete_form(self, form_id: str):
455
+ """
456
+ Delete a form by its ID
457
+ """
458
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json;charset=UTF-8'}
459
+ url = f'{self.protocol}://{self.server}/api/metadata/forms/{form_id}'
460
+ with self.session.delete(url, headers=headers) as request:
461
+ if request.status_code == requests.codes.unauthorized:
462
+ self.token = self.__token__()
463
+ return self.delete_form(form_id)
464
+ elif request.status_code == requests.codes.no_content:
465
+ return None
466
+ else:
467
+ exception = HTTPException(None, request.status_code, request.url, "delete_form",
468
+ request.content.decode('utf-8'))
469
+ logger.error(exception)
470
+ raise exception
471
+
472
+
473
+
454
474
  def form(self, form_id: str) -> dict:
455
475
  """
456
476
  Return a Form as a JSON dict object
@@ -74,6 +74,7 @@ class TriggerType(Enum):
74
74
  INDEXED = "FULL_TEXT_INDEXED"
75
75
  SECURITY_CHANGED = "CHANGED_SECURITY_DESCRIPTOR"
76
76
  INGEST_FAILED = "INGEST_FAILED"
77
+ CHANGE_ASSET_VISIBILITY = "CHANGE_ASSET_VISIBILITY"
77
78
 
78
79
 
79
80
  class WebHooksAPI(AuthenticatedAPI):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyPreservica
3
- Version: 3.2.2
3
+ Version: 3.2.3
4
4
  Summary: Python library for the Preservica API
5
5
  Home-page: https://pypreservica.readthedocs.io/
6
6
  Author: James Carr
@@ -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="3.2.2",
24
+ version="3.2.3",
25
25
  description="Python library for the Preservica API",
26
26
  long_description=README,
27
27
  long_description_content_type="text/markdown",
@@ -1,24 +1,48 @@
1
+ from os.path import isfile
2
+
1
3
  import pytest
2
4
 
3
5
  from zipfile import ZipFile
4
6
  from pyPreservica import *
5
7
 
6
8
 
7
- def test_export_file_wait():
9
+
10
+
11
+ def setup():
12
+ pass
13
+
14
+
15
+ def tear_down(zip_files):
16
+ for f in zip_files:
17
+ if os.path.exists(f):
18
+ os.remove(f)
19
+
20
+
21
+ @pytest.fixture
22
+ def setup_data():
23
+ print("\nSetting up resources...")
24
+ zip_files = []
25
+ setup()
26
+ yield zip_files
27
+ print(f"\nTearing down resources...")
28
+ tear_down(zip_files)
29
+
30
+ def test_export_file_wait(setup_data):
8
31
  client = EntityAPI()
9
32
  asset = client.asset("683f9db7-ff81-4859-9c03-f68cfa5d9c3d")
10
- zip_file = client.export_opex_sync(asset, IncludeContent='true')
33
+ zip_file = client.export_opex_sync(asset, IncludeContent='true', IncludeMetadata='true')
11
34
  assert os.path.exists(zip_file)
35
+ setup_data.append(zip_file)
12
36
  assert 1066650 < os.stat(zip_file).st_size
13
37
  with ZipFile(zip_file, 'r') as zipObj:
14
38
  assert len(zipObj.namelist()) == 6
15
39
  os.remove(zip_file)
16
40
 
17
41
 
18
- def test_export_file_no_wait():
42
+ def test_export_file_no_wait(setup_data):
19
43
  client = EntityAPI()
20
44
  asset = client.asset("683f9db7-ff81-4859-9c03-f68cfa5d9c3d")
21
- pid = client.export_opex_async(asset)
45
+ pid = client.export_opex_async(asset, IncludeContent='true', IncludeMetadata='true')
22
46
  status = "ACTIVE"
23
47
 
24
48
  while status == "ACTIVE":
@@ -28,6 +52,7 @@ def test_export_file_no_wait():
28
52
 
29
53
  zip_file = client.download_opex(pid)
30
54
  assert os.path.exists(zip_file)
55
+ setup_data.append(zip_file)
31
56
  assert 1066650 < os.stat(zip_file).st_size
32
57
  with ZipFile(zip_file, 'r') as zipObj:
33
58
  assert len(zipObj.namelist()) == 6
@@ -38,11 +38,11 @@ def test_get_xml_documents():
38
38
  xml_documents = client.xml_documents()
39
39
  assert type(xml_documents) is list
40
40
  assert len(xml_documents) > 10
41
- for xml in xml_documents:
42
- assert type(xml) is dict
43
- assert "Name" in xml
44
- assert "SchemaUri" in xml
45
- assert "ApiId" in xml
41
+ for x in xml_documents:
42
+ assert type(x) is dict
43
+ assert "Name" in x
44
+ assert "SchemaUri" in x
45
+ assert "ApiId" in x
46
46
 
47
47
 
48
48
  def test_get_xml_document_by_uri():
@@ -73,12 +73,12 @@ def test_get_xml_transforms():
73
73
  xml_transforms = client.xml_transforms()
74
74
  assert type(xml_transforms) is list
75
75
  assert len(xml_transforms) > 10
76
- for xml in xml_transforms:
77
- assert type(xml) is dict
78
- assert "Name" in xml
79
- assert "FromSchemaUri" in xml
80
- assert "ToSchemaUri" in xml
81
- assert "ApiId" in xml
76
+ for x in xml_transforms:
77
+ assert type(x) is dict
78
+ assert "Name" in x
79
+ assert "FromSchemaUri" in x
80
+ assert "ToSchemaUri" in x
81
+ assert "ApiId" in x
82
82
 
83
83
 
84
84
  def test_get_xml_transform_by_uri():
File without changes
File without changes
File without changes