cornflow 1.2.3a5__py3-none-any.whl → 1.3.0rc1__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 (57) hide show
  1. cornflow/app.py +24 -8
  2. cornflow/cli/service.py +93 -43
  3. cornflow/commands/auxiliar.py +4 -1
  4. cornflow/commands/dag.py +7 -7
  5. cornflow/commands/permissions.py +13 -7
  6. cornflow/commands/views.py +3 -0
  7. cornflow/config.py +26 -6
  8. cornflow/endpoints/__init__.py +27 -0
  9. cornflow/endpoints/case.py +37 -21
  10. cornflow/endpoints/dag.py +5 -5
  11. cornflow/endpoints/data_check.py +8 -7
  12. cornflow/endpoints/example_data.py +4 -2
  13. cornflow/endpoints/execution.py +215 -127
  14. cornflow/endpoints/health.py +30 -11
  15. cornflow/endpoints/instance.py +3 -3
  16. cornflow/endpoints/login.py +9 -2
  17. cornflow/endpoints/permission.py +1 -2
  18. cornflow/endpoints/schemas.py +3 -3
  19. cornflow/endpoints/signup.py +17 -11
  20. cornflow/migrations/versions/999b98e24225.py +34 -0
  21. cornflow/migrations/versions/cef1df240b27_.py +34 -0
  22. cornflow/models/__init__.py +2 -1
  23. cornflow/models/dag.py +8 -9
  24. cornflow/models/dag_permissions.py +3 -3
  25. cornflow/models/execution.py +2 -3
  26. cornflow/models/permissions.py +1 -0
  27. cornflow/models/user.py +1 -1
  28. cornflow/schemas/execution.py +14 -1
  29. cornflow/schemas/health.py +1 -1
  30. cornflow/shared/authentication/auth.py +14 -1
  31. cornflow/shared/authentication/decorators.py +32 -2
  32. cornflow/shared/const.py +58 -1
  33. cornflow/shared/exceptions.py +2 -1
  34. cornflow/tests/base_test_execution.py +798 -0
  35. cornflow/tests/const.py +1 -0
  36. cornflow/tests/integration/test_commands.py +2 -2
  37. cornflow/tests/integration/test_cornflowclient.py +2 -1
  38. cornflow/tests/unit/test_apiview.py +7 -1
  39. cornflow/tests/unit/test_cases.py +1 -1
  40. cornflow/tests/unit/test_cli.py +6 -3
  41. cornflow/tests/unit/test_commands.py +7 -6
  42. cornflow/tests/unit/test_dags.py +3 -3
  43. cornflow/tests/unit/test_example_data.py +1 -1
  44. cornflow/tests/unit/test_executions.py +115 -535
  45. cornflow/tests/unit/test_get_resources.py +103 -0
  46. cornflow/tests/unit/test_health.py +84 -3
  47. cornflow/tests/unit/test_main_alarms.py +1 -1
  48. cornflow/tests/unit/test_roles.py +2 -1
  49. cornflow/tests/unit/test_schema_from_models.py +1 -1
  50. cornflow/tests/unit/test_schemas.py +1 -1
  51. cornflow/tests/unit/test_sign_up.py +181 -6
  52. cornflow/tests/unit/tools.py +93 -10
  53. {cornflow-1.2.3a5.dist-info → cornflow-1.3.0rc1.dist-info}/METADATA +3 -3
  54. {cornflow-1.2.3a5.dist-info → cornflow-1.3.0rc1.dist-info}/RECORD +57 -53
  55. {cornflow-1.2.3a5.dist-info → cornflow-1.3.0rc1.dist-info}/WHEEL +0 -0
  56. {cornflow-1.2.3a5.dist-info → cornflow-1.3.0rc1.dist-info}/entry_points.txt +0 -0
  57. {cornflow-1.2.3a5.dist-info → cornflow-1.3.0rc1.dist-info}/top_level.txt +0 -0
@@ -3,11 +3,9 @@ External endpoints to manage the cases: create new cases from raw data, from an
3
3
  or from an existing case, update the case info, patch its data, get all of them or one, move them and delete them.
4
4
  These endpoints have different access url, but manage the same data entities
