pyPreservica 3.1.1__tar.gz → 3.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyPreservica might be problematic. Click here for more details.

Files changed (50) hide show
  1. {pypreservica-3.1.1 → pypreservica-3.2.1}/PKG-INFO +3 -3
  2. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica/__init__.py +2 -1
  3. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica/common.py +27 -2
  4. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica/contentAPI.py +5 -0
  5. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica/entityAPI.py +100 -13
  6. pypreservica-3.2.1/pyPreservica/settingsAPI.py +295 -0
  7. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica.egg-info/PKG-INFO +3 -3
  8. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica.egg-info/SOURCES.txt +2 -0
  9. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica.egg-info/requires.txt +2 -2
  10. {pypreservica-3.1.1 → pypreservica-3.2.1}/setup.py +2 -2
  11. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_identifier.py +12 -0
  12. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_metadata.py +6 -2
  13. pypreservica-3.2.1/tests/test_settings.py +28 -0
  14. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_workflow.py +1 -1
  15. {pypreservica-3.1.1 → pypreservica-3.2.1}/LICENSE.txt +0 -0
  16. {pypreservica-3.1.1 → pypreservica-3.2.1}/README.md +0 -0
  17. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica/adminAPI.py +0 -0
  18. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica/authorityAPI.py +0 -0
  19. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica/mdformsAPI.py +0 -0
  20. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica/monitorAPI.py +0 -0
  21. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica/opex.py +0 -0
  22. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica/parAPI.py +0 -0
  23. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica/retentionAPI.py +0 -0
  24. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica/uploadAPI.py +0 -0
  25. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica/webHooksAPI.py +0 -0
  26. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica/workflowAPI.py +0 -0
  27. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica.egg-info/dependency_links.txt +0 -0
  28. {pypreservica-3.1.1 → pypreservica-3.2.1}/pyPreservica.egg-info/top_level.txt +0 -0
  29. {pypreservica-3.1.1 → pypreservica-3.2.1}/setup.cfg +0 -0
  30. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_authority_records.py +0 -0
  31. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_bitstream.py +0 -0
  32. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_children.py +0 -0
  33. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_content_api.py +0 -0
  34. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_crawl_fs.py +0 -0
  35. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_delete.py +0 -0
  36. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_download.py +0 -0
  37. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_entity.py +0 -0
  38. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_export_opex.py +0 -0
  39. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_groups.py +0 -0
  40. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_ingest.py +0 -0
  41. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_integrity_check.py +0 -0
  42. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_par.py +0 -0
  43. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_replace.py +0 -0
  44. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_retention.py +0 -0
  45. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_schema.py +0 -0
  46. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_security.py +0 -0
  47. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_thumbnail.py +0 -0
  48. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_upload.py +0 -0
  49. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_users.py +0 -0
  50. {pypreservica-3.1.1 → pypreservica-3.2.1}/tests/test_xml_metadata.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyPreservica
3
- Version: 3.1.1
3
+ Version: 3.2.1
4
4
  Summary: Python library for the Preservica API
5
5
  Home-page: https://pypreservica.readthedocs.io/
6
6
  Author: James Carr
@@ -23,8 +23,8 @@ License-File: LICENSE.txt
23
23
  Requires-Dist: requests
24
24
  Requires-Dist: urllib3
25
25
  Requires-Dist: certifi
26
- Requires-Dist: boto3
27
- Requires-Dist: botocore
26
+ Requires-Dist: boto3>=1.36.0
27
+ Requires-Dist: botocore>=1.36.0
28
28
  Requires-Dist: s3transfer
29
29
  Requires-Dist: azure-storage-blob
30
30
  Requires-Dist: tqdm
@@ -30,10 +30,11 @@ from .monitorAPI import MonitorAPI, MonitorCategory, MonitorStatus, MessageStatu
30
30
  from .webHooksAPI import WebHooksAPI, TriggerType, WebHookHandler
31
31
  from .authorityAPI import AuthorityAPI, Table
32
32
  from .mdformsAPI import MetadataGroupsAPI, Group, GroupField, GroupFieldType
33
+ from .settingsAPI import SettingsAPI
33
34
 
34
35
  __author__ = "James Carr (drjamescarr@gmail.com)"
35
36
 
36
37
  # Version of the pyPreservica package
37
- __version__ = "3.1.1"
38
+ __version__ = "3.2.1"
38
39
 
