pyPreservica 3.0.2__py3-none-any.whl → 3.0.6__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
@@ -6,11 +6,22 @@ author: James Carr
6
6
  licence: Apache License 2.0
7
7
 
8
8
  """
9
+
9
10
  from .common import *
10
11
  from .contentAPI import ContentAPI, Field, SortOrder
11
12
  from .entityAPI import EntityAPI
12
- from .uploadAPI import UploadAPI, simple_asset_package, complex_asset_package, cvs_to_xsd, cvs_to_xml, \
13
- cvs_to_cmis_xslt, csv_to_search_xml, generic_asset_package, upload_config, multi_asset_package
13
+ from .uploadAPI import (
14
+ UploadAPI,
15
+ simple_asset_package,
16
+ complex_asset_package,
17
+ cvs_to_xsd,
18
+ cvs_to_xml,
19
+ cvs_to_cmis_xslt,
20
+ csv_to_search_xml,
21
+ generic_asset_package,
22
+ upload_config,
23
+ multi_asset_package,
24
+ )
14
25
  from .workflowAPI import WorkflowAPI, WorkflowContext, WorkflowInstance
15
26
  from .retentionAPI import RetentionAPI, RetentionAssignment, RetentionPolicy
16
27
  from .parAPI import PreservationActionRegistry
@@ -23,6 +34,6 @@ from .mdformsAPI import MetadataGroupsAPI, Group, GroupField, GroupFieldType
23
34
  __author__ = "James Carr (drjamescarr@gmail.com)"
24
35
 
25
36
  # Version of the pyPreservica package
26
- __version__ = "3.0.2"
37
+ __version__ = "3.0.6"
27
38
 
28
39
  __license__ = "Apache License Version 2.0"
pyPreservica/adminAPI.py CHANGED
@@ -444,7 +444,7 @@ class AdminAPI(AuthenticatedAPI):
444
444
  :param xml_data: The xml schema as a UTF-8 string or a file like object
445
445
  :type xml_data: Any
446
446
 
447
- :return: None
447
+ :return:
448
448
  :rtype: None
449
449
  """
450
450
 
@@ -493,7 +493,7 @@ class AdminAPI(AuthenticatedAPI):
493
493
  :param document_type: The type of the XML document, defaults to descriptive metadata templates
494
494
  :type document_type: str
495
495
 
496
- :return: None
496
+ :return:
497
497
  :rtype: None
498
498
 
499
499
  """
@@ -523,12 +523,12 @@ class AdminAPI(AuthenticatedAPI):
523
523
 
524
524
  def delete_xml_document(self, uri: str):
525
525
  """
526
- Delete a XML document from Preservica
526
+ Delete an XML document from Preservica's XML document store
527
527
 
528
528
  :param uri: The URI of the xml document to delete
529
529
  :type uri: str
530
530
 
531
- :return: None
531
+ :return:
532
532
  :rtype: None
533
533
 
534
534
  """
@@ -558,7 +558,7 @@ class AdminAPI(AuthenticatedAPI):
558
558
  :param uri: The URI of the xml schema to delete
559
559
  :type uri: str
560
560
 
561
- :return: None
561
+ :return:
562
562
  :rtype: None
563
563
 
564
564
  """
@@ -582,7 +582,7 @@ class AdminAPI(AuthenticatedAPI):
582
582
 
583
583
  def xml_schema(self, uri: str) -> str:
584
584
  """
585
- fetch the metadata schema XSD document as a string by its URI
585
+ Fetch the metadata schema XSD document as a string by its URI
586
586
 
587
587
  :param uri: The URI of the xml schema
588
588
  :type uri: str
@@ -793,7 +793,7 @@ class AdminAPI(AuthenticatedAPI):
793
793
  :param output_uri: The URI of the output XML document
794
794
  :type output_uri: str
795
795
 
796
- :return: None
796
+ :return:
797
797
  :rtype: None
798
798
 
799
799
  """
@@ -839,7 +839,7 @@ class AdminAPI(AuthenticatedAPI):
839
839
  :param xml_data: The transform xml as a string or file like object