5
5
  """
6
+
6
7
  # Import from libraries
7
- from cornflow_client.constants import (
8
- INSTANCE_SCHEMA,
9
- SOLUTION_SCHEMA
10
- )
8
+ from cornflow_client.constants import INSTANCE_SCHEMA, SOLUTION_SCHEMA
11
9
  from flask import current_app
12
10
  from flask_apispec import marshal_with, use_kwargs, doc
13
11
  from flask_inflate import inflate
@@ -16,7 +14,7 @@ import jsonpatch
16
14
 
17
15
  # Import from internal modules
18
16
  from cornflow.endpoints.meta_resource import BaseMetaResource
19
- from cornflow.models import CaseModel, ExecutionModel, DeployedDAG, InstanceModel
17
+ from cornflow.models import CaseModel, ExecutionModel, DeployedWorkflow, InstanceModel
20
18
  from cornflow.shared.authentication import Auth, authenticate
21
19
  from cornflow.shared.compress import compressed
22
20
  from cornflow.shared.const import VIEWER_ROLE, PLANNER_ROLE, ADMIN_ROLE
@@ -40,6 +38,7 @@ class CaseEndpoint(BaseMetaResource):
40
38
  """
41
39
  Endpoint used to create a new case or get all the cases and their related information
42
40
  """
41
+
43
42
  ROLES_WITH_ACCESS = [VIEWER_ROLE, PLANNER_ROLE, ADMIN_ROLE]
44
43
 
45
44
  def __init__(self):
@@ -79,15 +78,21 @@ class CaseEndpoint(BaseMetaResource):
79
78
 
80
79
  # We validate the instance data if it exists
81
80
  if kwargs.get("data") is not None:
82
- data_schema = DeployedDAG.get_one_schema(config, schema, INSTANCE_SCHEMA)
81
+ data_schema = DeployedWorkflow.get_one_schema(
82
+ config, schema, INSTANCE_SCHEMA
83
+ )
83
84
  data_errors = json_schema_validate_as_string(data_schema, kwargs["data"])
84
85
  if data_errors:
85
86
  raise InvalidData(payload=dict(jsonschema_errors=data_errors))
86
87
 
87
88
  # And the solution data if it exists
88
89
  if kwargs.get("solution") is not None:
89
- solution_schema = DeployedDAG.get_one_schema(config, schema, SOLUTION_SCHEMA)
90
- solution_errors = json_schema_validate_as_string(solution_schema, kwargs["solution"])
90
+ solution_schema = DeployedWorkflow.get_one_schema(
91
+ config, schema, SOLUTION_SCHEMA
92
+ )
93
+ solution_errors = json_schema_validate_as_string(
94
+ solution_schema, kwargs["solution"]
95
+ )
91
96
  if solution_errors:
92
97
  raise InvalidData(payload=dict(jsonschema_errors=solution_errors))
93
98
 
@@ -102,6 +107,7 @@ class CaseFromInstanceExecutionEndpoint(BaseMetaResource):
102
107
  """
103
108
  Endpoint used to create a new case from an already existing instance and execution
104
109
  """
110
+
105
111
  ROLES_WITH_ACCESS = [PLANNER_ROLE, ADMIN_ROLE]
106
112
 
107
113
  def __init__(self):
@@ -129,7 +135,7 @@ class CaseFromInstanceExecutionEndpoint(BaseMetaResource):
129
135
  error="You must provide a valid instance_id OR an execution_id",
130
136
  status_code=400,
131
137
  log_txt=f"Error while user {self.get_user()} tries to create case from instance and execution. "
132
- f"The instance id or execution id is not valid."
138
+ f"The instance id or execution id is not valid.",
133
139
  )
134
140
  user = self.get_user()
135
141
 
@@ -140,7 +146,7 @@ class CaseFromInstanceExecutionEndpoint(BaseMetaResource):
140
146
  raise ObjectDoesNotExist(
141
147
  err,
142
148
  log_txt=f"Error while user {self.get_user()} tries to create case "
143
- f"from instance and execution. " + err
149
+ f"from instance and execution. " + err,
144
150
  )
145
151
  return dict(
146
152
  data=instance.data, schema=instance.schema, checks=instance.checks
@@ -153,7 +159,7 @@ class CaseFromInstanceExecutionEndpoint(BaseMetaResource):
153
159
  raise ObjectDoesNotExist(
154
160
  err,
155
161
  log_txt=f"Error while user {self.get_user()} tries to create "
156
- f"case from instance and execution. " + err
162
+ f"case from instance and execution. " + err,
157
163
  )
158
164
  data = get_instance_data(execution.instance_id)
159
165
  data["solution"] = execution.data
@@ -179,6 +185,7 @@ class CaseCopyEndpoint(BaseMetaResource):
179
185
  """
180
186
  Copies the case to a new case. Original case id goes in the url