39
40
  __license__ = "Apache License Version 2.0"
@@ -27,6 +27,7 @@ from requests import Session
27
27
  from urllib3.util import Retry
28
28
  import requests
29
29
  from requests.adapters import HTTPAdapter
30
+ from typing import TypeVar
30
31
 
31
32
  import pyPreservica
32
33
 
@@ -420,6 +421,26 @@ class Bitstream:
420
421
  return self.__str__()
421
422
 
422
423
 
424
+ class ExternIdentifier:
425
+ """
426
+ Class to represent the External Identifier Object in the Preservica data model
427
+ """
428
+
429
+ def __init__(self, identifier_type: str, identifier_value: str):
430
+ self.type = identifier_type
431
+ self.value = identifier_value
432
+ self.id = None
433
+
434
+ def __str__(self):
435
+ return f"""
436
+ Identifier: {self.id}
437
+ Identifier Type: {self.type}
438
+ Identifier Value: {self.value}
439
+ """
440
+
441
+ def __repr__(self):
442
+ return self.__str__()
443
+
423
444
  class Generation:
424
445
  """
425
446
  Class to represent the Generation Object in the Preservica data model
@@ -528,6 +549,9 @@ class ContentObject(Entity):
528
549
  self.tag = "ContentObject"
529
550
 
530
551
 
552
+ EntityT = TypeVar("EntityT", Folder, Asset, ContentObject, None)
553
+
554
+
531
555
  class Representation:
532
556
  """
533
557
  Class to represent the Representation Object in the Preservica data model
@@ -738,7 +762,7 @@ class AuthenticatedAPI:
738
762
  Return the edition of this tenancy
739
763
  """
740
764
  if self.major_version < 8 and self.minor_version < 3:
741
- raise RuntimeError("Entitlement API is only available when connected to a v7.3 System")
765
+ raise RuntimeError("Entitlement API is only available when connected to a v7.3 System")
742
766
 
743
767
  headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json'}
744
768
 
@@ -777,6 +801,7 @@ class AuthenticatedAPI:
777
801
  self.sec_ns = f"{NS_SEC_ROOT}/v{self.major_version}.{self.minor_version}"
778
802
  self.admin_ns = f"{NS_ADMIN}/v{self.major_version}.{self.minor_version}"
779
803
 
804
+
780
805
  def __version_number__(self):
781
806
  """
782
807
  Determine the version number of the server
@@ -801,7 +826,7 @@ class AuthenticatedAPI:
801
826
  RuntimeError(request.status_code, "version number failed")
802
827
 
803
828
  def __str__(self):
804
- return f"pyPreservica version: {pyPreservica.__version__} (Preservica 7.0 Compatible) " \
829
+ return f"pyPreservica version: {pyPreservica.__version__} (Preservica 8.0 Compatible) " \
805
830
  f"Connected to: {self.server} Preservica version: {self.version} as {self.username} " \
806
831
  f"in tenancy {self.tenant}"
807
832
 
@@ -34,6 +34,11 @@ class Field:
34
34
 
35
35
 
36
36
  class ContentAPI(AuthenticatedAPI):
37
+ """
38
+ The ContentAPI class provides the search interface to the Preservica repository.
39
+
40
+ """
41
+
37
42
 
