pyPreservica 2.7.0__tar.gz → 2.7.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.
Files changed (48) hide show
  1. {pyPreservica-2.7.0 → pypreservica-2.7.3}/PKG-INFO +10 -1
  2. {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/__init__.py +3 -3
  3. {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/entityAPI.py +9 -8
  4. pypreservica-2.7.3/pyPreservica/mdformsAPI.py +100 -0
  5. {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/uploadAPI.py +115 -112
  6. {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/workflowAPI.py +4 -4
  7. {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica.egg-info/PKG-INFO +10 -1
  8. pypreservica-2.7.3/pyPreservica.egg-info/SOURCES.txt +45 -0
  9. {pyPreservica-2.7.0 → pypreservica-2.7.3}/setup.py +1 -1
  10. pypreservica-2.7.3/tests/test_authority_records.py +20 -0
  11. pypreservica-2.7.3/tests/test_bitstream.py +77 -0
  12. pypreservica-2.7.3/tests/test_children.py +75 -0
  13. pypreservica-2.7.3/tests/test_content_api.py +83 -0
  14. pypreservica-2.7.3/tests/test_crawl_fs.py +27 -0
  15. pypreservica-2.7.3/tests/test_delete.py +17 -0
  16. pypreservica-2.7.3/tests/test_download.py +102 -0
  17. pypreservica-2.7.3/tests/test_entity.py +323 -0
  18. pypreservica-2.7.3/tests/test_export_opex.py +36 -0
  19. pypreservica-2.7.3/tests/test_identifier.py +131 -0
  20. pypreservica-2.7.3/tests/test_ingest.py +102 -0
  21. pypreservica-2.7.3/tests/test_integrity_check.py +43 -0
  22. pypreservica-2.7.3/tests/test_metadata.py +141 -0
  23. pypreservica-2.7.3/tests/test_par.py +49 -0
  24. pypreservica-2.7.3/tests/test_replace.py +36 -0
  25. pypreservica-2.7.3/tests/test_retention.py +133 -0
  26. pypreservica-2.7.3/tests/test_schema.py +99 -0
  27. pypreservica-2.7.3/tests/test_security.py +67 -0
  28. pypreservica-2.7.3/tests/test_thumbnail.py +41 -0
  29. pypreservica-2.7.3/tests/test_upload.py +151 -0
  30. pypreservica-2.7.3/tests/test_users.py +43 -0
  31. pypreservica-2.7.3/tests/test_workflow.py +37 -0
  32. pypreservica-2.7.3/tests/test_xml_metadata.py +20 -0
  33. pyPreservica-2.7.0/pyPreservica.egg-info/SOURCES.txt +0 -21
  34. {pyPreservica-2.7.0 → pypreservica-2.7.3}/LICENSE.txt +0 -0
  35. {pyPreservica-2.7.0 → pypreservica-2.7.3}/README.md +0 -0
  36. {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/adminAPI.py +0 -0
  37. {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/authorityAPI.py +0 -0
  38. {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/common.py +0 -0
  39. {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/contentAPI.py +0 -0
  40. {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/monitorAPI.py +0 -0
  41. {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/opex.py +0 -0
  42. {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/parAPI.py +0 -0
  43. {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/retentionAPI.py +0 -0
  44. {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/webHooksAPI.py +0 -0
  45. {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica.egg-info/dependency_links.txt +0 -0
  46. {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica.egg-info/requires.txt +0 -0
  47. {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica.egg-info/top_level.txt +0 -0
  48. {pyPreservica-2.7.0 → pypreservica-2.7.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyPreservica
3
- Version: 2.7.0
3
+ Version: 2.7.3
4
4
  Summary: Python library for the Preservica API
5
5
  Home-page: https://pypreservica.readthedocs.io/
6
6
  Author: James Carr
@@ -20,6 +20,15 @@ Classifier: Operating System :: OS Independent
20
20
  Classifier: Topic :: System :: Archiving
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE.txt
23
+ Requires-Dist: requests
24
+ Requires-Dist: urllib3
25
+ Requires-Dist: certifi
26
+ Requires-Dist: boto3
27
+ Requires-Dist: botocore
28
+ Requires-Dist: s3transfer
29
+ Requires-Dist: azure-storage-blob
30
+ Requires-Dist: tqdm
31
+ Requires-Dist: pyotp
23
32
 
24
33
 
25
34
  # pyPreservica
@@ -18,11 +18,11 @@ from .adminAPI import AdminAPI
18
18
  from .monitorAPI import MonitorAPI, MonitorCategory, MonitorStatus, MessageStatus
19
19
  from .webHooksAPI import WebHooksAPI, TriggerType, WebHookHandler
20
20
  from .authorityAPI import AuthorityAPI, Table
21
-
21
+ from .mdformsAPI import MDFormsAPI
22
22
 
23
23
  __author__ = "James Carr (drjamescarr@gmail.com)"
24
24
 
25
- # Version of the Preservica API package
26
- __version__ = "2.7.0"
25
+ # Version of the pyPreservica package
26
+ __version__ = "2.7.3"
27
27
 
28
28
  __license__ = "Apache License Version 2.0"
@@ -796,11 +796,11 @@ class EntityAPI(AuthenticatedAPI):
796
796
  for url in entity.metadata:
797
797
  if schema == entity.metadata[url]:
798
798
  mref = url[url.rfind(f"{entity.reference}/metadata/") + len(f"{entity.reference}/metadata/"):]
799
- xml_object = xml.etree.ElementTree.Element('MetadataContainer',
800
- {"schemaUri": schema, "xmlns": self.xip_ns})
801
- xml.etree.ElementTree.SubElement(xml_object, "Ref").text = mref
802
- xml.etree.ElementTree.SubElement(xml_object, "Entity").text = entity.reference
803
- content = xml.etree.ElementTree.SubElement(xml_object, "Content")
799
+ xml_object = xml.etree.ElementTree.Element('xip:MetadataContainer',
800
+ {"schemaUri": schema, "xmlns:xip": self.xip_ns})
801
+ xml.etree.ElementTree.SubElement(xml_object, "xip:Ref").text = mref
802
+ xml.etree.ElementTree.SubElement(xml_object, "xip:Entity").text = entity.reference
803
+ content = xml.etree.ElementTree.SubElement(xml_object, "xip:Content")
804
804
  if isinstance(data, str):
805
805
  ob = xml.etree.ElementTree.fromstring(data)
806
806
  content.append(ob)
@@ -836,9 +836,10 @@ class EntityAPI(AuthenticatedAPI):
836
836
  """
837
837
  headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/xml;charset=UTF-8'}
838
838
 
839
- xml_object = xml.etree.ElementTree.Element('MetadataContainer', {"schemaUri": schema, "xmlns": self.xip_ns})
840
- xml.etree.ElementTree.SubElement(xml_object, "Entity").text = entity.reference
841
- content = xml.etree.ElementTree.SubElement(xml_object, "Content")
839
+ xml_object = xml.etree.ElementTree.Element('xip:MetadataContainer', {"schemaUri": schema,
840
+ "xmlns:xip": self.xip_ns})
841
+ xml.etree.ElementTree.SubElement(xml_object, "xip:Entity").text = entity.reference
842
+ content = xml.etree.ElementTree.SubElement(xml_object, "xip:Content")
842
843
  if isinstance(data, str):
843
844
  ob = xml.etree.ElementTree.fromstring(data)
844
845
  content.append(ob)
@@ -0,0 +1,100 @@
1
+ """
2
+ pyPreservica MDFormsAPI module definition
3
+
4
+ A client library for the Preservica Repository web services Metadata API
5
+ https://demo.preservica.com/api/metadata/documentation.html
6
+
7
+ author: James Carr
8
+ licence: Apache License 2.0
9
+
10
+ """
11
+ import json
12
+ import xml.etree.ElementTree
13
+
14
+ from pyPreservica.common import *
15
+
16
+
17
+ class MDFormsAPI(AuthenticatedAPI):
18
+ def __init__(self, username: str = None, password: str = None, tenant: str = None, server: str = None,
19
+ use_shared_secret: bool = False, two_fa_secret_key: str = None, protocol: str = "https"):
20
+ super().__init__(username, password, tenant, server, use_shared_secret, two_fa_secret_key, protocol)
21
+ xml.etree.ElementTree.register_namespace("oai_dc", "http://www.openarchives.org/OAI/2.0/oai_dc/")
22
+ xml.etree.ElementTree.register_namespace("ead", "urn:isbn:1-931666-22-9")
23
+
24
+ def delete_group(self, id: str):
25
+ """
26
+ Delete a group
27
+ :param id: Group ID
28
+ :return:
29
+ """
30
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json;charset=UTF-8'}
31
+ url = f'{self.protocol}://{self.server}/api/metadata/groups/{id}'
32
+ with self.session.delete(url, headers=headers) as request:
33
+ if request.status_code == requests.codes.unauthorized:
34
+ self.token = self.__token__()
35
+ return self.delete_group(id)
36
+ elif request.status_code == requests.codes.no_content:
37
+ return None
38
+ else:
39
+ exception = HTTPException(None, request.status_code, request.url, "delete_group",
40
+ request.content.decode('utf-8'))
41
+ logger.error(exception)
42
+ raise exception
43
+
44
+ def add_group(self, document):
45
+ """
46
+ Add a new group
47
+ :return:
48
+ """
49
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json;charset=UTF-8'}
50
+ url = f'{self.protocol}://{self.server}/api/metadata/groups/'
51
+ with self.session.post(url, headers=headers, json=document) as request:
52
+ if request.status_code == requests.codes.unauthorized:
53
+ self.token = self.__token__()
54
+ return self.add_group(document)
55
+ elif request.status_code == requests.codes.created:
56
+ return json.loads(str(request.content.decode('utf-8')))
57
+ else:
58
+ exception = HTTPException(None, request.status_code, request.url, "group",
59
+ request.content.decode('utf-8'))
60
+ logger.error(exception)
61
+ raise exception
62
+
63
+ def group(self, id: str):
64
+ """
65
+ Fetch a metadata Group by its id
66
+ :param id: The group ID
67
+ :return: JSON Document
68
+ """
69
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json;charset=UTF-8'}
70
+ url = f'{self.protocol}://{self.server}/api/metadata/groups/{id}'
71
+ with self.session.get(url, headers=headers) as request:
72
+ if request.status_code == requests.codes.unauthorized:
73
+ self.token = self.__token__()
74
+ return self.group(id)
75
+ elif request.status_code == requests.codes.ok:
76
+ return json.loads(str(request.content.decode('utf-8')))
77
+ else:
78
+ exception = HTTPException(None, request.status_code, request.url, "group",
79
+ request.content.decode('utf-8'))
80
+ logger.error(exception)
81
+ raise exception
82
+
83
+ def groups(self):
84
+ """
85
+ Fetch all the Metadata Groups as JSON
86
+ :return: JSON Document
87
+ """
88
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json;charset=UTF-8'}
89
+ url = f'{self.protocol}://{self.server}/api/metadata/groups'
90
+ with self.session.get(url, headers=headers) as request:
91
+ if request.status_code == requests.codes.unauthorized:
92
+ self.token = self.__token__()
93
+ return self.groups()
94
+ elif request.status_code == requests.codes.ok:
95
+ return json.loads(str(request.content.decode('utf-8')))['groups']
96
+ else:
97
+ exception = HTTPException(None, request.status_code, request.url, "groups",
98
+ request.content.decode('utf-8'))
99
+ logger.error(exception)
100
+ raise exception
@@ -98,11 +98,11 @@ def prettify(elem):
98
98
 
99
99
  def __create_io__(xip=None, file_name=None, parent_folder=None, **kwargs):
100
100
  if xip is None:
101
- xip = Element('XIP')
101
+ xip = Element('xip:XIP')
102
+ xip.set('xmlns:xip', 'http://preservica.com/XIP/v6.0')
102
103
  assert xip is not None
103
- xip.set('xmlns', 'http://preservica.com/XIP/v6.0')
104
- io = SubElement(xip, 'InformationObject')
105
- ref = SubElement(io, 'Ref')
104
+ io = SubElement(xip, 'xip:InformationObject')
105
+ ref = SubElement(io, 'xip:Ref')
106
106
 
107
107
  if 'IO_Identifier_callback' in kwargs:
108
108
  ident_callback = kwargs.get('IO_Identifier_callback')
@@ -110,15 +110,15 @@ def __create_io__(xip=None, file_name=None, parent_folder=None, **kwargs):
110
110
  else:
111
111
  ref.text = str(uuid.uuid4())
112
112
 
113
- title = SubElement(io, 'Title')
113
+ title = SubElement(io, 'xip:Title')
114
114
  title.text = kwargs.get('Title', file_name)
115
- description = SubElement(io, 'Description')
115
+ description = SubElement(io, 'xip:Description')
116
116
  description.text = kwargs.get('Description', file_name)
117
- security = SubElement(io, 'SecurityTag')
117
+ security = SubElement(io, 'xip:SecurityTag')
118
118
  security.text = kwargs.get('SecurityTag', "open")
119
- custom_type = SubElement(io, 'CustomType')
119
+ custom_type = SubElement(io, 'xip:CustomType')
120
120
  custom_type.text = kwargs.get('CustomType', "")
121
- parent = SubElement(io, 'Parent')
121
+ parent = SubElement(io, 'xip:Parent')
122
122
 
123
123
  if hasattr(parent_folder, "reference"):
124
124
  parent.text = parent_folder.reference
@@ -129,76 +129,76 @@ def __create_io__(xip=None, file_name=None, parent_folder=None, **kwargs):
129
129
 
130
130
 
131
131
  def __make_representation__(xip, rep_name, rep_type, io_ref):
132
- representation = SubElement(xip, 'Representation')
133
- io_link = SubElement(representation, 'InformationObject')
132
+ representation = SubElement(xip, 'xip:Representation')
133
+ io_link = SubElement(representation, 'xip:InformationObject')
134
134
  io_link.text = io_ref
135
- access_name = SubElement(representation, 'Name')
135
+ access_name = SubElement(representation, 'xip:Name')
136
136
  access_name.text = rep_name
137
- access_type = SubElement(representation, 'Type')
137
+ access_type = SubElement(representation, 'xip:Type')
138
138
  access_type.text = rep_type
139
- content_objects = SubElement(representation, 'ContentObjects')
140
- content_object = SubElement(content_objects, 'ContentObject')
139
+ content_objects = SubElement(representation, 'xip:ContentObjects')
140
+ content_object = SubElement(content_objects, 'xip:ContentObject')
141
141
  content_object_ref = str(uuid.uuid4())
142
142
  content_object.text = content_object_ref
143
143
  return content_object_ref
144
144
 
145
145
 
146
146
  def __make_content_objects__(xip, content_title, co_ref, io_ref, tag, content_description, content_type):
147
- content_object = SubElement(xip, 'ContentObject')
148
- ref_element = SubElement(content_object, "Ref")
147
+ content_object = SubElement(xip, 'xip:ContentObject')
148
+ ref_element = SubElement(content_object, "xip:Ref")
149
149
  ref_element.text = co_ref
150
- title = SubElement(content_object, "Title")
150
+ title = SubElement(content_object, "xip:Title")
151
151
  title.text = content_title
152
- description = SubElement(content_object, "Description")
152
+ description = SubElement(content_object, "xip:Description")
153
153
  description.text = content_description
154
- security_tag = SubElement(content_object, "SecurityTag")
154
+ security_tag = SubElement(content_object, "xip:SecurityTag")
155
155
  security_tag.text = tag
156
- custom_type = SubElement(content_object, "CustomType")
156
+ custom_type = SubElement(content_object, "xip:CustomType")
157
157
  custom_type.text = content_type
158
- parent = SubElement(content_object, "Parent")
158
+ parent = SubElement(content_object, "xip:Parent")
159
159
  parent.text = io_ref
160
160
 
161
161
 
162
162
  def __make_generation__(xip, filename, co_ref, generation_label, location=None):
163
- generation = SubElement(xip, 'Generation', {"original": "true", "active": "true"})
164
- content_object = SubElement(generation, "ContentObject")
163
+ generation = SubElement(xip, 'xip:Generation', {"original": "true", "active": "true"})
164
+ content_object = SubElement(generation, "xip:ContentObject")
165
165
  content_object.text = co_ref
166
- label = SubElement(generation, "Label")
166
+ label = SubElement(generation, "xip:Label")
167
167
  if generation_label:
168
168
  label.text = generation_label
169
169
  else:
170
170
  label.text = os.path.splitext(filename)[0]
171
- effective_date = SubElement(generation, "EffectiveDate")
171
+ effective_date = SubElement(generation, "xip:EffectiveDate")
172
172
  effective_date.text = datetime.now().isoformat()
173
- bitstreams = SubElement(generation, "Bitstreams")
174
- bitstream = SubElement(bitstreams, "Bitstream")
173
+ bitstreams = SubElement(generation, "xip:Bitstreams")
174
+ bitstream = SubElement(bitstreams, "xip:Bitstream")
175
175
  bitstream.text = f"{location}/{filename}"
176
- SubElement(generation, "Formats")
177
- SubElement(generation, "Properties")
176
+ SubElement(generation, "xip:Formats")
177
+ SubElement(generation, "xip:Properties")
178
178
 
179
179
 
180
180
  def __make_bitstream__(xip, file_name, full_path, callback, location=None):
181
- bitstream = SubElement(xip, 'Bitstream')
182
- filename_element = SubElement(bitstream, "Filename")
181
+ bitstream = SubElement(xip, 'xip:Bitstream')
182
+ filename_element = SubElement(bitstream, "xip:Filename")
183
183
  filename_element.text = file_name
184
- filesize = SubElement(bitstream, "FileSize")
184
+ filesize = SubElement(bitstream, "xip:FileSize")
185
185
  file_stats = os.stat(full_path)
186
186
  filesize.text = str(file_stats.st_size)
187
- physical_location = SubElement(bitstream, "PhysicalLocation")
187
+ physical_location = SubElement(bitstream, "xip:PhysicalLocation")
188
188
  physical_location.text = location
189
- fixities = SubElement(bitstream, "Fixities")
189
+ fixities = SubElement(bitstream, "xip:Fixities")
190
190
  fixity_result = callback(file_name, full_path)
191
191
  if type(fixity_result) == tuple:
192
- fixity = SubElement(fixities, "Fixity")
193
- fixity_algorithm_ref = SubElement(fixity, "FixityAlgorithmRef")
194
- fixity_value = SubElement(fixity, "FixityValue")
192
+ fixity = SubElement(fixities, "xip:Fixity")
193
+ fixity_algorithm_ref = SubElement(fixity, "xip:FixityAlgorithmRef")
194
+ fixity_value = SubElement(fixity, "xip:FixityValue")
195
195
  fixity_algorithm_ref.text = fixity_result[0]
196
196
  fixity_value.text = fixity_result[1]
197
197
  elif type(fixity_result) == dict:
198
198
  for key, val in fixity_result.items():
199
- fixity = SubElement(fixities, "Fixity")
200
- fixity_algorithm_ref = SubElement(fixity, "FixityAlgorithmRef")
201
- fixity_value = SubElement(fixity, "FixityValue")
199
+ fixity = SubElement(fixities, "xip:Fixity")
200
+ fixity_algorithm_ref = SubElement(fixity, "xip:FixityAlgorithmRef")
201
+ fixity_value = SubElement(fixity, "xip:FixityValue")
202
202
  fixity_algorithm_ref.text = key
203
203
  fixity_value.text = val
204
204
  else:
@@ -207,17 +207,17 @@ def __make_bitstream__(xip, file_name, full_path, callback, location=None):
207
207
 
208
208
 
209
209
  def __make_representation_multiple_co__(xip, rep_name, rep_type, rep_files, io_ref):
210
- representation = SubElement(xip, 'Representation')
211
- io_link = SubElement(representation, 'InformationObject')
210
+ representation = SubElement(xip, 'xip:Representation')
211
+ io_link = SubElement(representation, 'xip:InformationObject')
212
212
  io_link.text = io_ref
213
- access_name = SubElement(representation, 'Name')
213
+ access_name = SubElement(representation, 'xip:Name')
214
214
  access_name.text = rep_name
215
- access_type = SubElement(representation, 'Type')
215
+ access_type = SubElement(representation, 'xip:Type')
216
216
  access_type.text = rep_type
217
- content_objects = SubElement(representation, 'ContentObjects')
217
+ content_objects = SubElement(representation, 'xip:ContentObjects')
218
218
  refs_dict = {}
219
219
  for f in rep_files:
220
- content_object = SubElement(content_objects, 'ContentObject')
220
+ content_object = SubElement(content_objects, 'xip:ContentObject')
221
221
  content_object_ref = str(uuid.uuid4())
222
222
  content_object.text = content_object_ref
223
223
  refs_dict[content_object_ref] = f
@@ -598,12 +598,12 @@ def generic_asset_package(preservation_files_dict=None, access_files_dict=None,
598
598
  for identifier_key, identifier_value in identifier_map.items():
599
599
  if identifier_key:
600
600
  if identifier_value:
601
- identifier = SubElement(xip, 'Identifier')
602
- id_type = SubElement(identifier, "Type")
601
+ identifier = SubElement(xip, 'xip:Identifier')
602
+ id_type = SubElement(identifier, "xip:Type")
603
603
  id_type.text = identifier_key
604
- id_value = SubElement(identifier, "Value")
604
+ id_value = SubElement(identifier, "xip:Value")
605
605
  id_value.text = identifier_value
606
- id_io = SubElement(identifier, "Entity")
606
+ id_io = SubElement(identifier, "xip:Entity")
607
607
  id_io.text = io_ref
608
608
 
609
609
  if 'Asset_Metadata' in kwargs:
@@ -613,22 +613,22 @@ def generic_asset_package(preservation_files_dict=None, access_files_dict=None,
613
613
  if metadata_path:
614
614
  if os.path.exists(metadata_path) and os.path.isfile(metadata_path):
615
615
  descriptive_metadata = xml.etree.ElementTree.parse(source=metadata_path)
616
- metadata = SubElement(xip, 'Metadata', {'schemaUri': metadata_ns})
617
- metadata_ref = SubElement(metadata, 'Ref')
616
+ metadata = SubElement(xip, 'xip:Metadata', {'schemaUri': metadata_ns})
617
+ metadata_ref = SubElement(metadata, 'xip:Ref')
618
618
  metadata_ref.text = str(uuid.uuid4())
619
- entity = SubElement(metadata, 'Entity')
619
+ entity = SubElement(metadata, 'xip:Entity')
620
620
  entity.text = io_ref
621
- content = SubElement(metadata, 'Content')
621
+ content = SubElement(metadata, 'xip:Content')
622
622
  content.append(descriptive_metadata.getroot())
623
623
  elif isinstance(metadata_path, str):
624
624
  try:
625
625
  descriptive_metadata = xml.etree.ElementTree.fromstring(metadata_path)
626
- metadata = SubElement(xip, 'Metadata', {'schemaUri': metadata_ns})
627
- metadata_ref = SubElement(metadata, 'Ref')
626
+ metadata = SubElement(xip, 'xip:Metadata', {'schemaUri': metadata_ns})
627
+ metadata_ref = SubElement(metadata, 'xip:Ref')
628
628
  metadata_ref.text = str(uuid.uuid4())
629
- entity = SubElement(metadata, 'Entity')
629
+ entity = SubElement(metadata, 'xip:Entity')
630
630
  entity.text = io_ref
631
- content = SubElement(metadata, 'Content')
631
+ content = SubElement(metadata, 'xip:Content')
632
632
  content.append(descriptive_metadata)
633
633
  except RuntimeError:
634
634
  logging.info(f"Could not parse asset metadata in namespace {metadata_ns}")
@@ -712,71 +712,72 @@ def multi_asset_package(asset_file_list=None, export_folder=None, parent_folder=
712
712
  os.mkdir(os.path.join(inner_folder, CONTENT_FOLDER))
713
713
 
714
714
  asset_map = dict()
715
- xip = Element('XIP')
715
+ xip = Element('xip:XIP')
716
+ xip.set('xmlns:xip', 'http://preservica.com/XIP/v6.0')
716
717
  for file in asset_file_list:
717
718
  default_asset_title = os.path.splitext(os.path.basename(file))[0]
718
719
  xip, io_ref = __create_io__(xip, file_name=default_asset_title, parent_folder=parent_folder, **kwargs)
719
720
  asset_map[file] = io_ref
720
- representation = SubElement(xip, 'Representation')
721
- io_link = SubElement(representation, 'InformationObject')
721
+ representation = SubElement(xip, 'xip:Representation')
722
+ io_link = SubElement(representation, 'xip:InformationObject')
722
723
  io_link.text = io_ref
723
- access_name = SubElement(representation, 'Name')
724
+ access_name = SubElement(representation, 'xip:Name')
724
725
  access_name.text = "Preservation"
725
- access_type = SubElement(representation, 'Type')
726
+ access_type = SubElement(representation, 'xip:Type')
726
727
  access_type.text = "Preservation"
727
- content_objects = SubElement(representation, 'ContentObjects')
728
- content_object = SubElement(content_objects, 'ContentObject')
728
+ content_objects = SubElement(representation, 'xip:ContentObjects')
729
+ content_object = SubElement(content_objects, 'xip:ContentObject')
729
730
  content_object_ref = str(uuid.uuid4())
730
731
  content_object.text = content_object_ref
731
732
 
732
733
  default_content_objects_title = os.path.splitext(os.path.basename(file))[0]
733
- content_object = SubElement(xip, 'ContentObject')
734
- ref_element = SubElement(content_object, "Ref")
734
+ content_object = SubElement(xip, 'xip:ContentObject')
735
+ ref_element = SubElement(content_object, "xip:Ref")
735
736
  ref_element.text = content_object_ref
736
- title = SubElement(content_object, "Title")
737
+ title = SubElement(content_object, "xip:Title")
737
738
  title.text = default_content_objects_title
738
- description = SubElement(content_object, "Description")
739
+ description = SubElement(content_object, "xip:Description")
739
740
  description.text = default_content_objects_title
740
- security_tag_element = SubElement(content_object, "SecurityTag")
741
+ security_tag_element = SubElement(content_object, "xip:SecurityTag")
741
742
  security_tag_element.text = security_tag
742
- custom_type = SubElement(content_object, "CustomType")
743
+ custom_type = SubElement(content_object, "xip:CustomType")
743
744
  custom_type.text = content_type
744
- parent = SubElement(content_object, "Parent")
745
+ parent = SubElement(content_object, "xip:Parent")
745
746
  parent.text = io_ref
746
747
 
747
- generation = SubElement(xip, 'Generation', {"original": "true", "active": "true"})
748
- content_object = SubElement(generation, "ContentObject")
748
+ generation = SubElement(xip, 'xip:Generation', {"original": "true", "active": "true"})
749
+ content_object = SubElement(generation, "xip:ContentObject")
749
750
  content_object.text = content_object_ref
750
- label = SubElement(generation, "Label")
751
+ label = SubElement(generation, "xip:Label")
751
752
  label.text = os.path.splitext(os.path.basename(file))[0]
752
- effective_date = SubElement(generation, "EffectiveDate")
753
+ effective_date = SubElement(generation, "xip:EffectiveDate")
753
754
  effective_date.text = datetime.now().isoformat()
754
- bitstreams = SubElement(generation, "Bitstreams")
755
- bitstream = SubElement(bitstreams, "Bitstream")
755
+ bitstreams = SubElement(generation, "xip:Bitstreams")
756
+ bitstream = SubElement(bitstreams, "xip:Bitstream")
756
757
  bitstream.text = os.path.basename(file)
757
- SubElement(generation, "Formats")
758
- SubElement(generation, "Properties")
758
+ SubElement(generation, "xip:Formats")
759
+ SubElement(generation, "xip:Properties")
759
760
 
760
- bitstream = SubElement(xip, 'Bitstream')
761
- filename_element = SubElement(bitstream, "Filename")
761
+ bitstream = SubElement(xip, 'xip:Bitstream')
762
+ filename_element = SubElement(bitstream, "xip:Filename")
762
763
  filename_element.text = os.path.basename(file)
763
- filesize = SubElement(bitstream, "FileSize")
764
+ filesize = SubElement(bitstream, "xip:FileSize")
764
765
  file_stats = os.stat(file)
765
766
  filesize.text = str(file_stats.st_size)
766
- physical_location = SubElement(bitstream, "PhysicalLocation")
767
- fixities = SubElement(bitstream, "Fixities")
767
+ physical_location = SubElement(bitstream, "xip:PhysicalLocation")
768
+ fixities = SubElement(bitstream, "xip:Fixities")
768
769
  fixity_result = fixity_callback(filename_element.text, file)
769
770
  if type(fixity_result) == tuple:
770
- fixity = SubElement(fixities, "Fixity")
771
- fixity_algorithm_ref = SubElement(fixity, "FixityAlgorithmRef")
772
- fixity_value = SubElement(fixity, "FixityValue")
771
+ fixity = SubElement(fixities, "xip:Fixity")
772
+ fixity_algorithm_ref = SubElement(fixity, "xip:FixityAlgorithmRef")
773
+ fixity_value = SubElement(fixity, "xip:FixityValue")
773
774
  fixity_algorithm_ref.text = fixity_result[0]
774
775
  fixity_value.text = fixity_result[1]
775
776
  elif type(fixity_result) == dict:
776
777
  for key, val in fixity_result.items():
777
- fixity = SubElement(fixities, "Fixity")
778
- fixity_algorithm_ref = SubElement(fixity, "FixityAlgorithmRef")
779
- fixity_value = SubElement(fixity, "FixityValue")
778
+ fixity = SubElement(fixities, "xip:Fixity")
779
+ fixity_algorithm_ref = SubElement(fixity, "xip:FixityAlgorithmRef")
780
+ fixity_value = SubElement(fixity, "xip:FixityValue")
780
781
  fixity_algorithm_ref.text = key
781
782
  fixity_value.text = val
782
783
  else:
@@ -790,12 +791,12 @@ def multi_asset_package(asset_file_list=None, export_folder=None, parent_folder=
790
791
  for identifier_key, identifier_value in identifier_map_values.items():
791
792
  if identifier_key:
792
793
  if identifier_value:
793
- identifier = SubElement(xip, 'Identifier')
794
- id_type = SubElement(identifier, "Type")
794
+ identifier = SubElement(xip, 'xip:Identifier')
795
+ id_type = SubElement(identifier, "xip:Type")
795
796
  id_type.text = identifier_key
796
- id_value = SubElement(identifier, "Value")
797
+ id_value = SubElement(identifier, "xip:Value")
797
798
  id_value.text = identifier_value
798
- id_io = SubElement(identifier, "Entity")
799
+ id_io = SubElement(identifier, "xip:Entity")
799
800
  id_io.text = io_ref
800
801
 
801
802
  src_file = file
@@ -864,6 +865,8 @@ def complex_asset_package(preservation_files_list=None, access_files_list=None,
864
865
  'Preservation_Representation_Name' Name of the Preservation Representation
865
866
  'Access_Representation_Name' Name of the Access Representation
866
867
  """
868
+ xml.etree.ElementTree.register_namespace("xip", "http://preservica.com/XIP/v6.0")
869
+
867
870
  # some basic validation
868
871
  if export_folder is None:
869
872
  export_folder = tempfile.gettempdir()
@@ -990,12 +993,12 @@ def complex_asset_package(preservation_files_list=None, access_files_list=None,
990
993
  for identifier_key, identifier_value in identifier_map.items():
991
994
  if identifier_key:
992
995
  if identifier_value:
993
- identifier = SubElement(xip, 'Identifier')
994
- id_type = SubElement(identifier, "Type")
996
+ identifier = SubElement(xip, 'xip:Identifier')
997
+ id_type = SubElement(identifier, "xip:Type")
995
998
  id_type.text = identifier_key
996
- id_value = SubElement(identifier, "Value")
999
+ id_value = SubElement(identifier, "xip:Value")
997
1000
  id_value.text = identifier_value
998
- id_io = SubElement(identifier, "Entity")
1001
+ id_io = SubElement(identifier, "xip:Entity")
999
1002
  id_io.text = io_ref
1000
1003
 
1001
1004
  if 'Asset_Metadata' in kwargs:
@@ -1005,22 +1008,22 @@ def complex_asset_package(preservation_files_list=None, access_files_list=None,
1005
1008
  if metadata_path and isinstance(metadata_path, str):
1006
1009
  if os.path.exists(metadata_path) and os.path.isfile(metadata_path):
1007
1010
  descriptive_metadata = xml.etree.ElementTree.parse(source=metadata_path)
1008
- metadata = SubElement(xip, 'Metadata', {'schemaUri': metadata_ns})
1009
- metadata_ref = SubElement(metadata, 'Ref')
1011
+ metadata = SubElement(xip, 'xip:Metadata', {'schemaUri': metadata_ns})
1012
+ metadata_ref = SubElement(metadata, 'xip:Ref')
1010
1013
  metadata_ref.text = str(uuid.uuid4())
1011
- entity = SubElement(metadata, 'Entity')
1014
+ entity = SubElement(metadata, 'xip:Entity')
1012
1015
  entity.text = io_ref
1013
- content = SubElement(metadata, 'Content')
1016
+ content = SubElement(metadata, 'xip:Content')
1014
1017
  content.append(descriptive_metadata.getroot())
1015
1018
  elif isinstance(metadata_path, str):
1016
1019
  try:
1017
1020
  descriptive_metadata = xml.etree.ElementTree.fromstring(metadata_path)
1018
- metadata = SubElement(xip, 'Metadata', {'schemaUri': metadata_ns})
1019
- metadata_ref = SubElement(metadata, 'Ref')
1021
+ metadata = SubElement(xip, 'xip:Metadata', {'schemaUri': metadata_ns})
1022
+ metadata_ref = SubElement(metadata, 'xip:Ref')
1020
1023
  metadata_ref.text = str(uuid.uuid4())
1021
- entity = SubElement(metadata, 'Entity')
1024
+ entity = SubElement(metadata, 'xip:Entity')
1022
1025
  entity.text = io_ref
1023
- content = SubElement(metadata, 'Content')
1026
+ content = SubElement(metadata, 'xip:Content')
1024
1027
  content.append(descriptive_metadata)
1025
1028
  except RuntimeError:
1026
1029
  logging.info(f"Could not parse asset metadata in namespace {metadata_ns}")
@@ -1028,12 +1031,12 @@ def complex_asset_package(preservation_files_list=None, access_files_list=None,
1028
1031
  for path in metadata_path:
1029
1032
  if os.path.exists(path) and os.path.isfile(path):
1030
1033
  descriptive_metadata = xml.etree.ElementTree.parse(source=path)
1031
- metadata = SubElement(xip, 'Metadata', {'schemaUri': metadata_ns})
1032
- metadata_ref = SubElement(metadata, 'Ref')
1034
+ metadata = SubElement(xip, 'xip:Metadata', {'schemaUri': metadata_ns})
1035
+ metadata_ref = SubElement(metadata, 'xip:Ref')
1033
1036
  metadata_ref.text = str(uuid.uuid4())
1034
- entity = SubElement(metadata, 'Entity')
1037
+ entity = SubElement(metadata, 'xip:Entity')
1035
1038
  entity.text = io_ref
1036
- content = SubElement(metadata, 'Content')
1039
+ content = SubElement(metadata, 'xip:Content')
1037
1040
  content.append(descriptive_metadata.getroot())
1038
1041
 
1039
1042
  if xip is not None:
@@ -245,13 +245,13 @@ class WorkflowAPI(AuthenticatedAPI):
245
245
  assert instance_id == w_id
246
246
  workflow_instance = WorkflowInstance(int(instance_id))
247
247
  started_element = entity_response.find(f".//{{{NS_WORKFLOW}}}Started")
248
- if started_element:
248
+ if started_element is not None:
249
249
  if hasattr(started_element, "text"):
250
250
  workflow_instance.started = datetime.datetime.strptime(started_element.text,
251
251
  '%Y-%m-%dT%H:%M:%S.%fZ')
252
252
 
253
253
  finished_element = entity_response.find(f".//{{{NS_WORKFLOW}}}Finished")
254
- if finished_element:
254
+ if finished_element is not None:
255
255
  if hasattr(finished_element, "text"):
256
256
  workflow_instance.finished = datetime.datetime.strptime(finished_element.text,
257
257
  '%Y-%m-%dT%H:%M:%S.%fZ')
@@ -353,13 +353,13 @@ class WorkflowAPI(AuthenticatedAPI):
353
353
  workflow_instance = WorkflowInstance(int(instance_id))
354
354
 
355
355
  started_element = instance.find(f".//{{{NS_WORKFLOW}}}Started")
356
- if started_element:
356
+ if started_element is not None:
357
357
  if hasattr(started_element, "text"):
358
358
  workflow_instance.started = datetime.datetime.strptime(started_element.text,
359
359
  '%Y-%m-%dT%H:%M:%S.%fZ')
360
360
 
361
361
  finished_element = instance.find(f".//{{{NS_WORKFLOW}}}Finished")
362
- if finished_element:
362
+ if finished_element is not None:
363
363
  if hasattr(finished_element, "text"):
364
364
  workflow_instance.finished = datetime.datetime.strptime(finished_element.text,
365
365
  '%Y-%m-%dT%H:%M:%S.%fZ')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyPreservica
3
- Version: 2.7.0
3
+ Version: 2.7.3
4
4
  Summary: Python library for the Preservica API
5
5
  Home-page: https://pypreservica.readthedocs.io/
6
6
  Author: James Carr
@@ -20,6 +20,15 @@ Classifier: Operating System :: OS Independent
20
20
  Classifier: Topic :: System :: Archiving
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE.txt
23
+ Requires-Dist: requests
24
+ Requires-Dist: urllib3
25
+ Requires-Dist: certifi
26
+ Requires-Dist: boto3
27
+ Requires-Dist: botocore
28
+ Requires-Dist: s3transfer
29
+ Requires-Dist: azure-storage-blob
30
+ Requires-Dist: tqdm
31
+ Requires-Dist: pyotp
23
32
 
24
33
 
25
34
  # pyPreservica