840
840
  :type xml_data: Any
841
841
 
842
- :return: None
842
+ :return:
843
843
  :rtype: None
844
844
 
845
845
  """
@@ -8,9 +8,8 @@ author: James Carr
8
8
  licence: Apache License 2.0
9
9
 
10
10
  """
11
- import json
11
+
12
12
  import csv
13
- import requests
14
13
  from typing import List, Set
15
14
 
16
15
  from pyPreservica.common import *
@@ -92,7 +91,7 @@ class AuthorityAPI(AuthenticatedAPI):
92
91
  :param table: The Table to add the record to
93
92
  :type: table: Table
94
93
 
95
- :param record: The record
94
+ :param record: The record as a dictionary
96
95
  :type: record: dict
97
96
 
98
97
  :return: A single record
@@ -123,7 +122,7 @@ class AuthorityAPI(AuthenticatedAPI):
123
122
  """
124
123
  Return a record by its reference
125
124
 
126
- :param reference: The record reference
125
+ :param reference: The reference of the record
127
126
  :type: reference: str
128
127
 
129
128
  :return: A single record
@@ -149,7 +148,7 @@ class AuthorityAPI(AuthenticatedAPI):
149
148
  """
150
149
  Return all records from a table
151
150
 
152
- :param table: The authority table
151
+ :param table: The authority table to return the records from
153
152
  :type: table: Table
154
153
 
155
154
  :return: List of records
@@ -178,7 +177,7 @@ class AuthorityAPI(AuthenticatedAPI):
178
177
  :param reference: The reference for the authority table
179
178
  :type: reference: str
180
179
 
181
- :return: An authority table
180
+ :return: An authority table of interest
182
181
  :rtype: Table
183
182
 
184
183
  """
pyPreservica/common.py CHANGED
@@ -23,7 +23,7 @@ import xml.etree.ElementTree
23
23
  from enum import Enum
24
24
  from pathlib import Path
25
25
  import pyotp
26
- from requests import Response, Session
26
+ from requests import Session
27
27
  from urllib3.util import Retry
28
28
  import requests
29
29
  from requests.adapters import HTTPAdapter
@@ -568,6 +568,22 @@ class Thumbnail(Enum):
568
568
  LARGE = "large"
569
569
 
570
570
 
571
+ class AsyncProgress(Enum):
572
+ """
573
+ Enumeration of the possible status of an asynchronous process
574
+ """
575
+ ABORTED = "ABORTED"
576
+ ACTIVE = "ACTIVE"
577
+ COMPLETED = "COMPLETED"
578
+ PENDING = "PENDING"
579
+ SUSPENDING = "SUSPENDING"
580
+ SUSPENDED = "SUSPENDED"
581
+ UNKNOWN = "UNKNOWN"
582
+ FAILED = "FAILED"
583
+ FINISHED_MIXED_OUTCOME = "FINISHED_MIXED_OUTCOME"
584
+ CANCELLED = "CANCELLED"
585
+
586
+
571
587
  def sanitize(filename) -> str:
572
588
  """
573
589
  Return a fairly safe version of the filename.
@@ -316,7 +316,7 @@ class ContentAPI(AuthenticatedAPI):
316
316
  return search_results
317
317
  elif results.status_code == requests.codes.unauthorized:
318
318
  self.token = self.__token__()
319
- return self._search_predicates(query, predicates, start_index, page_size)
319
+ return self._search_fields(query, fields, start_index, page_size)
320
320
  else:
321
321
  logger.error(f"search failed with error code: {results.status_code}")
322
322
  raise RuntimeError(results.status_code, f"search_index_filter failed")
pyPreservica/entityAPI.py CHANGED
@@ -70,7 +70,7 @@ class EntityAPI(AuthenticatedAPI):
70
70
  with self.session.get(bitstream.content_url, headers={HEADER_TOKEN: self.token}, stream=True) as request:
71
71
  if request.status_code == requests.codes.unauthorized:
72
72
  self.token = self.__token__()
73
- return self.bitstream_chunks(bitstream)
73
+ yield from self.bitstream_chunks(bitstream)
74
74
  elif request.status_code == requests.codes.ok:
75
75
  for chunk in request.iter_content(chunk_size=chunk_size):
76
76
  yield chunk
@@ -1068,6 +1068,10 @@ class EntityAPI(AuthenticatedAPI):
1068
1068
  logger.error(exception)
1069
1069
  raise exception
1070
1070
 
1071
+ def get_progress(self, pid: str) -> AsyncProgress:
1072
+ return AsyncProgress[self.get_async_progress(pid)]
1073
+
1074
+
1071
1075
  def get_async_progress(self, pid: str) -> str:
1072
1076
  headers = {HEADER_TOKEN: self.token, 'Content-Type': 'text/plain'}
1073
1077
  request = self.session.get(f"{self.protocol}://{self.server}/api/entity/progress/{pid}", headers=headers)
@@ -1181,7 +1185,7 @@ class EntityAPI(AuthenticatedAPI):
1181
1185
  logger.error(exception)
1182
1186
  raise exception
1183
1187
 
1184
- def all_metadata(self, entity: Entity) -> Tuple:
1188
+ def all_metadata(self, entity: Entity) -> Generator[Tuple[str, str], None, None]:
1185
1189
  """
1186
1190
  Retrieve all metadata fragments on an entity
1187
1191
 
@@ -1511,6 +1515,7 @@ class EntityAPI(AuthenticatedAPI):
1511
1515
  Retrieve a list of generation objects
1512
1516
 
1513
1517
  :param url:
1518
+ :param content_ref:
1514
1519
  :returns Generation
1515
1520
  """
1516
1521
  headers = {HEADER_TOKEN: self.token}
@@ -1873,9 +1878,46 @@ class EntityAPI(AuthenticatedAPI):
1873
1878
  logger.error(exception)
1874
1879
  raise exception
1875
1880
 
1881
+ # def add_preservation_representation(self, entity: Entity, preservation_file: str, name: str = "Preservation"):
1882
+ # """
1883
+ # Add a new Preservation representation to an existing asset.
1884
+ #
1885
+ # :param entity: The existing asset which will receive the new representation
1886
+ # :param preservation_file: The new digital file
1887
+ # :param name: The name of the new access representation defaults to "Access"
1888
+ # :return:
1889
+ # """
1890
+ #
1891
+ # if self.major_version < 7 and self.minor_version < 12:
1892
+ # raise RuntimeError("Add Representation API is only available when connected to a v6.12 System")
1893
+ #
1894
+ # if isinstance(entity, Folder) or isinstance(entity, ContentObject):
1895
+ # raise RuntimeError("Add Representation cannot be added to Folders and Content Objects")
1896
+ #
1897
+ # headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/octet-stream'}
1898
+ #
1899
+ # filename = os.path.basename(preservation_file)
1900
+ #
1901
+ # params = {'type': 'Preservation', 'name': name, 'filename': filename}
1902
+ #
1903
+ # with open(preservation_file, 'rb') as fd:
1904
+ # request = self.session.post(
1905
+ # f'{self.protocol}://{self.server}/api/entity/{entity.path}/{entity.reference}/representations',
1906
+ # data=fd, headers=headers, params=params)
1907
+ # if request.status_code == requests.codes.accepted:
1908
+ # return str(request.content.decode('utf-8'))
1909
+ # elif request.status_code == requests.codes.unauthorized:
1910
+ # self.token = self.__token__()
1911
+ # return self.add_access_representation(entity, preservation_file, name)
1912
+ # else:
1913
+ # exception = HTTPException(entity.reference, request.status_code, request.url,
1914
+ # "add_preservation_representation", request.content.decode('utf-8'))
1915
+ # logger.error(exception)
1916
+ # raise exception
1917
+
1876
1918
  def add_access_representation(self, entity: Entity, access_file: str, name: str = "Access"):