38
43
  def __init__(self, username: str = None, password: str = None, tenant: str = None, server: str = None,
39
44
  use_shared_secret: bool = False, two_fa_secret_key: str = None,
@@ -486,6 +486,54 @@ class EntityAPI(AuthenticatedAPI):
486
486
  logger.error(request)
487
487
  raise RuntimeError(request.status_code, "delete_identifier failed")
488
488
 
489
+ def entity_identifiers(self, entity: Entity, external_identifier_type = None) -> set[ExternIdentifier]:
490
+ """
491
+ Get all external identifiers on an entity
492
+
493
+ Returns the set of external identifiers on the entity
494
+
495
+ :param entity: The Entity (Asset or Folder)
496
+ :param external_identifier_type: Optional identifier type to filter the results
497
+ :type entity: Entity
498
+ """
499
+ headers = {HEADER_TOKEN: self.token}
500
+ request = self.session.get(
501
+ f'{self.protocol}://{self.server}/api/entity/{entity.path}/{entity.reference}/identifiers',
502
+ headers=headers)
503
+ if request.status_code == requests.codes.ok:
504
+ xml_response = str(request.content.decode('utf-8'))
505
+ logger.debug(xml_response)
506
+ entity_response = xml.etree.ElementTree.fromstring(xml_response)
507
+ identifier_list = entity_response.findall(f'.//{{{self.xip_ns}}}Identifier')
508
+ result = set()
509
+ for identifier in identifier_list:
510
+ identifier_value = identifier_type = identifier_id = ""
511
+ for child in identifier:
512
+ if child.tag.endswith("Type"):
513
+ identifier_type = child.text
514
+ if child.tag.endswith("Value"):
515
+ identifier_value = child.text
516
+ if child.tag.endswith("ApiId"):
517
+ identifier_id = child.text
518
+ if external_identifier_type is None:
519
+ external_id: ExternIdentifier = ExternIdentifier(identifier_type, identifier_value)
520
+ external_id.identifier_id = identifier_id
521
+ result.add(external_id)
522
+ else:
523
+ if identifier_type == external_identifier_type:
524
+ external_id: ExternIdentifier = ExternIdentifier(identifier_type, identifier_value)
525
+ external_id.identifier_id = identifier_id
526
+ result.add(external_id)
527
+ return result
528
+ elif request.status_code == requests.codes.unauthorized:
529
+ self.token = self.__token__()
530
+ return self.entity_identifiers(entity)
531
+ else:
532
+ exception = HTTPException(entity.reference, request.status_code, request.url, "identifiers_for_entity",
533
+ request.content.decode('utf-8'))
534
+ logger.error(exception)
535
+ raise exception
536
+
489
537
 
490
538
  def identifiers_for_entity(self, entity: Entity) -> set[Tuple]:
491
539
  """
@@ -526,14 +574,14 @@ class EntityAPI(AuthenticatedAPI):
526
574
 
527
575
 
528
576
 
529
- def identifier(self, identifier_type: str, identifier_value: str) -> set[Entity]:
577
+ def identifier(self, identifier_type: str, identifier_value: str) -> set[EntityT]:
530
578
  """
531
- Get all entities which have the external identifier
579
+ Get all entities which have the external identifier
532
580
 
533
- Returns the set of entities which have the external identifier
581
+ Returns the set of entities which have the external identifier
534
582
 
535
- :param identifier_type: The identifier type
536
- :param identifier_value: The identifier value
583
+ :param identifier_type: The identifier type
584
+ :param identifier_value: The identifier value
537
585
  """
538
586
  headers = {HEADER_TOKEN: self.token}
539
587
  payload = {'type': identifier_type, 'value': identifier_value}
@@ -861,7 +909,7 @@ class EntityAPI(AuthenticatedAPI):
861
909
  logger.error(exception)
862
910
  raise exception
863
911
 
864
- def delete_metadata(self, entity: Entity, schema: str) -> Entity:
912
+ def delete_metadata(self, entity: EntityT, schema: str) -> EntityT:
865
913
  """
866
914
  Deletes all the metadata fragments on an entity which match the schema URI
867
915
 
@@ -887,7 +935,7 @@ class EntityAPI(AuthenticatedAPI):
887
935
 
888
936
  return self.entity(entity.entity_type, entity.reference)
889
937
 
890
- def update_metadata(self, entity: Entity, schema: str, data: Any) -> Entity:
938
+ def update_metadata(self, entity: EntityT, schema: str, data: Any) -> EntityT:
891
939
  """
892
940
  Update all existing metadata fragments which match the schema
893
941
 
@@ -933,7 +981,45 @@ class EntityAPI(AuthenticatedAPI):
933
981
  raise exception
934
982
  return self.entity(entity.entity_type, entity.reference)
935
983
 
936
- def add_metadata(self, entity: Entity, schema: str, data) -> Entity:
984
+ def add_metadata_as_fragment(
985
+ self, entity: EntityT, schema: str, xml_fragment: str
986
+ ) -> EntityT:
987
+ """
988
+ Add a metadata fragment with a given namespace URI to an Entity
989
+
990
+ Don't parse the xml fragment which may add extra namespaces etc
991
+
992
+ Returns The updated Entity
993
+
994
+ :param xml_fragment: The new XML as a string
995
+ :param entity: The Entity to update
996
+ :param schema: The schema URI of the XML document
997
+ """
998
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/xml;charset=UTF-8'}
999
+
1000
+ xml_doc = f"""<xip:MetadataContainer xmlns="{schema}" schemaUri="{schema}" xmlns:xip="{self.xip_ns}">
1001
+ <xip:Entity>{entity.reference}</xip:Entity>
1002
+ <xip:Content>
1003
+ {xml_fragment}
1004
+ </xip:Content>
1005
+ </xip:MetadataContainer>"""
1006
+
1007
+ end_point = f"/{entity.path}/{entity.reference}/metadata"
1008
+ logger.debug(xml_doc)
1009
+ request = self.session.post(f'{self.protocol}://{self.server}/api/entity{end_point}', data=xml_doc,
1010
+ headers=headers)
1011
+ if request.status_code == requests.codes.ok:
1012
+ return self.entity(entity_type=entity.entity_type, reference=entity.reference)
1013
+ elif request.status_code == requests.codes.unauthorized:
1014
+ self.token = self.__token__()
1015
+ return self.add_metadata(entity, schema, xml_fragment)
1016
+ else:
1017
+ exception = HTTPException(entity.reference, request.status_code, request.url, "add_metadata",
1018
+ request.content.decode('utf-8'))
1019
+ logger.error(exception)
1020
+ raise exception
1021
+
1022
+ def add_metadata(self, entity: EntityT, schema: str, data) -> EntityT:
937
1023
  """
938
1024
  Add a metadata fragment with a given namespace URI
939
1025
 
@@ -960,6 +1046,7 @@ class EntityAPI(AuthenticatedAPI):
960
1046
  xml_request = xml.etree.ElementTree.tostring(xml_object, encoding='utf-8')
961
1047
  end_point = f"/{entity.path}/{entity.reference}/metadata"
962
1048
  logger.debug(xml_request)
1049
+ print(xml_request)
963
1050
  request = self.session.post(f'{self.protocol}://{self.server}/api/entity{end_point}', data=xml_request,
964
1051
  headers=headers)
965
1052
  if request.status_code == requests.codes.ok:
@@ -973,7 +1060,7 @@ class EntityAPI(AuthenticatedAPI):
973
1060
  logger.error(exception)
974
1061
  raise exception
975
1062
 
976
- def save(self, entity: Entity) -> Entity:
1063
+ def save(self, entity: EntityT) -> EntityT:
977
1064
  """
978
1065
  Save the title and description of an entity
979
1066
 
@@ -1091,7 +1178,7 @@ class EntityAPI(AuthenticatedAPI):
1091
1178
  logger.error(exception)
1092
1179
  raise exception
1093
1180
 
1094
- def move_sync(self, entity: Entity, dest_folder: Folder) -> Entity:
1181
+ def move_sync(self, entity: EntityT, dest_folder: Folder) -> EntityT:
1095
1182
  """
1096
1183
  Move an Entity (Asset or Folder) to a new Folder
1097
1184
  If dest_folder is None then the entity must be a Folder and will be moved to the root of the repository
@@ -1131,7 +1218,7 @@ class EntityAPI(AuthenticatedAPI):
1131
1218
  logger.error(exception)
1132
1219
  raise exception
1133
1220
 
1134
- def move(self, entity: Entity, dest_folder: Folder) -> Entity:
1221
+ def move(self, entity: EntityT, dest_folder: Folder) -> EntityT:
1135
1222
  """
1136
1223
  Move an Entity (Asset or Folder) to a new Folder
1137
1224
  If dest_folder is None then the entity must be a Folder and will be moved to the root of the repository
@@ -1236,7 +1323,7 @@ class EntityAPI(AuthenticatedAPI):
1236
1323
  else:
1237
1324
  return xml_object.find(tag).text
1238
1325
 
1239
- def security_tag_sync(self, entity: Entity, new_tag: str):
1326
+ def security_tag_sync(self, entity: EntityT, new_tag: str) -> EntityT:
1240
1327
  """
1241
1328
  Change the security tag for a folder or asset
1242
1329
 
@@ -1316,7 +1403,7 @@ class EntityAPI(AuthenticatedAPI):
1316
1403
  logger.error(exception)
1317
1404
  raise exception
1318
1405
 
1319
- def entity(self, entity_type: EntityType, reference: str) -> Entity:
1406
+ def entity(self, entity_type: EntityType, reference: str) -> EntityT:
1320
1407
  """
1321
1408
  Retrieve an entity by its type and reference
1322
1409
 
@@ -0,0 +1,295 @@
1
+ """
2
+ pyPreservica Settings API module definition
3
+
4
+ API for retrieving information about configuration settings.
5
+
6
+ author: James Carr
7
+ licence: Apache License 2.0
8
+
9
+ """
10
+
11
+ from typing import Callable
12
+
13
+ from pyPreservica.common import *
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class SettingsAPI(AuthenticatedAPI):
19
+ """
20
+ API for retrieving information about configuration settings.
21
+
22
+ Includes methods for:
23
+
24
+ * metadata-enrichment
25
+
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ username=None,
31
+ password=None,
32
+ tenant=None,
33
+ server=None,
34
+ use_shared_secret=False,
35
+ two_fa_secret_key: str = None,
36
+ protocol: str = "https",
37
+ request_hook: Callable = None,
38
+ credentials_path: str = "credentials.properties",
39
+ ):
40
+ super().__init__(
41
+ username,
42
+ password,
43
+ tenant,
44
+ server,
45
+ use_shared_secret,
46
+ two_fa_secret_key,
47
+ protocol,
48
+ request_hook,
49
+ credentials_path,
50
+ )
51
+
52
+ if self.major_version < 7 and self.minor_version < 7:
53
+ raise RuntimeError(
54
+ "Settings API is only available when connected to a v7.7 System or higher"
55
+ )
56
+
57
+ self.base_url = "api/settings"
58
+
59
+ def metadata_enrichment_rules(self, profile_id: str = None) -> dict:
60
+ """
61
+ Returns a list of metadata enrichment rules.
62
+ An empty selection implies that the rule is applied to all content.
63
+ Rules define where particular behaviours, defined by profiles, will be applied.
64
+ Rules are evaluated in order, with the first matching rule being applied.
65
+
66
+ :param profile_id: The rules for a specific profile id, Set to None for all rules
67
+ :type profile_id: str
68
+
69
+ """
70
+ headers = {
71
+ HEADER_TOKEN: self.token,
72
+ "Accept": "application/json",
73
+ "Content-Type": "application/json;charset=UTF-8",
74
+ }
75
+
76
+ endpoint: str = "/metadata-enrichment/config/rules"
77
+
78
+ request = self.session.get(
79
+ f"{self.protocol}://{self.server}/{self.base_url}{endpoint}",
80
+ headers=headers)
81
+
82
+ if request.status_code == requests.codes.ok:
83
+ rules: dict = json.loads(request.content.decode("utf-8"))
84
+ if profile_id is None:
85
+ return rules
86
+ else:
87
+ profile_rules = []
88
+ for rule in rules["rules"]:
89
+ if rule["profileId"] == profile_id:
90
+ profile_rules.append(rule)
91
+ return {"rules": profile_rules}
92
+ else:
93
+ logger.debug(request.content.decode("utf-8"))
94
+ raise RuntimeError(request.status_code, f"metadata_enrichment_rules failed")
95
+
96
+ def metadata_enrichment_delete_rule(self, rule_id: str):
97
+ """
98
+ Deletes a metadata enrichment rule.
99
+
100
+ :param rule_id: The rule id
101
+ :type rule_id: str
102
+
103
+ :return: No return value
104
+ :rtype: None
105
+
106
+ """
107
+ headers = {
108
+ HEADER_TOKEN: self.token,
109
+ "Accept": "application/json",
110
+ "Content-Type": "application/json;charset=UTF-8",
111
+ }
112
+
113
+ endpoint: str = f"/metadata-enrichment/config/rules/{rule_id}"
114
+
115
+ request = self.session.delete(
116
+ f"{self.protocol}://{self.server}/{self.base_url}{endpoint}", headers=headers)
117
+
118
+ if request.status_code == requests.codes.no_content:
119
+ return
120
+ else:
121
+ logger.debug(request.content.decode("utf-8"))
122
+ raise RuntimeError(request.status_code, f"metadata_enrichment_delete_rule failed")
123
+
124
+ def metadata_enrichment_add_rule(self, profile_id: str, priority: int = 1):
125
+ """
126
+ Create a metadata enrichment rule to control when metadata enrichment profiles are applied and return it.
127
+ Rules define where particular behaviours, defined by profiles, will be applied.
128
+ Rules are evaluated in order, with the first matching rule being applied.
129
+ Note that not specifying, or specifying an empty selection implies that the rule will be applied to all content.
130
+ Currently only securityDescriptorSelector, representationSelector and hierarchySelector are supported selectors.
131
+ If a rule already exists for the requested priority, existing rules will be shifted down priority to accommodate the new entry.
132
+
133
+ :param profile_id: The profile id
134
+ :type profile_id: str
135
+
136
+ :param priority: The rule priority
137
+ :type priority: int
138
+
139
+ :return: The metadata enrichment rule
140
+ :rtype: dict
141
+ """
142
+
143
+ headers = {
144
+ HEADER_TOKEN: self.token,
145
+ "Accept": "application/json",
146
+ "Content-Type": "application/json;charset=UTF-8",
147
+ }
148
+
149
+ endpoint: str = "/metadata-enrichment/config/rules"
150
+
151
+ rule: dict = {
152
+ "profileId": profile_id,
153
+ "priority": str(priority),
154
+ "selectorSettings": {},
155
+ }
156
+
157
+ request = self.session.post(
158
+ f"{self.protocol}://{self.server}/{self.base_url}/{endpoint}",
159
+ headers=headers,
160
+ json=rule,
161
+ )
162
+ if request.status_code == requests.codes.created:
163
+ return json.loads(request.content.decode("utf-8"))
164
+ else:
165
+ logger.debug(request.content.decode("utf-8"))
166
+ raise RuntimeError(
167
+ request.status_code, f"metadata_enrichment_add_rule failed"
168
+ )
169
+
170
+ def metadata_enrichment_add_profile(self, name: str, active: bool = True):
171
+ """
172
+ Create a metadata enrichment profile to control automatic metadata enrichment of content and return it.
173
+ Profiles define a set of behaviours that will be applied when the profile is selected by a rule.
174
+ A profile has no effect if it is not used by a rule. Includes settings for PII identification.
175
+ PII detection tools may be run against the full text extracted from content.
176
+
177
+
178
+ :param name: The profile name
179
+ :type name: str
180
+
181
+ :param active: The profile active status
182
+ :type active: bool
183
+
184
+ :return: The metadata enrichment profile
185
+ :rtype: dict
186
+
187
+ """
188
+
189
+ headers = {
190
+ HEADER_TOKEN: self.token,
191
+ "Accept": "application/json",
192
+ "Content-Type": "application/json;charset=UTF-8",
193
+ }
194
+
195
+ endpoint: str = "/metadata-enrichment/config/profiles"
196
+
197
+ profile: dict = {"name": name, "piiSettings": {"active": str(active).lower()}}
198
+
199
+ request = self.session.post(
200
+ f"{self.protocol}://{self.server}/{self.base_url}{endpoint}",
201
+ headers=headers, json=profile)
202
+
203
+ if request.status_code == requests.codes.created:
204
+ return json.loads(request.content.decode("utf-8"))
205
+ else:
206
+ logger.debug(request.content.decode("utf-8"))
207
+ raise RuntimeError(request.status_code, f"metadata_enrichment_add_profile failed")
208
+
209
+ def metadata_enrichment_profile(self, profile_id: str) -> dict:
210
+ """
211
+ Returns a single profile by its ID
212
+ Profiles define a set of behaviours that will be applied when the profile is selected by a rule.
213
+ A profile has no effect if it is not used by a rule. Includes settings for PII identification.
214
+ PII detection tools may be run against the full text extracted from content.
215
+
216
+ :param profile_id: The profile name
217
+ :type profile_id: str
218
+
219
+ :return: The metadata enrichment profile
220
+ :rtype: dict
221
+
222
+ """
223
+ headers = {
224
+ HEADER_TOKEN: self.token,
225
+ "Accept": "application/json",
226
+ "Content-Type": "application/json;charset=UTF-8",
227
+ }
228
+
229
+ endpoint: str = f"/metadata-enrichment/config/profiles/{profile_id}"
230
+
231
+ request = self.session.get(
232
+ f"{self.protocol}://{self.server}/{self.base_url}{endpoint}", headers=headers)
233
+
234
+ if request.status_code == requests.codes.ok:
235
+ return json.loads(request.content.decode("utf-8"))
236
+ else:
237
+ logger.debug(request.content.decode("utf-8"))
238
+ raise RuntimeError(request.status_code, f"metadata_enrichment_profile failed")
239
+
240
+ def metadata_enrichment_delete_profile(self, profile_id: str) -> None:
241
+ """
242
+ Deletes a metadata enrichment profile
243
+
244
+ :param profile_id: The profile name
245
+ :type profile_id: str
246
+
247
+ :return: No return value
248
+ :rtype: None
249
+
250
+ """
251
+ headers = {
252
+ HEADER_TOKEN: self.token,
253
+ "Accept": "application/json",
254
+ "Content-Type": "application/json;charset=UTF-8",
255
+ }
256
+
257
+ endpoint: str = f"/metadata-enrichment/config/profiles/{profile_id}"
258
+
259
+ request = self.session.delete(
260
+ f"{self.protocol}://{self.server}/{self.base_url}{endpoint}", headers=headers)
261
+
262
+ if request.status_code == requests.codes.forbidden:
263
+ logger.debug(request.content.decode("utf-8"))
264
+ raise RuntimeError(request.status_code, f"Can't delete a profile with rules assigned")
265
+
266
+ if request.status_code == requests.codes.no_content:
267
+ return
268
+ else:
269
+ logger.debug(request.content.decode("utf-8"))
270
+ raise RuntimeError(request.status_code, f"metadata_enrichment_delete_profile failed")
271
+
272
+ def metadata_enrichment_profiles(self) -> dict:
273
+ """
274
+ Returns the list of all metadata enrichment profiles.
275
+ Profiles define a set of behaviours that will be applied when the profile is selected by a rule.
276
+ A profile has no effect if it is not used by a rule. Includes settings for PII identification.
277
+ PII detection tools may be run against the full text extracted from content.
278
+ """
279
+
280
+ headers = {
281
+ HEADER_TOKEN: self.token,
282
+ "Accept": "application/json",
283
+ "Content-Type": "application/json;charset=UTF-8",
284
+ }
285
+
286
+ endpoint: str = "/metadata-enrichment/config/profiles"
287
+
288
+ request = self.session.get(
289
+ f"{self.protocol}://{self.server}/{self.base_url}{endpoint}", headers=headers)
290
+
291
+ if request.status_code == requests.codes.ok:
292
+ return json.loads(request.content.decode("utf-8"))
293
+ else:
294
+ logger.debug(request.content.decode("utf-8"))
295
+ raise RuntimeError(request.status_code, f"metadata_enrichment_profiles failed")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyPreservica
3
- Version: 3.1.1
3
+ Version: 3.2.1
4
4
  Summary: Python library for the Preservica API
