pyPreservica 2.9.3__py3-none-any.whl → 3.3.3__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.
@@ -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
 
@@ -134,16 +133,45 @@ def _json_from_object_(group: Group) -> dict:
134
133
 
135
134
 
136
135
  class MetadataGroupsAPI(AuthenticatedAPI):
136
+
137
137
  def __init__(self, username: str = None, password: str = None, tenant: str = None, server: str = None,
138
138
  use_shared_secret: bool = False, two_fa_secret_key: str = None,
139
- protocol: str = "https", request_hook: Callable = None):
139
+ protocol: str = "https", request_hook: Callable = None, credentials_path: str = 'credentials.properties'):
140
140
 
141
141
  super().__init__(username, password, tenant, server, use_shared_secret, two_fa_secret_key,
142
- protocol, request_hook)
142
+ protocol, request_hook, credentials_path)
143
143
 
144
144
  xml.etree.ElementTree.register_namespace("oai_dc", "http://www.openarchives.org/OAI/2.0/oai_dc/")
145
145
  xml.etree.ElementTree.register_namespace("ead", "urn:isbn:1-931666-22-9")
146
146
 
147
+ def download_template(self, form_name: str):
148
+ """
149
+ Download a template csv to allow bulk input of data
150
+
151
+ """
152
+ headers = {HEADER_TOKEN: self.token}
153
+ url = f'{self.protocol}://{self.server}/api/metadata/csv-templates/download'
154
+
155
+ for form in self.forms():
156
+ if form['title'] == form_name:
157
+ form_id: str = form['id']
158
+ params = {'ids': form_id}
159
+ with self.session.get(url, headers=headers, params=params) as response:
160
+ if response.status_code == requests.codes.ok:
161
+ with open(f"{form_name}.csv", mode="wt", encoding="utf-8") as fd:
162
+ fd.write(response.content.decode("utf-8"))
163
+ fd.flush()
164
+ return f"{form_name}.csv"
165
+ if response.status_code == requests.codes.unauthorized:
166
+ self.token = self.__token__()
167
+ return self.download_template(form_name)
168
+ else:
169
+ exception = HTTPException(None, response.status_code, response.url, "download_template",
170
+ response.content.decode('utf-8'))
171
+ logger.error(exception)
172
+ raise exception
173
+ return None
174
+
147
175
  def delete_group_namespace(self, schema: str):
148
176
  """
149
177
  Delete a new Metadata Group using its schema URI
@@ -248,9 +276,42 @@ class MetadataGroupsAPI(AuthenticatedAPI):
248
276
  json_response: dict = self.add_group_json(json_document)
249
277
  return json_response
250
278
 
279
+ def update_form(self, form_id: str, json_form: Union[dict, str]):
280
+
281
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json;charset=UTF-8'}
282
+ url = f'{self.protocol}://{self.server}/api/metadata/forms/{form_id}'
283
+
284
+ if isinstance(json_form, dict):
285
+ with self.session.put(url, headers=headers, json=json_form) as request:
286
+ if request.status_code == requests.codes.unauthorized:
287
+ self.token = self.__token__()
288
+ return self.add_form(json_form)
289
+ elif request.status_code == requests.codes.ok:
290
+ return json.loads(str(request.content.decode('utf-8')))
291
+ else:
292
+ exception = HTTPException(None, request.status_code, request.url, "add_form_json",
293
+ request.content.decode('utf-8'))
294
+ logger.error(exception)
295
+ raise exception
296
+
297
+ elif isinstance(json_form, str):
298
+ with self.session.put(url, headers=headers, data=json_form) as request:
299
+ if request.status_code == requests.codes.unauthorized:
300
+ self.token = self.__token__()
301
+ return self.add_form(json_form)
302
+ elif request.status_code == requests.codes.ok:
303
+ return json.loads(str(request.content.decode('utf-8')))
304
+ else:
305
+ exception = HTTPException(None, request.status_code, request.url, "add_form_json",
306
+ request.content.decode('utf-8'))
307
+ logger.error(exception)
308
+ raise exception
309
+ else:
310
+ raise RuntimeError("Argument must be a JSON dictionary or a JSON str")
311
+
251
312
  def add_form(self, json_form: Union[dict, str]):
252
313
  """
253
- Create a new Metadata fORM using a JSON dictionary object or document
314
+ Create a new Metadata form using a JSON dictionary object or document
254
315
 
255
316
  :param json_form: JSON dictionary or string
256
317
  :type json_form: dict
@@ -266,7 +327,7 @@ class MetadataGroupsAPI(AuthenticatedAPI):
266
327
  with self.session.post(url, headers=headers, json=json_form) as request:
267
328
  if request.status_code == requests.codes.unauthorized:
268
329
  self.token = self.__token__()
269
- return self.add_form_json(json_form)
330
+ return self.add_form(json_form)
270
331
  elif request.status_code == requests.codes.created:
271
332
  return json.loads(str(request.content.decode('utf-8')))
272
333
  else:
