pyPreservica 3.2.2__py3-none-any.whl → 3.2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyPreservica might be problematic. Click here for more details.
- pyPreservica/__init__.py +1 -1
- pyPreservica/adminAPI.py +19 -13
- pyPreservica/authorityAPI.py +1 -1
- pyPreservica/common.py +10 -7
- pyPreservica/entityAPI.py +86 -30
- pyPreservica/mdformsAPI.py +20 -0
- pyPreservica/uploadAPI.py +46 -0
- pyPreservica/webHooksAPI.py +1 -0
- {pypreservica-3.2.2.dist-info → pypreservica-3.2.4.dist-info}/METADATA +1 -1
- pypreservica-3.2.4.dist-info/RECORD +20 -0
- pypreservica-3.2.2.dist-info/RECORD +0 -20
- {pypreservica-3.2.2.dist-info → pypreservica-3.2.4.dist-info}/WHEEL +0 -0
- {pypreservica-3.2.2.dist-info → pypreservica-3.2.4.dist-info}/licenses/LICENSE.txt +0 -0
- {pypreservica-3.2.2.dist-info → pypreservica-3.2.4.dist-info}/top_level.txt +0 -0
pyPreservica/__init__.py
CHANGED
pyPreservica/adminAPI.py
CHANGED
|
@@ -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__()
|
pyPreservica/authorityAPI.py
CHANGED
|
@@ -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'))
|
pyPreservica/common.py
CHANGED
|
@@ -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/
|
|
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
|
-
|
|
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
|
"""
|
|
@@ -817,8 +822,6 @@ class AuthenticatedAPI:
|
|
|
817
822
|
self.minor_version = int(version_numbers[1])
|
|
818
823
|
self.patch_version = int(version_numbers[2])
|
|
819
824
|
|
|
820
|
-
if self.server == "preview.preservica.com":
|
|
821
|
-
self.minor_version = 1
|
|
822
825
|
|
|
823
826
|
return version
|
|
824
827
|
elif request.status_code == requests.codes.unauthorized:
|
|
@@ -847,7 +850,7 @@ class AuthenticatedAPI:
|
|
|
847
850
|
with open('credentials.properties', 'wt', encoding="utf-8") as configfile:
|
|
848
851
|
config.write(configfile)
|
|
849
852
|
|
|
850
|
-
def manager_token(self, username: str, password: str):
|
|
853
|
+
def manager_token(self, username: str, password: str) -> str:
|
|
851
854
|
data = {'username': username, 'password': password, 'tenant': self.tenant}
|
|
852
855
|
response = self.session.post(f'{self.protocol}://{self.server}/api/accesstoken/login', data=data)
|
|
853
856
|
if response.status_code == requests.codes.ok:
|
|
@@ -859,7 +862,7 @@ class AuthenticatedAPI:
|
|
|
859
862
|
logger.error(str(response.content))
|
|
860
863
|
RuntimeError(response.status_code, "Could not generate valid manager approval token")
|
|
861
864
|
|
|
862
|
-
def __token__(self):
|
|
865
|
+
def __token__(self) -> str:
|
|
863
866
|
"""
|
|
864
867
|
Generate am API token to use to authenticate calls
|
|
865
868
|
:return: API Token
|
pyPreservica/entityAPI.py
CHANGED
|
@@ -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
|
-
|
|
85
|
+
Download a file represented as a Bitstream to a byteIO array
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
Returns the byteIO
|
|
88
|
+
Returns None if the file does not contain the correct number of bytes (default 2k)
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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
|
|
101
|
-
if
|
|
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
|
|
104
|
+
elif response.status_code == requests.codes.ok:
|
|
105
105
|
file_bytes = BytesIO()
|
|
106
|
-
for chunk in
|
|
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,
|
|
117
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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'))
|
|
@@ -1042,7 +1044,6 @@ class EntityAPI(AuthenticatedAPI):
|
|
|
1042
1044
|
xml_request = xml.etree.ElementTree.tostring(xml_object, encoding='utf-8')
|
|
1043
1045
|
end_point = f"/{entity.path}/{entity.reference}/metadata"
|
|
1044
1046
|
logger.debug(xml_request)
|
|
1045
|
-
print(xml_request)
|
|
1046
1047
|
request = self.session.post(f'{self.protocol}://{self.server}/api/entity{end_point}', data=xml_request,
|
|
1047
1048
|
headers=headers)
|
|
1048
1049
|
if request.status_code == requests.codes.ok:
|
|
@@ -1108,6 +1109,7 @@ class EntityAPI(AuthenticatedAPI):
|
|
|
1108
1109
|
if 'CustomType' in response:
|
|
1109
1110
|
content_object.custom_type = response['CustomType']
|
|
1110
1111
|
return content_object
|
|
1112
|
+
return None
|
|
1111
1113
|
elif request.status_code == requests.codes.unauthorized:
|
|
1112
1114
|
self.token = self.__token__()
|
|
1113
1115
|
return self.save(entity)
|
|
@@ -1296,9 +1298,9 @@ class EntityAPI(AuthenticatedAPI):
|
|
|
1296
1298
|
for uri, schema_name in entity.metadata.items():
|
|
1297
1299
|
if schema == schema_name:
|
|
1298
1300
|
return self.metadata(uri)
|
|
1299
|
-
return
|
|
1301
|
+
return None
|
|
1300
1302
|
|
|
1301
|
-
def metadata_tag_for_entity(self, entity: Entity, schema: str, tag: str, isXpath: bool = False) -> str:
|
|
1303
|
+
def metadata_tag_for_entity(self, entity: Entity, schema: str, tag: str, isXpath: bool = False) -> Union[str, None]:
|
|
1302
1304
|
"""
|
|
1303
1305
|
Retrieve the first value of the tag from a metadata template given by schema
|
|
1304
1306
|
|
|
@@ -1313,10 +1315,11 @@ class EntityAPI(AuthenticatedAPI):
|
|
|
1313
1315
|
xml_doc = self.metadata_for_entity(entity, schema)
|
|
1314
1316
|
if xml_doc:
|
|
1315
1317
|
xml_object = xml.etree.ElementTree.fromstring(xml_doc)
|
|
1316
|
-
if isXpath
|
|
1318
|
+
if not isXpath:
|
|
1317
1319
|
return xml_object.find(f'.//{{*}}{tag}').text
|
|
1318
1320
|
else:
|
|
1319
1321
|
return xml_object.find(tag).text
|
|
1322
|
+
return None
|
|
1320
1323
|
|
|
1321
1324
|
def security_tag_sync(self, entity: EntityT, new_tag: str) -> EntityT:
|
|
1322
1325
|
"""
|
|
@@ -1413,6 +1416,7 @@ class EntityAPI(AuthenticatedAPI):
|
|
|
1413
1416
|
return self.folder(reference)
|
|
1414
1417
|
if entity_type is EntityType.ASSET:
|
|
1415
1418
|
return self.asset(reference)
|
|
1419
|
+
return None
|
|
1416
1420
|
|
|
1417
1421
|
def add_physical_asset(self, title: str, description: str, parent: Folder, security_tag: str = "open") -> Asset:
|
|
1418
1422
|
"""
|
|
@@ -1460,11 +1464,62 @@ class EntityAPI(AuthenticatedAPI):
|
|
|
1460
1464
|
logger.error(exception)
|
|
1461
1465
|
raise exception
|
|
1462
1466
|
|
|
1463
|
-
def
|
|
1467
|
+
def merge_assets(self, assets: list[Asset], title: str, description: str) -> str:
|
|
1468
|
+
"""
|
|
1469
|
+
Create a new Asset with the content from each Asset in supplied list
|
|
1470
|
+
This call will create a new multipart Asset which contains all the content from list of Assets.
|
|
1471
|
+
|
|
1472
|
+
The return value is the progress status of the merge operation.
|
|
1473
|
+
"""
|
|
1474
|
+
|
|
1475
|
+
headers = {
|
|
1476
|
+
HEADER_TOKEN: self.token,
|
|
1477
|
+
"Content-Type": "application/xml;charset=UTF-8",
|
|
1478
|
+
"accept": "text/plain;charset=UTF-8",
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
merge_object = xml.etree.ElementTree.Element("MergeAction", {"xmlns": self.entity_ns, "xmlns:xip": self.xip_ns})
|
|
1482
|
+
xml.etree.ElementTree.SubElement(merge_object, "Title").text = str(title)
|
|
1483
|
+
xml.etree.ElementTree.SubElement(merge_object, "Description").text = str(description)
|
|
1484
|
+
for a in assets:
|
|
1485
|
+
xml.etree.ElementTree.SubElement(merge_object, "Entity", {
|
|
1486
|
+
"excludeIdentifiers": "true",
|
|
1487
|
+
"excludeLinks": "true",
|
|
1488
|
+
"excludeMetadata": "true",
|
|
1489
|
+
"ref": a.reference,
|
|
1490
|
+
"type": EntityType.ASSET.value}
|
|
1491
|
+
)
|
|
1492
|
+
# order_object = xml.etree.ElementTree.SubElement(merge_object, "Order")
|
|
1493
|
+
# for a in assets:
|
|
1494
|
+
# xml.etree.ElementTree.SubElement(order_object, "Entity", {
|
|
1495
|
+
# "ref": a.reference,
|
|
1496
|
+
# "type": EntityType.CONTENT_OBJECT.value}
|
|
1497
|
+
# )
|
|
1498
|
+
xml_request = xml.etree.ElementTree.tostring(merge_object, encoding="utf-8")
|
|
1499
|
+
print(xml_request)
|
|
1500
|
+
request = self.session.post(
|
|
1501
|
+
f"{self.protocol}://{self.server}/api/entity/actions/merges", data=xml_request, headers=headers)
|
|
1502
|
+
if request.status_code == requests.codes.accepted:
|
|
1503
|
+
return request.content.decode('utf-8')
|
|
1504
|
+
elif request.status_code == requests.codes.unauthorized:
|
|
1505
|
+
self.token = self.__token__()
|
|
1506
|
+
return self.merge_assets(assets, title, description)
|
|
1507
|
+
else:
|
|
1508
|
+
exception = HTTPException(
|
|
1509
|
+
"",
|
|
1510
|
+
request.status_code,
|
|
1511
|
+
request.url,
|
|
1512
|
+
"merge_assets",
|
|
1513
|
+
request.content.decode("utf-8"),
|
|
1514
|
+
)
|
|
1515
|
+
logger.error(exception)
|
|
1516
|
+
raise exception
|
|
1517
|
+
|
|
1518
|
+
def merge_folder(self, folder: Folder) -> str:
|
|
1464
1519
|
"""
|
|
1465
1520
|
Create a new Asset with the content from each Asset in the Folder
|
|
1466
1521
|
|
|
1467
|
-
This call will create a new
|
|
1522
|
+
This call will create a new multipart Asset which contains all the content from the Folder.
|
|
1468
1523
|
|
|
1469
1524
|
The new Asset which is created will have the same title, description and parent as the Folder.
|
|
1470
1525
|
|
|
@@ -1495,7 +1550,6 @@ class EntityAPI(AuthenticatedAPI):
|
|
|
1495
1550
|
logger.error(exception)
|
|
1496
1551
|
raise exception
|
|
1497
1552
|
|
|
1498
|
-
|
|
1499
1553
|
def asset(self, reference: str) -> Asset:
|
|
1500
1554
|
"""
|
|
1501
1555
|
Retrieve an Asset by its reference
|
|
@@ -2118,7 +2172,7 @@ class EntityAPI(AuthenticatedAPI):
|
|
|
2118
2172
|
params=params, headers=headers)
|
|
2119
2173
|
|
|
2120
2174
|
if request.status_code == requests.codes.ok:
|
|
2121
|
-
|
|
2175
|
+
return None
|
|
2122
2176
|
elif request.status_code == requests.codes.unauthorized:
|
|
2123
2177
|
self.token = self.__token__()
|
|
2124
2178
|
return self._event_actions(entity, maximum=maximum)
|
|
@@ -2262,6 +2316,7 @@ class EntityAPI(AuthenticatedAPI):
|
|
|
2262
2316
|
else:
|
|
2263
2317
|
url = next_url.text
|
|
2264
2318
|
return PagedSet(result_list, has_more, int(total_hits.text), url)
|
|
2319
|
+
return None
|
|
2265
2320
|
|
|
2266
2321
|
def entity_from_event(self, event_id: str) -> Generator:
|
|
2267
2322
|
self.token = self.__token__()
|
|
@@ -2592,3 +2647,4 @@ class EntityAPI(AuthenticatedAPI):
|
|
|
2592
2647
|
"_delete_entity", request.content.decode('utf-8'))
|
|
2593
2648
|
logger.error(exception)
|
|
2594
2649
|
raise exception
|
|
2650
|
+
|
pyPreservica/mdformsAPI.py
CHANGED
|
@@ -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
|
pyPreservica/uploadAPI.py
CHANGED
|
@@ -1633,6 +1633,52 @@ class UploadAPI(AuthenticatedAPI):
|
|
|
1633
1633
|
logger.error(exception)
|
|
1634
1634
|
raise exception
|
|
1635
1635
|
|
|
1636
|
+
def clean_upload_bucket(self, bucket_name: str, older_than_days: int = 90):
|
|
1637
|
+
"""
|
|
1638
|
+
Clean up objects in an upload bucket which are older than older_than_days.
|
|
1639
|
+
|
|
1640
|
+
"""
|
|
1641
|
+
from azure.storage.blob import ContainerClient
|
|
1642
|
+
|
|
1643
|
+
for location in self.upload_locations():
|
|
1644
|
+
if location['containerName'] == bucket_name:
|
|
1645
|
+
|
|
1646
|
+
if location['type'] != 'AWS':
|
|
1647
|
+
credentials = self.upload_credentials(location['apiId'])
|
|
1648
|
+
account_key = credentials['key']
|
|
1649
|
+
session_token = credentials['sessionToken']
|
|
1650
|
+
sas_url = f"https://{account_key}.blob.core.windows.net/{bucket_name}"
|
|
1651
|
+
container = ContainerClient.from_container_url(container_url=sas_url, credential=session_token)
|
|
1652
|
+
now = datetime.now(timezone.utc)
|
|
1653
|
+
for blob in container.list_blobs():
|
|
1654
|
+
if abs((blob.last_modified - now).days) > older_than_days:
|
|
1655
|
+
logger.debug(f"Deleting expired object {blob.name}")
|
|
1656
|
+
container.delete_blob(blob.name)
|
|
1657
|
+
|
|
1658
|
+
if location['type'] == 'AWS':
|
|
1659
|
+
credentials = self.upload_credentials(location['apiId'])
|
|
1660
|
+
access_key = credentials['key']
|
|
1661
|
+
secret_key = credentials['secret']
|
|
1662
|
+
session_token = credentials['sessionToken']
|
|
1663
|
+
session = boto3.Session(aws_access_key_id=access_key, aws_secret_access_key=secret_key,
|
|
1664
|
+
aws_session_token=session_token)
|
|
1665
|
+
s3_client = session.client("s3")
|
|
1666
|
+
paginator = s3_client.get_paginator('list_objects_v2')
|
|
1667
|
+
now = datetime.now(timezone.utc)
|
|
1668
|
+
for page in paginator.paginate(Bucket=bucket_name):
|
|
1669
|
+
if 'Contents' in page:
|
|
1670
|
+
for key in page['Contents']:
|
|
1671
|
+
last_modified = key["LastModified"]
|
|
1672
|
+
if abs((last_modified - now).days) > older_than_days:
|
|
1673
|
+
logger.debug(f"Deleting expired object {key["Key"]}")
|
|
1674
|
+
s3_client.delete_object(Bucket=bucket_name, Key=key["Key"])
|
|
1675
|
+
|
|
1676
|
+
|
|
1677
|
+
|
|
1678
|
+
|
|
1679
|
+
|
|
1680
|
+
|
|
1681
|
+
|
|
1636
1682
|
def upload_locations(self):
|
|
1637
1683
|
"""
|
|
1638
1684
|
Upload locations are configured on the Sources page as 'SIP Upload'.
|
pyPreservica/webHooksAPI.py
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
pyPreservica/__init__.py,sha256=p5E_7PI_ApqO8L4roS1tYj-MHMmqvt4jdovD_fkTrmc,1250
|
|
2
|
+
pyPreservica/adminAPI.py,sha256=Ls7uJA2Lu5t2k5vOFqrOlbZxmDrSp1-JuD_5z10w9w0,37990
|
|
3
|
+
pyPreservica/authorityAPI.py,sha256=A52sFiAK4E4mlend_dknNIOW2BEwKXcLFI02anZBt3U,9211
|
|
4
|
+
pyPreservica/common.py,sha256=qgKsiA6G9N88-QjV1sMrhhG_KsUtM5otd5G_YGwKC4I,39094
|
|
5
|
+
pyPreservica/contentAPI.py,sha256=ZvX2aGQEaksmw-m-oEUI6daVSqFe_IcE1cGwCNbSCDQ,22286
|
|
6
|
+
pyPreservica/entityAPI.py,sha256=aPQy4lMoBeuCgbd9E_r7FO213oU_SSFHiIsEwGU5VLA,132395
|
|
7
|
+
pyPreservica/mdformsAPI.py,sha256=D6p7uD2qNY76P6_k5MSaXnrYMp487hDXwladYvASejI,20052
|
|
8
|
+
pyPreservica/monitorAPI.py,sha256=LJOUrynBOWKlNiYpZ1iH8qB1oIIuKX1Ms1SRBcuXohA,6274
|
|
9
|
+
pyPreservica/opex.py,sha256=ccra1S4ojUXS3PlbU8WfxajOkJrwG4OykBnNrYP_jus,4875
|
|
10
|
+
pyPreservica/parAPI.py,sha256=f0ZUxLd0U-BW6kBx5K7W2Pv7NjG3MkTNydmxQ3U1ZVE,9296
|
|
11
|
+
pyPreservica/retentionAPI.py,sha256=QUTCbN4P3IpqmrebU_wd3n5ZVcyxVLTFAli8Y_GxOW4,24843
|
|
12
|
+
pyPreservica/settingsAPI.py,sha256=jXnMOCq3mimta6E-Os3J1I1if2pYsjLpOazAx8L-ZQI,10721
|
|
13
|
+
pyPreservica/uploadAPI.py,sha256=Bl12EK6hAlkVQtwGk9PIx-eB0TpZpCO3QENhnRwjAss,102186
|
|
14
|
+
pyPreservica/webHooksAPI.py,sha256=KMObsdHp_0K0HjJUl5oaFh-vs21GCAQZo2ZTKkY38R8,6872
|
|
15
|
+
pyPreservica/workflowAPI.py,sha256=OcOiiUdrQerbPllrkj1lWpmuW0jTuyyV0urwPSYcd_U,17561
|
|
16
|
+
pypreservica-3.2.4.dist-info/licenses/LICENSE.txt,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
17
|
+
pypreservica-3.2.4.dist-info/METADATA,sha256=yE4kMep8IGjI5D7FrefwyR_lBFtCr0r9jZD7-jIpxKU,3077
|
|
18
|
+
pypreservica-3.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
+
pypreservica-3.2.4.dist-info/top_level.txt,sha256=iIBh6NAznYQHOV8mv_y_kGKSDITek9rANyFDwJsbU-c,13
|
|
20
|
+
pypreservica-3.2.4.dist-info/RECORD,,
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
pyPreservica/__init__.py,sha256=tLUJ6UX2RCmqbq_evT5ZlorC4T9sI7Opv83-E7Rvzj4,1250
|
|
2
|
-
pyPreservica/adminAPI.py,sha256=aMN2twcUZOFoGx2yapC6GVtBTdYHUJFA-5bdWVkCwS8,37773
|
|
3
|
-
pyPreservica/authorityAPI.py,sha256=jpf_m9i-IakyNVooi2yELuKt4yhX73hWqQNbPRHZx2g,9206
|
|
4
|
-
pyPreservica/common.py,sha256=CLfHI0Fec_wp1zngqw7-iIl2Yp3hG0ohjuhdl-K84hU,39030
|
|
5
|
-
pyPreservica/contentAPI.py,sha256=ZvX2aGQEaksmw-m-oEUI6daVSqFe_IcE1cGwCNbSCDQ,22286
|
|
6
|
-
pyPreservica/entityAPI.py,sha256=mELG2TxnFBCuDtsrZ2eRNc8EF-D6dI-dumzTfK976TQ,129974
|
|
7
|
-
pyPreservica/mdformsAPI.py,sha256=_hBjT4-OzgLQGDfYX7b_01P27wc-RmsCEu57VtyAdh8,19173
|
|
8
|
-
pyPreservica/monitorAPI.py,sha256=LJOUrynBOWKlNiYpZ1iH8qB1oIIuKX1Ms1SRBcuXohA,6274
|
|
9
|
-
pyPreservica/opex.py,sha256=ccra1S4ojUXS3PlbU8WfxajOkJrwG4OykBnNrYP_jus,4875
|
|
10
|
-
pyPreservica/parAPI.py,sha256=f0ZUxLd0U-BW6kBx5K7W2Pv7NjG3MkTNydmxQ3U1ZVE,9296
|
|
11
|
-
pyPreservica/retentionAPI.py,sha256=QUTCbN4P3IpqmrebU_wd3n5ZVcyxVLTFAli8Y_GxOW4,24843
|
|
12
|
-
pyPreservica/settingsAPI.py,sha256=jXnMOCq3mimta6E-Os3J1I1if2pYsjLpOazAx8L-ZQI,10721
|
|
13
|
-
pyPreservica/uploadAPI.py,sha256=uX67mW-2q7FmjtXQ759GwHPL6Zs7R-iE8-86PBApvbY,99823
|
|
14
|
-
pyPreservica/webHooksAPI.py,sha256=B3C6PV_3JLlJrr9PtsTzL-21M0msx8Mnj18Xb3Bv4RE,6814
|
|
15
|
-
pyPreservica/workflowAPI.py,sha256=OcOiiUdrQerbPllrkj1lWpmuW0jTuyyV0urwPSYcd_U,17561
|
|
16
|
-
pypreservica-3.2.2.dist-info/licenses/LICENSE.txt,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
17
|
-
pypreservica-3.2.2.dist-info/METADATA,sha256=_sr1wxUFF5oIuFQB6A90sDUAw88cWb11jhQ-kJGSBrk,3077
|
|
18
|
-
pypreservica-3.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
-
pypreservica-3.2.2.dist-info/top_level.txt,sha256=iIBh6NAznYQHOV8mv_y_kGKSDITek9rANyFDwJsbU-c,13
|
|
20
|
-
pypreservica-3.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|