cornflow 1.2.0a3__py3-none-any.whl → 1.2.2__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.
Files changed (46) hide show
  1. cornflow/app.py +3 -1
  2. cornflow/cli/__init__.py +4 -0
  3. cornflow/cli/actions.py +4 -0
  4. cornflow/cli/config.py +4 -0
  5. cornflow/cli/migrations.py +13 -8
  6. cornflow/cli/permissions.py +4 -0
  7. cornflow/cli/roles.py +4 -0
  8. cornflow/cli/schemas.py +5 -0
  9. cornflow/cli/service.py +235 -131
  10. cornflow/cli/tools/api_generator.py +13 -10
  11. cornflow/cli/tools/endpoint_tools.py +191 -196
  12. cornflow/cli/tools/models_tools.py +87 -60
  13. cornflow/cli/tools/schema_generator.py +161 -67
  14. cornflow/cli/tools/schemas_tools.py +4 -5
  15. cornflow/cli/users.py +8 -0
  16. cornflow/cli/views.py +4 -0
  17. cornflow/commands/dag.py +3 -2
  18. cornflow/commands/schemas.py +6 -4
  19. cornflow/commands/users.py +12 -17
  20. cornflow/endpoints/dag.py +27 -25
  21. cornflow/endpoints/data_check.py +128 -165
  22. cornflow/endpoints/example_data.py +9 -3
  23. cornflow/endpoints/execution.py +40 -34
  24. cornflow/endpoints/health.py +7 -7
  25. cornflow/endpoints/instance.py +39 -12
  26. cornflow/endpoints/meta_resource.py +4 -5
  27. cornflow/schemas/execution.py +9 -1
  28. cornflow/schemas/health.py +1 -0
  29. cornflow/shared/authentication/auth.py +73 -42
  30. cornflow/shared/const.py +10 -1
  31. cornflow/shared/exceptions.py +3 -1
  32. cornflow/shared/utils_tables.py +36 -8
  33. cornflow/shared/validators.py +1 -1
  34. cornflow/tests/const.py +1 -0
  35. cornflow/tests/custom_test_case.py +4 -4
  36. cornflow/tests/unit/test_alarms.py +1 -2
  37. cornflow/tests/unit/test_cases.py +4 -7
  38. cornflow/tests/unit/test_executions.py +22 -1
  39. cornflow/tests/unit/test_health.py +4 -1
  40. cornflow/tests/unit/test_log_in.py +46 -9
  41. cornflow/tests/unit/test_tables.py +3 -3
  42. {cornflow-1.2.0a3.dist-info → cornflow-1.2.2.dist-info}/METADATA +5 -5
  43. {cornflow-1.2.0a3.dist-info → cornflow-1.2.2.dist-info}/RECORD +46 -46
  44. {cornflow-1.2.0a3.dist-info → cornflow-1.2.2.dist-info}/WHEEL +1 -1
  45. {cornflow-1.2.0a3.dist-info → cornflow-1.2.2.dist-info}/entry_points.txt +0 -0
  46. {cornflow-1.2.0a3.dist-info → cornflow-1.2.2.dist-info}/top_level.txt +0 -0
cornflow/endpoints/dag.py CHANGED
@@ -2,6 +2,7 @@
2
2
  Internal endpoint for getting and posting execution data
3
3
  These are the endpoints used by airflow in its communication with cornflow
4
4
  """
5
+
5
6
  # Import from libraries
6
7
  from cornflow_client.constants import SOLUTION_SCHEMA
7
8
  from flask import current_app
@@ -17,7 +18,6 @@ from cornflow.schemas.execution import (
17
18
  ExecutionDagPostRequest,
18
19
  ExecutionDagRequest,
19
20
  ExecutionDetailsEndpointResponse,
20
- ExecutionSchema,
21
21
  )
22
22
 
23
23
  from cornflow.shared.authentication import Auth, authenticate
@@ -85,7 +85,7 @@ class DAGDetailEndpoint(BaseMetaResource):
85
85
  @doc(description="Edit an execution", tags=["DAGs"])
86
86
  @authenticate(auth_class=Auth())
87
87
  @use_kwargs(ExecutionDagRequest, location="json")
88
- def put(self, idx, **req_data):
88
+ def put(self, idx, **kwargs):
89
89
  """
90
90
  API method to write the results of the execution
