pyPreservica 0.9.9__py3-none-any.whl → 3.3.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.
@@ -0,0 +1,572 @@
1
+ """
2
+ pyPreservica MDFormsAPI module definition
3
+
4
+ A client library for the Preservica Repository web services Metadata API
5
+ https://demo.preservica.com/api/metadata/documentation.html
6
+
7
+ author: James Carr
8
+ licence: Apache License 2.0
9
+
10
+ """
11
+ import xml.etree.ElementTree
12
+ from typing import Callable, List, Union, Generator
13
+
14
+ from pyPreservica.common import *
15
+
16
+
17
+ class GroupFieldType(Enum):
18
+ STRING = "STRING"
19
+ LONG_STRING = "LONGSTRING"
20
+ DATE = "DATE"
21
+ NUMBER = "NUMBER"
22
+
23
+
24
+ class GroupField:
25
+ field_id: str
26
+ name: str
27
+ field_type: GroupFieldType
28
+ maxLength: int
29
+ default: str
30
+ visible: bool
31
+ editable: bool
32
+ minOccurs: int
33
+ maxOccurs: int
34
+ values: List[str]
35
+ indexed: bool
36
+
37
+ def __init__(self, field_id: str, name: str, field_type: GroupFieldType = GroupFieldType.STRING,
38
+ maxLength: int = -1, default: str = "", visible: bool = True, editable: bool = True,
39
+ minOccurs: int = 0, maxOccurs: int = 1, indexed: bool = True, values: List = None):
40
+ self.field_id = field_id
41
+ self.name = name
42
+ self.field_type = field_type
43
+ self.maxLength = maxLength
44
+ self.default = default
45
+ self.visible = visible
46
+ self.editable = editable
47
+ self.minOccurs = minOccurs
48
+ self.maxOccurs = maxOccurs
49
+ self.values = values
50
+ self.indexed = indexed
51
+
52
+ def __str__(self):
53
+ return (f"Field ID: {self.field_id}\n" + f"Field Name: {self.name}\n" + f"Field Type: {self.field_type}\n" +
54
+ f"Field Visible: {self.visible}\n" + f"Field Editable: {self.editable}\n")
55
+
56
+
57
+ class Group:
58
+ group_id: str
59
+ name: str
60
+ description: str
61
+ schemaUri: str
62
+ fields: List[GroupField]
63
+
64
+ def __init__(self, name: str, description: str):
65
+ self.name = name
66
+ self.description = description
67
+ self.fields = []
68
+
69
+ def __str__(self):
70
+ return (f"Group ID: {self.group_id}\n" + f"Group Name: {self.name}\n" +
71
+ f"Group Description: {self.description}\n" + f"Group Schema URI: {self.schemaUri}")
72
+
73
+
74
+ def _object_from_json_(json_doc: dict) -> Group:
75
+ """
76
+ Create a JSON dict object from a Group object
77
+ """
78
+
79
+ group: Group = Group(name=json_doc['name'], description=json_doc['description'])
80
+ group.fields = []
81
+ if 'id' in json_doc:
82
+ group.group_id = json_doc['id']
83
+ if 'schemaUri' in json_doc:
84
+ group.schemaUri = json_doc['schemaUri']
85
+
86
+ if 'fields' in json_doc:
87
+ for field in json_doc['fields']:
88
+ gf: GroupField = GroupField(field['id'], field['name'], GroupFieldType(str(field['type'])))
89
+ if 'minOccurs' in field:
90
+ gf.minOccurs = int(field['minOccurs'])
91
+ if 'maxOccurs' in field:
92
+ gf.maxOccurs = int(field['maxOccurs'])
93
+ if 'visible' in field:
94
+ gf.visible = bool(field['visible'])
95
+ if 'editable' in field:
96
+ gf.editable = bool(field['editable'])
97
+ if 'values' in field:
98
+ for v in field['values']:
99
+ if gf.values is None:
100
+ gf.values = []
101
+ gf.values.append(str(v))
102
+ if 'defaultValue' in field:
103
+ gf.default = str(field['defaultValue'])
104
+ if 'indexed' in field:
105
+ gf.indexed = bool(field['indexed'])
106
+
107
+ group.fields.append(gf)
108
+
109
+ return group
110
+
111
+
112
+ def _json_from_object_(group: Group) -> dict:
113
+ """
114
+ Create a JSON dict object from a Group object
115
+ """
116
+
117
+ fields = []
118
+ for field in group.fields:
119
+ f = {"id": field.field_id, "name": field.name, "type": str(field.field_type.value)}
120
+ f["minOccurs"] = str(field.minOccurs)
121
+ f["maxOccurs"] = str(field.maxOccurs)
122
+ f["visible"] = str(field.visible)
123
+ f["editable"] = str(field.editable)
124
+ if (field.values is not None) and (len(field.values) > 0):
125
+ f["values"] = [item for item in field.values]
126
+ f["defaultValue"] = str(field.default)
127
+ f["indexed"] = str(field.indexed)
128
+ fields.append(f)
129
+
130
+ json_doc = {"name": group.name, "description": group.description, "fields": fields}
131
+
132
+ return json_doc
133
+
134
+
135
+ class MetadataGroupsAPI(AuthenticatedAPI):
136
+
137
+ def __init__(self, username: str = None, password: str = None, tenant: str = None, server: str = None,
138
+ use_shared_secret: bool = False, two_fa_secret_key: str = None,
139
+ protocol: str = "https", request_hook: Callable = None, credentials_path: str = 'credentials.properties'):
140
+
141
+ super().__init__(username, password, tenant, server, use_shared_secret, two_fa_secret_key,
142
+ protocol, request_hook, credentials_path)
143
+
144
+ xml.etree.ElementTree.register_namespace("oai_dc", "http://www.openarchives.org/OAI/2.0/oai_dc/")
145
+ xml.etree.ElementTree.register_namespace("ead", "urn:isbn:1-931666-22-9")
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
+
175
+ def delete_group_namespace(self, schema: str):
176
+ """
177
+ Delete a new Metadata Group using its schema URI
178
+
179
+ :param schema: The Group namespace schema URI
180
+ :type schema: str
181
+
182
+ :return: None
183
+ :rtype: None
184
+
185
+ """
186
+ for group in self.groups():
187
+ if group.schemaUri == schema:
188
+ self.delete_group(group.group_id)
189
+
190
+ def delete_group(self, group_id: str):
191
+ """
192
+ Delete a new Metadata Group using its ID
193
+
194
+ :param group_id: Group ID
195
+ :type group_id: str
196
+
197
+ :return:
198
+ :rtype: None
199
+
200
+ """
201
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json;charset=UTF-8'}
202
+ url = f'{self.protocol}://{self.server}/api/metadata/groups/{group_id}'
203
+ with self.session.delete(url, headers=headers) as request:
204
+ if request.status_code == requests.codes.unauthorized:
205
+ self.token = self.__token__()
206
+ return self.delete_group(group_id)
207
+ elif request.status_code == requests.codes.no_content:
208
+ return None
209
+ else:
210
+ exception = HTTPException(None, request.status_code, request.url, "delete_group",
211
+ request.content.decode('utf-8'))
212
+ logger.error(exception)
213
+ raise exception
214
+
215
+ def add_fields(self, group_id: str, new_fields: List[GroupField]) -> dict:
216
+ """
217
+ Add new metadata fields to an existing Group
218
+
219
+ The new fields are appended to the end of the group
220
+
221
+ :param group_id: The group ID of the group to update
222
+ :type group_id: str
223
+
224
+ :param new_fields: The list of new fields to add to the group
225
+ :type new_fields: List[GroupField]
226
+
227
+ :return: The updated Metadata Group as a JSON dict
228
+ :rtype: dict
229
+
230
+
231
+ """
232
+
233
+ this_group: Group = self.group(group_id)
234
+
235
+ for field in new_fields:
236
+ this_group.fields.append(field)
237
+
238
+ doc = _json_from_object_(this_group)
239
+
240
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json;charset=UTF-8'}
241
+ url = f'{self.protocol}://{self.server}/api/metadata/groups/{group_id}'
242
+ with self.session.put(url, headers=headers, json=doc) as request:
243
+ if request.status_code == requests.codes.unauthorized:
244
+ self.token = self.__token__()
245
+ return self.add_fields(group_id, new_fields)
246
+ elif request.status_code == requests.codes.created:
247
+ return json.loads(str(request.content.decode('utf-8')))
248
+ else:
249
+ exception = HTTPException(None, request.status_code, request.url, "add_fields",
250
+ request.content.decode('utf-8'))
251
+ logger.error(exception)
252
+ raise exception
253
+
254
+ def add_group(self, group_name: str, group_description: str, fields: List[GroupField]) -> dict:
255
+ """
256
+ Create a new Metadata Group GroupFields
257
+
258
+ :param group_name: The name of the new Group
259
+ :type group_name: str
260
+
261
+ :param group_description: The description of the new Group
262
+ :type group_description: str
263
+
264
+ :param fields: The list of fields
265
+ :type fields: List[GroupField]
266
+
267
+ :return: The new metadata Group as a JSON dict
268
+ :rtype: dict
269
+
270
+ """
271
+
272
+ group: Group = Group(group_name, group_description)
273
+ group.fields = fields
274
+
275
+ json_document: dict = _json_from_object_(group)
276
+ json_response: dict = self.add_group_json(json_document)
277
+ return json_response
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
+
312
+ def add_form(self, json_form: Union[dict, str]):
313
+ """
314
+ Create a new Metadata form using a JSON dictionary object or document
315
+
316
+ :param json_form: JSON dictionary or string
317
+ :type json_form: dict
318
+
319
+ :return: JSON document
320
+ :rtype: dict
321
+
322
+ """
323
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json;charset=UTF-8'}
324
+ url = f'{self.protocol}://{self.server}/api/metadata/forms/'
325
+
326
+ if isinstance(json_form, dict):
327
+ with self.session.post(url, headers=headers, json=json_form) as request:
328
+ if request.status_code == requests.codes.unauthorized:
329
+ self.token = self.__token__()
330
+ return self.add_form(json_form)
331
+ elif request.status_code == requests.codes.created:
332
+ return json.loads(str(request.content.decode('utf-8')))
333
+ else:
334
+ exception = HTTPException(None, request.status_code, request.url, "add_form_json",
335
+ request.content.decode('utf-8'))
336
+ logger.error(exception)
337
+ raise exception
338
+
339
+ elif isinstance(json_form, str):
340
+ with self.session.post(url, headers=headers, data=json_form) as request:
341
+ if request.status_code == requests.codes.unauthorized:
342
+ self.token = self.__token__()
343
+ return self.add_form(json_form)
344
+ elif request.status_code == requests.codes.created:
345
+ return json.loads(str(request.content.decode('utf-8')))
346
+ else:
347
+ exception = HTTPException(None, request.status_code, request.url, "add_form_json",
348
+ request.content.decode('utf-8'))
349
+ logger.error(exception)
350
+ raise exception
351
+ else:
352
+ raise RuntimeError("Argument must be a JSON dictionary or a JSON str")
353
+
354
+
355
+ # def set_default_form(self, form_id: str):
356
+ # """
357
+ # Set the default form
358
+ #
359
+ # """
360
+ #
361
+ # headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json;charset=UTF-8'}
362
+ # url = f'{self.protocol}://{self.server}/api/metadata/forms/{form_id}/default'
363
+ #
364
+ # payload: dict = {"default": True, "useAsDefault": True}
365
+ #
366
+ # with self.session.get(url, headers=headers, json=json.dumps(payload)) as request:
367
+ # if request.status_code == requests.codes.unauthorized:
368
+ # self.token = self.__token__()
369
+ # return self.set_default_form(form_id)
370
+ # elif request.status_code == requests.codes.ok:
371
+ # return json.loads(str(request.content.decode('utf-8')))
372
+ # else:
373
+ # exception = HTTPException(None, request.status_code, request.url, "set_default_form",
374
+ # request.content.decode('utf-8'))
375
+ # logger.error(exception)
376
+ # raise exception
377
+
378
+
379
+
380
+
381
+ def add_group_json(self, json_object: Union[dict, str]) -> dict:
382
+ """
383
+ Create a new Metadata Group using a JSON dictionary object or document
384
+
385
+ :param json_object: JSON dictionary or string
386
+ :type json_object: dict
387
+
388
+ :return: JSON document
389
+ :rtype: dict
390
+
391
+ """
392
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json;charset=UTF-8'}
393
+ url = f'{self.protocol}://{self.server}/api/metadata/groups/'
394
+
395
+ if isinstance(json_object, dict):
396
+ with self.session.post(url, headers=headers, json=json_object) as request:
397
+ if request.status_code == requests.codes.unauthorized:
398
+ self.token = self.__token__()
399
+ return self.add_group_json(json_object)
400
+ elif request.status_code == requests.codes.created:
401
+ return json.loads(str(request.content.decode('utf-8')))
402
+ else:
403
+ exception = HTTPException(None, request.status_code, request.url, "add_group_json",
404
+ request.content.decode('utf-8'))
405
+ logger.error(exception)
406
+ raise exception
407
+
408
+ elif isinstance(json_object, str):
409
+ with self.session.post(url, headers=headers, data=json_object) as request:
410
+ if request.status_code == requests.codes.unauthorized:
411
+ self.token = self.__token__()
412
+ return self.add_group_json(json_object)
413
+ elif request.status_code == requests.codes.created:
414
+ return json.loads(str(request.content.decode('utf-8')))
415
+ else:
416
+ exception = HTTPException(None, request.status_code, request.url, "add_group_json",
417
+ request.content.decode('utf-8'))
418
+ logger.error(exception)
419
+ raise exception
420
+ else:
421
+ raise RuntimeError("Argument must be a JSON dictionary or a JSON str")
422
+
423
+ def group_json(self, group_id: str) -> dict:
424
+ """
425
+ Return a Group as a JSON object
426
+
427
+ :param group_id: The Group id
428
+ :type group_id: str
429
+
430
+ :return: JSON document
431
+ :rtype: dict
432
+
433
+ """
434
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json;charset=UTF-8'}
435
+ url = f'{self.protocol}://{self.server}/api/metadata/groups/{group_id}'
436
+ with self.session.get(url, headers=headers) as request:
437
+ if request.status_code == requests.codes.unauthorized:
438
+ self.token = self.__token__()
439
+ return self.group_json(group_id)
440
+ elif request.status_code == requests.codes.ok:
441
+ return json.loads(str(request.content.decode('utf-8')))
442
+ else:
443
+ exception = HTTPException(None, request.status_code, request.url, "group_json",
444
+ request.content.decode('utf-8'))
445
+ logger.error(exception)
446
+ raise exception
447
+
448
+ def group(self, group_id: str) -> Group:
449
+ """
450
+ Return a Group object by its ID
451
+
452
+ :param group_id: The Group id
453
+ :type group_id: str
454
+
455
+ :return: The metadata Group Object
456
+ :rtype: Group
457
+
458
+ """
459
+
460
+ return _object_from_json_(self.group_json(group_id))
461
+
462
+ def groups_json(self) -> List[dict]:
463
+ """
464
+ Return all the metadata Groups in the tenancy as a list of JSON dict objects
465
+
466
+ :return: List of JSON dictionary object
467
+ :rtype: List[dict]
468
+
469
+ """
470
+
471
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json;charset=UTF-8'}
472
+ url = f'{self.protocol}://{self.server}/api/metadata/groups'
473
+ with self.session.get(url, headers=headers) as request:
474
+ if request.status_code == requests.codes.unauthorized:
475
+ self.token = self.__token__()
476
+ return self.groups_json()
477
+ elif request.status_code == requests.codes.ok:
478
+ return json.loads(str(request.content.decode('utf-8')))['groups']
479
+ else:
480
+ exception = HTTPException(None, request.status_code, request.url, "groups_json",
481
+ request.content.decode('utf-8'))
482
+ logger.error(exception)
483
+ raise exception
484
+
485
+ def forms(self, schema_uri: Union[str, None] = None) -> dict:
486
+ """
487
+ Return all the metadata Forms in the tenancy as a list of JSON dict objects
488
+
489
+ :param schema_uri: The Form schema Uri
490
+ :type schema_uri: str
491
+
492
+ :return: List of JSON dictionary object
493
+ :rtype: dict
494
+
495
+ """
496
+
497
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json;charset=UTF-8'}
498
+ url = f'{self.protocol}://{self.server}/api/metadata/forms'
499
+ params = {}
500
+ if schema_uri is not None:
501
+ params = {'schemaUri': schema_uri}
502
+ with self.session.get(url, headers=headers, params=params) as request:
503
+ if request.status_code == requests.codes.unauthorized:
504
+ self.token = self.__token__()
505
+ return self.forms()
506
+ elif request.status_code == requests.codes.ok:
507
+ return json.loads(str(request.content.decode('utf-8')))['metadataForms']
508
+ else:
509
+ exception = HTTPException(None, request.status_code, request.url, "forms_json",
510
+ request.content.decode('utf-8'))
511
+ logger.error(exception)
512
+ raise exception
513
+
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
+
535
+ def form(self, form_id: str) -> dict:
536
+ """
537
+ Return a Form as a JSON dict object
538
+
539
+ :param form_id: The Form id
540
+ :type form_id: str
541
+
542
+ :return: JSON document
543
+ :rtype: dict
544
+
545
+ """
546
+ headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/json;charset=UTF-8'}
547
+ url = f'{self.protocol}://{self.server}/api/metadata/forms/{form_id}'
548
+ with self.session.get(url, headers=headers) as request:
549
+ if request.status_code == requests.codes.unauthorized:
550
+ self.token = self.__token__()
551
+ return self.form(form_id)
552
+ elif request.status_code == requests.codes.ok:
553
+ return json.loads(str(request.content.decode('utf-8')))
554
+ else:
555
+ exception = HTTPException(None, request.status_code, request.url, "form_json",
556
+ request.content.decode('utf-8'))
557
+ logger.error(exception)
558
+ raise exception
559
+
560
+
561
+
562
+ def groups(self) -> Generator[Group, None, None]:
563
+ """
564
+ Return all the metadata Groups in the tenancy as Group Objects
565
+
566
+ :return: Generator of Group Objects
567
+ :rtype: Group
568
+
569
+ """
570
+
571
+ for group in self.groups_json():
572
+ yield _object_from_json_(group)