5
5
  Home-page: https://pypreservica.readthedocs.io/
6
6
  Author: James Carr
@@ -23,8 +23,8 @@ License-File: LICENSE.txt
23
23
  Requires-Dist: requests
24
24
  Requires-Dist: urllib3
25
25
  Requires-Dist: certifi
26
- Requires-Dist: boto3
27
- Requires-Dist: botocore
26
+ Requires-Dist: boto3>=1.36.0
27
+ Requires-Dist: botocore>=1.36.0
28
28
  Requires-Dist: s3transfer
29
29
  Requires-Dist: azure-storage-blob
30
30
  Requires-Dist: tqdm
@@ -12,6 +12,7 @@ pyPreservica/monitorAPI.py
12
12
  pyPreservica/opex.py
13
13
  pyPreservica/parAPI.py
14
14
  pyPreservica/retentionAPI.py
15
+ pyPreservica/settingsAPI.py
15
16
  pyPreservica/uploadAPI.py
16
17
  pyPreservica/webHooksAPI.py
17
18
  pyPreservica/workflowAPI.py
@@ -39,6 +40,7 @@ tests/test_replace.py
39
40
  tests/test_retention.py
40
41
  tests/test_schema.py
41
42
  tests/test_security.py
43
+ tests/test_settings.py
42
44
  tests/test_thumbnail.py
