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.
- cornflow/app.py +24 -8
- cornflow/cli/service.py +93 -43
- cornflow/commands/auxiliar.py +4 -1
- cornflow/commands/dag.py +7 -7
- cornflow/commands/permissions.py +13 -7
- cornflow/commands/views.py +3 -0
- cornflow/config.py +26 -6
- cornflow/endpoints/__init__.py +27 -0
- cornflow/endpoints/case.py +37 -21
- cornflow/endpoints/dag.py +5 -5
- cornflow/endpoints/data_check.py +8 -7
- cornflow/endpoints/example_data.py +4 -2
- cornflow/endpoints/execution.py +215 -127
- cornflow/endpoints/health.py +30 -11
- cornflow/endpoints/instance.py +3 -3
- cornflow/endpoints/login.py +9 -2
- cornflow/endpoints/permission.py +1 -2
- cornflow/endpoints/schemas.py +3 -3
- cornflow/endpoints/signup.py +17 -11
- cornflow/migrations/versions/999b98e24225.py +34 -0
- cornflow/migrations/versions/cef1df240b27_.py +34 -0
- cornflow/models/__init__.py +2 -1
- cornflow/models/dag.py +8 -9
- cornflow/models/dag_permissions.py +3 -3
- cornflow/models/execution.py +2 -3
- cornflow/models/permissions.py +1 -0
- cornflow/models/user.py +1 -1
- cornflow/schemas/execution.py +14 -1
- cornflow/schemas/health.py +1 -1
- cornflow/shared/authentication/auth.py +14 -1
- cornflow/shared/authentication/decorators.py +32 -2
- cornflow/shared/const.py +58 -1
- cornflow/shared/exceptions.py +2 -1
- cornflow/tests/base_test_execution.py +798 -0
- cornflow/tests/const.py +1 -0
- cornflow/tests/integration/test_commands.py +2 -2
- cornflow/tests/integration/test_cornflowclient.py +2 -1
- cornflow/tests/unit/test_apiview.py +7 -1
- cornflow/tests/unit/test_cases.py +1 -1
- cornflow/tests/unit/test_cli.py +6 -3
- cornflow/tests/unit/test_commands.py +7 -6
- cornflow/tests/unit/test_dags.py +3 -3
- cornflow/tests/unit/test_example_data.py +1 -1
- cornflow/tests/unit/test_executions.py +115 -535
- cornflow/tests/unit/test_get_resources.py +103 -0
- cornflow/tests/unit/test_health.py +84 -3
- cornflow/tests/unit/test_main_alarms.py +1 -1
- cornflow/tests/unit/test_roles.py +2 -1
- cornflow/tests/unit/test_schema_from_models.py +1 -1
- cornflow/tests/unit/test_schemas.py +1 -1
- cornflow/tests/unit/test_sign_up.py +181 -6
- cornflow/tests/unit/tools.py +93 -10
- {cornflow-1.2.3a5.dist-info → cornflow-1.3.0rc1.dist-info}/METADATA +3 -3
- {cornflow-1.2.3a5.dist-info → cornflow-1.3.0rc1.dist-info}/RECORD +57 -53
- {cornflow-1.2.3a5.dist-info → cornflow-1.3.0rc1.dist-info}/WHEEL +0 -0
- {cornflow-1.2.3a5.dist-info → cornflow-1.3.0rc1.dist-info}/entry_points.txt +0 -0
- {cornflow-1.2.3a5.dist-info → cornflow-1.3.0rc1.dist-info}/top_level.txt +0 -0
cornflow/endpoints/case.py
CHANGED
@@ -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,
|
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 =
|
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 =
|
90
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
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}. "
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
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
|
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 =
|
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 =
|
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 =
|
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 =
|
273
|
+
self.data_model = DeployedWorkflow
|
274
274
|
|
275
275
|
@doc(
|
276
276
|
description="Endpoint to update the schemas of a deployed DAG",
|
cornflow/endpoints/data_check.py
CHANGED
@@ -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,
|
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.
|
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.
|
89
|
-
execution.id,
|
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.
|
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 =
|
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 =
|
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.
|
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.
|
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
|
|