pyPreservica 2.7.2__py3-none-any.whl → 2.7.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.
pyPreservica/__init__.py CHANGED
@@ -23,6 +23,6 @@ from .mdformsAPI import MDFormsAPI
23
23
  __author__ = "James Carr (drjamescarr@gmail.com)"
24
24
 
25
25
  # Version of the pyPreservica package
26
- __version__ = "2.7.2"
26
+ __version__ = "2.7.4"
27
27
 
28
28
  __license__ = "Apache License Version 2.0"
pyPreservica/common.py CHANGED
@@ -477,10 +477,10 @@ class Entity:
477
477
  def __repr__(self):
478
478
  return self.__str__()
479
479
 
480
- def has_metadata(self):
480
+ def has_metadata(self) -> bool:
481
481
  return bool(self.metadata)
482
482
 
483
- def metadata_namespaces(self):
483
+ def metadata_namespaces(self) -> list:
484
484
  return list(self.metadata.values())
485
485
 
486
486
 
@@ -714,11 +714,33 @@ class AuthenticatedAPI:
714
714
 
715
715
  return entity_dict
716
716
 
717
+ def edition(self) -> str:
718
+ """
719
+ Return the edition of this tenancy
720
+ """
721
+ if self.major_version < 8 and self.minor_version < 3:
722
+ raise RuntimeError("Entitlement API is only available when connected to a v7.3 System")
723
+
724
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json'}
725
+
726
+ response = self.session.get(f'{self.protocol}://{self.server}/api/entitlement/edition', headers=headers)
727
+
728
+ if response.status_code == requests.codes.ok:
729
+ return response.json()['edition']
730
+ elif response.status_code == requests.codes.unauthorized:
731
+ self.token = self.__token__()
732
+ return self.edition()
733
+ else:
734
+ exception = HTTPException("", response.status_code, response.url,
735
+ "edition", response.content.decode('utf-8'))
736
+ logger.error(exception)
737
+ raise exception
738
+
717
739
  def __version_namespace__(self):
718
740
  """
719
741
  Generate version specific namespaces from the server version
720
742
  """
721
- if self.major_version == 7:
743
+ if self.major_version > 6:
722
744
  self.xip_ns = f"{NS_XIP_ROOT}v{self.major_version}.{self.minor_version}"
723
745
  self.entity_ns = f"{NS_ENTITY_ROOT}v{self.major_version}.{self.minor_version}"
724
746
  self.rm_ns = f"{NS_RM_ROOT}v{6}.{2}"
@@ -857,12 +879,16 @@ class AuthenticatedAPI:
857
879
  raise RuntimeError(response.status_code, msg)
858
880
 
859
881
  def __init__(self, username: str = None, password: str = None, tenant: str = None, server: str = None,
860
- use_shared_secret: bool = False, two_fa_secret_key: str = None, protocol: str = "https"):
882
+ use_shared_secret: bool = False, two_fa_secret_key: str = None,
883
+ protocol: str = "https", request_hook=None):
861
884
 
862
885
  config = configparser.ConfigParser(interpolation=configparser.Interpolation())
863
886
  config.read('credentials.properties', encoding='utf-8')
864
887
  self.session: Session = requests.Session()
865
888
 
889
+ if request_hook is not None:
890
+ self.session.hooks['response'].append(request_hook)
891
+
866
892
  retries = Retry(
867
893
  total=3,
868
894
  backoff_factor=0.1,
@@ -10,7 +10,7 @@ licence: Apache License 2.0
10
10
  """
11
11
 
12
12
  import csv
13
- from typing import Generator
13
+ from typing import Generator, Callable
14
14
  from pyPreservica.common import *
15
15
 
16
16
  logger = logging.getLogger(__name__)
@@ -19,8 +19,10 @@ logger = logging.getLogger(__name__)
19
19
  class ContentAPI(AuthenticatedAPI):
20
20
 
21
21
  def __init__(self, username=None, password=None, tenant=None, server=None, use_shared_secret=False,
22
- two_fa_secret_key: str = None, protocol: str = "https"):
23
- super().__init__(username, password, tenant, server, use_shared_secret, two_fa_secret_key, protocol)
22
+ two_fa_secret_key: str = None, protocol: str = "https", request_hook: Callable = None):
23
+
24
+ super().__init__(username, password, tenant, server, use_shared_secret, two_fa_secret_key,
25
+ protocol, request_hook)
24
26
  self.callback = None
25
27
 
26
28
  class SearchResult:
@@ -130,7 +132,8 @@ class ContentAPI(AuthenticatedAPI):
130
132
  logger.error(f"indexed_fields failed with error code: {results.status_code}")
131
133
  raise RuntimeError(results.status_code, f"indexed_fields failed with error code: {results.status_code}")
132
134
 
133
- def simple_search_csv(self, query: str = "%", page_size: int = 50, csv_file="search.csv", list_indexes: list = None):
135
+ def simple_search_csv(self, query: str = "%", page_size: int = 50, csv_file="search.csv",
136
+ list_indexes: list = None):
134
137
  if list_indexes is None or len(list_indexes) == 0:
135
138
  metadata_fields = ["xip.reference", "xip.title", "xip.description", "xip.document_type",
136
139
  "xip.parent_ref", "xip.security_descriptor"]
@@ -193,7 +196,8 @@ class ContentAPI(AuthenticatedAPI):
193
196
  logger.error(f"search failed with error code: {results.status_code}")
194
197
  raise RuntimeError(results.status_code, f"simple_search failed with error code: {results.status_code}")
195
198
 
196
- def search_index_filter_csv(self, query: str = "%", csv_file="search.csv", page_size: int = 50, filter_values: dict = None,
199
+ def search_index_filter_csv(self, query: str = "%", csv_file="search.csv", page_size: int = 50,
200
+ filter_values: dict = None,
197
201
  sort_values: dict = None):
198
202
  if filter_values is None:
199
203
  filter_values = {}
pyPreservica/entityAPI.py CHANGED
@@ -15,7 +15,7 @@ import xml.etree.ElementTree
15
15
  from datetime import datetime, timedelta, timezone
16
16
  from io import BytesIO
17
17
  from time import sleep
18
- from typing import Any, Generator, Tuple, Iterable, Union
18
+ from typing import Any, Generator, Tuple, Iterable, Union, Callable
19
19
 
20
20
  from pyPreservica.common import *
21
21
 
@@ -34,8 +34,12 @@ class EntityAPI(AuthenticatedAPI):
34
34
  """
35
35
 
36
36
  def __init__(self, username: str = None, password: str = None, tenant: str = None, server: str = None,
37
- use_shared_secret: bool = False, two_fa_secret_key: str = None, protocol: str = "https"):
38
- super().__init__(username, password, tenant, server, use_shared_secret, two_fa_secret_key, protocol)
37
+ use_shared_secret: bool = False, two_fa_secret_key: str = None,
38
+ protocol: str = "https", request_hook: Callable = None):
39
+
40
+ super().__init__(username, password, tenant, server, use_shared_secret, two_fa_secret_key,
41
+ protocol, request_hook)
42
+
39
43
  xml.etree.ElementTree.register_namespace("oai_dc", "http://www.openarchives.org/OAI/2.0/oai_dc/")
40
44
  xml.etree.ElementTree.register_namespace("ead", "urn:isbn:1-931666-22-9")
41
45
 