@@ -279,7 +340,7 @@ class MetadataGroupsAPI(AuthenticatedAPI):
279
340
  with self.session.post(url, headers=headers, data=json_form) as request:
280
341
  if request.status_code == requests.codes.unauthorized:
281
342
  self.token = self.__token__()
282
- return self.add_form_json(json_form)
343
+ return self.add_form(json_form)
283
344
  elif request.status_code == requests.codes.created:
284
345
  return json.loads(str(request.content.decode('utf-8')))
285
346
  else:
@@ -451,6 +512,26 @@ class MetadataGroupsAPI(AuthenticatedAPI):
451
512
  raise exception
452
513
 
453
514
 
515
+ def delete_form(self, form_id: str):
516
+ """
517
+ Delete a form by its ID
518
+ """
519
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json;charset=UTF-8'}
520
+ url = f'{self.protocol}://{self.server}/api/metadata/forms/{form_id}'
521
+ with self.session.delete(url, headers=headers) as request:
522
+ if request.status_code == requests.codes.unauthorized:
523
+ self.token = self.__token__()
524
+ return self.delete_form(form_id)
525
+ elif request.status_code == requests.codes.no_content:
526
+ return None
527
+ else:
528
+ exception = HTTPException(None, request.status_code, request.url, "delete_form",
529
+ request.content.decode('utf-8'))
530
+ logger.error(exception)
531
+ raise exception
532
+
533
+
534
+
454
535
  def form(self, form_id: str) -> dict:
455
536
  """
456
537
  Return a Form as a JSON dict object
@@ -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")
pyPreservica/parAPI.py CHANGED
@@ -23,43 +23,7 @@ def __get_contents__(document) -> AnyStr:
23
23
  return json.dumps(json.loads(document))
24
24
 
25
25
 
26
- class PreservationActionRegistry:
27
-
28
- def __init__(self, server: str = None, username: str = None, password: str = None, protocol: str = 'https'):
29
- self.protocol = protocol
30
- self.session = requests.Session()
31
- self.session.headers.update({'accept': 'application/json;charset=UTF-8'})
32
- config = configparser.ConfigParser()
33
- config.read('credentials.properties')
34
- if not server:
35
- server = os.environ.get('PRESERVICA_SERVER')
36
- if server is None:
37
- try:
38
- server = config['credentials']['server']
39
- except KeyError:
40
- pass
41
- if not server:
42
- msg = "No valid server found in method arguments, environment variables or credentials.properties file"
43
- logger.error(msg)
44
- raise RuntimeError(msg)
45
- else:
46
- self.server = server
47
- if not username:
48
- username = os.environ.get('PRESERVICA_USERNAME')
49
- if username is None:
50
- try:
51
- username = config['credentials']['username']
52
- except KeyError:
53
- pass
54
- self.username = username
55
- if not password:
56
- password = os.environ.get('PRESERVICA_PASSWORD')
57
- if password is None:
58
- try:
59
- password = config['credentials']['password']
60
- except KeyError:
61
- pass
62
- self.password = password
26
+ class PreservationActionRegistry(AuthenticatedAPI):
63
27
 
64
28
  def format_family(self, guid: str) -> str:
65
29
  return self.__guid__(guid, "format-families")
@@ -58,9 +58,10 @@ class RetentionPolicy:
58
58
  class RetentionAPI(AuthenticatedAPI):
59
59
 
60
60
  def __init__(self, username=None, password=None, tenant=None, server=None, use_shared_secret=False,
61
- two_fa_secret_key: str = None, protocol: str = "https", request_hook: Callable = None):
61
+ two_fa_secret_key: str = None, protocol: str = "https", request_hook: Callable = None, credentials_path: str = 'credentials.properties'):
62
62
  super().__init__(username, password, tenant, server, use_shared_secret, two_fa_secret_key,
63
- protocol, request_hook)
63
+ protocol, request_hook, credentials_path)
64
+
64
65
  if self.major_version < 7 and self.minor_version < 2:
65
66
  raise RuntimeError("Retention API is only available when connected to a v6.2 System")
66
67
 
@@ -95,7 +96,7 @@ class RetentionAPI(AuthenticatedAPI):
95
96
  if start_date_field is not None:
96
97
  rp.start_date_field = start_date_field.text
97
98
  else:
98
- start_date_field = None
99
+ rp.start_date_field = None
99
100
  period = entity_response.find(f'.//{{{self.rm_ns}}}RetentionPolicy/{{{self.rm_ns}}}Period')
100
101
  if period is not None:
101
102
  rp.period = period.text
@@ -403,7 +404,7 @@ class RetentionAPI(AuthenticatedAPI):
403
404
  def policies(self, maximum: int = 250, next_page: str = None) -> PagedSet:
404
405
  """
405
406
  Return a list of all retention policies
406
- Returns a maxmium of 250 policies by default
407
+ Returns a maximum of 250 policies by default
407
408
 
408
409
 
409
410
  :return: Set of retention policies
@@ -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")