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.
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/PKG-INFO +10 -1
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/__init__.py +3 -3
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/entityAPI.py +9 -8
- pypreservica-2.7.3/pyPreservica/mdformsAPI.py +100 -0
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/uploadAPI.py +115 -112
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/workflowAPI.py +4 -4
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica.egg-info/PKG-INFO +10 -1
- pypreservica-2.7.3/pyPreservica.egg-info/SOURCES.txt +45 -0
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/setup.py +1 -1
- pypreservica-2.7.3/tests/test_authority_records.py +20 -0
- pypreservica-2.7.3/tests/test_bitstream.py +77 -0
- pypreservica-2.7.3/tests/test_children.py +75 -0
- pypreservica-2.7.3/tests/test_content_api.py +83 -0
- pypreservica-2.7.3/tests/test_crawl_fs.py +27 -0
- pypreservica-2.7.3/tests/test_delete.py +17 -0
- pypreservica-2.7.3/tests/test_download.py +102 -0
- pypreservica-2.7.3/tests/test_entity.py +323 -0
- pypreservica-2.7.3/tests/test_export_opex.py +36 -0
- pypreservica-2.7.3/tests/test_identifier.py +131 -0
- pypreservica-2.7.3/tests/test_ingest.py +102 -0
- pypreservica-2.7.3/tests/test_integrity_check.py +43 -0
- pypreservica-2.7.3/tests/test_metadata.py +141 -0
- pypreservica-2.7.3/tests/test_par.py +49 -0
- pypreservica-2.7.3/tests/test_replace.py +36 -0
- pypreservica-2.7.3/tests/test_retention.py +133 -0
- pypreservica-2.7.3/tests/test_schema.py +99 -0
- pypreservica-2.7.3/tests/test_security.py +67 -0
- pypreservica-2.7.3/tests/test_thumbnail.py +41 -0
- pypreservica-2.7.3/tests/test_upload.py +151 -0
- pypreservica-2.7.3/tests/test_users.py +43 -0
- pypreservica-2.7.3/tests/test_workflow.py +37 -0
- pypreservica-2.7.3/tests/test_xml_metadata.py +20 -0
- pyPreservica-2.7.0/pyPreservica.egg-info/SOURCES.txt +0 -21
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/LICENSE.txt +0 -0
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/README.md +0 -0
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/adminAPI.py +0 -0
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/authorityAPI.py +0 -0
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/common.py +0 -0
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/contentAPI.py +0 -0
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/monitorAPI.py +0 -0
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/opex.py +0 -0
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/parAPI.py +0 -0
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/retentionAPI.py +0 -0
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica/webHooksAPI.py +0 -0
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica.egg-info/dependency_links.txt +0 -0
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica.egg-info/requires.txt +0 -0
- {pyPreservica-2.7.0 → pypreservica-2.7.3}/pyPreservica.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|
|
26
|
-
__version__ = "2.7.
|
|
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,
|
|
840
|
-
|
|
841
|
-
|
|
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
|
|
104
|
-
|
|
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.
|
|
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
|