1877
1919
  """
1878
- Add a new representation to an existing asset.
1920
+ Add a new Access representation to an existing asset.
1879
1921
 
1880
1922
  :param entity: The existing asset which will receive the new representation
1881
1923
  :param access_file: The new digital file
@@ -2324,6 +2366,7 @@ class EntityAPI(AuthenticatedAPI):
2324
2366
  :param asset: The Asset
2325
2367
  :param operator_comment: The operator comment on the deletion
2326
2368
  :param supervisor_comment: The supervisor comment on the deletion
2369
+ :param credentials_path: The path to the credentials file
2327
2370
  """
2328
2371
  if isinstance(asset, Asset):
2329
2372
  return self._delete_entity(asset, operator_comment, supervisor_comment, credentials_path)
@@ -2338,6 +2381,7 @@ class EntityAPI(AuthenticatedAPI):
2338
2381
  :param folder: The Folder
2339
2382
  :param operator_comment: The operator comment on the deletion
2340
2383
  :param supervisor_comment: The supervisor comment on the deletion
2384
+ :param credentials_path: The path to the credentials file
2341
2385
  """
2342
2386
  if isinstance(folder, Folder):
2343
2387
  return self._delete_entity(folder, operator_comment, supervisor_comment, credentials_path)
@@ -8,7 +8,6 @@ author: James Carr
8
8
  licence: Apache License 2.0
9
9
 
10
10
  """
11
- import json
12
11
  import xml.etree.ElementTree
13
12
  from typing import Callable, List, Union, Generator
14
13
 
@@ -251,7 +250,7 @@ class MetadataGroupsAPI(AuthenticatedAPI):
251
250
 
252
251
  def add_form(self, json_form: Union[dict, str]):
253
252
  """
254
- Create a new Metadata fORM using a JSON dictionary object or document
253
+ Create a new Metadata form using a JSON dictionary object or document
255
254
 
256
255
  :param json_form: JSON dictionary or string
257
256
  :type json_form: dict
@@ -267,7 +266,7 @@ class MetadataGroupsAPI(AuthenticatedAPI):
267
266
  with self.session.post(url, headers=headers, json=json_form) as request:
268
267
  if request.status_code == requests.codes.unauthorized:
269
268
  self.token = self.__token__()
270
- return self.add_form_json(json_form)
269
+ return self.add_form(json_form)
271
270
  elif request.status_code == requests.codes.created:
272
271
  return json.loads(str(request.content.decode('utf-8')))
273
272
  else:
@@ -280,7 +279,7 @@ class MetadataGroupsAPI(AuthenticatedAPI):
280
279
  with self.session.post(url, headers=headers, data=json_form) as request:
281
280
  if request.status_code == requests.codes.unauthorized:
282
281
  self.token = self.__token__()
283
- return self.add_form_json(json_form)
282
+ return self.add_form(json_form)
284
283
  elif request.status_code == requests.codes.created:
285
284
  return json.loads(str(request.content.decode('utf-8')))
286
285
  else:
@@ -89,7 +89,7 @@ class MonitorAPI(AuthenticatedAPI):
89
89
 
90
90
  :param monitor_id: The Process ID
91
91
  :type monitor_id: str
92
- :param status: The message status, info, warning, error etc
92
+ :param status: The message status, info, warning, error etc.
93
93
  :type status: MessageStatus
94
94
  :return: Generator for each message, each message is a dict object
95
95
  """
@@ -147,7 +147,7 @@ class MonitorAPI(AuthenticatedAPI):
147
147
  yield monitor
148
148
  elif request.status_code == requests.codes.unauthorized:
149
149
  self.token = self.__token__()
150
- return self.monitors(status, category)
150
+ yield from self.monitors(status, category)
151
151
  else:
152
152
  logger.error(request.content.decode('utf-8'))
153
153
  raise RuntimeError(request.status_code, "monitors failed")
@@ -96,7 +96,7 @@ class RetentionAPI(AuthenticatedAPI):
96
96
  if start_date_field is not None:
97
97
  rp.start_date_field = start_date_field.text
98
98
  else:
99
- start_date_field = None
99
+ rp.start_date_field = None
100
100
  period = entity_response.find(f'.//{{{self.rm_ns}}}RetentionPolicy/{{{self.rm_ns}}}Period')
101
101
  if period is not None:
102
102
  rp.period = period.text