43
45
  tests/test_upload.py
44
46
  tests/test_users.py
@@ -1,8 +1,8 @@
1
1
  requests
2
2
  urllib3
3
3
  certifi
4
- boto3
5
- botocore
4
+ boto3>=1.36.0
5
+ botocore>=1.36.0
6
6
  s3transfer
7
7
  azure-storage-blob
8
8
  tqdm
@@ -21,7 +21,7 @@ if sys.argv[-1] == 'publish':
21
21
  # This call to setup() does all the work
22
22
  setup(
23
23
  name=PKG,
24
- version="3.1.1",
24
+ version="3.2.1",
25
25
  description="Python library for the Preservica API",
26
26
  long_description=README,
27
27
  long_description_content_type="text/markdown",
@@ -41,7 +41,7 @@ setup(
41
41
  "Topic :: System :: Archiving",
42
42
  ],
43
43
  keywords='Preservica API Preservation',
44
- install_requires=["requests", "urllib3", "certifi", "boto3", "botocore", "s3transfer", "azure-storage-blob", "tqdm", "pyotp", "python-dateutil"],
44
+ install_requires=["requests", "urllib3", "certifi", "boto3>=1.36.0", "botocore>=1.36.0", "s3transfer", "azure-storage-blob", "tqdm", "pyotp", "python-dateutil"],
45
45
  project_urls={
46
46
  'Documentation': 'https://pypreservica.readthedocs.io',
47
47
  'Source': 'https://github.com/carj/pyPreservica',
@@ -113,6 +113,18 @@ def test_get_identifier_asset(setup_data):
113
113
  assert identifier[1] == "1234567890"
114
114
  assert test_pass
115
115
 
116
+ def test_get_identifier_asset2(setup_data):
117
+ client = EntityAPI()
118
+ asset = client.asset(ASSET_ID)
119
+
120
+ client.add_identifier(asset, "ARK", "1234567890")
121
+
122
+ identifiers = client.entity_identifiers(asset)
123
+ assert len(identifiers) == 1
124
+ identifier = identifiers.pop()
125
+ assert identifier.type == "ARK"
126
+ assert identifier.value == "1234567890"
127
+
116
128
 
117
129
  def test_delete_identifier_folder(setup_data):
118
130
  client = EntityAPI()
@@ -65,21 +65,25 @@ def test_add_folder_metadata_no_prefix():
65
65
  client = EntityAPI()
66
66
  entity = client.entity(EntityType.FOLDER, FOLDER_ID)
67
67
  folder = client.add_metadata(entity, "https://www.person.com/person", XML_DOCUMENT_NO_PREFIX)
68
+ client.delete_metadata(folder, "https://www.person.com/person")
68
69
 
69
70
  def test_add_folder_metadata_with_ns():
70
71
  client = EntityAPI()
71
72
  entity = client.entity(EntityType.FOLDER, FOLDER_ID)
72
73
  folder = client.add_metadata(entity, "https://www.person.com/person", XML_DOCUMENT)
74
+ client.delete_metadata(folder, "https://www.person.com/person")
73
75
 
74
76
  def test_add_asset_metadata_no_prefix():
75
77
  client = EntityAPI()
76
78
  entity = client.entity(EntityType.ASSET, ASSET_ID)
77
- folder = client.add_metadata(entity, "https://www.person.com/person", XML_DOCUMENT_NO_PREFIX)
79
+ asset = client.add_metadata(entity, "https://www.person.com/person", XML_DOCUMENT_NO_PREFIX)
80
+ client.delete_metadata(asset, "https://www.person.com/person")
78
81
 
79
82
  def test_add_asset_metadata_with_ns():
80
83
  client = EntityAPI()
81
84
  entity = client.entity(EntityType.ASSET, ASSET_ID)
82
- folder = client.add_metadata(entity, "https://www.person.com/person", XML_DOCUMENT)
85
+ asset = client.add_metadata(entity, "https://www.person.com/person", XML_DOCUMENT)
86
+ client.delete_metadata(asset, "https://www.person.com/person")
83
87
 
84
88
 
85
89
 
@@ -0,0 +1,28 @@
1
+ import pytest
2
+
3
+ from pyPreservica.settingsAPI import SettingsAPI
4
+
5
+
6
+ def setup():
7
+ pass
8
+
9
+
10
+ def tear_down():
11
+ pass
12
+
13
+
14
+ @pytest.fixture
15
+ def setup_data():
16
+ setup()
17
+ yield
18
+ tear_down()
19
+
20
+
21
+
22
+ def test_get_enrichment_profiles(setup_data):
23
+ client = SettingsAPI()
24
+ profiles = client.metadata_enrichment_profiles()
25
+ assert profiles is not None
26
+
27
+ def test_can_add_enrichment_profile(setup_data):
28
+ client = SettingsAPI()
@@ -24,7 +24,7 @@ def test_get_workflow_contexts_type():
24
24
  workflow = WorkflowAPI()
25
25
 
26
26
  workflows = workflow.get_workflow_contexts_by_type("Ingest")
27
- assert len(workflows) == 8
27
+ assert len(workflows) == 9
28
28
 
29
29
  workflows = workflow.get_workflow_contexts_by_type("Access")
30
30
  assert len(workflows) == 5
File without changes
File without changes
File without changes