@@ -10,14 +10,19 @@ licence: Apache License 2.0
10
10
  """
11
11
  import json
12
12
  import xml.etree.ElementTree
13
+ from typing import Callable
13
14
 
14
15
  from pyPreservica.common import *
15
16
 
16
17
 
17
18
  class MDFormsAPI(AuthenticatedAPI):
18
19
  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)
20
+ use_shared_secret: bool = False, two_fa_secret_key: str = None,
21
+ protocol: str = "https", request_hook: Callable = None):
22
+
23
+ super().__init__(username, password, tenant, server, use_shared_secret, two_fa_secret_key,
24
+ protocol, request_hook)
25
+
21
26
  xml.etree.ElementTree.register_namespace("oai_dc", "http://www.openarchives.org/OAI/2.0/oai_dc/")
22
27
  xml.etree.ElementTree.register_namespace("ead", "urn:isbn:1-931666-22-9")
23
28
 
@@ -11,7 +11,7 @@ licence: Apache License 2.0
11
11
 
12
12
 
13
13
  import xml.etree.ElementTree
14
- from typing import Set
14
+ from typing import Set, Callable
15
15
 
16
16
  from pyPreservica.common import *
17
17
 
@@ -59,8 +59,10 @@ class RetentionPolicy:
59
59
  class RetentionAPI(AuthenticatedAPI):
60
60
 
61
61
  def __init__(self, username=None, password=None, tenant=None, server=None, use_shared_secret=False,
62
- two_fa_secret_key: str = None, protocol: str = "https"):
63
- super().__init__(username, password, tenant, server, use_shared_secret, two_fa_secret_key, protocol)
62
+ two_fa_secret_key: str = None, protocol: str = "https", request_hook: Callable = None):
63
+
64
+ super().__init__(username, password, tenant, server, use_shared_secret, two_fa_secret_key,
65
+ protocol, request_hook)
64
66
  if self.major_version < 7 and self.minor_version < 2:
65
67
  raise RuntimeError("Retention API is only available when connected to a v6.2 System")
66
68
 
pyPreservica/uploadAPI.py CHANGED
@@ -80,7 +80,8 @@ class PutObjectTask(s3transfer.tasks.Task):
80
80
  class CompleteMultipartUploadTask(s3transfer.tasks.Task):
81
81
  # Copied from s3transfer/tasks.py, changed to return a result.
82
82
  def _main(self, client, bucket, key, upload_id, parts, extra_args):
83
- return client.complete_multipart_upload(Bucket=bucket, Key=key, UploadId=upload_id, MultipartUpload={"Parts": parts},
83
+ return client.complete_multipart_upload(Bucket=bucket, Key=key, UploadId=upload_id,
84
+ MultipartUpload={"Parts": parts},
84
85
  **extra_args, )
85
86
 
86
87
 
@@ -99,8 +100,8 @@ def prettify(elem):
99
100
  def __create_io__(xip=None, file_name=None, parent_folder=None, **kwargs):
100
101
  if xip is None:
101
102
  xip = Element('xip:XIP')
103
+ xip.set('xmlns:xip', 'http://preservica.com/XIP/v6.0')
102
104
  assert xip is not None
103
- xip.set('xmlns:xip', 'http://preservica.com/XIP/v6.0')
104
105
  io = SubElement(xip, 'xip:InformationObject')
105
106
  ref = SubElement(io, 'xip:Ref')
106
107
 
@@ -224,7 +225,8 @@ def __make_representation_multiple_co__(xip, rep_name, rep_type, rep_files, io_r
224
225
  return refs_dict
225
226
 
226
227
 
227
- def cvs_to_cmis_xslt(csv_file, xml_namespace, root_element, title="Metadata Title", export_folder=None, additional_namespaces=None):
228
+ def cvs_to_cmis_xslt(csv_file, xml_namespace, root_element, title="Metadata Title", export_folder=None,
229
+ additional_namespaces=None):
228
230
  """
229
231
  Create a custom CMIS transform to display metadata within UA.
230
232
 
@@ -242,7 +244,8 @@ def cvs_to_cmis_xslt(csv_file, xml_namespace, root_element, title="Metadata Titl
242
244
 
243
245
  namespaces = {"version": "2.0", "xmlns:xsl": "http://www.w3.org/1999/XSL/Transform",
244
246
  "xmlns:fn": "http://www.w3.org/2005/xpath-functions", "xmlns:xs": "http://www.w3.org/2001/XMLSchema",
245
- "xmlns:csv": xml_namespace, "xmlns": "http://www.tessella.com/sdb/cmis/metadata", "exclude-result-prefixes": "csv"}
247
+ "xmlns:csv": xml_namespace, "xmlns": "http://www.tessella.com/sdb/cmis/metadata",
248
+ "exclude-result-prefixes": "csv"}
246
249
 
247
250
  if additional_namespaces is not None:
248
251
  for prefix, uri in additional_namespaces.items():