@@ -404,7 +404,7 @@ class RetentionAPI(AuthenticatedAPI):
404
404
  def policies(self, maximum: int = 250, next_page: str = None) -> PagedSet:
405
405
  """
406
406
  Return a list of all retention policies
407
- Returns a maxmium of 250 policies by default
407
+ Returns a maximum of 250 policies by default
408
408
 
409
409
 
410
410
  :return: Set of retention policies
pyPreservica/uploadAPI.py CHANGED
@@ -20,7 +20,6 @@ from xml.etree import ElementTree
20
20
  from xml.etree.ElementTree import Element, SubElement
21
21
 
22
22
  import boto3
23
- import botocore
24
23
  import s3transfer.tasks
25
24
  import s3transfer.upload
26
25
  from botocore.session import get_session
@@ -483,7 +482,7 @@ def generic_asset_package(preservation_files_dict=None, access_files_dict=None,
483
482
  content_type = kwargs.get('CustomType', "")
484
483
 
485
484
  if not compress:
486
- shutil.register_archive_format("szip", _make_stored_zipfile, None, "UnCompressed ZIP file")
485
+ shutil.register_archive_format(name="szip", function=_make_stored_zipfile, extra_args=None, description="UnCompressed ZIP file")
487
486
 
488
487
  has_preservation_files = bool((preservation_files_dict is not None) and (len(preservation_files_dict) > 0))
489
488
  has_access_files = bool((access_files_dict is not None) and (len(access_files_dict) > 0))
@@ -1531,7 +1530,7 @@ class UploadAPI(AuthenticatedAPI):
1531
1530
  """
1532
1531
  Ingest a web video such as YouTube etc based on the URL
1533
1532
 
1534
- :param str url: URL to the youtube video
1533
+ :param str url: URL to the YouTube video
1535
1534
  :param Folder parent_folder: The folder to ingest the video into
1536
1535
  :param str Title: Optional asset title
1537
1536
  :param str Description: Optional asset description
@@ -1659,57 +1658,59 @@ class UploadAPI(AuthenticatedAPI):
1659
1658
 
1660
1659
  def crawl_filesystem(self, filesystem_path, bucket_name, preservica_parent, callback: bool = False,
1661
1660
  security_tag: str = "open",
1662
- delete_after_upload: bool = True, max_MB_ingested: int = -1, max_workflows = 8):
1663
- """
1664
- Crawl a filesystem and upload the contents to Preservica, the filesystem structure is mirrored in Preservica
1665
-
1666
-
1667
- :param str filesystem_path: The path to the filesystem to crawl
1668
- :param str bucket_name: The name of the bucket to upload to, use None to send directly to Preservica
1669
- :param str preservica_parent: The parent folder in Preservica to upload to
1670
- :param bool callback: Show upload progress bar
1671
- :param str security_tag: The security tag to apply to the uploaded assets
1672
- :param bool delete_after_upload: Delete the local copy of the package after the upload has completed
1673
- :param int max_MB_ingested: The maximum number of MB to ingest
1674
- :param int max_workflows: The maximum number of workflows to run concurrently
1661
+ delete_after_upload: bool = True, max_MB_ingested: int = -1):
1675
1662
 
1663
+ from pyPreservica import EntityAPI
1676
1664
 
1677
- """
1665
+ def entity_value(client: EntityAPI, identifier: str) -> Entity:
1666
+ back_off: int = 5
1667
+ while True:
1668
+ try:
1669
+ entities = client.identifier("code", identifier)
1670
+ if bool(len(entities) > 0):
1671
+ return entities.pop()
1672
+ else:
1673
+ return None
1674
+ except HTTPException as e:
1675
+ sleep(back_off)
1676
+ back_off = back_off * 2
1677
+
1678
+ def entity_exists(client: EntityAPI, identifier: str) -> bool:
1679
+ back_off: int = 5
1680
+ while True:
1681
+ try:
1682
+ entities = client.identifier("code", identifier)
1683
+ return bool(len(entities) > 0)
1684
+ except HTTPException as e:
1685
+ sleep(back_off)
1686
+ back_off = back_off * 2
1678
1687
 
