sapdswsdlclient 0.1.2__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.
- sapdswsdlclient-0.1.2/LICENSE.md +22 -0
- sapdswsdlclient-0.1.2/PKG-INFO +48 -0
- sapdswsdlclient-0.1.2/README.md +21 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient/__init__.py +7 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient/exceptions/exceptions.py +2 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient/models/batch_job.py +509 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient/models/dataflow.py +97 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient/models/items.py +80 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient/models/job_server.py +92 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient/models/logs.py +147 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient/models/realtime_service.py +127 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient/models/repo.py +278 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient/server/__init__.py +1 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient/server/auth.py +167 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient/server/re_auth.py +17 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient/templates/templates.py +13 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient/utilities/check_for_fault_or_error.py +14 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient/utilities/clean_xml.py +55 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient.egg-info/PKG-INFO +48 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient.egg-info/SOURCES.txt +23 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient.egg-info/dependency_links.txt +1 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient.egg-info/requires.txt +1 -0
- sapdswsdlclient-0.1.2/sapdswsdlclient.egg-info/top_level.txt +1 -0
- sapdswsdlclient-0.1.2/setup.cfg +4 -0
- sapdswsdlclient-0.1.2/setup.py +31 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2025 <a href="https://github.com/sparklingSky">sparklingSky</a>
|
|
2
|
+
|
|
3
|
+
### General Information
|
|
4
|
+
|
|
5
|
+
The library is provided 'as is'. No warranty of any kind is granted, and the author is not liable for any kind of potential damage or other consequences of using the code.
|
|
6
|
+
|
|
7
|
+
### Conditions
|
|
8
|
+
You can freely use, copy, modify and distribute the code of the library 'sapwsdlclient' - totally or partially - with the conditions that:
|
|
9
|
+
* you use the code for non-commercial or internal business purposes;
|
|
10
|
+
* you retain the original copyright notice and this license for your product as for 'sapwsdlclient', and you must retain attribution for all contributors of the modified code, if there are such.
|
|
11
|
+
|
|
12
|
+
For any other use, including a product intended for direct commercial distribution, you must contact the author and obtain a separate commercial license.
|
|
13
|
+
me[__!dot!__]sparklingsky[__!at!__]gmail[__!dot!__]com
|
|
14
|
+
|
|
15
|
+
### Terms Definition
|
|
16
|
+
* The term '**author**' means the author of the original code of the library 'sapwsdlclient'.
|
|
17
|
+
* The term '**non-commercial or internal business purposes**' means any of your non-profit products or activities (including personal usage, education, research, charitable work, or community service), or using the library's code internally within your business without directly distributing it to third parties for financial benefit.
|
|
18
|
+
* The term '**direct commercial distribution**' means including the code of 'sapwsdlclient' - totally or partially - in your product intended for sale or for any other form of gaining the financial benefit from your product.
|
|
19
|
+
|
|
20
|
+
### Additional Information
|
|
21
|
+
If you want to thank the author, you are welcome to donate to the volunteer charitable organization <a href="https://spgr.org.ua/en/donate/">Solidarna Sprava Hromad</a> (supporting humanitarian and defense efforts in Ukraine).
|
|
22
|
+
The author is not affiliated with this organization but fully endorses its projects and activities.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sapdswsdlclient
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: A Python library for SAP Data Services
|
|
5
|
+
Home-page: https://github.com/sparklingSky/sap-ds-web-service-client
|
|
6
|
+
Author: sparklingSky
|
|
7
|
+
License: Custom Dual License
|
|
8
|
+
Keywords: SAP,Data Services,SOAP,WSDL
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: Other/Proprietary License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Requires-Python: >=3.12
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE.md
|
|
15
|
+
Requires-Dist: requests~=2.32.3
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: keywords
|
|
22
|
+
Dynamic: license
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
## SAP Data Services Web Service Client
|
|
29
|
+
|
|
30
|
+
This library can simplify your operations on SAP DS server with utilizing its web service via SOAP requests:
|
|
31
|
+
* retrieve a list of available repositories;
|
|
32
|
+
* validate repository objects;
|
|
33
|
+
* run or stop batch jobs;
|
|
34
|
+
* retrieve a list of batch jobs in the repository;
|
|
35
|
+
* retrieve a list of jobs or job executions for a selected repository and job - all or within the specified period of time ;
|
|
36
|
+
* retrieve error, monitor or trace logs for a batch job;
|
|
37
|
+
* retrieve a list of scheduled tasks for the selected repository;
|
|
38
|
+
* retrieve a list or status of job servers for a repository;
|
|
39
|
+
* retrieve a list of system configurations, substitution parameters, and batch job global variables;
|
|
40
|
+
* etc.
|
|
41
|
+
|
|
42
|
+
For use cases, explore [test_client](https://github.com/sparklingSky/sap-ds-web-service-client/blob/main/test_client.py)
|
|
43
|
+
|
|
44
|
+
## ⚖️ License
|
|
45
|
+
This library is distributed under a **Custom Dual License**.
|
|
46
|
+
It permits **free use for non-commercial or internal business purposes only**.
|
|
47
|
+
|
|
48
|
+
**Commercial distribution** requires a separate commercial license. Please see the full terms in the [LICENSE.md](https://github.com/sparklingSky/sap-ds-web-service-client/blob/main/LICENSE.md) file.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## SAP Data Services Web Service Client
|
|
2
|
+
|
|
3
|
+
This library can simplify your operations on SAP DS server with utilizing its web service via SOAP requests:
|
|
4
|
+
* retrieve a list of available repositories;
|
|
5
|
+
* validate repository objects;
|
|
6
|
+
* run or stop batch jobs;
|
|
7
|
+
* retrieve a list of batch jobs in the repository;
|
|
8
|
+
* retrieve a list of jobs or job executions for a selected repository and job - all or within the specified period of time ;
|
|
9
|
+
* retrieve error, monitor or trace logs for a batch job;
|
|
10
|
+
* retrieve a list of scheduled tasks for the selected repository;
|
|
11
|
+
* retrieve a list or status of job servers for a repository;
|
|
12
|
+
* retrieve a list of system configurations, substitution parameters, and batch job global variables;
|
|
13
|
+
* etc.
|
|
14
|
+
|
|
15
|
+
For use cases, explore [test_client](https://github.com/sparklingSky/sap-ds-web-service-client/blob/main/test_client.py)
|
|
16
|
+
|
|
17
|
+
## ⚖️ License
|
|
18
|
+
This library is distributed under a **Custom Dual License**.
|
|
19
|
+
It permits **free use for non-commercial or internal business purposes only**.
|
|
20
|
+
|
|
21
|
+
**Commercial distribution** requires a separate commercial license. Please see the full terms in the [LICENSE.md](https://github.com/sparklingSky/sap-ds-web-service-client/blob/main/LICENSE.md) file.
|
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
from typing import Optional, Literal
|
|
2
|
+
import requests
|
|
3
|
+
import html
|
|
4
|
+
import re
|
|
5
|
+
from sapdswsdlclient.templates.templates import request_template, headers
|
|
6
|
+
from sapdswsdlclient.models.items import SystemConfigurations, SubstitutionParameters
|
|
7
|
+
from sapdswsdlclient.utilities.clean_xml import clean_xml_response
|
|
8
|
+
from sapdswsdlclient.utilities.check_for_fault_or_error import check_for_fault_or_error
|
|
9
|
+
from sapdswsdlclient.server.re_auth import re_logon
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BatchJob:
|
|
13
|
+
def __init__(self, server_instance):
|
|
14
|
+
"""
|
|
15
|
+
:param server_instance: server instance object
|
|
16
|
+
"""
|
|
17
|
+
self._server = server_instance
|
|
18
|
+
self.request_template = request_template
|
|
19
|
+
self.headers = headers
|
|
20
|
+
|
|
21
|
+
@re_logon
|
|
22
|
+
|
|
23
|
+
def get_exe_detail(self, repo_name, job_name, start_time: Optional[str] = '', end_time: Optional[str] = ''):
|
|
24
|
+
"""
|
|
25
|
+
:param repo_name: name of the repository
|
|
26
|
+
:param job_name: name of the batch job
|
|
27
|
+
:param start_time: [Optional] date and time that a job has started running as YYYY-MM-DD HH:mm:ss
|
|
28
|
+
:param end_time: [Optional] ending date and time of the start time range as YYYY-MM-DD HH:mm:ss
|
|
29
|
+
:return: a list of all job executions for a selected repository and job
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
request_body = f'''<ser:GetBatchJobExeDetailRequest>
|
|
33
|
+
<repoName>{repo_name}</repoName>
|
|
34
|
+
<jobName>{job_name}</jobName>
|
|
35
|
+
<startTime>{start_time}</startTime>
|
|
36
|
+
<endTime>{end_time}</endTime>
|
|
37
|
+
</ser:GetBatchJobExeDetailRequest>'''
|
|
38
|
+
request = self.request_template.format(session_id=self._server.session_id, request_body=request_body)
|
|
39
|
+
self.headers['SOAPAction'] = 'jobAdmin=Get_BatchJob_ExeDetail'
|
|
40
|
+
response = requests.get(self._server.wsdl_url, data=request, headers=self.headers)
|
|
41
|
+
|
|
42
|
+
response = clean_xml_response(response.text)
|
|
43
|
+
job_exe_details = list()
|
|
44
|
+
|
|
45
|
+
root = response.findall('.//jobDetail')
|
|
46
|
+
if root:
|
|
47
|
+
for job in root:
|
|
48
|
+
job_item = dict()
|
|
49
|
+
job_item['JobName'] = job.find('JobName').text
|
|
50
|
+
job_item['ObjID'] = job.find('ObjID').text
|
|
51
|
+
job_item['RunID'] = job.find('runID').text
|
|
52
|
+
job_item['StartTime'] = job.find('StartTime').text
|
|
53
|
+
job_item['EndTime'] = job.find('EndTime').text
|
|
54
|
+
job_item['ExecutionTime'] = job.find('ExecutionTime').text
|
|
55
|
+
job_item['Status'] = job.find('Status').text
|
|
56
|
+
job_item['JobServerUsed'] = job.find('JobServerUsed').text
|
|
57
|
+
job_exe_details.append(job_item)
|
|
58
|
+
return job_exe_details
|
|
59
|
+
else:
|
|
60
|
+
return {'returnCode': '0', 'returnMessage': 'No job execution details found'}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_detail(self, repo_name, job_name):
|
|
64
|
+
"""
|
|
65
|
+
:param repo_name: name of the repository
|
|
66
|
+
:param job_name: name of the batch job
|
|
67
|
+
:return: a list of a job's global variables, all the available system configurations
|
|
68
|
+
and substitution parameters
|
|
69
|
+
"""
|
|
70
|
+
request_body = f'''<ser:Get_BatchJob_DetailRequest>
|
|
71
|
+
<jobName>{job_name}</jobName>
|
|
72
|
+
<repoName>{repo_name}</repoName>
|
|
73
|
+
</ser:Get_BatchJob_DetailRequest>'''
|
|
74
|
+
request = self.request_template.format(session_id=self._server.session_id, request_body=request_body)
|
|
75
|
+
self.headers['SOAPAction'] = 'jobAdmin=Get_BatchJob_Details'
|
|
76
|
+
response = requests.get(self._server.wsdl_url, data=request, headers=self.headers)
|
|
77
|
+
|
|
78
|
+
response = clean_xml_response(response.text)
|
|
79
|
+
|
|
80
|
+
check_for_fault_or_error(response, ['ErrorMessage', 'faultstring'])
|
|
81
|
+
|
|
82
|
+
job_details = list()
|
|
83
|
+
|
|
84
|
+
global_variables = response.find('.//globalVariables')
|
|
85
|
+
system_configurations = response.find('.//systemConfigurations')
|
|
86
|
+
substitution_parameters = response.find('.//substitutionParameters')
|
|
87
|
+
|
|
88
|
+
if global_variables is not None:
|
|
89
|
+
var_list = list()
|
|
90
|
+
for var in global_variables:
|
|
91
|
+
var_dict = dict()
|
|
92
|
+
if var is not None:
|
|
93
|
+
var_dict['name'] = var.get('name')
|
|
94
|
+
var_dict['type'] = var.get('type')
|
|
95
|
+
var_dict['default_value'] = var.text
|
|
96
|
+
var_list.append(var_dict)
|
|
97
|
+
job_details.append({'globalVariables': var_list})
|
|
98
|
+
|
|
99
|
+
if system_configurations is not None:
|
|
100
|
+
config_list = list()
|
|
101
|
+
for conf in system_configurations:
|
|
102
|
+
if conf is not None:
|
|
103
|
+
config_list.append(conf.get('name'))
|
|
104
|
+
system_configurations_instance = SystemConfigurations(config_list)
|
|
105
|
+
job_details.append(system_configurations_instance)
|
|
106
|
+
|
|
107
|
+
if substitution_parameters is not None:
|
|
108
|
+
param_list = list()
|
|
109
|
+
for param in substitution_parameters:
|
|
110
|
+
if param is not None:
|
|
111
|
+
param_list.append({param.get('name'): param.text})
|
|
112
|
+
substitution_parameters_instance = SubstitutionParameters(param_list)
|
|
113
|
+
job_details.append(substitution_parameters_instance)
|
|
114
|
+
|
|
115
|
+
return job_details
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_by_time_range(self, repo_name, range_start_time, range_end_time, job_name: Optional[str] = ''):
|
|
119
|
+
"""
|
|
120
|
+
:param repo_name: name of the repository
|
|
121
|
+
:param range_start_time: earliest datetime of the range as YYYY-MM-DD HH:mm:ss
|
|
122
|
+
:param range_end_time: latest datetime of the range as YYYY-MM-DD HH:mm:ss
|
|
123
|
+
:param job_name: [Optional] the name of the batch job
|
|
124
|
+
:return: a list of all jobs or all instances of the specified job running during the specified time range
|
|
125
|
+
"""
|
|
126
|
+
request_body = f'''<ser:Get_BatchJob_By_TimeRangeRequest>
|
|
127
|
+
<repoName>{repo_name}</repoName>
|
|
128
|
+
<jobName>{job_name}</jobName>
|
|
129
|
+
<rangeStartTime>{range_start_time}</rangeStartTime>
|
|
130
|
+
<rangeEndTime>{range_end_time}</rangeEndTime>
|
|
131
|
+
</ser:Get_BatchJob_By_TimeRangeRequest>'''
|
|
132
|
+
self.headers['SOAPAction'] = 'jobAdmin=Get_BatchJob_By_TimeRange'
|
|
133
|
+
request = self.request_template.format(session_id=self._server.session_id, request_body=request_body)
|
|
134
|
+
response = requests.get(self._server.wsdl_url, data=request, headers=self.headers)
|
|
135
|
+
|
|
136
|
+
response = clean_xml_response(response.text)
|
|
137
|
+
jobs_by_time_range = list()
|
|
138
|
+
|
|
139
|
+
root = response.findall('.//jobDetail')
|
|
140
|
+
if root:
|
|
141
|
+
for job in root:
|
|
142
|
+
job_item = dict()
|
|
143
|
+
job_item['JobName'] = job.find('JobName').text
|
|
144
|
+
job_item['ObjID'] = job.find('ObjID').text
|
|
145
|
+
job_item['RunID'] = job.find('runID').text
|
|
146
|
+
job_item['StartTime'] = job.find('StartTime').text
|
|
147
|
+
job_item['EndTime'] = job.find('EndTime').text
|
|
148
|
+
job_item['ExecutionTime'] = job.find('ExecutionTime').text
|
|
149
|
+
job_item['Status'] = job.find('Status').text
|
|
150
|
+
job_item['JobServerUsed'] = job.find('JobServerUsed').text
|
|
151
|
+
jobs_by_time_range.append(job_item)
|
|
152
|
+
return jobs_by_time_range
|
|
153
|
+
else:
|
|
154
|
+
return f'No jobs found in the time range {range_start_time} - {range_end_time}.'
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def get_list(self, repo_name, is_all_batch_jobs: Literal[0, 1] = 1):
|
|
158
|
+
"""
|
|
159
|
+
:param repo_name: name of the repository
|
|
160
|
+
:param is_all_batch_jobs: [Optional] 0 if the jobs published as Web services; 1 if all the jobs
|
|
161
|
+
:return: the list of batch jobs
|
|
162
|
+
"""
|
|
163
|
+
request_body = f'''<ser:Get_BatchJob_ListRequest>
|
|
164
|
+
<repoName>{repo_name}</repoName>
|
|
165
|
+
<allBatchJobs>{is_all_batch_jobs}</allBatchJobs>
|
|
166
|
+
</ser:Get_BatchJob_ListRequest>'''
|
|
167
|
+
self.headers['SOAPAction'] = 'jobAdmin=Get_BatchJob_List'
|
|
168
|
+
request = self.request_template.format(session_id=self._server.session_id, request_body=request_body)
|
|
169
|
+
response = requests.get(self._server.wsdl_url, data=request, headers=self.headers)
|
|
170
|
+
|
|
171
|
+
response = clean_xml_response(response.text)
|
|
172
|
+
|
|
173
|
+
check_for_fault_or_error(response, ['errorMessage', 'faultstring'])
|
|
174
|
+
|
|
175
|
+
root = response.findall('.//jobName')
|
|
176
|
+
job_list = list()
|
|
177
|
+
for job in root:
|
|
178
|
+
if job is not None:
|
|
179
|
+
job_list.append(job.text)
|
|
180
|
+
return {repo_name: job_list}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def get_flow_details(self, repo_name, run_id):
|
|
184
|
+
"""
|
|
185
|
+
:param repo_name: name of the repository
|
|
186
|
+
:param run_id: run ID of the batch job instance
|
|
187
|
+
:return: the batch job instance objects execution statistics
|
|
188
|
+
"""
|
|
189
|
+
request_body = f'''<ser:Get_BatchJob_FlowDetailsRequest>
|
|
190
|
+
<repoName>{repo_name}</repoName>
|
|
191
|
+
<runID>{run_id}</runID>
|
|
192
|
+
</ser:Get_BatchJob_FlowDetailsRequest>'''
|
|
193
|
+
self.headers['SOAPAction'] = 'jobAdmin=Get_BatchJob_FlowDetails'
|
|
194
|
+
request = self.request_template.format(session_id=self._server.session_id, request_body=request_body)
|
|
195
|
+
response = requests.get(self._server.wsdl_url, data=request, headers=self.headers)
|
|
196
|
+
|
|
197
|
+
response = clean_xml_response(response.text)
|
|
198
|
+
|
|
199
|
+
check_for_fault_or_error(response, ['ErrorMessage', 'faultstring'])
|
|
200
|
+
|
|
201
|
+
flow_details = list()
|
|
202
|
+
|
|
203
|
+
root = response.findall('.//flowDetail')
|
|
204
|
+
if root:
|
|
205
|
+
for flow in root:
|
|
206
|
+
flow_item = dict()
|
|
207
|
+
flow_item['ObjectName'] = flow.find('ObjectName').text
|
|
208
|
+
flow_item['ObjectType'] = flow.find('ObjectType').text
|
|
209
|
+
flow_item['ParentObject'] = flow.find('ParentObject').text
|
|
210
|
+
flow_item['ParentType'] = flow.find('ParentType').text
|
|
211
|
+
flow_item['StartTime'] = flow.find('StartTime').text
|
|
212
|
+
flow_item['EndTime'] = flow.find('EndTime').text
|
|
213
|
+
flow_item['Duration'] = flow.find('Duration').text
|
|
214
|
+
flow_item['RowsRead'] = flow.find('RowsRead').text
|
|
215
|
+
flow_item['JobServerUsed'] = flow.find('JobserverUsed').text
|
|
216
|
+
flow_item['hasAuditData'] = flow.find('hasAuditData').text
|
|
217
|
+
flow_details.append(flow_item)
|
|
218
|
+
return flow_details
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def get_options(self, job_name, repo_name):
|
|
222
|
+
"""
|
|
223
|
+
:param job_name: name of the job
|
|
224
|
+
:param repo_name: name of the repository
|
|
225
|
+
:return: dict of job execution and trace options
|
|
226
|
+
"""
|
|
227
|
+
request_body = f'''<ser:Get_BatchJob_OptionsRequest>
|
|
228
|
+
<jobName>{job_name}</jobName>
|
|
229
|
+
<repoName>{repo_name}</repoName>
|
|
230
|
+
</ser:Get_BatchJob_OptionsRequest>'''
|
|
231
|
+
self.headers['SOAPAction'] = 'jobAdmin=Get_BatchJob_Options'
|
|
232
|
+
request = self.request_template.format(session_id=self._server.session_id, request_body=request_body)
|
|
233
|
+
response = requests.get(self._server.wsdl_url, data=request, headers=self.headers)
|
|
234
|
+
|
|
235
|
+
response = clean_xml_response(response.text)
|
|
236
|
+
|
|
237
|
+
check_for_fault_or_error(response, ['ErrorMessage', 'faultstring'])
|
|
238
|
+
|
|
239
|
+
job_options = dict()
|
|
240
|
+
job_options['sampling_rate'] = response.find('.//sampling_rate').text
|
|
241
|
+
job_options['auditing'] = response.find('.//auditing').text
|
|
242
|
+
job_options['disableValidationStatisticsCollection'] = (
|
|
243
|
+
response.find('.//disableValidationStatisticsCollection').text)
|
|
244
|
+
job_options['recovery'] = response.find('.//recovery').text
|
|
245
|
+
job_options['recoverfromlastfailedexec'] = (
|
|
246
|
+
response.find('.//recoverfromlastfailedexec').text)
|
|
247
|
+
job_options['collectstatsformonitoring'] = (
|
|
248
|
+
response.find('.//collectstatsformonitoring').text)
|
|
249
|
+
job_options['collectstatsforoptimization'] = (
|
|
250
|
+
response.find('.//collectstatsforoptimization').text)
|
|
251
|
+
job_options['usecollectedstats'] = response.find('.//usecollectedstats').text
|
|
252
|
+
job_options['exportdataqualityreports'] = (
|
|
253
|
+
response.find('.//exportdataqualityreports').text)
|
|
254
|
+
job_options['trace'] = list()
|
|
255
|
+
trace_options = response.findall('.//trace')
|
|
256
|
+
for trace in trace_options:
|
|
257
|
+
if trace is not None:
|
|
258
|
+
job_options['trace'].append(trace.text)
|
|
259
|
+
job_options['StatusCode'] = response.find('.//StatusCode').text
|
|
260
|
+
|
|
261
|
+
return job_options
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def get_run_exe_detail(self, repo_name, job_name, run_id):
|
|
265
|
+
"""
|
|
266
|
+
:param job_name: name of the job
|
|
267
|
+
:param repo_name: name of the repository
|
|
268
|
+
:param run_id: run ID of the job instance
|
|
269
|
+
:return: dict of details for the job instance execution
|
|
270
|
+
"""
|
|
271
|
+
request_body = f'''<ser:Get_BatchJob_Run_ExeDetailRequest>
|
|
272
|
+
<repoName>{repo_name}</repoName>
|
|
273
|
+
<jobName>{job_name}</jobName>
|
|
274
|
+
<runID>{run_id}</runID>
|
|
275
|
+
</ser:Get_BatchJob_Run_ExeDetailRequest>'''
|
|
276
|
+
|
|
277
|
+
request = self.request_template.format(session_id=self._server.session_id, request_body=request_body)
|
|
278
|
+
self.headers['SOAPAction'] = 'jobAdmin=Get_BatchJob_Run_ExeDetail'
|
|
279
|
+
response = requests.get(self._server.wsdl_url, data=request, headers=self.headers)
|
|
280
|
+
|
|
281
|
+
response = clean_xml_response(response.text)
|
|
282
|
+
job_run_exe_details = dict()
|
|
283
|
+
|
|
284
|
+
root = response.find('.//jobDetail')
|
|
285
|
+
if root is not None:
|
|
286
|
+
job_run_exe_details['ObjID'] = root.find('ObjID').text
|
|
287
|
+
job_run_exe_details['StartTime'] = root.find('StartTime').text
|
|
288
|
+
job_run_exe_details['EndTime'] = root.find('EndTime').text
|
|
289
|
+
job_run_exe_details['ExecutionTime'] = root.find('ExecutionTime').text
|
|
290
|
+
job_run_exe_details['Status'] = root.find('Status').text
|
|
291
|
+
job_run_exe_details['JobServerUsed'] = root.find('JobServerUsed').text
|
|
292
|
+
|
|
293
|
+
else:
|
|
294
|
+
raise ValueError(f'No execution details for run ID {run_id} of {job_name} in {repo_name}.')
|
|
295
|
+
|
|
296
|
+
return job_run_exe_details
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def get_run_ids(self, repo_name, job_name, status: Optional[str] = 'all'):
|
|
300
|
+
"""
|
|
301
|
+
:param repo_name: name of the repository
|
|
302
|
+
:param job_name: name of the job
|
|
303
|
+
:param status: the options are running, succeeded, error, warning, all
|
|
304
|
+
:return: list of run IDs for batch job instances
|
|
305
|
+
"""
|
|
306
|
+
request_body = f'''<ser:Get_BatchJob_RunIDsRequest>
|
|
307
|
+
<repoName>{repo_name}</repoName>
|
|
308
|
+
<jobName>{job_name}</jobName>
|
|
309
|
+
<status>{status}</status>
|
|
310
|
+
</ser:Get_BatchJob_RunIDsRequest>'''
|
|
311
|
+
|
|
312
|
+
request = self.request_template.format(session_id=self._server.session_id, request_body=request_body)
|
|
313
|
+
self.headers['SOAPAction'] = 'jobAdmin=Get_BatchJob_RunIDs'
|
|
314
|
+
response = requests.get(self._server.wsdl_url, data=request, headers=self.headers)
|
|
315
|
+
|
|
316
|
+
response = clean_xml_response(response.text)
|
|
317
|
+
|
|
318
|
+
check_for_fault_or_error(response, ['errorMessage', 'faultstring'])
|
|
319
|
+
|
|
320
|
+
runs = list()
|
|
321
|
+
|
|
322
|
+
runs_data = response.findall('.//run')
|
|
323
|
+
for run in runs_data:
|
|
324
|
+
run_dict = dict()
|
|
325
|
+
if run is not None:
|
|
326
|
+
run_dict['runID'] = run.find('runID').text
|
|
327
|
+
run_dict['status'] = run.find('status').text
|
|
328
|
+
run_dict['repoName'] = run.find('repoName').text
|
|
329
|
+
runs.append(run_dict)
|
|
330
|
+
return runs
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def get_input_format(self, repo_name, job_name):
|
|
334
|
+
"""
|
|
335
|
+
:param repo_name: name of the repository
|
|
336
|
+
:param job_name: name of the job
|
|
337
|
+
:return: the input format for a batch job
|
|
338
|
+
"""
|
|
339
|
+
request_body = f'''<ser:Get_Job_Input_FormatRequest>
|
|
340
|
+
<repoName>{repo_name}</repoName>
|
|
341
|
+
<jobName>{job_name}</jobName>
|
|
342
|
+
</ser:Get_Job_Input_FormatRequest>'''
|
|
343
|
+
|
|
344
|
+
request = self.request_template.format(session_id=self._server.session_id, request_body=request_body)
|
|
345
|
+
self.headers['SOAPAction'] = 'jobAdmin=Get_Job_Input_Format'
|
|
346
|
+
response = requests.get(self._server.wsdl_url, data=request, headers=self.headers)
|
|
347
|
+
|
|
348
|
+
response = clean_xml_response(response.text)
|
|
349
|
+
|
|
350
|
+
check_for_fault_or_error(response, ['errorMessage', 'faultstring'])
|
|
351
|
+
|
|
352
|
+
input_format = dict()
|
|
353
|
+
job_input = None
|
|
354
|
+
var_list = list()
|
|
355
|
+
|
|
356
|
+
xml_namespaces = {'localtypes': 'http://www.businessobjects.com/DataServices/ServerX.xsd'}
|
|
357
|
+
xml_root = response
|
|
358
|
+
format_element = xml_root.find('.//format', xml_namespaces).text
|
|
359
|
+
|
|
360
|
+
xsd_string = html.unescape(format_element)
|
|
361
|
+
xsd_lines = xsd_string.split('xsd:element')
|
|
362
|
+
for line in xsd_lines:
|
|
363
|
+
job_match = re.search(r"name='(.*?_GlobalVariables)'", line)
|
|
364
|
+
if job_match:
|
|
365
|
+
job_input = job_match.group(1)
|
|
366
|
+
xsd_lines.remove(line)
|
|
367
|
+
|
|
368
|
+
for line in xsd_lines:
|
|
369
|
+
var_dict = dict()
|
|
370
|
+
var_name_match = re.search(r"name='(.*?)'", line)
|
|
371
|
+
if var_name_match:
|
|
372
|
+
var_dict['name'] = var_name_match.group(1)
|
|
373
|
+
|
|
374
|
+
var_type_match = re.search(r"data type '(.*?)'", line)
|
|
375
|
+
if var_type_match:
|
|
376
|
+
var_size_match = re.search(r"with size=(.*?)--", line)
|
|
377
|
+
if var_size_match:
|
|
378
|
+
var_dict['dataType'] = f'{var_type_match.group(1)}({var_size_match.group(1)})'
|
|
379
|
+
else:
|
|
380
|
+
var_dict['dataType'] = var_type_match.group(1)
|
|
381
|
+
|
|
382
|
+
if var_dict:
|
|
383
|
+
var_list.append(var_dict)
|
|
384
|
+
input_format[job_input] = var_list
|
|
385
|
+
return input_format
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def get_scheduled_tasks(self, repo_name, all_batch_jobs: Optional[str] = 'false'):
|
|
389
|
+
"""
|
|
390
|
+
:param repo_name: name of the repository
|
|
391
|
+
:param all_batch_jobs: true (all) / false (active)
|
|
392
|
+
:return: list of scheduled tasks
|
|
393
|
+
"""
|
|
394
|
+
request_body = f'''<ser:Get_Scheduled_TasksRequest>
|
|
395
|
+
<repoName>{repo_name}</repoName>
|
|
396
|
+
<allBatchJobs>{all_batch_jobs}</allBatchJobs>
|
|
397
|
+
</ser:Get_Scheduled_TasksRequest>'''
|
|
398
|
+
request = self.request_template.format(session_id=self._server.session_id, request_body=request_body)
|
|
399
|
+
self.headers['SOAPAction'] = 'jobAdmin=Get_Scheduled_Tasks'
|
|
400
|
+
response = requests.get(self._server.wsdl_url, data=request, headers=self.headers)
|
|
401
|
+
|
|
402
|
+
response = clean_xml_response(response.text)
|
|
403
|
+
|
|
404
|
+
check_for_fault_or_error(response, ['errorMessage', 'faultstring'])
|
|
405
|
+
|
|
406
|
+
scheduled_tasks = list()
|
|
407
|
+
tasks = response.findall('.//SchduledTask')
|
|
408
|
+
for t in tasks:
|
|
409
|
+
task_description = dict()
|
|
410
|
+
if t is not None:
|
|
411
|
+
task_description['ScheduledTaskName'] = t.find('ScheduledTaskName').text
|
|
412
|
+
task_description['JobName'] = t.find('JobName').text
|
|
413
|
+
task_description['RepoName'] = t.find('RepoName').text
|
|
414
|
+
task_description['RecurrenceType'] = t.find('RecurrenceType').text
|
|
415
|
+
task_description['RecurrenceDays'] = t.find('RecurrenceDays').text
|
|
416
|
+
task_description['StartTime'] = t.find('StartTime').text
|
|
417
|
+
task_description['DurationTime'] = t.find('DurationTime').text
|
|
418
|
+
task_description['RepeatInterval'] = t.find('RepeatInterval').text
|
|
419
|
+
task_description['NextRunTime'] = t.find('NextRunTime').text
|
|
420
|
+
task_description['IsActive'] = t.find('IsActive').text
|
|
421
|
+
scheduled_tasks.append(task_description)
|
|
422
|
+
return scheduled_tasks
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def run_job(self, repo_name, job_name,
|
|
426
|
+
job_parameters: Optional[str] = '', global_variables: Optional[dict] = '',
|
|
427
|
+
job_server: Optional[str] = '', server_group: Optional[str] = ''):
|
|
428
|
+
"""
|
|
429
|
+
:param repo_name: name of the repository
|
|
430
|
+
:param job_name: name of the job
|
|
431
|
+
:param job_parameters: [Optional] XML element that sets specific job execution parameters
|
|
432
|
+
:param global_variables: [Optional] dict in format {'var_name': 'var_value'}
|
|
433
|
+
:param job_server: [Optional] name of the job server to execute the job;
|
|
434
|
+
can't be specified if serverGroup is also specified.
|
|
435
|
+
:param server_group: [Optional] name of the server group to use to execute the job;
|
|
436
|
+
can't be specified if jobServer is specified
|
|
437
|
+
:return: process ID, counter ID, run ID, repository name associated with the batch job execution
|
|
438
|
+
"""
|
|
439
|
+
if job_server and server_group:
|
|
440
|
+
raise ValueError('You can only specify either Job Server or Server Group, not both.')
|
|
441
|
+
|
|
442
|
+
global_variables_xml = ''
|
|
443
|
+
if global_variables:
|
|
444
|
+
existing_vars = list(self.get_input_format(repo_name, job_name).values())[0]
|
|
445
|
+
existing_vars_list = list()
|
|
446
|
+
for var in existing_vars:
|
|
447
|
+
for name, value in var.items():
|
|
448
|
+
if name == 'name':
|
|
449
|
+
existing_vars_list.append(value)
|
|
450
|
+
|
|
451
|
+
for name, value in global_variables.items():
|
|
452
|
+
if name in existing_vars_list:
|
|
453
|
+
global_variables_xml += f'''<variable name="{name}">'{value}'</variable>'''
|
|
454
|
+
else:
|
|
455
|
+
raise ValueError(f'''No '{name}' variable existing for {job_name}''')
|
|
456
|
+
|
|
457
|
+
request_body = f'''<ser:Run_Batch_JobRequest>
|
|
458
|
+
<repoName>{repo_name}</repoName>
|
|
459
|
+
<jobName>{job_name}</jobName>
|
|
460
|
+
<jobParameters>{job_parameters}</jobParameters>
|
|
461
|
+
<globalVariables>
|
|
462
|
+
{global_variables_xml}
|
|
463
|
+
</globalVariables>
|
|
464
|
+
<jobServer>{job_server}</jobServer>
|
|
465
|
+
<serverGroup>{server_group}</serverGroup>
|
|
466
|
+
</ser:Run_Batch_JobRequest>'''
|
|
467
|
+
request = self.request_template.format(session_id=self._server.session_id, request_body=request_body)
|
|
468
|
+
self.headers['SOAPAction'] = 'jobAdmin=Run_Batch_Job'
|
|
469
|
+
response = requests.post(self._server.wsdl_url, data=request, headers=self.headers)
|
|
470
|
+
|
|
471
|
+
response = clean_xml_response(response.text)
|
|
472
|
+
|
|
473
|
+
check_for_fault_or_error(response, ['errorMessage', 'faultstring'])
|
|
474
|
+
|
|
475
|
+
run_status = dict()
|
|
476
|
+
run_status['processID'] = response.find('.//pid').text
|
|
477
|
+
run_status['counterID'] = response.find('.//cid').text
|
|
478
|
+
run_status['runID'] = response.find('.//rid').text
|
|
479
|
+
run_status['repoName'] = response.find('.//repoName').text
|
|
480
|
+
return run_status
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def stop_job(self, repo_name, run_id):
|
|
484
|
+
"""
|
|
485
|
+
:param repo_name: name of the repository
|
|
486
|
+
:param run_id: run ID of the bath job instance
|
|
487
|
+
:return: status of the operation
|
|
488
|
+
"""
|
|
489
|
+
request_body = f'''<ser:Run_Batch_JobRequest>
|
|
490
|
+
<repoName>{repo_name}</repoName>
|
|
491
|
+
<runID>{run_id}</runID>
|
|
492
|
+
</ser:Run_Batch_JobRequest>'''
|
|
493
|
+
request = self.request_template.format(session_id=self._server.session_id, request_body=request_body)
|
|
494
|
+
self.headers['SOAPAction'] = 'jobAdmin=Stop_Batch_Job'
|
|
495
|
+
response = requests.get(self._server.wsdl_url, data=request, headers=self.headers)
|
|
496
|
+
|
|
497
|
+
response = clean_xml_response(response.text)
|
|
498
|
+
|
|
499
|
+
check_for_fault_or_error(response, ['errorMessage', 'faultstring'])
|
|
500
|
+
|
|
501
|
+
status = dict()
|
|
502
|
+
return_code = response.find('.//returnCode')
|
|
503
|
+
if return_code is not None:
|
|
504
|
+
status['returnCode'] = return_code.text
|
|
505
|
+
if return_code.text == '0':
|
|
506
|
+
status['returnMessage'] = 'The operation successfully stopped the specified batch job.'
|
|
507
|
+
if return_code.text == '1':
|
|
508
|
+
status['returnMessage'] = 'The operation failed to stop the specified batch job in the specified repository.'
|
|
509
|
+
return status
|