181
187
  """
188
+
182
189
  ROLES_WITH_ACCESS = [PLANNER_ROLE, ADMIN_ROLE]
183
190
 
184
191
  def __init__(self):
@@ -214,7 +221,9 @@ class CaseCopyEndpoint(BaseMetaResource):
214
221
  payload[key] = "Copy_" + data[key]
215
222
 
216
223
  response = self.post_list(payload)
217
- current_app.logger.info(f"User {self.get_user()} copied case {idx} into {response[0].id}")
224
+ current_app.logger.info(
225
+ f"User {self.get_user()} copied case {idx} into {response[0].id}"
226
+ )
218
227
  return response
219
228
 
220
229
 
@@ -222,6 +231,7 @@ class CaseDetailsEndpoint(BaseMetaResource):
222
231
  """
223
232
  Endpoint used to get the information of a single case, edit it or delete it
224
233
  """
234
+
225
235
  ROLES_WITH_ACCESS = [VIEWER_ROLE, PLANNER_ROLE, ADMIN_ROLE]
226
236
 
227
237
  def __init__(self):
@@ -264,7 +274,8 @@ class CaseDetailsEndpoint(BaseMetaResource):
264
274
  err = "The data entity does not exist on the database."
265
275
  raise ObjectDoesNotExist(
266
276
  err,
267
- log_txt=f"Error while user {self.get_user()} tries to edit case {idx}. " + err
277
+ log_txt=f"Error while user {self.get_user()} tries to edit case {idx}. "
278
+ + err,
268
279
  )
269
280
  if parent_id is not None:
270
281
  parent_case = self.data_model.get_one_object(
@@ -275,7 +286,7 @@ class CaseDetailsEndpoint(BaseMetaResource):
275
286
  raise ObjectDoesNotExist(
276
287
  err,
277
288
  log_txt=f"Error while user {self.get_user()} tries to move "
278
- f"case {idx} to directory {idx}. " + err
289
+ f"case {idx} to directory {idx}. " + err,
279
290
  )
280
291
 
281
292
  case.move_to(parent_case)
@@ -302,6 +313,7 @@ class CaseDataEndpoint(CaseDetailsEndpoint):
302
313
  """
303
314
  Endpoint used to get the data of a given case
304
315
  """
316
+
305
317
  ROLES_WITH_ACCESS = [VIEWER_ROLE, PLANNER_ROLE, ADMIN_ROLE]
306
318
 
307
319
  @doc(description="Get data of a case", tags=["Cases"], inherit=False)
@@ -338,6 +350,7 @@ class CaseToInstance(BaseMetaResource):
338
350
  """
339
351
  Endpoint used to create a new instance or instance and execution from a stored case
340
352
  """
353
+
341
354
  ROLES_WITH_ACCESS = [PLANNER_ROLE, ADMIN_ROLE]
342
355
 
343
356
  def __init__(self):
@@ -383,13 +396,13 @@ class CaseToInstance(BaseMetaResource):
383
396
  config = current_app.config
384
397
 
385
398
  # Data validation
386
- jsonschema = DeployedDAG.get_one_schema(config, schema, INSTANCE_SCHEMA)
399
+ jsonschema = DeployedWorkflow.get_one_schema(config, schema, INSTANCE_SCHEMA)
387
400
  data_errors = json_schema_validate_as_string(jsonschema, payload["data"])
388
401
  if data_errors:
389
402
  raise InvalidData(
390
403
  payload=dict(jsonschema_errors=data_errors),
391
404
  log_txt=f"Error while user {self.get_user()} tries to create instance from case {idx}. "
392
- f"Data do not match the jsonschema.",
405
+ f"Data do not match the jsonschema.",
393
406
  )
394
407
 
395
408
  response = self.post_list(payload)
@@ -403,6 +416,7 @@ class CaseCompare(BaseMetaResource):
403
416
  """
404
417
  Endpoint used to generate the json patch of two given cases