91
91
  It requires authentication to be passed in the form of a token that has to be linked to
@@ -95,13 +95,20 @@ class DAGDetailEndpoint(BaseMetaResource):
95
95
  :return: A dictionary with a message (body) and an integer with the HTTP status code
96
96
  :rtype: Tuple(dict, integer)
97
97
  """
98
- solution_schema = req_data.pop("solution_schema", "pulp")
98
+ execution = ExecutionModel.get_one_object(user=self.get_user(), idx=idx)
99
+ if execution is None:
100
+ err = "The execution does not exist."
101
+ raise ObjectDoesNotExist(
102
+ error=err,
103
+ log_txt=f"Error while user {self.get_user()} tries to edit execution {idx}."
104
+ + err,
105
+ )
106
+
107
+ solution_schema = execution.schema
99
108
 
100
- # TODO: the solution_schema maybe we should get it from the created execution_id?
101
- # at least, check they have the same schema-name
102
109
  # Check data format
103
- data = req_data.get("data")
104
- checks = req_data.get("checks")
110
+ data = kwargs.get("data")
111
+ checks = kwargs.get("checks")
105
112
  if data is None:
106
113
  # only check format if executions_results exist
107
114
  solution_schema = None
@@ -111,24 +118,19 @@ class DAGDetailEndpoint(BaseMetaResource):
111
118
  if solution_schema is not None:
112
119
  config = current_app.config
113
120
 
114
- solution_schema = DeployedDAG.get_one_schema(config, solution_schema, SOLUTION_SCHEMA)
121
+ solution_schema = DeployedDAG.get_one_schema(
122
+ config, solution_schema, SOLUTION_SCHEMA
123
+ )
115
124
  solution_errors = json_schema_validate_as_string(solution_schema, data)
116
125
 
117
126
  if solution_errors:
118
127
  raise InvalidData(
119
128
  payload=dict(jsonschema_errors=solution_errors),
120
129
  log_txt=f"Error while user {self.get_user()} tries to edit execution {idx}. "
121
- f"Solution data do not match the jsonschema.",
130
+ f"Solution data do not match the jsonschema.",
122
131
  )
123
- execution = ExecutionModel.get_one_object(user=self.get_user(), idx=idx)
124
- if execution is None:
125
- err = "The execution does not exist."
126
- raise ObjectDoesNotExist(
127
- error=err,
128
- log_txt=f"Error while user {self.get_user()} tries to edit execution {idx}."
129
- + err,
130
- )
131
- state = req_data.get("state", EXEC_STATE_CORRECT)
132
+
133
+ state = kwargs.get("state", EXEC_STATE_CORRECT)
132
134
  new_data = dict(
133
135
  state=state,
134
136
  state_message=EXECUTION_STATE_MESSAGE_DICT[state],
@@ -141,10 +143,9 @@ class DAGDetailEndpoint(BaseMetaResource):
141
143
  new_data["data"] = data
142
144
  if checks is not None:
143
145
  new_data["checks"] = checks
144
- req_data.update(new_data)
145
- execution.update(req_data)
146
- # TODO: is this save necessary?
147
- execution.save()
146
+ kwargs.update(new_data)
147
+ execution.update(kwargs)
148
+
148
149
  current_app.logger.info(f"User {self.get_user()} edits execution {idx}")
149
150
  return {"message": "results successfully saved"}, 200
150
151
 
@@ -207,7 +208,6 @@ class DAGEndpointManual(BaseMetaResource):
207
208
 
208
209
  # Check data format
209
210
  data = kwargs.get("data")
210
- # TODO: create a function to validate and replace data/ execution_results
211
211
  if data is None:
212
212
  # only check format if executions_results exist
213
213
  solution_schema = None
@@ -215,14 +215,16 @@ class DAGEndpointManual(BaseMetaResource):
215
215
  solution_schema = "solve_model_dag"
216
216
  if solution_schema is not None:
217
217
  config = current_app.config
218
- solution_schema = DeployedDAG.get_one_schema(config, solution_schema, SOLUTION_SCHEMA)
218
+ solution_schema = DeployedDAG.get_one_schema(
219
+ config, solution_schema, SOLUTION_SCHEMA
220
+ )
219
221
  solution_errors = json_schema_validate_as_string(solution_schema, data)
220
222
 
221
223
  if solution_errors:
222
224
  raise InvalidData(
223
225
  payload=dict(jsonschema_errors=solution_errors),
224
226
  log_txt=f"Error while user {self.get_user()} tries to manually create an execution. "
225
- f"Solution data do not match the jsonschema.",
227
+ f"Solution data do not match the jsonschema.",
226
228
  )
227
229
 
228
230
  kwargs_copy = dict(kwargs)
@@ -14,25 +14,105 @@ from cornflow.models import InstanceModel, ExecutionModel, CaseModel, DeployedDA
14
14
  from cornflow.schemas.execution import ExecutionDetailsEndpointResponse
15
15
  from cornflow.shared.authentication import Auth, authenticate
16
16
  from cornflow.shared.const import (
17
+ AIRFLOW_ERROR_MSG,
18
+ AIRFLOW_NOT_REACHABLE_MSG,
19
+ DAG_PAUSED_MSG,
17
20
  EXEC_STATE_QUEUED,
18
21
  EXEC_STATE_ERROR,
19
22
  EXEC_STATE_ERROR_START,
20
23
  EXEC_STATE_NOT_RUN,
21
- EXECUTION_STATE_MESSAGE_DICT, VIEWER_ROLE, PLANNER_ROLE, ADMIN_ROLE,
24
+ EXECUTION_STATE_MESSAGE_DICT,
25
+ PLANNER_ROLE,
26
+ ADMIN_ROLE,
22
27
  )
23
28
  from cornflow.shared.exceptions import (
24
29
  AirflowError,
25
30
  ObjectDoesNotExist,
26
31
  InvalidUsage,
27
- InvalidData
32
+ InvalidData,
28
33
  )
29
34
  from cornflow.shared.validators import json_schema_validate_as_string
30
35
 
31
36
 
37
+ def _run_airflow_data_check(
38
+ af_client: Airflow,
39
+ execution: ExecutionModel,
40
+ schema: str,
41
+ user,
42
+ context_str: str,
43
+ **run_dag_kwargs,
44
+ ):
45
+ """
46
+ Helper function to check Airflow status and run a data check DAG.
47
+
48
+ :param af_client: Initialized Airflow client.
49
+ :param execution: The ExecutionModel object to update.
50
+ :param schema: The name of the schema/DAG to run.
51
+ :param user: The user object performing the action.
52
+ :param context_str: A string describing the context (e.g., "execution {id}") for logging.
53
+ :param run_dag_kwargs: Additional keyword arguments for af_client.run_dag.
54
+ :return: None. Updates execution object in place and raises AirflowError on failure.
55
+ """
56
+ # Check Airflow liveness
57
+ if not af_client.is_alive():
58
+ current_app.logger.error(AIRFLOW_NOT_REACHABLE_MSG)
59
+ execution.update_state(EXEC_STATE_ERROR_START)
60
+ raise AirflowError(
61
+ error=AIRFLOW_NOT_REACHABLE_MSG,
62
+ payload=dict(
63
+ message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
64
+ state=EXEC_STATE_ERROR_START,
65
+ ),
66
+ log_txt=f"Error while user {user} tries to run data checks on {context_str}. "
67
+ + AIRFLOW_NOT_REACHABLE_MSG,
68
+ )
69
+
70
+ # Check if DAG is paused
71
+ schema_info = af_client.get_dag_info(schema)
72
+ info = schema_info.json()
73
+ if info.get("is_paused", False):
74
+ current_app.logger.error(DAG_PAUSED_MSG)
75
+ execution.update_state(EXEC_STATE_ERROR_START)
76
+ raise AirflowError(
77
+ error=DAG_PAUSED_MSG,
78
+ payload=dict(
79
+ message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
80
+ state=EXEC_STATE_ERROR_START,
81
+ ),
82
+ log_txt=f"Error while user {user} tries to run data checks on {context_str}. "
83
+ + DAG_PAUSED_MSG,
84
+ )
85
+
86
+ # Run the DAG
87
+ try:
88
+ response = af_client.run_dag(
89
+ execution.id, dag_name=schema, checks_only=True, **run_dag_kwargs
90
+ )
91
+ except AirflowError as err:
92
+ error = f"{AIRFLOW_ERROR_MSG} {err}"
93
+ current_app.logger.error(error)
94
+ execution.update_state(EXEC_STATE_ERROR)
95
+ raise AirflowError(
96
+ error=error,
97
+ payload=dict(
98
+ message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR],
99
+ state=EXEC_STATE_ERROR,
100
+ ),
101
+ log_txt=f"Error while user {user} tries to run data checks on {context_str}. "
102
+ + error,
103
+ )
104
+
105
+ # Update execution on success
106
+ af_data = response.json()
107
+ execution.dag_run_id = af_data.get("dag_run_id")
108
+ execution.update_state(EXEC_STATE_QUEUED)
109
+
110
+
32
111
  class DataCheckExecutionEndpoint(BaseMetaResource):
33
112
  """
