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.
Files changed (25) hide show
  1. sapdswsdlclient-0.1.2/LICENSE.md +22 -0
  2. sapdswsdlclient-0.1.2/PKG-INFO +48 -0
  3. sapdswsdlclient-0.1.2/README.md +21 -0
  4. sapdswsdlclient-0.1.2/sapdswsdlclient/__init__.py +7 -0
  5. sapdswsdlclient-0.1.2/sapdswsdlclient/exceptions/exceptions.py +2 -0
  6. sapdswsdlclient-0.1.2/sapdswsdlclient/models/batch_job.py +509 -0
  7. sapdswsdlclient-0.1.2/sapdswsdlclient/models/dataflow.py +97 -0
  8. sapdswsdlclient-0.1.2/sapdswsdlclient/models/items.py +80 -0
  9. sapdswsdlclient-0.1.2/sapdswsdlclient/models/job_server.py +92 -0
  10. sapdswsdlclient-0.1.2/sapdswsdlclient/models/logs.py +147 -0
  11. sapdswsdlclient-0.1.2/sapdswsdlclient/models/realtime_service.py +127 -0
  12. sapdswsdlclient-0.1.2/sapdswsdlclient/models/repo.py +278 -0
  13. sapdswsdlclient-0.1.2/sapdswsdlclient/server/__init__.py +1 -0
  14. sapdswsdlclient-0.1.2/sapdswsdlclient/server/auth.py +167 -0
  15. sapdswsdlclient-0.1.2/sapdswsdlclient/server/re_auth.py +17 -0
  16. sapdswsdlclient-0.1.2/sapdswsdlclient/templates/templates.py +13 -0
  17. sapdswsdlclient-0.1.2/sapdswsdlclient/utilities/check_for_fault_or_error.py +14 -0
  18. sapdswsdlclient-0.1.2/sapdswsdlclient/utilities/clean_xml.py +55 -0
  19. sapdswsdlclient-0.1.2/sapdswsdlclient.egg-info/PKG-INFO +48 -0
  20. sapdswsdlclient-0.1.2/sapdswsdlclient.egg-info/SOURCES.txt +23 -0
  21. sapdswsdlclient-0.1.2/sapdswsdlclient.egg-info/dependency_links.txt +1 -0
  22. sapdswsdlclient-0.1.2/sapdswsdlclient.egg-info/requires.txt +1 -0
  23. sapdswsdlclient-0.1.2/sapdswsdlclient.egg-info/top_level.txt +1 -0
  24. sapdswsdlclient-0.1.2/setup.cfg +4 -0
  25. 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,7 @@
1
+ from sapdswsdlclient.server.auth import Server
2
+
3
+
4
+ def logon(wsdl_url, username, password, cms_system, cms_authentication):
5
+ server_instance = Server(wsdl_url, username, password, cms_system, cms_authentication)
6
+ server_instance.logon()
7
+ return server_instance
@@ -0,0 +1,2 @@
1
+ class NotSignedInError(Exception):
2
+ pass
@@ -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