1679
1688
  def get_parent(client, identifier, parent_reference):
1680
- id = str(os.path.dirname(identifier))
1681
- if not id:
1682
- id = identifier
1683
- entities = client.identifier("code", id)
1684
- if len(entities) > 0:
1685
- folder = entities.pop()
1689
+ dirname_id: str = str(os.path.dirname(identifier))
1690
+ if not dirname_id:
1691
+ dirname_id = identifier
1692
+ folder = entity_value(client, dirname_id)
1693
+ if folder is not None:
1686
1694
  folder = client.folder(folder.reference)
1687
1695
  return folder.reference
1688
1696
  else:
1689
1697
  return parent_reference
1690
1698
 
1691
1699
  def get_folder(client, name, tag, parent_reference, identifier):
1692
- entities = client.identifier("code", identifier)
1693
- if len(entities) == 0:
1700
+ folder = entity_value(client, identifier)
1701
+ if folder is None:
1694
1702
  logger.info(f"Creating new folder with name {name}")
1695
1703
  folder = client.create_folder(name, name, tag, parent_reference)
1696
1704
  client.add_identifier(folder, "code", identifier)
1697
1705
  else:
1698
1706
  logger.info(f"Found existing folder with name {name}")
1699
- folder = entities.pop()
1700
1707
  return folder
1701
1708
 
1702
- from pyPreservica import EntityAPI, WorkflowAPI
1703
1709
  entity_client = EntityAPI(username=self.username, password=self.password, server=self.server,
1704
1710
  tenant=self.tenant,
1705
1711
  two_fa_secret_key=self.two_fa_secret_key, use_shared_secret=self.shared_secret,
1706
1712
  protocol=self.protocol)
1707
1713
 
1708
- workflow_client = WorkflowAPI(username=self.username, password=self.password, server=self.server,
1709
- tenant=self.tenant,
1710
- two_fa_secret_key=self.two_fa_secret_key, use_shared_secret=self.shared_secret,
1711
- protocol=self.protocol)
1712
-
1713
1714
  if preservica_parent:
1714
1715
  parent = entity_client.folder(preservica_parent)
1715
1716
  logger.info(f"Folders will be created inside Preservica collection {parent.title}")
@@ -1734,7 +1735,7 @@ class UploadAPI(AuthenticatedAPI):
1734
1735
  files.remove(file)
1735
1736
  continue
1736
1737
  asset_code = os.path.join(code, file)
1737
- if len(entity_client.identifier("code", asset_code)) == 0:
1738
+ if not entity_exists(entity_client, asset_code):
1738
1739
  bytes_ingested = bytes_ingested + os.stat(full_path).st_size
1739
1740
  logger.info(f"Adding new file: {file} to package ready for upload")
1740
1741
  file_identifiers = {"code": asset_code}
@@ -1752,18 +1753,13 @@ class UploadAPI(AuthenticatedAPI):
1752
1753
  else:
1753
1754
  progress_display = None
1754
1755
 
1755
- workflow_queue_length = len(list(workflow_client.workflow_instances(workflow_state="Active", workflow_type="Ingest")))
1756
- while workflow_queue_length > max_workflows:
1757
- sleep(30)
1758
- workflow_queue_length = len(list(workflow_client.workflow_instances(workflow_state="Active", workflow_type="Ingest")))
1759
-
1760
1756
  if bucket_name is None:
1761
1757
  self.upload_zip_package(path_to_zip_package=package, callback=progress_display,
1762
1758
  delete_after_upload=delete_after_upload)
1763
1759
  else:
1764
1760
  self.upload_zip_to_Source(path_to_zip_package=package, container_name=bucket_name,
1765
- show_progress= bool(progress_display is not None),
1766
- delete_after_upload=delete_after_upload)
1761
+ show_progress=bool(progress_display is not None),
1762
+ delete_after_upload=delete_after_upload)
1767
1763
 
1768
1764
  logger.info(f"Uploaded " + "{:.1f}".format(bytes_ingested / (1024 * 1024)) + " MB")
1769
1765
 
