-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTES_gen.py
More file actions
352 lines (307 loc) · 11.9 KB
/
Copy pathTES_gen.py
File metadata and controls
352 lines (307 loc) · 11.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
### update teh TES Json to update the name, query, and output?
### name and output are just to be able to identify the query, different filenames can be done when they're retrieved
import json
import os
import requests
from typing import Dict, Any, Tuple
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
def generate_tes_task(
query: str,
name: str = "analysis task",
image: str = None,
db_host: str = None,
db_port: str = None,
db_username: str = None,
db_password: str = None,
db_name: str = None,
output_path: str = "/outputs",
output_bucket: str = None
) -> dict:
"""
Generate a TES task JSON configuration.
Args:
query (str): SQL query to execute
name (str): Name of the analysis task
image (str): Docker image to use
db_host (str): Database host
db_port (str): Database port
db_username (str): Database username
db_password (str): Database password
db_name (str): Database name
output_path (str): Path for output files
output_bucket (str): S3 bucket for outputs (defaults to MINIO_OUTPUT_BUCKET env var)
Returns:
dict: TES task configuration
"""
# Use environment variables - required
image = image or os.getenv('TES_DOCKER_IMAGE')
if not image:
raise ValueError("TES_DOCKER_IMAGE environment variable is required")
db_host = db_host or os.getenv('DB_HOST')
if not db_host:
raise ValueError("DB_HOST environment variable is required")
db_port = db_port or os.getenv('DB_PORT')
if not db_port:
raise ValueError("DB_PORT environment variable is required")
db_username = db_username or os.getenv('DB_USERNAME')
if not db_username:
raise ValueError("DB_USERNAME environment variable is required")
db_password = db_password or os.getenv('DB_PASSWORD')
if not db_password:
raise ValueError("DB_PASSWORD environment variable is required")
db_name = db_name or os.getenv('DB_NAME')
if not db_name:
raise ValueError("DB_NAME environment variable is required")
# Use environment variable for output bucket if not provided
output_bucket = output_bucket or os.getenv('MINIO_OUTPUT_BUCKET')
if not output_bucket:
raise ValueError("MINIO_OUTPUT_BUCKET environment variable is required when output_bucket parameter is not provided")
task = {
"name": name,
"inputs": [],
"outputs": [
{
"url": f"s3://{output_bucket}",
"path": output_path,
"type": "DIRECTORY",
"name": "workdir"
}
],
"executors": [
{
"image": image,
"command": [
f"--Connection=Host={db_host}:{db_port};Username={db_username};Password={db_password};Database={db_name}",
f"--Output={output_path}/output.csv",
f"--Query={query}"
],
"env": {
"DATASOURCE_DB_DATABASE": db_name,
"DATASOURCE_DB_HOST": db_host,
"DATASOURCE_DB_PASSWORD": db_password,
"DATASOURCE_DB_USERNAME": db_username
},
"workdir": "/app"
}
]
}
return task
def save_tes_task(task: dict, output_file: str):
"""
Save the TES task configuration to a JSON file.
Args:
task (dict): TES task configuration
output_file (str): Path to save the JSON file
"""
with open(output_file, 'w') as f:
json.dump(task, f, indent=4)
def generate_submission_template(
tes_task: dict = None,
name: str = "Analysis Submission Test",
description: str = "Federated analysis task",
tres: list = ["Nottingham"],
project: str = None,
output_bucket: str = None,
output_path: str = "/outputs",
image: str = None,
db_host: str = None,
db_port: str = None,
db_username: str = None,
db_password: str = None,
db_name: str = None,
query: str = None) -> tuple[dict, int]:
"""
Generate a submission template JSON configuration.
Args:
tes_task (dict, optional): Existing TES task to use for executor configuration
name (str): Name of the analysis submission
description (str): Description of the analysis task
tres (list): List of TREs to run the analysis on
project (str): Project name (defaults to TRE_FX_PROJECT env var)
output_bucket (str): S3 bucket name for outputs (defaults to MINIO_OUTPUT_BUCKET env var)
output_path (str): Path for output files
image (str): Docker image to use
db_host (str): Database host
db_port (str): Database port
db_username (str): Database username
db_password (str): Database password
db_name (str): Database name
query (str): SQL query to execute
Returns:
tuple[dict, int]: Submission template configuration and number of TREs
"""
# Use environment variables - required
image = image or os.getenv('TES_DOCKER_IMAGE')
if not image:
raise ValueError("TES_DOCKER_IMAGE environment variable is required")
db_host = db_host or os.getenv('DB_HOST')
if not db_host:
raise ValueError("DB_HOST environment variable is required")
db_port = db_port or os.getenv('DB_PORT')
if not db_port:
raise ValueError("DB_PORT environment variable is required")
db_username = db_username or os.getenv('DB_USERNAME')
if not db_username:
raise ValueError("DB_USERNAME environment variable is required")
db_password = db_password or os.getenv('DB_PASSWORD')
if not db_password:
raise ValueError("DB_PASSWORD environment variable is required")
db_name = db_name or os.getenv('DB_NAME')
if not db_name:
raise ValueError("DB_NAME environment variable is required")
# Use environment variables for project and output bucket if not provided
project = project or os.getenv('TRE_FX_PROJECT')
if not project:
raise ValueError("TRE_FX_PROJECT environment variable is required when project parameter is not provided")
output_bucket = output_bucket or os.getenv('MINIO_OUTPUT_BUCKET')
if not output_bucket:
raise ValueError("MINIO_OUTPUT_BUCKET environment variable is required when output_bucket parameter is not provided")
# If a TES task is provided, use its executor configuration
if tes_task is not None:
executors = tes_task.get("executors", [])
else:
# Create executor configuration using provided parameters
executors = [{
"image": image,
"command": [
f"--Connection=Host={db_host}:{db_port};Username={db_username};Password={db_password};Database={db_name}",
f"--Output={output_path}/output.csv",
f"--Query={query}" if query else ""
],
"env": {
"DATASOURCE_DB_DATABASE": db_name,
"DATASOURCE_DB_HOST": db_host,
"DATASOURCE_DB_PASSWORD": db_password,
"DATASOURCE_DB_USERNAME": db_username
},
"workdir": "/app"
}]
template = {
"id": None,
"state": 1,
"name": name,
"description": description,
"inputs": None,
"outputs": [
{
"name": "workdir",
"description": "analysis test output",
"url": f"s3://{output_bucket}",
"path": output_path,
"type": "DIRECTORY"
}
],
"resources": None,
"executors": executors,
"volumes": None,
"tags": {
"Project": project,
"tres": "|".join(tres)
},
"logs": None,
"creation_time": None,
"tesTask": {
"id": None,
"state": 1,
"name": name,
"description": description,
"inputs": None,
"outputs": [
{
"name": "workdir",
"description": "analysis test output",
"url": f"s3://{output_bucket}",
"path": output_path,
"type": "DIRECTORY"
}
],
"resources": None,
"executors": executors,
"volumes": None,
"tags": {
"Project": project,
"tres": "|".join(tres)
},
"logs": None,
"creation_time": None
}
}
return template, len(tres)
def save_submission_template(template: dict, output_file: str):
"""
Save the submission template configuration to a JSON file.
Args:
template (dict): Submission template configuration
output_file (str): Path to save the JSON file
"""
with open(output_file, 'w') as f:
json.dump(template, f, indent=4)
def generate_curl_command(template: dict, base_url: str = None) -> str:
"""
Generate a curl command for submitting the template.
Args:
template (dict): Submission template configuration
base_url (str): Base URL for the API endpoint
Returns:
str: Formatted curl command
"""
# Use environment variable - required
base_url = base_url or os.getenv('TES_BASE_URL')
if not base_url:
raise ValueError("TES_BASE_URL environment variable is required")
# Convert template to JSON string with proper escaping
template_json = json.dumps(template).replace('"', '\"')
curl_command = f"""curl -X 'POST' \\
'{base_url}' \\
-H 'accept: text/plain' \\
-H 'Authorization: Bearer **TOKEN-HERE**' \\
-H 'Content-Type: application/json' \\
-d '{template_json}'"""
return curl_command
def submit_tes_task(template: dict, token: str, base_url: str = None) -> dict:
"""
Submit a TES task using the requests library.
Args:
template (dict): The TES task template
token (str): Authentication token
base_url (str): Base URL for the API endpoint
Returns:
dict: Response from the server
Raises:
requests.exceptions.RequestException: If the request fails
"""
# Use environment variable - required
base_url = base_url or os.getenv('TES_BASE_URL')
if not base_url:
raise ValueError("TES_BASE_URL environment variable is required")
headers = {
'accept': 'text/plain',
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
try:
response = requests.post(base_url, headers=headers, json=template)
response.raise_for_status() # Raise an exception for bad status codes
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error submitting task: {str(e)}")
if hasattr(e.response, 'text'):
print(f"Response content: {e.response.text}")
raise
# Example usage:
if __name__ == "__main__":
# Example parameters
task = generate_tes_task(
query="SELECT g.concept_name AS gender_name, r.concept_name AS race_name, COUNT(*) AS n FROM person p JOIN concept g ON p.gender_concept_id = g.concept_id JOIN concept r ON p.race_concept_id = r.concept_id WHERE p.race_concept_id IN (38003574, 38003584) GROUP BY g.concept_name, r.concept_name ORDER BY g.concept_name, r.concept_name;",
)
# Save to file
save_tes_task(task, "TRE-FX Analytics/TES/tes-task.json")
# Generate and save submission template using the TES task
template = generate_submission_template(tes_task=task)
save_submission_template(template, "TRE-FX Analytics/TES/submission_template.json")
# Generate curl command
curl_cmd = generate_curl_command(template)
print("\nCurl command for submission:")
print(curl_cmd)