@@ -311,7 +314,8 @@ def cvs_to_xsd(csv_file, xml_namespace, root_element, export_folder=None, additi
311
314
  headers.add(xml_tag)
312
315
  break
313
316
 
314
- namespaces = {"xmlns:xs": "http://www.w3.org/2001/XMLSchema", "attributeFormDefault": "unqualified", "elementFormDefault": "qualified",
317
+ namespaces = {"xmlns:xs": "http://www.w3.org/2001/XMLSchema", "attributeFormDefault": "unqualified",
318
+ "elementFormDefault": "qualified",
315
319
  "targetNamespace": xml_namespace}
316
320
 
317
321
  if additional_namespaces is not None:
@@ -333,7 +337,8 @@ def cvs_to_xsd(csv_file, xml_namespace, root_element, export_folder=None, additi
333
337
  prefix, sep, tag = header.partition(":")
334
338
  try:
335
339
  namespace = additional_namespaces[prefix]
336
- xml.etree.ElementTree.SubElement(xml_sequence, "xs:element", {"ref": header, "xmlns:" + prefix: namespace})
340
+ xml.etree.ElementTree.SubElement(xml_sequence, "xs:element",
341
+ {"ref": header, "xmlns:" + prefix: namespace})
337
342
  except KeyError:
338
343
  xml.etree.ElementTree.SubElement(xml_sequence, "xs:element", {"type": "xs:string", "name": header})
339
344
  else:
@@ -350,7 +355,8 @@ def cvs_to_xsd(csv_file, xml_namespace, root_element, export_folder=None, additi
350
355
  return xsd_file
351
356
 
352
357
 
353
- def csv_to_search_xml(csv_file, xml_namespace, root_element, title="Metadata Title", export_folder=None, additional_namespaces=None):
358
+ def csv_to_search_xml(csv_file, xml_namespace, root_element, title="Metadata Title", export_folder=None,
359
+ additional_namespaces=None):
354
360
  """
355
361
  Create a custom Preservica search index based on the columns in a csv file
356
362
 
@@ -400,7 +406,8 @@ def csv_to_search_xml(csv_file, xml_namespace, root_element, title="Metadata Tit
400
406
  return search_xml
401
407
 
402
408
 
403
- def cvs_to_xml(csv_file, xml_namespace, root_element, file_name_column="filename", export_folder=None, additional_namespaces=None):
409
+ def cvs_to_xml(csv_file, xml_namespace, root_element, file_name_column="filename", export_folder=None,
410
+ additional_namespaces=None):
404
411
  """
405
412
  Export the rows of a CSV file as XML metadata documents which can be added to Preservica assets
406
413
 
@@ -451,7 +458,8 @@ def cvs_to_xml(csv_file, xml_namespace, root_element, file_name_column="filename
451
458
  yield name
452
459
 
453
460
 
454
- def generic_asset_package(preservation_files_dict=None, access_files_dict=None, export_folder=None, parent_folder=None, compress=True,
461
+ def generic_asset_package(preservation_files_dict=None, access_files_dict=None, export_folder=None, parent_folder=None,
462
+ compress=True,
455
463
  **kwargs):
456
464
  # some basic validation
457
465
  if export_folder is None:
@@ -501,8 +509,10 @@ def generic_asset_package(preservation_files_dict=None, access_files_dict=None,
501
509
  if has_preservation_files:
502
510
  for representation_name in preservation_files_dict.keys():
503
511
  preservation_files_list = preservation_files_dict[representation_name]
504
- preservation_refs_dict = __make_representation_multiple_co__(xip, rep_name=representation_name, rep_type="Preservation",
505
- rep_files=preservation_files_list, io_ref=io_ref)
512
+ preservation_refs_dict = __make_representation_multiple_co__(xip, rep_name=representation_name,
513
+ rep_type="Preservation",
514
+ rep_files=preservation_files_list,
515
+ io_ref=io_ref)
506
516
  preservation_representation_refs_dict[representation_name] = preservation_refs_dict
507
517
 
508
518
  if has_access_files:
@@ -519,13 +529,16 @@ def generic_asset_package(preservation_files_dict=None, access_files_dict=None,
519
529
  default_content_objects_title = os.path.splitext(os.path.basename(filename))[0]
520
530
 
521
531
  preservation_content_title = kwargs.get('Preservation_Content_Title', default_content_objects_title)
522
- preservation_content_description = kwargs.get('Preservation_Content_Description', default_content_objects_title)
532
+ preservation_content_description = kwargs.get('Preservation_Content_Description',
533
+ default_content_objects_title)
523
534
 
524
535
  if isinstance(preservation_content_title, dict):
525
- preservation_content_title = preservation_content_title.get("filename", default_content_objects_title)
536
+ preservation_content_title = preservation_content_title.get("filename",
537
+ default_content_objects_title)
526
538
 
527
539
  if isinstance(preservation_content_description, dict):
528
- preservation_content_description = preservation_content_description.get("filename", default_content_objects_title)
540
+ preservation_content_description = preservation_content_description.get("filename",
541
+ default_content_objects_title)
529
542
 
530
543
  __make_content_objects__(xip, preservation_content_title, content_ref, io_ref, security_tag,
531
544
  preservation_content_description, content_type)
@@ -545,7 +558,8 @@ def generic_asset_package(preservation_files_dict=None, access_files_dict=None,
545
558
  if isinstance(access_content_description, dict):
546
559
  access_content_description = access_content_title.get("filename", default_content_objects_title)
547
560
 
548
- __make_content_objects__(xip, access_content_title, content_ref, io_ref, security_tag, access_content_description,
561
+ __make_content_objects__(xip, access_content_title, content_ref, io_ref, security_tag,
562
+ access_content_description,
549
563
  content_type)
550
564
 
551
565
  if has_preservation_files:
@@ -598,12 +612,12 @@ def generic_asset_package(preservation_files_dict=None, access_files_dict=None,
598
612
  for identifier_key, identifier_value in identifier_map.items():
599
613
  if identifier_key:
600
614
  if identifier_value:
601
- identifier = SubElement(xip, 'Identifier')
602
- id_type = SubElement(identifier, "Type")
615
+ identifier = SubElement(xip, 'xip:Identifier')
616
+ id_type = SubElement(identifier, "xip:Type")
603
617
  id_type.text = identifier_key
604
- id_value = SubElement(identifier, "Value")
618
+ id_value = SubElement(identifier, "xip:Value")
605
619
  id_value.text = identifier_value
606
- id_io = SubElement(identifier, "Entity")
620
+ id_io = SubElement(identifier, "xip:Entity")
607
621
  id_io.text = io_ref
608
622
 
609
623
  if 'Asset_Metadata' in kwargs:
@@ -613,22 +627,22 @@ def generic_asset_package(preservation_files_dict=None, access_files_dict=None,
613
627
  if metadata_path:
614
628
  if os.path.exists(metadata_path) and os.path.isfile(metadata_path):
615
629
  descriptive_metadata = xml.etree.ElementTree.parse(source=metadata_path)
616
- metadata = SubElement(xip, 'Metadata', {'schemaUri': metadata_ns})
617
- metadata_ref = SubElement(metadata, 'Ref')
630
+ metadata = SubElement(xip, 'xip:Metadata', {'schemaUri': metadata_ns})
631
+ metadata_ref = SubElement(metadata, 'xip:Ref')
618
632
  metadata_ref.text = str(uuid.uuid4())
619
- entity = SubElement(metadata, 'Entity')
633
+ entity = SubElement(metadata, 'xip:Entity')
620
634
  entity.text = io_ref
621
- content = SubElement(metadata, 'Content')
635
+ content = SubElement(metadata, 'xip:Content')
622
636
  content.append(descriptive_metadata.getroot())
623
637
  elif isinstance(metadata_path, str):
624
638
  try:
625
639
  descriptive_metadata = xml.etree.ElementTree.fromstring(metadata_path)
626
- metadata = SubElement(xip, 'Metadata', {'schemaUri': metadata_ns})
627
- metadata_ref = SubElement(metadata, 'Ref')
640
+ metadata = SubElement(xip, 'xip:Metadata', {'schemaUri': metadata_ns})
641
+ metadata_ref = SubElement(metadata, 'xip:Ref')
628
642
  metadata_ref.text = str(uuid.uuid4())
629
- entity = SubElement(metadata, 'Entity')
643
+ entity = SubElement(metadata, 'xip:Entity')
630
644
  entity.text = io_ref
631
- content = SubElement(metadata, 'Content')
645
+ content = SubElement(metadata, 'xip:Content')
632
646
  content.append(descriptive_metadata)
633
647
  except RuntimeError:
634
648
  logging.info(f"Could not parse asset metadata in namespace {metadata_ns}")
@@ -712,71 +726,72 @@ def multi_asset_package(asset_file_list=None, export_folder=None, parent_folder=
712
726
  os.mkdir(os.path.join(inner_folder, CONTENT_FOLDER))
713
727
 
714
728
  asset_map = dict()
715
- xip = Element('XIP')
729
+ xip = Element('xip:XIP')
730
+ xip.set('xmlns:xip', 'http://preservica.com/XIP/v6.0')
716
731
  for file in asset_file_list:
717
732
  default_asset_title = os.path.splitext(os.path.basename(file))[0]
718
733
  xip, io_ref = __create_io__(xip, file_name=default_asset_title, parent_folder=parent_folder, **kwargs)
719
734
  asset_map[file] = io_ref
720
- representation = SubElement(xip, 'Representation')
721
- io_link = SubElement(representation, 'InformationObject')
735
+ representation = SubElement(xip, 'xip:Representation')
736
+ io_link = SubElement(representation, 'xip:InformationObject')
722
737
  io_link.text = io_ref
723
- access_name = SubElement(representation, 'Name')
738
+ access_name = SubElement(representation, 'xip:Name')
724
739
  access_name.text = "Preservation"
725
- access_type = SubElement(representation, 'Type')
740
+ access_type = SubElement(representation, 'xip:Type')
726
741
  access_type.text = "Preservation"
727
- content_objects = SubElement(representation, 'ContentObjects')
728
- content_object = SubElement(content_objects, 'ContentObject')
742
+ content_objects = SubElement(representation, 'xip:ContentObjects')
743
+ content_object = SubElement(content_objects, 'xip:ContentObject')
729
744
  content_object_ref = str(uuid.uuid4())
730
745
  content_object.text = content_object_ref
731
746
 
732
747
  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")
748
+ content_object = SubElement(xip, 'xip:ContentObject')
749
+ ref_element = SubElement(content_object, "xip:Ref")
735
750
  ref_element.text = content_object_ref
736
- title = SubElement(content_object, "Title")
751
+ title = SubElement(content_object, "xip:Title")
737
752
  title.text = default_content_objects_title
738
- description = SubElement(content_object, "Description")
753
+ description = SubElement(content_object, "xip:Description")
739
754
  description.text = default_content_objects_title
740
- security_tag_element = SubElement(content_object, "SecurityTag")
755
+ security_tag_element = SubElement(content_object, "xip:SecurityTag")
741
756
  security_tag_element.text = security_tag
742
- custom_type = SubElement(content_object, "CustomType")
757
+ custom_type = SubElement(content_object, "xip:CustomType")
743
758
  custom_type.text = content_type
744
- parent = SubElement(content_object, "Parent")
759
+ parent = SubElement(content_object, "xip:Parent")
745
760
  parent.text = io_ref
746
761
 
747
- generation = SubElement(xip, 'Generation', {"original": "true", "active": "true"})
748
- content_object = SubElement(generation, "ContentObject")
762
+ generation = SubElement(xip, 'xip:Generation', {"original": "true", "active": "true"})
763
+ content_object = SubElement(generation, "xip:ContentObject")
749
764
  content_object.text = content_object_ref
750
- label = SubElement(generation, "Label")
765
+ label = SubElement(generation, "xip:Label")
751
766
  label.text = os.path.splitext(os.path.basename(file))[0]
752
- effective_date = SubElement(generation, "EffectiveDate")
767
+ effective_date = SubElement(generation, "xip:EffectiveDate")
753
768
  effective_date.text = datetime.now().isoformat()
754
- bitstreams = SubElement(generation, "Bitstreams")
755
- bitstream = SubElement(bitstreams, "Bitstream")
769
+ bitstreams = SubElement(generation, "xip:Bitstreams")
770
+ bitstream = SubElement(bitstreams, "xip:Bitstream")
756
771
  bitstream.text = os.path.basename(file)
757
- SubElement(generation, "Formats")
758
- SubElement(generation, "Properties")
772
+ SubElement(generation, "xip:Formats")
773
+ SubElement(generation, "xip:Properties")
759
774
 
760
- bitstream = SubElement(xip, 'Bitstream')
761
- filename_element = SubElement(bitstream, "Filename")
775
+ bitstream = SubElement(xip, 'xip:Bitstream')
776
+ filename_element = SubElement(bitstream, "xip:Filename")
762
777
  filename_element.text = os.path.basename(file)
763
- filesize = SubElement(bitstream, "FileSize")
778
+ filesize = SubElement(bitstream, "xip:FileSize")
764
779
  file_stats = os.stat(file)
765
780
  filesize.text = str(file_stats.st_size)
766
- physical_location = SubElement(bitstream, "PhysicalLocation")
767
- fixities = SubElement(bitstream, "Fixities")
781
+ physical_location = SubElement(bitstream, "xip:PhysicalLocation")
782
+ fixities = SubElement(bitstream, "xip:Fixities")
768
783
  fixity_result = fixity_callback(filename_element.text, file)
769
784
  if type(fixity_result) == tuple:
770
- fixity = SubElement(fixities, "Fixity")
771
- fixity_algorithm_ref = SubElement(fixity, "FixityAlgorithmRef")
772
- fixity_value = SubElement(fixity, "FixityValue")
785
+ fixity = SubElement(fixities, "xip:Fixity")
786
+ fixity_algorithm_ref = SubElement(fixity, "xip:FixityAlgorithmRef")
787
+ fixity_value = SubElement(fixity, "xip:FixityValue")
773
788
  fixity_algorithm_ref.text = fixity_result[0]
774
789
  fixity_value.text = fixity_result[1]
775
790
  elif type(fixity_result) == dict:
776
791
  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")
792
+ fixity = SubElement(fixities, "xip:Fixity")
793
+ fixity_algorithm_ref = SubElement(fixity, "xip:FixityAlgorithmRef")
794
+ fixity_value = SubElement(fixity, "xip:FixityValue")
780
795
  fixity_algorithm_ref.text = key
781
796
  fixity_value.text = val
782
797
  else:
@@ -790,12 +805,12 @@ def multi_asset_package(asset_file_list=None, export_folder=None, parent_folder=
790
805
  for identifier_key, identifier_value in identifier_map_values.items():
791
806
  if identifier_key:
792
807
  if identifier_value:
793
- identifier = SubElement(xip, 'Identifier')
794
- id_type = SubElement(identifier, "Type")
808
+ identifier = SubElement(xip, 'xip:Identifier')
809
+ id_type = SubElement(identifier, "xip:Type")
795
810
  id_type.text = identifier_key
796
- id_value = SubElement(identifier, "Value")
811
+ id_value = SubElement(identifier, "xip:Value")
797
812
  id_value.text = identifier_value
798
- id_io = SubElement(identifier, "Entity")
813
+ id_io = SubElement(identifier, "xip:Entity")
799
814
  id_io.text = io_ref
800
815
 
801
816
  src_file = file
@@ -815,7 +830,8 @@ def multi_asset_package(asset_file_list=None, export_folder=None, parent_folder=
815
830
  return top_level_folder + ".zip"
816
831
 
817
832
 
818
- def complex_asset_package(preservation_files_list=None, access_files_list=None, export_folder=None, parent_folder=None, compress=True,
833
+ def complex_asset_package(preservation_files_list=None, access_files_list=None, export_folder=None, parent_folder=None,
834
+ compress=True,
819
835
  **kwargs):
820
836
  """
821
837
 
@@ -908,13 +924,15 @@ def complex_asset_package(preservation_files_list=None, access_files_list=None,
908
924
  if has_preservation_files:
909
925
  # add the content objects
910
926
  representation_name = kwargs.get('Preservation_Representation_Name', "Preservation")
911
- preservation_refs_dict = __make_representation_multiple_co__(xip, rep_name=representation_name, rep_type="Preservation",
927
+ preservation_refs_dict = __make_representation_multiple_co__(xip, rep_name=representation_name,
928
+ rep_type="Preservation",
912
929
  rep_files=preservation_files_list, io_ref=io_ref)
913
930
 
914
931
  if has_access_files:
915
932
  # add the content objects
916
933
  access_name = kwargs.get('Access_Representation_Name', "Access")
917
- access_refs_dict = __make_representation_multiple_co__(xip, rep_name=access_name, rep_type="Access", rep_files=access_files_list,
934
+ access_refs_dict = __make_representation_multiple_co__(xip, rep_name=access_name, rep_type="Access",
935
+ rep_files=access_files_list,
918
936
  io_ref=io_ref)
919
937
 
920
938
  if has_preservation_files:
@@ -922,7 +940,8 @@ def complex_asset_package(preservation_files_list=None, access_files_list=None,
922
940
  for content_ref, filename in preservation_refs_dict.items():
923
941
  default_content_objects_title = os.path.splitext(os.path.basename(filename))[0]
924
942
  preservation_content_title = kwargs.get('Preservation_Content_Title', default_content_objects_title)
925
- preservation_content_description = kwargs.get('Preservation_Content_Description', default_content_objects_title)
943
+ preservation_content_description = kwargs.get('Preservation_Content_Description',
944
+ default_content_objects_title)
926
945
 
927
946
  if isinstance(preservation_content_title, dict):
928
947
  preservation_content_title = preservation_content_title[filename]
@@ -930,7 +949,8 @@ def complex_asset_package(preservation_files_list=None, access_files_list=None,
930
949
  if isinstance(preservation_content_description, dict):
931
950
  preservation_content_description = preservation_content_description[filename]
932
951
 
933
- __make_content_objects__(xip, preservation_content_title, content_ref, io_ref, security_tag, preservation_content_description,
952
+ __make_content_objects__(xip, preservation_content_title, content_ref, io_ref, security_tag,
953
+ preservation_content_description,
934
954
  content_type)
935
955
 
936
956
  if has_access_files:
@@ -947,7 +967,8 @@ def complex_asset_package(preservation_files_list=None, access_files_list=None,
947
967
  if isinstance(access_content_description, dict):
948
968
  access_content_title = access_content_title[filename]
949
969
 
950
- __make_content_objects__(xip, access_content_title, content_ref, io_ref, security_tag, access_content_description, content_type)
970
+ __make_content_objects__(xip, access_content_title, content_ref, io_ref, security_tag,
971
+ access_content_description, content_type)
951
972
 
952
973
  if has_preservation_files:
953
974
 
@@ -955,7 +976,8 @@ def complex_asset_package(preservation_files_list=None, access_files_list=None,
955
976
 
956
977
  for content_ref, filename in preservation_refs_dict.items():
957
978
  preservation_file_name = os.path.basename(filename)
958
- __make_generation__(xip, preservation_file_name, content_ref, preservation_generation_label, PRESERVATION_CONTENT_FOLDER)
979
+ __make_generation__(xip, preservation_file_name, content_ref, preservation_generation_label,
980
+ PRESERVATION_CONTENT_FOLDER)
959
981
 
960
982
  if has_access_files:
961
983
 
@@ -1070,7 +1092,8 @@ def complex_asset_package(preservation_files_list=None, access_files_list=None,
1070
1092
  return top_level_folder + ".zip"
1071
1093
 
1072
1094
 
1073
- def simple_asset_package(preservation_file=None, access_file=None, export_folder=None, parent_folder=None, compress=True, **kwargs):
1095
+ def simple_asset_package(preservation_file=None, access_file=None, export_folder=None, parent_folder=None,
1096
+ compress=True, **kwargs):
1074
1097
  """
1075
1098
  Create a Preservica package containing a single Asset from a single preservation file
1076
1099
  and an optional access file.
@@ -1130,7 +1153,8 @@ def _unpad(s):
1130
1153
 
1131
1154
  class UploadAPI(AuthenticatedAPI):
1132
1155
 
1133
- def ingest_tweet(self, twitter_user=None, tweet_id: int = 0, twitter_consumer_key=None, twitter_secret_key=None, folder=None,
1156
+ def ingest_tweet(self, twitter_user=None, tweet_id: int = 0, twitter_consumer_key=None, twitter_secret_key=None,
1157
+ folder=None,
1134
1158
  callback=None, **kwargs):
1135
1159
 
1136
1160
  """
@@ -1174,7 +1198,8 @@ class UploadAPI(AuthenticatedAPI):
1174
1198
  video_name_document_.close()
1175
1199
  return video_name_, True
1176
1200
 
1177
- entity_client = pyPreservica.EntityAPI(username=self.username, password=self.password, server=self.server, tenant=self.tenant)
1201
+ entity_client = pyPreservica.EntityAPI(username=self.username, password=self.password, server=self.server,
1202
+ tenant=self.tenant)
1178
1203
  if hasattr(folder, "reference"):
1179
1204
  folder = entity_client.folder(folder.reference)
1180
1205
  else:
@@ -1312,7 +1337,8 @@ class UploadAPI(AuthenticatedAPI):
1312
1337
  os.remove(ob)
1313
1338
  os.remove("metadata.xml")
1314
1339
 
1315
- def ingest_twitter_feed(self, twitter_user=None, num_tweets: int = 25, twitter_consumer_key=None, twitter_secret_key=None, folder=None,
1340
+ def ingest_twitter_feed(self, twitter_user=None, num_tweets: int = 25, twitter_consumer_key=None,
1341
+ twitter_secret_key=None, folder=None,
1316
1342
  callback=None, **kwargs):
1317
1343
 
1318
1344
  """
@@ -1357,7 +1383,8 @@ class UploadAPI(AuthenticatedAPI):
1357
1383
  video_name_document_.flush()
1358
1384
  return video_name_, True
1359
1385
 
1360
- entity_client = pyPreservica.EntityAPI(username=self.username, password=self.password, server=self.server, tenant=self.tenant)
1386
+ entity_client = pyPreservica.EntityAPI(username=self.username, password=self.password, server=self.server,
1387
+ tenant=self.tenant)
1361
1388
  if hasattr(folder, "reference"):
1362
1389
  folder = entity_client.folder(folder.reference)
1363
1390
  else:
@@ -1488,7 +1515,8 @@ class UploadAPI(AuthenticatedAPI):
1488
1515
  asset_title = kwargs.get("Title", text)
1489
1516
  asset_description = kwargs.get("Description", full_text)
1490
1517
 
1491
- p = complex_asset_package(preservation_files_list=content_objects, parent_folder=folder, Title=asset_title,
1518
+ p = complex_asset_package(preservation_files_list=content_objects, parent_folder=folder,
1519
+ Title=asset_title,
1492
1520
  Description=asset_description, CustomType="Tweet", Identifiers=identifiers,
1493
1521
  Asset_Metadata=asset_metadata, SecurityTag=security_tag)
1494
1522
  self.upload_zip_package(p, folder=folder, callback=callback)
@@ -1572,7 +1600,8 @@ class UploadAPI(AuthenticatedAPI):
1572
1600
  duration = meta.get('duration')
1573
1601
 
1574
1602
  package = simple_asset_package(preservation_file=f"{vid_id}.mp4", parent_folder=parent_folder, Title=title,
1575
- Description=description, Identifiers=identifier_map, Asset_Metadata=descriptive_metadata,
1603
+ Description=description, Identifiers=identifier_map,
1604
+ Asset_Metadata=descriptive_metadata,
1576
1605
  Preservation_Content_Title=title, SecurityTag=security_tag)
1577
1606
 
1578
1607
  self.upload_zip_package(path_to_zip_package=package, folder=parent_folder, callback=callback)
@@ -1593,7 +1622,8 @@ class UploadAPI(AuthenticatedAPI):
1593
1622
  self.token = self.__token__()
1594
1623
  return self.upload_credentials(location_id)
1595
1624
  else:
1596
- exception = HTTPException(location_id, request.status_code, request.url, "upload_credentials", request.content.decode('utf-8'))
1625
+ exception = HTTPException(location_id, request.status_code, request.url, "upload_credentials",
1626
+ request.content.decode('utf-8'))
1597
1627
  logger.error(exception)
1598
1628
  raise exception
1599
1629
 
@@ -1612,7 +1642,8 @@ class UploadAPI(AuthenticatedAPI):
1612
1642
  self.token = self.__token__()
1613
1643
  return self.upload_locations()
1614
1644
  else:
1615
- exception = HTTPException("", request.status_code, request.url, "upload_locations", request.content.decode('utf-8'))
1645
+ exception = HTTPException("", request.status_code, request.url, "upload_locations",
1646
+ request.content.decode('utf-8'))
1616
1647
  logger.error(exception)
1617
1648
  raise exception
1618
1649
 
@@ -1624,7 +1655,8 @@ class UploadAPI(AuthenticatedAPI):
1624
1655
  """
1625
1656
  return self.upload_locations()
1626
1657
 
1627
- def crawl_filesystem(self, filesystem_path, bucket_name, preservica_parent, callback: bool = False, security_tag: str = "open",
1658
+ def crawl_filesystem(self, filesystem_path, bucket_name, preservica_parent, callback: bool = False,
1659
+ security_tag: str = "open",
1628
1660
  delete_after_upload: bool = True, max_MB_ingested: int = -1):
1629
1661
 
1630
1662
  def get_parent(client, identifier, parent_reference):
@@ -1651,8 +1683,10 @@ class UploadAPI(AuthenticatedAPI):
1651
1683
  return folder
1652
1684
 
1653
1685
  from pyPreservica import EntityAPI
1654
- entity_client = EntityAPI(username=self.username, password=self.password, server=self.server, tenant=self.tenant,
1655
- two_fa_secret_key=self.two_fa_secret_key, use_shared_secret=self.shared_secret, protocol=self.protocol)
1686
+ entity_client = EntityAPI(username=self.username, password=self.password, server=self.server,
1687
+ tenant=self.tenant,
1688
+ two_fa_secret_key=self.two_fa_secret_key, use_shared_secret=self.shared_secret,
1689
+ protocol=self.protocol)
1656
1690
 
1657
1691
  if preservica_parent:
1658
1692
  parent = entity_client.folder(preservica_parent)
@@ -1696,8 +1730,14 @@ class UploadAPI(AuthenticatedAPI):
1696
1730
  else:
1697
1731
  progress_display = None
1698
1732
 
1699
- self.upload_zip_package_to_S3(path_to_zip_package=package, bucket_name=bucket_name, callback=progress_display,
1700
- delete_after_upload=delete_after_upload)
1733
+ if bucket_name is None:
1734
+ self.upload_zip_package(path_to_zip_package=package, callback=progress_display,
1735
+ delete_after_upload=delete_after_upload)
1736
+ else:
1737
+ self.upload_zip_package_to_S3(path_to_zip_package=package, bucket_name=bucket_name,
1738
+ callback=progress_display,
1739
+ delete_after_upload=delete_after_upload)
1740
+
1701
1741
  logger.info(f"Uploaded " + "{:.1f}".format(bytes_ingested / (1024 * 1024)) + " MB")
1702
1742
 
1703
1743
  if max_MB_ingested > 0:
@@ -1705,7 +1745,8 @@ class UploadAPI(AuthenticatedAPI):
1705
1745
  logger.info(f"Reached Max Upload Limit")
1706
1746
  break
1707
1747
 
1708
- def upload_zip_to_Source(self, path_to_zip_package, container_name, folder=None, delete_after_upload=False, show_progress=False):
1748
+ def upload_zip_to_Source(self, path_to_zip_package, container_name, folder=None, delete_after_upload=False,
1749
+ show_progress=False):
1709
1750
 
1710
1751
  """
1711
1752
  Uploads a zip file package to either an Azure container or S3 bucket
@@ -1726,13 +1767,17 @@ class UploadAPI(AuthenticatedAPI):
1726
1767
  callback = None
1727
1768
  if show_progress:
1728
1769
  callback = UploadProgressConsoleCallback(path_to_zip_package)
1729
- self.upload_zip_package_to_S3(path_to_zip_package=path_to_zip_package, bucket_name=container_name, folder=folder,
1770
+ self.upload_zip_package_to_S3(path_to_zip_package=path_to_zip_package, bucket_name=container_name,
1771
+ folder=folder,
1730
1772
  callback=callback, delete_after_upload=delete_after_upload)
1731
1773
  else:
1732
- self.upload_zip_package_to_Azure(path_to_zip_package=path_to_zip_package, container_name=container_name, folder=folder,
1733
- delete_after_upload=delete_after_upload, show_progress=show_progress)
1774
+ self.upload_zip_package_to_Azure(path_to_zip_package=path_to_zip_package,
1775
+ container_name=container_name, folder=folder,
1776
+ delete_after_upload=delete_after_upload,
1777
+ show_progress=show_progress)
1734
1778
 
1735
- def upload_zip_package_to_Azure(self, path_to_zip_package, container_name, folder=None, delete_after_upload=False, show_progress=False):
1779
+ def upload_zip_package_to_Azure(self, path_to_zip_package, container_name, folder=None, delete_after_upload=False,
1780
+ show_progress=False):
1736
1781
 
1737
1782
  """
1738
1783
  Uploads a zip file package to an Azure container connected to a Preservica Cloud System
@@ -1745,7 +1790,8 @@ class UploadAPI(AuthenticatedAPI):
1745
1790
  """
1746
1791
 
1747
1792
  if (self.major_version < 7) and (self.minor_version < 5):
1748
- raise RuntimeError("This call [upload_zip_package_to_Azure] is only available against v6.5 systems and above")
1793
+ raise RuntimeError(
1794
+ "This call [upload_zip_package_to_Azure] is only available against v6.5 systems and above")
1749
1795
 
1750
1796
  from azure.storage.blob import ContainerClient
1751
1797
 
@@ -1773,11 +1819,13 @@ class UploadAPI(AuthenticatedAPI):
1773
1819
 
1774
1820
  if show_progress:
1775
1821
  with tqdm.wrapattr(open(path_to_zip_package, 'rb'), "read", total=len_bytes) as data:
1776
- blob_client = container.upload_blob(name=upload_key, data=data, metadata=metadata, length=len_bytes)
1822
+ blob_client = container.upload_blob(name=upload_key, data=data, metadata=metadata,
1823
+ length=len_bytes)
1777
1824
  properties = blob_client.get_blob_properties()
1778
1825
  else:
1779
1826
  with open(path_to_zip_package, "rb") as data:
1780
- blob_client = container.upload_blob(name=upload_key, data=data, metadata=metadata, length=len_bytes)
1827
+ blob_client = container.upload_blob(name=upload_key, data=data, metadata=metadata,
1828
+ length=len_bytes)
1781
1829
  properties = blob_client.get_blob_properties()
1782
1830
 
1783
1831
  if delete_after_upload:
@@ -1785,7 +1833,8 @@ class UploadAPI(AuthenticatedAPI):
1785
1833
 
1786
1834
  return properties
1787
1835
 
1788
- def upload_zip_package_to_S3(self, path_to_zip_package, bucket_name, folder=None, callback=None, delete_after_upload=False):
1836
+ def upload_zip_package_to_S3(self, path_to_zip_package, bucket_name, folder=None, callback=None,
1837
+ delete_after_upload=False):
1789
1838
 
1790
1839
  """
1791
1840
  Uploads a zip file package to an S3 bucket connected to a Preservica Cloud System
@@ -1814,7 +1863,8 @@ class UploadAPI(AuthenticatedAPI):
1814
1863
  session_token = credentials['sessionToken']
1815
1864
  endpoint = credentials['endpoint']
1816
1865
 
1817
- session = boto3.Session(aws_access_key_id=access_key, aws_secret_access_key=secret_key, aws_session_token=session_token)
1866
+ session = boto3.Session(aws_access_key_id=access_key, aws_secret_access_key=secret_key,
1867
+ aws_session_token=session_token)
1818
1868
  s3 = session.resource(service_name="s3")
1819
1869
 
1820
1870
  logger.debug(f"S3 Session: {s3}")
@@ -1833,7 +1883,8 @@ class UploadAPI(AuthenticatedAPI):
1833
1883
 
1834
1884
  metadata_map = {'Metadata': metadata}
1835
1885
 
1836
- s3_object.upload_file(path_to_zip_package, Callback=callback, ExtraArgs=metadata_map, Config=transfer_config)
1886
+ s3_object.upload_file(path_to_zip_package, Callback=callback, ExtraArgs=metadata_map,
1887
+ Config=transfer_config)
1837
1888
 
1838
1889
  if delete_after_upload:
1839
1890
  os.remove(path_to_zip_package)
@@ -1859,7 +1910,8 @@ class UploadAPI(AuthenticatedAPI):
1859
1910
  endpoint = f'{self.protocol}://{self.server}/api/s3/buckets'
1860
1911
  self.token = self.__token__()
1861
1912
 
1862
- s3_client = boto3.client('s3', endpoint_url=endpoint, aws_access_key_id=self.token, aws_secret_access_key="NOT_USED",
1913
+ s3_client = boto3.client('s3', endpoint_url=endpoint, aws_access_key_id=self.token,
1914
+ aws_secret_access_key="NOT_USED",
1863
1915
  config=Config(s3={'addressing_style': 'path'}))
1864
1916
 
1865
1917
  metadata = {}
@@ -1879,7 +1931,8 @@ class UploadAPI(AuthenticatedAPI):
1879
1931
  transfer.CompleteMultipartUploadTask = CompleteMultipartUploadTask
1880
1932
  transfer.upload_file = upload_file
1881
1933
 
1882
- response = transfer.upload_file(self=transfer, filename=path_to_zip_package, bucket=bucket, key=key_id, extra_args=metadata,
1934
+ response = transfer.upload_file(self=transfer, filename=path_to_zip_package, bucket=bucket, key=key_id,
1935
+ extra_args=metadata,
1883
1936
  callback=callback)
1884
1937
 
1885
1938
  if delete_after_upload:
@@ -11,6 +11,7 @@ licence: Apache License 2.0
11
11
 
12
12
  import uuid
13
13
  import datetime
14
+ from typing import Callable
14
15
  from xml.etree import ElementTree
15
16
 
16
17
  from pyPreservica.common import *
@@ -21,7 +22,7 @@ logger = logging.getLogger(__name__)
21
22
  class WorkflowInstance:
22
23
  """
23
24
  Defines a workflow Instance.
24
- The workflow Instance is context which has been executed
25
+ The workflow Instance is a context which has been executed
25
26
  """
26
27
 
27
28
  def __init__(self, instance_id: int):
@@ -79,8 +80,11 @@ class WorkflowAPI(AuthenticatedAPI):
79
80
  workflow_types = ['Ingest', 'Access', 'Transformation', 'DataManagement']
80
81
 
81
82
  def __init__(self, username: str = None, password: str = None, tenant: str = None, server: str = None,
82
- use_shared_secret: bool = False, two_fa_secret_key: str = None, protocol: str = "https"):
83
- super().__init__(username, password, tenant, server, use_shared_secret, two_fa_secret_key, protocol)
83
+ use_shared_secret: bool = False, two_fa_secret_key: str = None,
84
+ protocol: str = "https", request_hook: Callable = None):
85
+
86
+ super().__init__(username, password, tenant, server, use_shared_secret, two_fa_secret_key,
87
+ protocol, request_hook)
84
88
  self.base_url = "sdb/rest/workflow"
85
89
 
86
90
  def get_workflow_contexts_by_type(self, workflow_type: str):
@@ -245,13 +249,13 @@ class WorkflowAPI(AuthenticatedAPI):
245
249
  assert instance_id == w_id
246
250
  workflow_instance = WorkflowInstance(int(instance_id))
247
251
  started_element = entity_response.find(f".//{{{NS_WORKFLOW}}}Started")
248
- if started_element:
252
+ if started_element is not None:
249
253
  if hasattr(started_element, "text"):
250
254
  workflow_instance.started = datetime.datetime.strptime(started_element.text,
251
255
  '%Y-%m-%dT%H:%M:%S.%fZ')
252
256
 
253
257
  finished_element = entity_response.find(f".//{{{NS_WORKFLOW}}}Finished")
254
- if finished_element:
258
+ if finished_element is not None:
255
259
  if hasattr(finished_element, "text"):
256
260
  workflow_instance.finished = datetime.datetime.strptime(finished_element.text,
257
261
  '%Y-%m-%dT%H:%M:%S.%fZ')
@@ -353,13 +357,13 @@ class WorkflowAPI(AuthenticatedAPI):
353
357
  workflow_instance = WorkflowInstance(int(instance_id))
354
358
 
355
359
  started_element = instance.find(f".//{{{NS_WORKFLOW}}}Started")
356
- if started_element:
360
+ if started_element is not None:
357
361
  if hasattr(started_element, "text"):
358
362
  workflow_instance.started = datetime.datetime.strptime(started_element.text,
359
363
  '%Y-%m-%dT%H:%M:%S.%fZ')
360
364
 
361
365
  finished_element = instance.find(f".//{{{NS_WORKFLOW}}}Finished")
362
- if finished_element:
366
+ if finished_element is not None:
363
367
  if hasattr(finished_element, "text"):
364
368
  workflow_instance.finished = datetime.datetime.strptime(finished_element.text,
365
369
  '%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.2
3
+ Version: 2.7.4
4
4
  Summary: Python library for the Preservica API
5
5
  Home-page: https://pypreservica.readthedocs.io/
6
6
  Author: James Carr
@@ -0,0 +1,19 @@
1
+ pyPreservica/__init__.py,sha256=RmOn1lvHV9iH7Hmz5YQ0F06ASdqetV0_-m1I8S4erqY,1117
2
+ pyPreservica/adminAPI.py,sha256=511bc5KtrCAXbDyBk39dmDnxUVDaOu6xaiyu0jYhxa4,37781
3
+ pyPreservica/authorityAPI.py,sha256=Eule8g6LXr8c8SFcJgpRah4lH1FgevUItO5HhHDEaZE,9172
4
+ pyPreservica/common.py,sha256=yZNMlq8aOOLSbFS2DDHYBUWyN5ojDjYUYmcePVbUd44,37636
5
+ pyPreservica/contentAPI.py,sha256=F3VwaybSUel0OfhWOckqfM77AVQCD1erHbu-Xrv4cd0,17388
6
+ pyPreservica/entityAPI.py,sha256=f-RlCEtUq0KDB62LuSPy0Kb-lT6Hp2gPOmSiTeomqkM,114853
7
+ pyPreservica/mdformsAPI.py,sha256=As5YN5m1k59N6SpM8fLldVviJ0n4lhlZD8rV5v0Jehs,4698
8
+ pyPreservica/monitorAPI.py,sha256=HD-PUPdSI9wGAa07e2_2_-FLINH8PoWUwpFogz7F-j4,6269
9
+ pyPreservica/opex.py,sha256=ccra1S4ojUXS3PlbU8WfxajOkJrwG4OykBnNrYP_jus,4875
10
+ pyPreservica/parAPI.py,sha256=bgaQvYfWNnzdD7ibKMV3ZV85pNkEdSoLsgVigoiFFfw,10771
11
+ pyPreservica/retentionAPI.py,sha256=EmQvmUW_I_sPslCiTZDZ2uqloesjfxmmc5AQImWX2cs,23695
12
+ pyPreservica/uploadAPI.py,sha256=Nl6Z4h1cW9TmZyrhVLPff3jSAnzCdkfn0xDJ9Q_ZhdI,96444
13
+ pyPreservica/webHooksAPI.py,sha256=0wP-59mep8gtlIZ9P5vV68-HnNdTuuo2kzGcDWj0bNg,6790
14
+ pyPreservica/workflowAPI.py,sha256=wDDR5_CsJ3dhX79E5mJaziAtgYb830J0ZpNJppzgvqk,17493
15
+ pyPreservica-2.7.4.dist-info/LICENSE.txt,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
16
+ pyPreservica-2.7.4.dist-info/METADATA,sha256=bbipiUj1ByD22LxVh2X7a3ytTLitfSt9GVi_nKfVoXs,2784
17
+ pyPreservica-2.7.4.dist-info/WHEEL,sha256=YiKiUUeZQGmGJoR_0N1Y933DOBowq4AIvDe2-UIy8E4,91
18
+ pyPreservica-2.7.4.dist-info/top_level.txt,sha256=iIBh6NAznYQHOV8mv_y_kGKSDITek9rANyFDwJsbU-c,13
19
+ pyPreservica-2.7.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.38.4)
2
+ Generator: setuptools (71.0.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,141 +0,0 @@
1
- """
2
- pyPreservica ControlledVocabularyAPI module definition
3
-
4
- A client library for the Preservica Repository web services Webhook API
5
- https://us.preservica.com/api/reference-metadata/documentation.html
6
-
7
- author: James Carr
8
- licence: Apache License 2.0
9
-
10
- """
11
-
12
- from pyPreservica.common import *
13
-
14
- logger = logging.getLogger(__name__)
15
-
16
- BASE_ENDPOINT = '/api/reference-metadata'
17
-
18
-
19
- class Table:
20
- def __init__(self, reference: str, name: str, security_tag: str, displayField: str, metadataConnections: list):
21
- self.reference = reference
22
- self.name = name
23
- self.security_tag = security_tag
24
- self.displayField = displayField
25
- self.metadataConnections = metadataConnections
26
- self.fields = None
27
-
28
- def __str__(self):
29
- return f"Ref:\t\t\t{self.reference}\n" \
30
- f"Name:\t\t\t{self.name}\n" \
31
- f"Security Tag:\t{self.security_tag}\n" \
32
- f"Display Field:\t\t\t{self.displayField}\n" \
33
- f"Metadata Connections:\t\t\t{self.metadataConnections}\n" \
34
- f"Fields:\t\t\t{self.fields}\n"
35
-
36
-
37
- class ControlledVocabularyAPI(AuthenticatedAPI):
38
-
39
-
40
- def load_skos(self, uri):
41
- """
42
- Load a SKOS controlled vocabulary in skos RDF format
43
-
44
- Simple Knowledge Organization System (SKOS)
45
-
46
- :param uri:
47
- :return:
48
- """
49
- pass
50
-
51
- def record(self, reference: str):
52
- """
53
- Get individual record by its ref.
54
- :param reference:
55
- :return:
56
- """
57
- headers = {HEADER_TOKEN: self.token, 'accept': 'application/json;charset=UTF-8'}
58
- response = self.session.get(f'{self.protocol}://{self.server}{BASE_ENDPOINT}/records/{reference}',
59
- headers=headers)
60
- if response.status_code == requests.codes.unauthorized:
61
- self.token = self.__token__()
62
- return self.record(reference)
63
- if response.status_code == requests.codes.ok:
64
- json_response = str(response.content.decode('utf-8'))
65
- return json.loads(json_response)
66
- else:
67
- exception = HTTPException("", response.status_code, response.url, "record",
68
- response.content.decode('utf-8'))
69
- logger.error(exception)
70
- raise exception
71
-
72
- def records(self, table: Table):
73
- """
74
- Get all records from a table.
75
- :return:
76
- """
77
- headers = {HEADER_TOKEN: self.token, 'accept': 'application/json;charset=UTF-8'}
78
- response = self.session.get(f'{self.protocol}://{self.server}{BASE_ENDPOINT}/tables/{table.reference}/records',
79
- headers=headers)
80
- if response.status_code == requests.codes.unauthorized:
81
- self.token = self.__token__()
82
- return self.records(table)
83
- if response.status_code == requests.codes.ok:
84
- json_response = str(response.content.decode('utf-8'))
85
- return json.loads(json_response)['records']
86
- else:
87
- exception = HTTPException("", response.status_code, response.url, "records",
88
- response.content.decode('utf-8'))
89
- logger.error(exception)
90
- raise exception
91
-
92
- def table(self, reference: str):
93
- """
94
- fetch a metadata table by id
95
-
96
- :param reference:
97
- :return:
98
- """
99
- headers = {HEADER_TOKEN: self.token, 'accept': 'application/json;charset=UTF-8'}
100
- response = self.session.get(f'{self.protocol}://{self.server}{BASE_ENDPOINT}/tables/{reference}',
101
- headers=headers)
102
- if response.status_code == requests.codes.unauthorized:
103
- self.token = self.__token__()
104
- return self.table(reference)
105
- if response.status_code == requests.codes.ok:
106
- json_response = str(response.content.decode('utf-8'))
107
- doc = json.loads(json_response)
108
- table = Table(doc['ref'], doc['name'], doc['securityDescriptor'], doc['displayField'],
109
- doc['metadataConnections'])
110
- table.fields = doc['fields']
111
- return table
112
- else:
113
- exception = HTTPException("", response.status_code, response.url, "table",
114
- response.content.decode('utf-8'))
115
- logger.error(exception)
116
- raise exception
117
-
118
- def tables(self):
119
- """
120
- List reference metadata tables, optionally filtering by metadata connections.
121
- :return:
122
- """
123
- headers = {HEADER_TOKEN: self.token, 'accept': 'application/json;charset=UTF-8'}
124
- response = self.session.get(f'{self.protocol}://{self.server}{BASE_ENDPOINT}/tables', headers=headers)
125
- if response.status_code == requests.codes.unauthorized:
126
- self.token = self.__token__()
127
- return self.tables()
128
- if response.status_code == requests.codes.ok:
129
- json_response = str(response.content.decode('utf-8'))
130
- doc = json.loads(json_response)
131
- results = set()
132
- for table in doc['tables']:
133
- t = Table(table['ref'], table['name'], table['securityDescriptor'], table['displayField'],
134
- table['metadataConnections'])
135
- results.add(t)
136
- return results
137
- else:
138
- exception = HTTPException("", response.status_code, response.url, "tables",
139
- response.content.decode('utf-8'))
140
- logger.error(exception)
141
- raise exception
@@ -1,20 +0,0 @@
1
- pyPreservica/__init__.py,sha256=7niGhdk2fS4pMgHaqr-x-oZMJQg5-Nte_jfMNxZ3Lkk,1117
2
- pyPreservica/adminAPI.py,sha256=511bc5KtrCAXbDyBk39dmDnxUVDaOu6xaiyu0jYhxa4,37781
3
- pyPreservica/authorityAPI.py,sha256=Eule8g6LXr8c8SFcJgpRah4lH1FgevUItO5HhHDEaZE,9172
4
- pyPreservica/common.py,sha256=upTmwvPK9kcgSO3L8mfIMVrCOWJCCYIHi5GK516jOok,36518
5
- pyPreservica/contentAPI.py,sha256=d6bK7qXFLep8rOs8y-dJGzoFuqanO3fo82G4dW0YWWE,17245
6
- pyPreservica/entityAPI.py,sha256=qNUjhAAdboBuY319AtyQV1n5N3Yrs1VY1enBxqQf5RQ,114750
7
- pyPreservica/mdformsAPI.py,sha256=wHRCv5mcvztVFt9zBnUgHeGgSiNIrzR9IQOSV25daJ0,4576
8
- pyPreservica/monitorAPI.py,sha256=HD-PUPdSI9wGAa07e2_2_-FLINH8PoWUwpFogz7F-j4,6269
9
- pyPreservica/opex.py,sha256=ccra1S4ojUXS3PlbU8WfxajOkJrwG4OykBnNrYP_jus,4875
10
- pyPreservica/parAPI.py,sha256=bgaQvYfWNnzdD7ibKMV3ZV85pNkEdSoLsgVigoiFFfw,10771
11
- pyPreservica/retentionAPI.py,sha256=Cx1ofz9V31a8c8utEfKYLlfQaHSaaqg_D4R3LUFBEx0,23612
12
- pyPreservica/uploadAPI.py,sha256=8GCQlofg-fxJEY_djeValbVwBZoTBujJEDFxpS_fEkQ,93894
13
- pyPreservica/vocabularyAPI.py,sha256=jPl6KDZoBGqlY0oEYjTpZ9kNEPzchDW-gyp-HH-MSKk,5729
14
- pyPreservica/webHooksAPI.py,sha256=0wP-59mep8gtlIZ9P5vV68-HnNdTuuo2kzGcDWj0bNg,6790
15
- pyPreservica/workflowAPI.py,sha256=ENFWxcuPW5WX9jG2CAha6UzTywULWvosgTUVsmvs8f8,17323
16
- pyPreservica-2.7.2.dist-info/LICENSE.txt,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
17
- pyPreservica-2.7.2.dist-info/METADATA,sha256=GDQ2AtPbqDpmjxUGGIwq-8EIoOXkz_fsxwkpMf68TDc,2784
18
- pyPreservica-2.7.2.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
19
- pyPreservica-2.7.2.dist-info/top_level.txt,sha256=iIBh6NAznYQHOV8mv_y_kGKSDITek9rANyFDwJsbU-c,13
20
- pyPreservica-2.7.2.dist-info/RECORD,,