@@ -1939,18 +1935,18 @@ class UploadAPI(AuthenticatedAPI):
1939
1935
 
1940
1936
 
1941
1937
  retries= {
1942
- 'max_attempts': 10,
1938
+ 'max_attempts': 5,
1943
1939
  'mode': 'adaptive'
1944
1940
  }
1945
1941
 
1946
1942
  def new_credentials():
1947
- metadata: dict = {}
1948
- metadata['access_key'] = self.__token__()
1949
- metadata['secret_key'] = "NOT_USED"
1950
- metadata['token'] = ""
1951
- metadata["expiry_time"] = (datetime.now(tzlocal()) + timedelta(minutes=12)).isoformat()
1943
+ cred_metadata: dict = {}
1944
+ cred_metadata['access_key'] = self.__token__()
1945
+ cred_metadata['secret_key'] = "NOT_USED"
1946
+ cred_metadata['token'] = ""
1947
+ cred_metadata["expiry_time"] = (datetime.now(tzlocal()) + timedelta(minutes=12)).isoformat()
1952
1948
  logger.info("Refreshing credentials at: " + str(datetime.now(tzlocal())))
1953
- return metadata
1949
+ return cred_metadata
1954
1950
 
1955
1951
  session = get_session()
1956
1952
 
@@ -1966,9 +1962,13 @@ class UploadAPI(AuthenticatedAPI):
1966
1962
 
1967
1963
  session._credentials = session_credentials
1968
1964
 
1969
- s3_client = autorefresh_session.client('s3', endpoint_url=endpoint,
1970
- config=Config(s3={'addressing_style': 'path'}, read_timeout=120, connect_timeout=120,
1971
- retries=retries, tcp_keepalive=True))
1965
+ config = Config(s3={'addressing_style': 'path'}, read_timeout=120, connect_timeout=120,
1966
+ request_checksum_calculation="WHEN_REQUIRED",
1967
+ response_checksum_validation="WHEN_REQUIRED",
1968
+ retries=retries, tcp_keepalive=True)
1969
+
1970
+
1971
+ s3_client = autorefresh_session.client('s3', endpoint_url=endpoint, config=config)
1972
1972
 
1973
1973
  metadata = {}
1974
1974
  if folder is not None:
@@ -8,7 +8,6 @@ author: James Carr
8
8
  licence: Apache License 2.0
9
9
 