405
418
  """
419
+
406
420
  ROLES_WITH_ACCESS = [VIEWER_ROLE, PLANNER_ROLE, ADMIN_ROLE]
407
421
 
408
422
  def __init__(self):
@@ -435,7 +449,7 @@ class CaseCompare(BaseMetaResource):
435
449
  "The case identifiers should be different.",
436
450
  400,
437
451
  log_txt=f"Error while user {self.get_user()} tries to compare cases. "
438
- f"The cases to compare have the same identifier."
452
+ f"The cases to compare have the same identifier.",
439
453
  )
440
454
  case_1 = self.model.get_one_object(user=self.get_user(), idx=idx1)
441
455
  case_2 = self.model.get_one_object(user=self.get_user(), idx=idx2)
@@ -444,19 +458,19 @@ class CaseCompare(BaseMetaResource):
444
458
  raise ObjectDoesNotExist(
445
459
  "You don't have access to the first case or it doesn't exist",
446
460
  log_txt=f"Error while user {self.get_user()} tries to compare cases {idx1} and {idx2}. "
447
- f"The user doesn't have access to case {idx1} or it does not exist."
461
+ f"The user doesn't have access to case {idx1} or it does not exist.",
448
462
  )
449
463
  elif case_2 is None:
450
464
  raise ObjectDoesNotExist(
451
465
  "You don't have access to the second case or it doesn't exist",
452
466
  log_txt=f"Error while user {self.get_user()} tries to compare cases {idx1} and {idx2}. "
453
- f"The user doesn't have access to case {idx2} or it does not exist."
467
+ f"The user doesn't have access to case {idx2} or it does not exist.",
454
468
  )
455
469
  elif case_1.schema != case_2.schema:
456
470
  raise InvalidData(
457
471
  "The cases asked to compare do not share the same schema",
458
472
  log_txt=f"Error while user {self.get_user()} tries to compare cases {idx1} and {idx2}. "
459
- f"The cases don't have the same schemas."
473
+ f"The cases don't have the same schemas.",
460
474
  )
461
475
 
462
476
  data = kwargs.get("data", True)
@@ -471,5 +485,7 @@ class CaseCompare(BaseMetaResource):
471
485
  ).patch
472
486
 
473
487
  payload["schema"] = case_1.schema
474
- current_app.logger.info(f"User {self.get_user()} compared cases {idx1} and {idx2}")
488
+ current_app.logger.info(
489
+ f"User {self.get_user()} compared cases {idx1} and {idx2}"
490
+ )
475
491
  return payload, 200
cornflow/endpoints/dag.py CHANGED
@@ -10,7 +10,7 @@ from flask_apispec import use_kwargs, doc, marshal_with
10
10
 
11
11
  # Import from internal modules
12
12
  from cornflow.endpoints.meta_resource import BaseMetaResource
13
- from cornflow.models import DeployedDAG, ExecutionModel, InstanceModel, CaseModel
13
+ from cornflow.models import DeployedWorkflow, ExecutionModel, InstanceModel, CaseModel
14
14
  from cornflow.schemas import DeployedDAGSchema, DeployedDAGEditSchema
15
15
  from cornflow.schemas.case import CaseCheckRequest
16
16
  from cornflow.schemas.instance import InstanceCheckRequest
@@ -118,7 +118,7 @@ class DAGDetailEndpoint(BaseMetaResource):
118
118
  if solution_schema is not None:
119
119
  config = current_app.config
120
120
 
121
- solution_schema = DeployedDAG.get_one_schema(
121
+ solution_schema = DeployedWorkflow.get_one_schema(
122
122
  config, solution_schema, SOLUTION_SCHEMA
123
123
  )
124
124
  solution_errors = json_schema_validate_as_string(solution_schema, data)
@@ -215,7 +215,7 @@ 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(
218
+ solution_schema = DeployedWorkflow.get_one_schema(
219
219
  config, solution_schema, SOLUTION_SCHEMA
220
220
  )
221
221
  solution_errors = json_schema_validate_as_string(solution_schema, data)
@@ -246,7 +246,7 @@ class DeployedDAGEndpoint(BaseMetaResource):
246
246
 
247
247
  def __init__(self):
248
248
  super().__init__()
249
- self.data_model = DeployedDAG
249
+ self.data_model = DeployedWorkflow
250
250
 
251
251
  @doc(
252
252
  description="Get list of deployed dags registered on the data base",
@@ -270,7 +270,7 @@ class DeployedDagDetailEndpoint(BaseMetaResource):
270
270
 
271
271
  def __init__(self):
272
272
  super().__init__()
273
- self.data_model = DeployedDAG
273
+ self.data_model = DeployedWorkflow
274
274
 
275
275
  @doc(
276
276
  description="Endpoint to update the schemas of a deployed DAG",
@@ -10,7 +10,7 @@ from flask_apispec import marshal_with, doc
10
10
 
11
11
  # Import from internal modules
12
12
  from cornflow.endpoints.meta_resource import BaseMetaResource
13
- from cornflow.models import InstanceModel, ExecutionModel, CaseModel, DeployedDAG
13
+ from cornflow.models import InstanceModel, ExecutionModel, CaseModel, DeployedWorkflow
14
14
  from cornflow.schemas.execution import ExecutionDetailsEndpointResponse
15
15
  from cornflow.shared.authentication import Auth, authenticate
16
16
  from cornflow.shared.const import (
@@ -22,6 +22,7 @@ from cornflow.shared.const import (
22
22
  EXEC_STATE_ERROR_START,
23
23
  EXEC_STATE_NOT_RUN,
24
24
  EXECUTION_STATE_MESSAGE_DICT,
25
+ VIEWER_ROLE,
25
26
  PLANNER_ROLE,
26
27
  ADMIN_ROLE,
27
28
  )
@@ -68,7 +69,7 @@ def _run_airflow_data_check(
68
69
  )
69
70
 
70
71
  # Check if DAG is paused
71
- schema_info = af_client.get_dag_info(schema)
72
+ schema_info = af_client.get_workflow_info(workflow_name=schema)
72
73
  info = schema_info.json()
73
74
  if info.get("is_paused", False):
74
75
  current_app.logger.error(DAG_PAUSED_MSG)
@@ -85,8 +86,8 @@ def _run_airflow_data_check(
85
86
 
86
87
  # Run the DAG
87
88
  try:
88
- response = af_client.run_dag(
89
- execution.id, dag_name=schema, checks_only=True, **run_dag_kwargs
89
+ response = af_client.run_workflow(
90
+ execution.id, workflow_name=schema, checks_only=True, **run_dag_kwargs
90
91
  )
91
92
  except AirflowError as err:
92
93
  error = f"{AIRFLOW_ERROR_MSG} {err}"
@@ -104,7 +105,7 @@ def _run_airflow_data_check(
104
105
 
105
106
  # Update execution on success
106
107
  af_data = response.json()
107
- execution.dag_run_id = af_data.get("dag_run_id")
108
+ execution.run_id = af_data.get("dag_run_id")
108
109
  execution.update_state(EXEC_STATE_QUEUED)
109
110
 
110
111
 
@@ -309,7 +310,7 @@ class DataCheckCaseEndpoint(BaseMetaResource):
309
310
  if schema == "pulp":
310
311
  validation_schema = "solve_model_dag"
311
312
 
312
- data_jsonschema = DeployedDAG.get_one_schema(
313
+ data_jsonschema = DeployedWorkflow.get_one_schema(
313
314
  config, validation_schema, INSTANCE_SCHEMA
314
315
  )
315
316
  validation_errors = json_schema_validate_as_string(
@@ -338,7 +339,7 @@ class DataCheckCaseEndpoint(BaseMetaResource):
338
339
 
339
340
  payload["data"] = case.solution
340
341
 
341
- data_jsonschema = DeployedDAG.get_one_schema(
342
+ data_jsonschema = DeployedWorkflow.get_one_schema(
342
343
  config, validation_schema, SOLUTION_SCHEMA
343
344
  )
344
345
  validation_errors = json_schema_validate_as_string(
@@ -26,6 +26,8 @@ class ExampleDataListEndpoint(BaseMetaResource):
26
26
  Endpoint used to obtain schemas for one app
27
27
  """