34
113
  Endpoint used to execute the instance and solution checks on an execution
35
114
  """
115
+
36
116
  ROLES_WITH_ACCESS = [PLANNER_ROLE, ADMIN_ROLE]
37
117
 
38
118
  def __init__(self):
@@ -56,13 +136,16 @@ class DataCheckExecutionEndpoint(BaseMetaResource):
56
136
  :rtype: Tuple(dict, integer)
57
137
  """
58
138
  config = current_app.config
139
+ user = self.get_user()
140
+ context_str = f"execution {idx}"
59
141
 
60
142
  execution = ExecutionModel.get_one_object(user=self.get_user(), idx=idx)
61
143
  if execution is None:
62
144
  err = "The execution to check does not exist"
63
145
  raise ObjectDoesNotExist(
64
146
  error=err,
65
- log_txt=f"Error while user {self.get_user()} tries to run data checks on execution {idx}. " + err
147
+ log_txt=f"Error while user {user} tries to run data checks on {context_str}. "
148
+ + err,
66
149
  )
67
150
 
68
151
  schema = execution.schema
@@ -72,7 +155,8 @@ class DataCheckExecutionEndpoint(BaseMetaResource):
72
155
  err = "The execution is still running"
73
156
  raise InvalidUsage(
74
157
  error=err,
75
- log_txt=f"Error while user {self.get_user()} tries to run data checks on execution {idx}. " + err
158
+ log_txt=f"Error while user {user} tries to run data checks on {context_str}. "
159
+ + err,
76
160
  )
