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.
- pyPreservica/__init__.py +26 -8
- pyPreservica/adminAPI.py +877 -0
- pyPreservica/authorityAPI.py +229 -0
- pyPreservica/common.py +553 -94
- pyPreservica/contentAPI.py +331 -65
- pyPreservica/entityAPI.py +1805 -446
- pyPreservica/mdformsAPI.py +572 -0
- pyPreservica/monitorAPI.py +153 -0
- pyPreservica/opex.py +98 -0
- pyPreservica/parAPI.py +226 -0
- pyPreservica/retentionAPI.py +155 -44
- pyPreservica/settingsAPI.py +295 -0
- pyPreservica/uploadAPI.py +1120 -321
- pyPreservica/webHooksAPI.py +211 -0
- pyPreservica/workflowAPI.py +99 -47
- {pyPreservica-0.9.9.dist-info → pypreservica-3.3.4.dist-info}/METADATA +93 -66
- pypreservica-3.3.4.dist-info/RECORD +20 -0
- {pyPreservica-0.9.9.dist-info → pypreservica-3.3.4.dist-info}/WHEEL +5 -5
- pyPreservica-0.9.9.dist-info/RECORD +0 -12
- {pyPreservica-0.9.9.dist-info → pypreservica-3.3.4.dist-info/licenses}/LICENSE.txt +0 -0
- {pyPreservica-0.9.9.dist-info → pypreservica-3.3.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pyPreservica WebHooksAPI module definition
|
|
3
|
+
|
|
4
|
+
A client library for the Preservica Repository web services Webhook API
|
|
5
|
+
https://us.preservica.com/api/webhook/documentation.html
|
|
6
|
+
|
|
7
|
+
author: James Carr
|
|
8
|
+
licence: Apache License 2.0
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
from http.server import BaseHTTPRequestHandler
|
|
12
|
+
from typing import Generator
|
|
13
|
+
from urllib.parse import urlparse, parse_qs
|
|
14
|
+
import hmac
|
|
15
|
+
from pyPreservica.common import *
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
BASE_ENDPOINT = '/api/webhook'
|
|
20
|
+
|
|
21
|
+
class FlaskWebhookHandler:
|
|
22
|
+
|
|
23
|
+
def __init__(self, request, secret_key: str):
|
|
24
|
+
from flask import request
|
|
25
|
+
self.request = request
|
|
26
|
+
self.secret_key = secret_key
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def response_ok(self):
|
|
30
|
+
return json.dumps({'success':True}), 200, {'ContentType':'application/json'}
|
|
31
|
+
|
|
32
|
+
def is_challenge(self) -> bool:
|
|
33
|
+
challenge_code = self.request.args.get('challengeCode')
|
|
34
|
+
return challenge_code is not None
|
|
35
|
+
|
|
36
|
+
def verify_challenge(self):
|
|
37
|
+
challenge_code = self.request.args.get('challengeCode')
|
|
38
|
+
if challenge_code is not None:
|
|
39
|
+
challenge_response: str = hmac.new(key=bytes(self.secret_key, 'latin-1'), msg=bytes(challenge_code, 'latin-1'),
|
|
40
|
+
digestmod=hashlib.sha256).hexdigest()
|
|
41
|
+
body = json.dumps({"challengeCode": f"{challenge_code}", "challengeResponse": f"{challenge_response}"})
|
|
42
|
+
return body, 200, {"application/json": 'text/plain; charset=utf-8'}
|
|
43
|
+
|
|
44
|
+
return json.dumps({'success': True}), 200, {'ContentType': 'application/json'}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def process_request(self) -> Generator:
|
|
49
|
+
preservica_signature = self.request.headers.get('Preservica-Signature')
|
|
50
|
+
if preservica_signature is not None:
|
|
51
|
+
message_body = data = self.request.data
|
|
52
|
+
verify_body = f"preservica-webhook-auth{message_body.decode('utf-8')}"
|
|
53
|
+
digest = hmac.new(key=bytes(self.secret_key, 'latin-1'), msg=bytes(verify_body, 'latin-1'),
|
|
54
|
+
digestmod=hashlib.sha256).hexdigest()
|
|
55
|
+
if preservica_signature == digest:
|
|
56
|
+
json_body = json.loads(message_body.decode('utf-8'))
|
|
57
|
+
for event in json_body['events']:
|
|
58
|
+
yield event
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class WebHookHandler(BaseHTTPRequestHandler):
|
|
62
|
+
"""
|
|
63
|
+
A sample web hook web server which provides handshake verification
|
|
64
|
+
The shared secret key is passed in via the HTTPServer
|
|
65
|
+
|
|
66
|
+
Extend the class and implement do_WORK() method
|
|
67
|
+
The JSON document is passed into do_WORK()
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def hmac(self, key, message):
|
|
72
|
+
return hmac.new(key=bytes(key, 'latin-1'), msg=bytes(message, 'latin-1'), digestmod=hashlib.sha256).hexdigest()
|
|
73
|
+
|
|
74
|
+
def do_POST(self):
|
|
75
|
+
result = urlparse(self.path)
|
|
76
|
+
q = parse_qs(result.query)
|
|
77
|
+
if 'challengeCode' in q:
|
|
78
|
+
code = q['challengeCode'][0]
|
|
79
|
+
signature = self.hmac(self.server.secret_key, code)
|
|
80
|
+
response = f'{{ "challengeCode": "{code}", "challengeResponse": "{signature}" }}'
|
|
81
|
+
self.send_response(200)
|
|
82
|
+
self.send_header("Content-type", "application/json")
|
|
83
|
+
self.end_headers()
|
|
84
|
+
self.wfile.write(bytes(response.encode('utf-8')))
|
|
85
|
+
self.log_message(f"Handshake Completed. {response.encode('utf-8')}")
|
|
86
|
+
else:
|
|
87
|
+
verif_sig = self.headers.get("Preservica-Signature", None)
|
|
88
|
+
if "chunked" in self.headers.get("Transfer-Encoding", "") and (verif_sig is not None):
|
|
89
|
+
payload = ""
|
|
90
|
+
while True:
|
|
91
|
+
line = self.rfile.readline().strip()
|
|
92
|
+
chunk_length = int(line, 16)
|
|
93
|
+
if chunk_length != 0:
|
|
94
|
+
chunk = self.rfile.read(chunk_length)
|
|
95
|
+
payload = payload + chunk.decode("utf-8")
|
|
96
|
+
self.rfile.readline()
|
|
97
|
+
if chunk_length == 0:
|
|
98
|
+
verify_body = f"preservica-webhook-auth{payload}"
|
|
99
|
+
signature = self.hmac(self.server.secret_key, verify_body)
|
|
100
|
+
if signature == verif_sig:
|
|
101
|
+
self.log_message("Signature Verified. Doing Work...")
|
|
102
|
+
self.log_message(payload)
|
|
103
|
+
self.send_response(200)
|
|
104
|
+
self.end_headers()
|
|
105
|
+
self.do_WORK(json.loads(payload))
|
|
106
|
+
break
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class TriggerType(Enum):
|
|
110
|
+
"""
|
|
111
|
+
Enumeration of the Web hooks Trigger Types
|
|
112
|
+
"""
|
|
113
|
+
MOVED = "MOVED"
|
|
114
|
+
INDEXED = "FULL_TEXT_INDEXED"
|
|
115
|
+
SECURITY_CHANGED = "CHANGED_SECURITY_DESCRIPTOR"
|
|
116
|
+
INGEST_FAILED = "INGEST_FAILED"
|
|
117
|
+
CHANGE_ASSET_VISIBILITY = "CHANGE_ASSET_VISIBILITY"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class WebHooksAPI(AuthenticatedAPI):
|
|
121
|
+
"""
|
|
122
|
+
Class to register new webhook endpoints
|
|
123
|
+
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def subscriptions(self):
|
|
127
|
+
"""
|
|
128
|
+
Return all the current active web hook subscriptions as a json document
|
|
129
|
+
|
|
130
|
+
:return: list of web hooks
|
|
131
|
+
"""
|
|
132
|
+
self._check_if_user_has_manager_role()
|
|
133
|
+
headers = {HEADER_TOKEN: self.token}
|
|
134
|
+
response = self.session.get(f'{self.protocol}://{self.server}{BASE_ENDPOINT}/subscriptions', headers=headers)
|
|
135
|
+
if response.status_code == requests.codes.unauthorized:
|
|
136
|
+
self.token = self.__token__()
|
|
137
|
+
return self.subscriptions()
|
|
138
|
+
if response.status_code == requests.codes.ok:
|
|
139
|
+
json_response = str(response.content.decode('utf-8'))
|
|
140
|
+
doc = json.loads(json_response)
|
|
141
|
+
return doc
|
|
142
|
+
else:
|
|
143
|
+
exception = HTTPException("", response.status_code, response.url, "subscriptions",
|
|
144
|
+
response.content.decode('utf-8'))
|
|
145
|
+
logger.error(exception)
|
|
146
|
+
raise exception
|
|
147
|
+
|
|
148
|
+
def unsubscribe_all(self):
|
|
149
|
+
"""
|
|
150
|
+
Unsubscribe from all webhooks.
|
|
151
|
+
:return:
|
|
152
|
+
"""
|
|
153
|
+
self._check_if_user_has_manager_role()
|
|
154
|
+
subscriptions = self.subscriptions()
|
|
155
|
+
for sub in subscriptions:
|
|
156
|
+
self.unsubscribe(sub['id'])
|
|
157
|
+
|
|
158
|
+
def unsubscribe(self, subscription_id: str):
|
|
159
|
+
"""
|
|
160
|
+
Unsubscribe from the provided webhook.
|
|
161
|
+
|
|
162
|
+
:param subscription_id:
|
|
163
|
+
:return:
|
|
164
|
+
"""
|
|
165
|
+
self._check_if_user_has_manager_role()
|
|
166
|
+
headers = {HEADER_TOKEN: self.token}
|
|
167
|
+
response = self.session.delete(
|
|
168
|
+
f'{self.protocol}://{self.server}{BASE_ENDPOINT}/subscriptions/{subscription_id}',
|
|
169
|
+
headers=headers)
|
|
170
|
+
if response.status_code == requests.codes.unauthorized:
|
|
171
|
+
self.token = self.__token__()
|
|
172
|
+
return self.unsubscribe(subscription_id)
|
|
173
|
+
if response.status_code == requests.codes.no_content:
|
|
174
|
+
json_response = str(response.content.decode('utf-8'))
|
|
175
|
+
logger.debug(json_response)
|
|
176
|
+
return json_response
|
|
177
|
+
else:
|
|
178
|
+
exception = HTTPException(str(subscription_id), response.status_code, response.url, "unsubscribe",
|
|
179
|
+
response.content.decode('utf-8'))
|
|
180
|
+
logger.error(exception)
|
|
181
|
+
raise exception
|
|
182
|
+
|
|
183
|
+
def subscribe(self, url: str, triggerType: TriggerType, secret: str):
|
|
184
|
+
"""
|
|
185
|
+
Subscribe to a new web hook
|
|
186
|
+
|
|
187
|
+
:param url:
|
|
188
|
+
:param triggerType:
|
|
189
|
+
:param secret:
|
|
190
|
+
:return: json_response
|
|
191
|
+
"""
|
|
192
|
+
self._check_if_user_has_manager_role()
|
|
193
|
+
headers = {HEADER_TOKEN: self.token, 'Accept': 'application/json', 'Content-Type': 'application/json'}
|
|
194
|
+
|
|
195
|
+
json_payload = f'{{"url": "{url}", "triggerType": "{triggerType.value}", "secret": "{secret}", ' \
|
|
196
|
+
f'"includeIdentifiers": "true"}}'
|
|
197
|
+
|
|
198
|
+
response = self.session.post(f'{self.protocol}://{self.server}{BASE_ENDPOINT}/subscriptions', headers=headers,
|
|
199
|
+
data=json.dumps(json.loads(json_payload)))
|
|
200
|
+
if response.status_code == requests.codes.unauthorized:
|
|
201
|
+
self.token = self.__token__()
|
|
202
|
+
return self.subscribe(url, triggerType, secret)
|
|
203
|
+
if response.status_code == requests.codes.ok:
|
|
204
|
+
json_response = str(response.content.decode('utf-8'))
|
|
205
|
+
logger.debug(json_response)
|
|
206
|
+
return json_response
|
|
207
|
+
else:
|
|
208
|
+
exception = HTTPException(str(url), response.status_code, response.url, "subscribe",
|
|
209
|
+
response.content.decode('utf-8'))
|
|
210
|
+
logger.error(response.content.decode('utf-8'))
|
|
211
|
+
raise exception
|
pyPreservica/workflowAPI.py
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pyPreservica WorkflowAPI module definition
|
|
3
|
+
|
|
4
|
+
A client library for the Preservica Repository web services Workflow API
|
|
5
|
+
https://us.preservica.com/sdb/rest/workflow/documentation.html
|
|
6
|
+
|
|
7
|
+
author: James Carr
|
|
8
|
+
licence: Apache License 2.0
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
1
12
|
import uuid
|
|
2
13
|
import datetime
|
|
3
|
-
from
|
|
14
|
+
from typing import Callable
|
|
4
15
|
from xml.etree import ElementTree
|
|
5
16
|
|
|
6
17
|
from pyPreservica.common import *
|
|
@@ -8,15 +19,12 @@ from pyPreservica.common import *
|
|
|
8
19
|
logger = logging.getLogger(__name__)
|
|
9
20
|
|
|
10
21
|
|
|
11
|
-
|
|
12
|
-
"""
|
|
22
|
+
class WorkflowInstance:
|
|
23
|
+
"""
|
|
24
|
+
Defines a workflow Instance.
|
|
25
|
+
The workflow Instance is a context which has been executed
|
|
13
26
|
"""
|
|
14
|
-
rough_string = ElementTree.tostring(elem, 'utf-8')
|
|
15
|
-
re_parsed = minidom.parseString(rough_string)
|
|
16
|
-
return re_parsed.toprettyxml(indent=" ")
|
|
17
|
-
|
|
18
27
|
|
|
19
|
-
class WorkflowInstance:
|
|
20
28
|
def __init__(self, instance_id: int):
|
|
21
29
|
self.instance_id = instance_id
|
|
22
30
|
self.started = None
|
|
@@ -28,6 +36,7 @@ class WorkflowInstance:
|
|
|
28
36
|
self.workflow_context_id = None
|
|
29
37
|
self.workflow_context_name = None
|
|
30
38
|
self.workflow_definition_id = None
|
|
39
|
+
self.xml_response = None
|
|
31
40
|
|
|
32
41
|
def __str__(self):
|
|
33
42
|
return f"Workflow Instance ID: {self.instance_id}"
|
|
@@ -37,13 +46,20 @@ class WorkflowInstance:
|
|
|
37
46
|
|
|
38
47
|
|
|
39
48
|
class WorkflowContext:
|
|
40
|
-
|
|
49
|
+
"""
|
|
50
|
+
Defines a workflow context.
|
|
51
|
+
The workflow context is the pre-defined workflow which is ready to run
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, workflow_id, workflow_name: str):
|
|
41
55
|
self.workflow_id = workflow_id
|
|
42
56
|
self.workflow_name = workflow_name
|
|
43
57
|
|
|
44
58
|
def __str__(self):
|
|
45
|
-
return f"
|
|
46
|
-
|
|
59
|
+
return f"""
|
|
60
|
+
Workflow ID: {self.workflow_id}
|
|
61
|
+
Workflow Name: {self.workflow_name}
|
|
62
|
+
"""
|
|
47
63
|
|
|
48
64
|
def __repr__(self):
|
|
49
65
|
return self.__str__()
|
|
@@ -51,8 +67,11 @@ class WorkflowContext:
|
|
|
51
67
|
|
|
52
68
|
class WorkflowAPI(AuthenticatedAPI):
|
|
53
69
|
"""
|
|
54
|
-
|
|
55
|
-
|
|
70
|
+
A class for calling the Preservica Workflow API
|
|
71
|
+
|
|
72
|
+
This API can be used to programmatically manage the Preservica Workflows.
|
|
73
|
+
|
|
74
|
+
https://preview.preservica.com/sdb/rest/workflow/documentation.html
|
|
56
75
|
|
|
57
76
|
"""
|
|
58
77
|
|
|
@@ -60,22 +79,30 @@ class WorkflowAPI(AuthenticatedAPI):
|
|
|
60
79
|
'Failed']
|
|
61
80
|
workflow_types = ['Ingest', 'Access', 'Transformation', 'DataManagement']
|
|
62
81
|
|
|
63
|
-
def __init__(self, username=None, password=None, tenant=None, server=None,
|
|
64
|
-
|
|
82
|
+
def __init__(self, username: str = None, password: str = None, tenant: str = None, server: str = None,
|
|
83
|
+
use_shared_secret: bool = False, two_fa_secret_key: str = None,
|
|
84
|
+
protocol: str = "https", request_hook: Callable = None, credentials_path: str = 'credentials.properties'):
|
|
85
|
+
|
|
86
|
+
super().__init__(username, password, tenant, server, use_shared_secret, two_fa_secret_key,
|
|
87
|
+
protocol, request_hook, credentials_path)
|
|
65
88
|
self.base_url = "sdb/rest/workflow"
|
|
66
89
|
|
|
67
90
|
def get_workflow_contexts_by_type(self, workflow_type: str):
|
|
68
91
|
"""
|
|
69
92
|
Return a list of Workflow Contexts which have the same Workflow type
|
|
70
93
|
|
|
71
|
-
:param workflow_type: The Workflow type
|
|
72
|
-
|
|
94
|
+
:param workflow_type: The Workflow type Ingest, Access, Transformation or DataManagement
|
|
95
|
+
:type workflow_type: str
|
|
96
|
+
|
|
97
|
+
:return: List of Workflow Contexts
|
|
98
|
+
:rtype: list
|
|
73
99
|
|
|
74
100
|
"""
|
|
101
|
+
|
|
75
102
|
headers = {HEADER_TOKEN: self.token}
|
|
76
103
|
params = {"type": workflow_type}
|
|
77
|
-
workflow_contexts =
|
|
78
|
-
request =
|
|
104
|
+
workflow_contexts = []
|
|
105
|
+
request = self.session.get(f'{self.protocol}://{self.server}/{self.base_url}/contexts', headers=headers, params=params)
|
|
79
106
|
if request.status_code == requests.codes.ok:
|
|
80
107
|
xml_response = str(request.content.decode('utf-8'))
|
|
81
108
|
entity_response = xml.etree.ElementTree.fromstring(xml_response)
|
|
@@ -95,15 +122,20 @@ class WorkflowAPI(AuthenticatedAPI):
|
|
|
95
122
|
|
|
96
123
|
def get_workflow_contexts(self, definition: str):
|
|
97
124
|
"""
|
|
98
|
-
Return a list of Workflow Contexts which have the same Workflow Definition
|
|
125
|
+
Return a list of Workflow Contexts which have the same Workflow Definition
|
|
99
126
|
|
|
100
127
|
:param definition: The Workflow Definition ID
|
|
128
|
+
:type definition: str
|
|
129
|
+
|
|
130
|
+
:return: List of Workflow Contexts
|
|
131
|
+
:rtype: list
|
|
101
132
|
|
|
102
133
|
"""
|
|
134
|
+
|
|
103
135
|
headers = {HEADER_TOKEN: self.token}
|
|
104
136
|
params = {"workflowDefinitionId": definition}
|
|
105
|
-
workflow_contexts =
|
|
106
|
-
request =
|
|
137
|
+
workflow_contexts = []
|
|
138
|
+
request = self.session.get(f'{self.protocol}://{self.server}/{self.base_url}/contexts', headers=headers, params=params)
|
|
107
139
|
if request.status_code == requests.codes.ok:
|
|
108
140
|
xml_response = str(request.content.decode('utf-8'))
|
|
109
141
|
entity_response = xml.etree.ElementTree.fromstring(xml_response)
|
|
@@ -121,17 +153,22 @@ class WorkflowAPI(AuthenticatedAPI):
|
|
|
121
153
|
logger.error(request.content)
|
|
122
154
|
raise RuntimeError(request.status_code, "get_workflow_contexts")
|
|
123
155
|
|
|
124
|
-
def start_workflow_instance(self, workflow_context, **kwargs):
|
|
156
|
+
def start_workflow_instance(self, workflow_context: WorkflowContext, **kwargs):
|
|
125
157
|
"""
|
|
126
|
-
|
|
127
158
|
Start a workflow context
|
|
128
159
|
|
|
129
160
|
Returns a Correlation Id which is used to monitor the workflow progress
|
|
130
161
|
|
|
131
162
|
:param workflow_context: The workflow context to start
|
|
132
|
-
:
|
|
163
|
+
:type workflow_context: WorkflowContext
|
|
164
|
+
|
|
165
|
+
:param kwargs: Key/Values to pass to the workflow instance
|
|
166
|
+
|
|
167
|
+
:return: correlation_id
|
|
168
|
+
:rtype: str
|
|
133
169
|
|
|
134
170
|
"""
|
|
171
|
+
|
|
135
172
|
headers = {HEADER_TOKEN: self.token, 'Content-Type': 'application/xml;charset=UTF-8'}
|
|
136
173
|
|
|
137
174
|
correlation_id = str(uuid.uuid4())
|
|
@@ -149,7 +186,8 @@ class WorkflowAPI(AuthenticatedAPI):
|
|
|
149
186
|
xml.etree.ElementTree.SubElement(request_payload, "CorrelationId").text = correlation_id
|
|
150
187
|
|
|
151
188
|
xml_request = xml.etree.ElementTree.tostring(request_payload, encoding='utf-8')
|
|
152
|
-
request =
|
|
189
|
+
request = self.session.post(f'{self.protocol}://{self.server}/{self.base_url}/instances', headers=headers,
|
|
190
|
+
data=xml_request)
|
|
153
191
|
if request.status_code == requests.codes.created:
|
|
154
192
|
return correlation_id
|
|
155
193
|
if request.status_code == requests.codes.unauthorized:
|
|
@@ -163,9 +201,11 @@ class WorkflowAPI(AuthenticatedAPI):
|
|
|
163
201
|
"""
|
|
164
202
|
Terminate a workflow by its instance id
|
|
165
203
|
|
|
166
|
-
:param instance_ids: The Workflow instance
|
|
204
|
+
:param instance_ids: The Workflow instance
|
|
205
|
+
:type instance_ids: int or a list of int
|
|
167
206
|
|
|
168
207
|
"""
|
|
208
|
+
|
|
169
209
|
if isinstance(instance_ids, list):
|
|
170
210
|
converted_list = [str(int(e)) for e in instance_ids]
|
|
171
211
|
param_string = ",".join(converted_list)
|
|
@@ -174,8 +214,8 @@ class WorkflowAPI(AuthenticatedAPI):
|
|
|
174
214
|
|
|
175
215
|
headers = {HEADER_TOKEN: self.token}
|
|
176
216
|
params = {"workflowInstanceIds": param_string}
|
|
177
|
-
request =
|
|
178
|
-
|
|
217
|
+
request = self.session.post(f'{self.protocol}://{self.server}/{self.base_url}/instances/terminate',
|
|
218
|
+
headers=headers, params=params)
|
|
179
219
|
if request.status_code == requests.codes.accepted:
|
|
180
220
|
return
|
|
181
221
|
elif request.status_code == requests.codes.unauthorized:
|
|
@@ -185,15 +225,22 @@ class WorkflowAPI(AuthenticatedAPI):
|
|
|
185
225
|
logger.error(request.content)
|
|
186
226
|
raise RuntimeError(request.status_code, "terminate_workflow_instance")
|
|
187
227
|
|
|
188
|
-
def workflow_instance(self, instance_id: int):
|
|
228
|
+
def workflow_instance(self, instance_id: int) -> WorkflowInstance:
|
|
189
229
|
"""
|
|
190
230
|
Return a workflow instance by its Id
|
|
191
231
|
|
|
192
|
-
:param instance_id: The Workflow instance
|
|
232
|
+
:param instance_id: The Workflow instance
|
|
233
|
+
:type instance_id: int
|
|
234
|
+
|
|
235
|
+
:return: workflow_instance
|
|
236
|
+
:rtype: WorkflowInstance
|
|
193
237
|
|
|
194
238
|
"""
|
|
239
|
+
|
|
195
240
|
headers = {HEADER_TOKEN: self.token}
|
|
196
|
-
|
|
241
|
+
params = {"includeErrors": "true"}
|
|
242
|
+
request = self.session.get(f'{self.protocol}://{self.server}/{self.base_url}/instances/{str(instance_id)}',
|
|
243
|
+
headers=headers, params=params)
|
|
197
244
|
if request.status_code == requests.codes.ok:
|
|
198
245
|
xml_response = str(request.content.decode('utf-8'))
|
|
199
246
|
logger.debug(xml_response)
|
|
@@ -202,13 +249,13 @@ class WorkflowAPI(AuthenticatedAPI):
|
|
|
202
249
|
assert instance_id == w_id
|
|
203
250
|
workflow_instance = WorkflowInstance(int(instance_id))
|
|
204
251
|
started_element = entity_response.find(f".//{{{NS_WORKFLOW}}}Started")
|
|
205
|
-
if started_element:
|
|
252
|
+
if started_element is not None:
|
|
206
253
|
if hasattr(started_element, "text"):
|
|
207
254
|
workflow_instance.started = datetime.datetime.strptime(started_element.text,
|
|
208
255
|
'%Y-%m-%dT%H:%M:%S.%fZ')
|
|
209
256
|
|
|
210
257
|
finished_element = entity_response.find(f".//{{{NS_WORKFLOW}}}Finished")
|
|
211
|
-
if finished_element:
|
|
258
|
+
if finished_element is not None:
|
|
212
259
|
if hasattr(finished_element, "text"):
|
|
213
260
|
workflow_instance.finished = datetime.datetime.strptime(finished_element.text,
|
|
214
261
|
'%Y-%m-%dT%H:%M:%S.%fZ')
|
|
@@ -223,6 +270,8 @@ class WorkflowAPI(AuthenticatedAPI):
|
|
|
223
270
|
workflow_instance.workflow_definition_id = entity_response.find(
|
|
224
271
|
f".//{{{NS_WORKFLOW}}}WorkflowDefinitionTextId").text
|
|
225
272
|
|
|
273
|
+
workflow_instance.xml_response = xml_response
|
|
274
|
+
|
|
226
275
|
return workflow_instance
|
|
227
276
|
elif request.status_code == requests.codes.unauthorized:
|
|
228
277
|
self.token = self.__token__()
|
|
@@ -235,10 +284,11 @@ class WorkflowAPI(AuthenticatedAPI):
|
|
|
235
284
|
"""
|
|
236
285
|
Return a list of Workflow instances
|
|
237
286
|
|
|
238
|
-
:param workflow_state: The Workflow state
|
|
239
|
-
:param workflow_type: The Workflow type
|
|
287
|
+
:param workflow_state: The Workflow state Aborted, Active, Completed, Finished_Mixed_Outcome, Pending, Suspended, Unknown, or Failed
|
|
288
|
+
:param workflow_type: The Workflow type Ingest, Access, Transformation or DataManagement
|
|
240
289
|
|
|
241
290
|
"""
|
|
291
|
+
|
|
242
292
|
start_value = int(0)
|
|
243
293
|
maximum = int(25)
|
|
244
294
|
total_count = maximum
|
|
@@ -256,10 +306,12 @@ class WorkflowAPI(AuthenticatedAPI):
|
|
|
256
306
|
"""
|
|
257
307
|
Return a list of Workflow instances
|
|
258
308
|
|
|
259
|
-
:param workflow_state: The Workflow state: Aborted, Active, Completed, Finished_Mixed_Outcome, Pending,
|
|
309
|
+
:param workflow_state: The Workflow state: Aborted, Active, Completed, Finished_Mixed_Outcome, Pending,
|
|
310
|
+
Suspended, Unknown, or Failed
|
|
260
311
|
:param workflow_type: The Workflow type: Ingest, Access, Transformation or DataManagement
|
|
261
312
|
|
|
262
313
|
"""
|
|
314
|
+
|
|
263
315
|
headers = {HEADER_TOKEN: self.token}
|
|
264
316
|
|
|
265
317
|
if workflow_state not in self.workflow_states:
|
|
@@ -280,18 +332,18 @@ class WorkflowAPI(AuthenticatedAPI):
|
|
|
280
332
|
creator = kwargs.get("creator")
|
|
281
333
|
params["creator"] = creator
|
|
282
334
|
|
|
283
|
-
if "
|
|
284
|
-
from_date = kwargs.get("
|
|
285
|
-
params["from"] = from_date
|
|
335
|
+
if "from_date" in kwargs:
|
|
336
|
+
from_date = kwargs.get("from_date")
|
|
337
|
+
params["from"] = parse_date_to_iso(from_date)
|
|
286
338
|
|
|
287
|
-
if "
|
|
288
|
-
to_date = kwargs.get("
|
|
289
|
-
params["to"] = to_date
|
|
339
|
+
if "to_date" in kwargs:
|
|
340
|
+
to_date = kwargs.get("to_date")
|
|
341
|
+
params["to"] = parse_date_to_iso(to_date)
|
|
290
342
|
|
|
291
343
|
params["start"] = int(start_value)
|
|
292
344
|
params["max"] = int(maximum)
|
|
293
345
|
|
|
294
|
-
request =
|
|
346
|
+
request = self.session.get(f'{self.protocol}://{self.server}/{self.base_url}/instances', headers=headers, params=params)
|
|
295
347
|
if request.status_code == requests.codes.ok:
|
|
296
348
|
xml_response = str(request.content.decode('utf-8'))
|
|
297
349
|
logger.debug(xml_response)
|
|
@@ -299,19 +351,19 @@ class WorkflowAPI(AuthenticatedAPI):
|
|
|
299
351
|
total_count = int(entity_response.find(f".//{{{NS_WORKFLOW}}}TotalCount").text)
|
|
300
352
|
count = int(entity_response.find(f".//{{{NS_WORKFLOW}}}Count").text)
|
|
301
353
|
workflow_instance = entity_response.findall(f".//{{{NS_WORKFLOW}}}WorkflowInstance")
|
|
302
|
-
workflow_instances =
|
|
354
|
+
workflow_instances = []
|
|
303
355
|
for instance in workflow_instance:
|
|
304
356
|
instance_id = instance.find(f".//{{{NS_WORKFLOW}}}Id").text
|
|
305
357
|
workflow_instance = WorkflowInstance(int(instance_id))
|
|
306
358
|
|
|
307
359
|
started_element = instance.find(f".//{{{NS_WORKFLOW}}}Started")
|
|
308
|
-
if started_element:
|
|
360
|
+
if started_element is not None:
|
|
309
361
|
if hasattr(started_element, "text"):
|
|
310
362
|
workflow_instance.started = datetime.datetime.strptime(started_element.text,
|
|
311
363
|
'%Y-%m-%dT%H:%M:%S.%fZ')
|
|
312
364
|
|
|
313
365
|
finished_element = instance.find(f".//{{{NS_WORKFLOW}}}Finished")
|
|
314
|
-
if finished_element:
|
|
366
|
+
if finished_element is not None:
|
|
315
367
|
if hasattr(finished_element, "text"):
|
|
316
368
|
workflow_instance.finished = datetime.datetime.strptime(finished_element.text,
|
|
317
369
|
'%Y-%m-%dT%H:%M:%S.%fZ')
|