2
2
"""
3
3
import io
4
4
import logging
5
+ from time import sleep
5
6
from typing import List
6
7
from urllib .parse import urlparse
7
8
8
9
import httpx
9
10
10
11
from sync .api .predictions import generate_presigned_url , get_predictions
11
12
from sync .clients .sync import get_default_client
12
- from sync .models import Platform , Preference , ProjectError , Response , SubmissionError
13
+ from sync .models import (
14
+ Platform ,
15
+ Preference ,
16
+ ProjectError ,
17
+ RecommendationError ,
18
+ Response ,
19
+ SubmissionError ,
20
+ )
13
21
14
22
logger = logging .getLogger ()
15
23
@@ -45,7 +53,7 @@ def create_project(
45
53
product_code : str ,
46
54
description : str = None ,
47
55
job_id : str = None ,
48
- s3_url : str = None ,
56
+ cluster_log_url : str = None ,
49
57
prediction_preference : Preference = Preference .ECONOMY ,
50
58
prediction_params : dict = None ,
51
59
app_id : str = None ,
@@ -60,8 +68,8 @@ def create_project(
60
68
:type description: str, optional
61
69
:param job_id: Databricks job ID, defaults to None
62
70
:type job_id: str, optional
63
- :param s3_url : S3 URL under which to store project configurations and logs, defaults to None
64
- :type s3_url : str, optional
71
+ :param cluster_log_url : S3 or DBFS URL under which to store project configurations and logs, defaults to None
72
+ :type cluster_log_url : str, optional
65
73
:param prediction_preference: preferred prediction solution, defaults to `Preference.ECONOMY`
66
74
:type prediction_preference: Preference, optional
67
75
:param prediction_params: dictionary of prediction parameters, defaults to None. Valid options are documented `here <https://developers.synccomputing.com/reference/create_project_v1_projects_post>`__
@@ -78,7 +86,7 @@ def create_project(
78
86
"product_code" : product_code ,
79
87
"description" : description ,
80
88
"job_id" : job_id ,
81
- "s3_url " : s3_url ,
89
+ "cluster_log_url " : cluster_log_url ,
82
90
"prediction_preference" : prediction_preference ,
83
91
"prediction_params" : prediction_params ,
84
92
"app_id" : app_id ,
@@ -101,7 +109,7 @@ def get_project(project_id: str) -> Response[dict]:
101
109
def update_project (
102
110
project_id : str ,
103
111
description : str = None ,
104
- s3_url : str = None ,
112
+ cluster_log_url : str = None ,
105
113
app_id : str = None ,
106
114
prediction_preference : Preference = None ,
107
115
prediction_params : dict = None ,
@@ -112,8 +120,8 @@ def update_project(
112
120
:type project_id: str
113
121
:param description: description, defaults to None
114
122
:type description: str, optional
115
- :param s3_url : location of project event logs and configurations, defaults to None
116
- :type s3_url : str, optional
123
+ :param cluster_log_url : location of project event logs and configurations, defaults to None
124
+ :type cluster_log_url : str, optional
117
125
:param app_id: external identifier, defaults to None
118
126
:type app_id: str, optional
119
127
:param prediction_preference: default preference for predictions, defaults to None
@@ -126,8 +134,8 @@ def update_project(
126
134
project_update = {}
127
135
if description :
128
136
project_update ["description" ] = description
129
- if s3_url :
130
- project_update ["s3_url " ] = s3_url
137
+ if cluster_log_url :
138
+ project_update ["cluster_log_url " ] = cluster_log_url
131
139
if app_id :
132
140
project_update ["app_id" ] = app_id
133
141
if prediction_preference :
@@ -187,7 +195,7 @@ def delete_project(project_id: str) -> Response[str]:
187
195
def create_project_submission (
188
196
platform : Platform , cluster_report : dict , eventlog_url : str , project_id : str
189
197
) -> Response [str ]:
190
- """Create prediction
198
+ """Create a submission
191
199
192
200
:param platform: platform, e.g. "aws-emr"
193
201
:type platform: Platform
@@ -211,13 +219,17 @@ def create_project_submission(
211
219
else :
212
220
return Response (error = SubmissionError (message = "Unsupported event log URL scheme" ))
213
221
222
+ payload = {
223
+ "product" : platform ,
224
+ "cluster_report" : cluster_report ,
225
+ "event_log_uri" : eventlog_http_url ,
226
+ }
227
+
228
+ logger .info (payload )
229
+
214
230
response = get_default_client ().create_project_submission (
215
231
project_id ,
216
- {
217
- "product" : platform ,
218
- "cluster_report" : cluster_report ,
219
- "event_log_uri" : eventlog_http_url ,
220
- },
232
+ payload ,
221
233
)
222
234
223
235
if response .get ("error" ):
@@ -226,14 +238,43 @@ def create_project_submission(
226
238
return Response (result = response ["result" ]["submission_id" ])
227
239
228
240
241
+ def _clear_cluster_report_errors (cluster_report_orig : dict ) -> dict :
242
+ """Clears error messages from the cluster_events field
243
+ This circumvents issues where certain strange characters in the error fields of Azure cluster
244
+ reports were causing the client to throw errors when trying to make submissions.
245
+
246
+ :param cluster_report_orig: cluster_report
247
+ :type cluster_report_orig: dict
248
+ :return: cleared cluster report
249
+ :rtype: dict
250
+ """
251
+ cluster_report = cluster_report_orig .copy ()
252
+
253
+ def clear_error (event : dict ):
254
+ try :
255
+ del event ["details" ]["reason" ]["parameters" ]["azure_error_message" ]
256
+ except KeyError :
257
+ pass
258
+ try :
259
+ del event ["details" ]["reason" ]["parameters" ]["databricks_error_message" ]
260
+ except KeyError :
261
+ pass
262
+
263
+ try :
264
+ list (map (clear_error , cluster_report ["cluster_events" ]["events" ]))
265
+ except KeyError :
266
+ pass
267
+ return cluster_report
268
+
269
+
229
270
def create_project_submission_with_eventlog_bytes (
230
271
platform : Platform ,
231
272
cluster_report : dict ,
232
273
eventlog_name : str ,
233
274
eventlog_bytes : bytes ,
234
275
project_id : str ,
235
276
) -> Response [str ]:
236
- """Creates a prediction giving event log bytes instead of a URL
277
+ """Creates a submission given event log bytes instead of a URL
237
278
238
279
:param platform: platform, e.g. "aws-emr"
239
280
:type platform: Platform
@@ -243,14 +284,15 @@ def create_project_submission_with_eventlog_bytes(
243
284
:type eventlog_name: str
244
285
:param eventlog_bytes: encoded event log
245
286
:type eventlog_bytes: bytes
246
- :param project_id: ID of project to which the prediction belongs, defaults to None
247
- :type project_id: str, optional
287
+ :param project_id: ID of project to which the submission belongs
288
+ :type project_id: str
248
289
:return: prediction ID
249
290
:rtype: Response[str]
250
291
"""
251
292
# TODO - best way to handle "no eventlog"
293
+ cluster_report_clear = _clear_cluster_report_errors (cluster_report )
252
294
response = get_default_client ().create_project_submission (
253
- project_id , {"product_code" : platform , "cluster_report" : cluster_report }
295
+ project_id , {"product_code" : platform , "cluster_report" : cluster_report_clear }
254
296
)
255
297
256
298
if response .get ("error" ):
@@ -269,3 +311,60 @@ def create_project_submission_with_eventlog_bytes(
269
311
return Response (error = SubmissionError (message = "Failed to upload event log" ))
270
312
271
313
return Response (result = response ["result" ]["submission_id" ])
314
+
315
+
316
+ def create_project_recommendation (project_id : str , ** options ) -> Response [str ]:
317
+ """Creates a prediction given a project id
318
+
319
+ :param project_id: ID of project to which the prediction belongs, defaults to None
320
+ :type project_id: str, optional
321
+ :return: prediction ID
322
+ :rtype: Response[str]
323
+ """
324
+ response = get_default_client ().create_project_recommendation (project_id , ** options )
325
+
326
+ if response .get ("error" ):
327
+ return Response (** response )
328
+
329
+ return Response (result = response ["result" ]["id" ])
330
+
331
+
332
+ def wait_for_recommendation (project_id : str , recommendation_id : str ) -> Response [dict ]:
333
+ """Get a recommendation, wait if it's not ready
334
+
335
+ :param project_id: project ID
336
+ :type project_id: str
337
+ :param recommendation_id: recommendation ID
338
+ :type recommendation_id: str
339
+ :return: recommendation object
340
+ :rtype: Response[dict]
341
+ """
342
+ response = get_project_recommendation (project_id , recommendation_id )
343
+ while response :
344
+ result = response .result
345
+ if result :
346
+ if result ["state" ] == "SUCCESS" :
347
+ return Response (result = result )
348
+ if result ["state" ] == "FAILURE" :
349
+ return Response (error = RecommendationError (message = "Recommendation failed" ))
350
+ logger .info ("Waiting for recommendation" )
351
+ sleep (10 )
352
+ response = get_project_recommendation (project_id , recommendation_id )
353
+
354
+
355
+ def get_project_recommendation (project_id : str , recommendation_id : str ) -> Response [dict ]:
356
+ """Get a specific recommendation for a project id
357
+
358
+ :param project_id: project ID
359
+ :type project_id: str
360
+ :param recommendation_id: recommendation ID
361
+ :type recommendation_id: str
362
+ :return: recommendation object
363
+ :rtype: Response[dict]
364
+ """
365
+ response = get_default_client ().get_project_recommendation (project_id , recommendation_id )
366
+
367
+ if response .get ("error" ):
368
+ return Response (** response )
369
+
370
+ return Response (result = response ["result" ])
0 commit comments