77
161
 
78
162
  # this allows testing without airflow interaction:
@@ -80,58 +164,12 @@ class DataCheckExecutionEndpoint(BaseMetaResource):
80
164
  execution.update_state(EXEC_STATE_NOT_RUN)
81
165
  return execution, 201
82
166
 
83
- # We now try to launch the task in airflow
167
+ # Initialize Airflow client
84
168
  af_client = Airflow.from_config(config)
85
- if not af_client.is_alive():
86
- err = "Airflow is not accessible"
87
- current_app.logger.error(err)
88
- execution.update_state(EXEC_STATE_ERROR_START)
89
- raise AirflowError(
90
- error=err,
91
- payload=dict(
92
- message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
93
- state=EXEC_STATE_ERROR_START,
94
- ),
95
- log_txt=f"Error while user {self.get_user()} tries to run data checks on execution {idx}. " + err
96
- )
97
- # ask airflow if dag_name exists
98
- schema_info = af_client.get_dag_info(schema)
99
-
100
- info = schema_info.json()
101
- if info["is_paused"]:
102
- err = "The dag exists but it is paused in airflow"
103
- current_app.logger.error(err)
104
- execution.update_state(EXEC_STATE_ERROR_START)
105
- raise AirflowError(
106
- error=err,
107
- payload=dict(
108
- message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
109
- state=EXEC_STATE_ERROR_START,
110
- ),
111
- log_txt=f"Error while user {self.get_user()} tries to run data checks on execution {idx}. " + err
112
- )
113
169
 
114
- try:
115
- response = af_client.run_dag(
116
- execution.id, dag_name=schema, checks_only=True
117
- )
118
- except AirflowError as err:
119
- error = "Airflow responded with an error: {}".format(err)
120
- current_app.logger.error(error)
121
- execution.update_state(EXEC_STATE_ERROR)
122
- raise AirflowError(
123
- error=error,
124
- payload=dict(
125
- message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR],
126
- state=EXEC_STATE_ERROR,
127
- ),
128
- log_txt=f"Error while user {self.get_user()} tries to run data checks on execution {idx}. " + error
129
- )
170
+ # Call the shared Airflow execution logic
171
+ _run_airflow_data_check(af_client, execution, schema, user, context_str)
130
172
 