28
28
 
29
+ # TODO: DATABRICKS NOT IMPLEMENTED YET
30
+
29
31
  ROLES_WITH_ACCESS = [VIEWER_ROLE, PLANNER_ROLE, ADMIN_ROLE]
30
32
 
31
33
  @doc(description="Get lsit of example data from DAG", tags=["DAG"])
@@ -53,7 +55,7 @@ class ExampleDataListEndpoint(BaseMetaResource):
53
55
  raise AirflowError(error=f"{AIRFLOW_NOT_REACHABLE_MSG}")
54
56
 
55
57
  # try airflow and see if dag_name exists
56
- af_client.get_dag_info(dag_name)
58
+ af_client.get_workflow_info(workflow_name=dag_name)
57
59
 
58
60
  current_app.logger.info("User gets example data from {}".format(dag_name))
59
61
 
@@ -96,7 +98,7 @@ class ExampleDataDetailEndpoint(BaseMetaResource):
96
98
  raise AirflowError(error=f"{AIRFLOW_NOT_REACHABLE_MSG}")
97
99
 
98
100
  # try airflow and see if dag_name exists
99
- af_client.get_dag_info(dag_name)
101
+ af_client.get_workflow_info(workflow_name=dag_name)
100
102
 
101
103
  current_app.logger.info("User gets example data from {}".format(dag_name))
102
104