10
10
  """
11
- import json
12
11
  from http.server import BaseHTTPRequestHandler
13
12
  from urllib.parse import urlparse, parse_qs
14
13
  import hmac
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pyPreservica
3
- Version: 3.0.2
3
+ Version: 3.0.6
4
4
  Summary: Python library for the Preservica API
5
5
  Home-page: https://pypreservica.readthedocs.io/
6
6
  Author: James Carr
@@ -30,6 +30,7 @@ Requires-Dist: s3transfer
30
30
  Requires-Dist: azure-storage-blob
31
31
  Requires-Dist: tqdm
32
32
  Requires-Dist: pyotp
33
+ Requires-Dist: python-dateutil
33
34
  Dynamic: author
34
35
  Dynamic: author-email
35
36
  Dynamic: classifier
@@ -0,0 +1,19 @@
1
+ pyPreservica/__init__.py,sha256=b2cWepSUrTlRg-YBHg2Nue8zJHse-RXN7__aUmo9lLs,1212
2
+ pyPreservica/adminAPI.py,sha256=aMN2twcUZOFoGx2yapC6GVtBTdYHUJFA-5bdWVkCwS8,37773
3
+ pyPreservica/authorityAPI.py,sha256=jpf_m9i-IakyNVooi2yELuKt4yhX73hWqQNbPRHZx2g,9206
4
+ pyPreservica/common.py,sha256=4V9cwtb5LGIaKvm6hYVk-u4QltM7BDg4qXyz2gXR9X4,38197
5
+ pyPreservica/contentAPI.py,sha256=nOj7WciYARhLQWW65215Ghwz3CG61AVvikETPdtN4r0,22174
6
+ pyPreservica/entityAPI.py,sha256=-XZCys4nZRFEL6w0EpCQBUwADbMTi7BzDwrRzIG5PZQ,123493
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/uploadAPI.py,sha256=icEwzu4-dDdMJ7tlnWKDGxMbqIcLqfj7DVw2QSFrSBY,99517
13
+ pyPreservica/webHooksAPI.py,sha256=B3C6PV_3JLlJrr9PtsTzL-21M0msx8Mnj18Xb3Bv4RE,6814
14
+ pyPreservica/workflowAPI.py,sha256=OcOiiUdrQerbPllrkj1lWpmuW0jTuyyV0urwPSYcd_U,17561
15
+ pyPreservica-3.0.6.dist-info/LICENSE.txt,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
16
+ pyPreservica-3.0.6.dist-info/METADATA,sha256=NI2dhIZ1issZztZyrDnXWD1W4ZzPOGWwHk1CNxxrP_A,3050
17
+ pyPreservica-3.0.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
18
+ pyPreservica-3.0.6.dist-info/top_level.txt,sha256=iIBh6NAznYQHOV8mv_y_kGKSDITek9rANyFDwJsbU-c,13
19
+ pyPreservica-3.0.6.dist-info/RECORD,,
@@ -1,19 +0,0 @@
1
- pyPreservica/__init__.py,sha256=HkIx9xYPB52tEV5TP_tH6MFW57o7yDtmSOU9DXOPW8o,1177
2
- pyPreservica/adminAPI.py,sha256=511bc5KtrCAXbDyBk39dmDnxUVDaOu6xaiyu0jYhxa4,37781
3
- pyPreservica/authorityAPI.py,sha256=Eule8g6LXr8c8SFcJgpRah4lH1FgevUItO5HhHDEaZE,9172
4
- pyPreservica/common.py,sha256=nZeUObfypPXFauag5yYXEutvRC72sNXwVCN0wAFnssg,37796
5
- pyPreservica/contentAPI.py,sha256=z_IwndqzpTMtsDKUgneqWec5YPhi2Yb110Z-4tC42qU,22182
6
- pyPreservica/entityAPI.py,sha256=9BU55nvohydmwE4E3EviAkBrUcIj5ahmHQg2kLYZ8oY,121101
7
- pyPreservica/mdformsAPI.py,sha256=cTHxHgPV-EZAFN6C1JfnxrVNv3FLzqI1SV_fb5ZOxeE,19196
8
- pyPreservica/monitorAPI.py,sha256=HD-PUPdSI9wGAa07e2_2_-FLINH8PoWUwpFogz7F-j4,6269
9
- pyPreservica/opex.py,sha256=ccra1S4ojUXS3PlbU8WfxajOkJrwG4OykBnNrYP_jus,4875
10
- pyPreservica/parAPI.py,sha256=f0ZUxLd0U-BW6kBx5K7W2Pv7NjG3MkTNydmxQ3U1ZVE,9296
11
- pyPreservica/retentionAPI.py,sha256=F6okFSyqtnLhfMbcyChd_5V-D_PAxNLwyx8XohH2DEM,24840
12
- pyPreservica/uploadAPI.py,sha256=_ldYoDWD6cOIlMXM98KTF2Xnc7GaZ8SKZNHPzbJyfQI,100105
13
- pyPreservica/webHooksAPI.py,sha256=_K3KUOsmwYf8qMa-mD47sAmNUW7Pzb9oKVpS0VoSbC0,6827
14
- pyPreservica/workflowAPI.py,sha256=OcOiiUdrQerbPllrkj1lWpmuW0jTuyyV0urwPSYcd_U,17561
15
- pyPreservica-3.0.2.dist-info/LICENSE.txt,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
16
- pyPreservica-3.0.2.dist-info/METADATA,sha256=aPMxioiNMabjITQ0OR-5cSUsk6VgNqSwfsg1QizXVNM,3018
17
- pyPreservica-3.0.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
18
- pyPreservica-3.0.2.dist-info/top_level.txt,sha256=iIBh6NAznYQHOV8mv_y_kGKSDITek9rANyFDwJsbU-c,13
19
- pyPreservica-3.0.2.dist-info/RECORD,,