131
- # if we succeed, we register the dag_run_id in the execution table:
132
- af_data = response.json()
133
- execution.dag_run_id = af_data["dag_run_id"]
134
- execution.update_state(EXEC_STATE_QUEUED)
135
173
  current_app.logger.info(
136
174
  "User {} launches checks of execution {}".format(
137
175
  self.get_user_id(), execution.id
@@ -144,7 +182,9 @@ class DataCheckInstanceEndpoint(BaseMetaResource):
144
182
  """
145
183
  Endpoint used to execute the instance and solution checks on an execution
146
184
  """
185
+
147
186
  ROLES_WITH_ACCESS = [PLANNER_ROLE, ADMIN_ROLE]
187
+
148
188
  def __init__(self):
149
189
  super().__init__()
150
190
  self.model = ExecutionModel
@@ -169,13 +209,16 @@ class DataCheckInstanceEndpoint(BaseMetaResource):
169
209
  :rtype: Tuple(dict, integer)
170
210
  """
171
211
  config = current_app.config
212
+ user = self.get_user()
213
+ context_str = f"instance {idx}"
172
214
 
173
215
  instance = InstanceModel.get_one_object(user=self.get_user(), idx=idx)
174
216
  if instance is None:
175
217
  err = "The instance to check does not exist"
176
218
  raise ObjectDoesNotExist(
177
219
  error=err,
178
- log_txt=f"Error while user {self.get_user()} tries to run data checks on instance {idx}. " + err
220
+ log_txt=f"Error while user {user} tries to run data checks on {context_str}. "
221
+ + err,
179
222
  )
180
223
  payload = dict(
181
224
  config=dict(checks_only=True),
@@ -185,67 +228,19 @@ class DataCheckInstanceEndpoint(BaseMetaResource):
185
228
  )
186
229
  schema = instance.schema
187
230
 
188
- execution, status_code = self.post_list(data=payload)
231
+ execution, _ = self.post_list(data=payload)
189
232
 
190
233
  # this allows testing without airflow interaction:
191
234
  if request.args.get("run", "1") == "0":
192
235
  execution.update_state(EXEC_STATE_NOT_RUN)
193
236
  return execution, 201
194
237
 
195
- # We now try to launch the task in airflow
238
+ # Initialize Airflow client
196
239
  af_client = Airflow.from_config(config)
197
- if not af_client.is_alive():
198
- err = "Airflow is not accessible"
199
- current_app.logger.error(err)
200
- execution.update_state(EXEC_STATE_ERROR_START)
201
- raise AirflowError(
202
- error=err,
203
- payload=dict(
204
- message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
205
- state=EXEC_STATE_ERROR_START,
206
- ),
207
- log_txt=f"Error while user {self.get_user()} tries to run data checks on instance {idx}. " + err
208
240
 
209
- )
210
- # ask airflow if dag_name exists
211
- schema_info = af_client.get_dag_info(schema)
212
-
213
- info = schema_info.json()
214
- if info["is_paused"]:
215
- err = "The dag exists but it is paused in airflow"
216
- current_app.logger.error(err)
217
- execution.update_state(EXEC_STATE_ERROR_START)
218
- raise AirflowError(
219
- error=err,
220
- payload=dict(
221
- message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
222
- state=EXEC_STATE_ERROR_START,
223
- ),
224
- log_txt=f"Error while user {self.get_user()} tries to run data checks on instance {idx}. " + err
225
-
226
- )
241
+ # Call the shared Airflow execution logic
242
+ _run_airflow_data_check(af_client, execution, schema, user, context_str)
227
243
 
228
- try:
229
- response = af_client.run_dag(
230
- execution.id, dag_name=schema, checks_only=True
231
- )
232
- except AirflowError as err:
233
- error = "Airflow responded with an error: {}".format(err)
234
- current_app.logger.error(error)
235
- execution.update_state(EXEC_STATE_ERROR)
236
- raise AirflowError(
237
- error=error,
238
- payload=dict(
239
- message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR],
240
- state=EXEC_STATE_ERROR,
241
- ),
242
- log_txt=f"Error while user {self.get_user()} tries to run data checks on instance {idx}. " + error
243
- )
244
-
245
- # if we succeed, we register the dag_run_id in the execution table:
246
- af_data = response.json()
247
- execution.dag_run_id = af_data["dag_run_id"]
248
- execution.update_state(EXEC_STATE_QUEUED)
249
244
  current_app.logger.info(
250
245
  "User {} creates instance check execution {}".format(
251
246
  self.get_user_id(), execution.id
@@ -258,7 +253,9 @@ class DataCheckCaseEndpoint(BaseMetaResource):
258
253
  """
259
254
  Endpoint used to execute the instance and solution checks on an execution
260
255
  """
256
+
261
257
  ROLES_WITH_ACCESS = [PLANNER_ROLE, ADMIN_ROLE]
258
+
262
259
  def __init__(self):
263
260
  super().__init__()
264
261
  self.model = ExecutionModel
@@ -283,13 +280,16 @@ class DataCheckCaseEndpoint(BaseMetaResource):
283
280
  :rtype: Tuple(dict, integer)
284
281
  """
285
282
  config = current_app.config
283
+ user = self.get_user()
284
+ context_str = f"case {idx}"
286
285
 
287
286
  case = CaseModel.get_one_object(user=self.get_user(), idx=idx)
288
287
  if case is None:
289
288
  err = "The case to check does not exist"
290
289
  raise ObjectDoesNotExist(
291
290
  error=err,
292
- log_txt=f"Error while user {self.get_user()} tries to run data checks on case {idx}. " + err
291
+ log_txt=f"Error while user {user} tries to run data checks on {context_str}. "
292
+ + err,
293
293
  )
294
294
 
295
295
  schema = case.schema or "solve_model_dag"
@@ -309,14 +309,18 @@ class DataCheckCaseEndpoint(BaseMetaResource):
309
309
  if schema == "pulp":
310
310
  validation_schema = "solve_model_dag"
311
311
 
312
- data_jsonschema = DeployedDAG.get_one_schema(config, validation_schema, INSTANCE_SCHEMA)
313
- validation_errors = json_schema_validate_as_string(data_jsonschema, instance_payload["data"])
312
+ data_jsonschema = DeployedDAG.get_one_schema(
313
+ config, validation_schema, INSTANCE_SCHEMA
314
+ )
315
+ validation_errors = json_schema_validate_as_string(
316
+ data_jsonschema, instance_payload["data"]
317
+ )
314
318
 
315
319
  if validation_errors:
316
320
  raise InvalidData(
317
321
  payload=dict(jsonschema_errors=validation_errors),
318
- log_txt=f"Error while user {self.get_user()} tries to run data checks on case {idx}. "
319
- f"Instance data does not match the jsonschema.",
322
+ log_txt=f"Error while user {user} tries to run data checks on {context_str}. "
323
+ f"Instance data does not match the jsonschema.",
320
324
  )
321
325
 
322
326
  instance, _ = self.post_list(data=instance_payload)
@@ -334,14 +338,18 @@ class DataCheckCaseEndpoint(BaseMetaResource):
334
338
 
335
339
  payload["data"] = case.solution
336
340
 
337
- data_jsonschema = DeployedDAG.get_one_schema(config, validation_schema, SOLUTION_SCHEMA)
338
- validation_errors = json_schema_validate_as_string(data_jsonschema, payload["data"])
341
+ data_jsonschema = DeployedDAG.get_one_schema(
342
+ config, validation_schema, SOLUTION_SCHEMA
343
+ )
344
+ validation_errors = json_schema_validate_as_string(
345
+ data_jsonschema, payload["data"]
346
+ )
339
347
 
340
348
  if validation_errors:
341
349
  raise InvalidData(
342
350
  payload=dict(jsonschema_errors=validation_errors),
343
- log_txt=f"Error while user {self.get_user()} tries to run data checks on case {idx}. "
344
- f"Solution data does not match the jsonschema.",
351
+ log_txt=f"Error while user {user} tries to run data checks on {context_str}. "
352
+ f"Solution data does not match the jsonschema.",
345
353
  )
346
354
 
347
355
  self.data_model = ExecutionModel
@@ -354,59 +362,14 @@ class DataCheckCaseEndpoint(BaseMetaResource):
354
362
  execution.update_state(EXEC_STATE_NOT_RUN)
355
363
  return execution, 201
356
364
 
357
- # We now try to launch the task in airflow
365
+ # Initialize Airflow client
358
366
  af_client = Airflow.from_config(config)
359
- if not af_client.is_alive():
360
- err = "Airflow is not accessible"
361
- current_app.logger.error(err)
362
- execution.update_state(EXEC_STATE_ERROR_START)
363
- raise AirflowError(
364
- error=err,
365
- payload=dict(
366
- message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
367
- state=EXEC_STATE_ERROR_START,
368
- ),
369
- log_txt=f"Error while user {self.get_user()} tries to run data checks on case {idx}. " + err
370
- )
371
- # ask airflow if dag_name exists
372
- schema_info = af_client.get_dag_info(schema)
373
-
374
- info = schema_info.json()
375
- if info["is_paused"]:
376
- err = "The dag exists but it is paused in airflow"
377
- current_app.logger.error(err)
378
- execution.update_state(EXEC_STATE_ERROR_START)
379
- raise AirflowError(
380
- error=err,
381
- payload=dict(
382
- message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
383
- state=EXEC_STATE_ERROR_START,
384
- ),
385
- log_txt=f"Error while user {self.get_user()} tries to run data checks on case {idx}. " + err
386
- )
387
-
388
- try:
389
- response = af_client.run_dag(
390
- execution.id, dag_name=schema, checks_only=True, case_id=idx
391
- )
392
367
 
393
- except AirflowError as err:
394
- error = "Airflow responded with an error: {}".format(err)
395
- current_app.logger.error(error)
396
- execution.update_state(EXEC_STATE_ERROR)
397
- raise AirflowError(
398
- error=error,
399
- payload=dict(
400
- message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR],
401
- state=EXEC_STATE_ERROR,
402
- ),
403
- log_txt=f"Error while user {self.get_user()} tries to run data checks on case {idx}. " + error
404
- )
368
+ # Call the shared Airflow execution logic, passing case_id
369
+ _run_airflow_data_check(
370
+ af_client, execution, schema, user, context_str, case_id=idx
371
+ )
405
372
 
406
- # if we succeed, we register the dag_run_id in the execution table:
407
- af_data = response.json()
408
- execution.dag_run_id = af_data["dag_run_id"]
409
- execution.update_state(EXEC_STATE_QUEUED)
410
373
  current_app.logger.info(
411
374
  "User {} creates case check execution {}".format(
412
375
  self.get_user_id(), execution.id
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Endpoints to get the example data from a DAG
3
3
  """
4
+
4
5
  import json
5
6
 
6
7
  from cornflow_client.airflow.api import Airflow
@@ -11,7 +12,12 @@ from cornflow.endpoints.meta_resource import BaseMetaResource
11
12
  from cornflow.models import PermissionsDAG
12
13
  from cornflow.schemas.example_data import ExampleListData, ExampleDetailData
13
14
  from cornflow.shared.authentication import Auth, authenticate
14
- from cornflow.shared.const import VIEWER_ROLE, PLANNER_ROLE, ADMIN_ROLE
15
+ from cornflow.shared.const import (
16
+ AIRFLOW_NOT_REACHABLE_MSG,
17
+ VIEWER_ROLE,
18
+ PLANNER_ROLE,
19
+ ADMIN_ROLE,
20
+ )
15
21
  from cornflow.shared.exceptions import AirflowError, NoPermission, ObjectDoesNotExist
16
22
 
17
23
 
@@ -44,7 +50,7 @@ class ExampleDataListEndpoint(BaseMetaResource):
44
50
  current_app.logger.error(
45
51
  "Airflow not accessible when getting data {}".format(dag_name)
46
52
  )
47
- raise AirflowError(error="Airflow is not accessible")
53
+ raise AirflowError(error=f"{AIRFLOW_NOT_REACHABLE_MSG}")
48
54
 
49
55
  # try airflow and see if dag_name exists
50
56
  af_client.get_dag_info(dag_name)
@@ -87,7 +93,7 @@ class ExampleDataDetailEndpoint(BaseMetaResource):
87
93
  current_app.logger.error(
88
94
  "Airflow not accessible when getting data {}".format(dag_name)
89
95
  )
90
- raise AirflowError(error="Airflow is not accessible")
96
+ raise AirflowError(error=f"{AIRFLOW_NOT_REACHABLE_MSG}")
91
97
 
92
98
  # try airflow and see if dag_name exists
93
99
  af_client.get_dag_info(dag_name)