cornflow 2.0.0a11__py3-none-any.whl → 2.0.0a13__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.
- airflow_config/airflow_local_settings.py +1 -1
- cornflow/app.py +8 -3
- cornflow/cli/migrations.py +23 -3
- cornflow/cli/service.py +18 -18
- cornflow/cli/utils.py +16 -1
- cornflow/commands/dag.py +1 -1
- cornflow/config.py +13 -8
- cornflow/endpoints/__init__.py +8 -2
- cornflow/endpoints/alarms.py +66 -2
- cornflow/endpoints/data_check.py +53 -26
- cornflow/endpoints/execution.py +387 -132
- cornflow/endpoints/login.py +81 -63
- cornflow/endpoints/meta_resource.py +11 -3
- cornflow/migrations/versions/999b98e24225.py +34 -0
- cornflow/models/base_data_model.py +4 -32
- cornflow/models/execution.py +2 -3
- cornflow/models/meta_models.py +28 -22
- cornflow/models/user.py +7 -10
- cornflow/schemas/alarms.py +8 -0
- cornflow/schemas/execution.py +2 -1
- cornflow/schemas/query.py +2 -1
- cornflow/schemas/user.py +5 -20
- cornflow/shared/authentication/auth.py +201 -264
- cornflow/shared/const.py +3 -14
- cornflow/shared/databricks.py +5 -1
- cornflow/tests/const.py +2 -0
- cornflow/tests/custom_test_case.py +77 -26
- cornflow/tests/unit/test_actions.py +2 -2
- cornflow/tests/unit/test_alarms.py +55 -1
- cornflow/tests/unit/test_apiview.py +108 -3
- cornflow/tests/unit/test_cases.py +20 -29
- cornflow/tests/unit/test_cli.py +6 -5
- cornflow/tests/unit/test_commands.py +3 -3
- cornflow/tests/unit/test_dags.py +5 -6
- cornflow/tests/unit/test_executions.py +491 -118
- cornflow/tests/unit/test_instances.py +14 -2
- cornflow/tests/unit/test_instances_file.py +1 -1
- cornflow/tests/unit/test_licenses.py +1 -1
- cornflow/tests/unit/test_log_in.py +230 -207
- cornflow/tests/unit/test_permissions.py +8 -8
- cornflow/tests/unit/test_roles.py +48 -10
- cornflow/tests/unit/test_schemas.py +1 -1
- cornflow/tests/unit/test_tables.py +7 -7
- cornflow/tests/unit/test_token.py +19 -5
- cornflow/tests/unit/test_users.py +22 -6
- cornflow/tests/unit/tools.py +75 -10
- {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a13.dist-info}/METADATA +16 -15
- {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a13.dist-info}/RECORD +51 -51
- {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a13.dist-info}/WHEEL +1 -1
- cornflow/endpoints/execution_databricks.py +0 -808
- {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a13.dist-info}/entry_points.txt +0 -0
- {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a13.dist-info}/top_level.txt +0 -0
@@ -43,7 +43,7 @@ class TestPermissionsViewRoleEndpoint(CustomTestCase):
|
|
43
43
|
follow_redirects=True,
|
44
44
|
headers={
|
45
45
|
"Content-Type": "application/json",
|
46
|
-
"Authorization": "Bearer
|
46
|
+
"Authorization": f"Bearer {self.token}",
|
47
47
|
},
|
48
48
|
)
|
49
49
|
|
@@ -58,7 +58,7 @@ class TestPermissionsViewRoleEndpoint(CustomTestCase):
|
|
58
58
|
follow_redirects=True,
|
59
59
|
headers={
|
60
60
|
"Content-Type": "application/json",
|
61
|
-
"Authorization": "Bearer
|
61
|
+
"Authorization": f"Bearer {self.token}",
|
62
62
|
},
|
63
63
|
)
|
64
64
|
|
@@ -103,7 +103,7 @@ class TestPermissionViewRolesDetailEndpoint(CustomTestCase):
|
|
103
103
|
data=json.dumps(self.payload),
|
104
104
|
headers={
|
105
105
|
"Content-Type": "application/json",
|
106
|
-
"Authorization": "Bearer
|
106
|
+
"Authorization": f"Bearer {self.token}",
|
107
107
|
},
|
108
108
|
).json["id"]
|
109
109
|
view_id = 2
|
@@ -127,7 +127,7 @@ class TestPermissionViewRolesDetailEndpoint(CustomTestCase):
|
|
127
127
|
data=json.dumps(self.payload),
|
128
128
|
headers={
|
129
129
|
"Content-Type": "application/json",
|
130
|
-
"Authorization": "Bearer
|
130
|
+
"Authorization": f"Bearer {self.token}",
|
131
131
|
},
|
132
132
|
).json["id"]
|
133
133
|
for role in ROLES_MAP:
|
@@ -151,7 +151,7 @@ class TestPermissionViewRolesDetailEndpoint(CustomTestCase):
|
|
151
151
|
data=json.dumps(self.payload),
|
152
152
|
headers={
|
153
153
|
"Content-Type": "application/json",
|
154
|
-
"Authorization": "Bearer
|
154
|
+
"Authorization": f"Bearer {self.token}",
|
155
155
|
},
|
156
156
|
).json["id"]
|
157
157
|
|
@@ -160,7 +160,7 @@ class TestPermissionViewRolesDetailEndpoint(CustomTestCase):
|
|
160
160
|
follow_redirects=True,
|
161
161
|
headers={
|
162
162
|
"Content-Type": "application/json",
|
163
|
-
"Authorization": "Bearer
|
163
|
+
"Authorization": f"Bearer {self.token}",
|
164
164
|
},
|
165
165
|
)
|
166
166
|
self.assertEqual(200, response.status_code)
|
@@ -174,7 +174,7 @@ class TestPermissionViewRolesDetailEndpoint(CustomTestCase):
|
|
174
174
|
data=json.dumps(self.payload),
|
175
175
|
headers={
|
176
176
|
"Content-Type": "application/json",
|
177
|
-
"Authorization": "Bearer
|
177
|
+
"Authorization": f"Bearer {self.token}",
|
178
178
|
},
|
179
179
|
).json["id"]
|
180
180
|
|
@@ -186,7 +186,7 @@ class TestPermissionViewRolesDetailEndpoint(CustomTestCase):
|
|
186
186
|
follow_redirects=True,
|
187
187
|
headers={
|
188
188
|
"Content-Type": "application/json",
|
189
|
-
"Authorization": "Bearer
|
189
|
+
"Authorization": f"Bearer {self.token}",
|
190
190
|
},
|
191
191
|
)
|
192
192
|
self.assertEqual(403, response.status_code)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
"""
|
2
2
|
Unit test for the role endpoints
|
3
3
|
"""
|
4
|
+
|
4
5
|
import json
|
5
6
|
import logging as log
|
6
7
|
from cornflow.models import PermissionViewRoleModel, RoleModel
|
@@ -47,7 +48,7 @@ class TestRolesListEndpoint(CustomTestCase):
|
|
47
48
|
follow_redirects=True,
|
48
49
|
headers={
|
49
50
|
"Content-Type": "application/json",
|
50
|
-
"Authorization": "Bearer
|
51
|
+
"Authorization": f"Bearer {self.token}",
|
51
52
|
},
|
52
53
|
)
|
53
54
|
self.assertEqual(200, response.status_code)
|
@@ -63,7 +64,7 @@ class TestRolesListEndpoint(CustomTestCase):
|
|
63
64
|
follow_redirects=True,
|
64
65
|
headers={
|
65
66
|
"Content-Type": "application/json",
|
66
|
-
"Authorization": "Bearer
|
67
|
+
"Authorization": f"Bearer {self.token}",
|
67
68
|
},
|
68
69
|
)
|
69
70
|
|
@@ -75,7 +76,7 @@ class TestRolesListEndpoint(CustomTestCase):
|
|
75
76
|
follow_redirects=True,
|
76
77
|
headers={
|
77
78
|
"Content-Type": "application/json",
|
78
|
-
"Authorization": "Bearer
|
79
|
+
"Authorization": f"Bearer {self.token}",
|
79
80
|
},
|
80
81
|
)
|
81
82
|
|
@@ -91,7 +92,7 @@ class TestRolesListEndpoint(CustomTestCase):
|
|
91
92
|
follow_redirects=True,
|
92
93
|
headers={
|
93
94
|
"Content-Type": "application/json",
|
94
|
-
"Authorization": "Bearer
|
95
|
+
"Authorization": f"Bearer {self.token}",
|
95
96
|
},
|
96
97
|
)
|
97
98
|
self.assertEqual(403, response.status_code)
|
@@ -157,7 +158,7 @@ class TestRolesDetailEndpoint(CustomTestCase):
|
|
157
158
|
follow_redirects=True,
|
158
159
|
headers={
|
159
160
|
"Content-Type": "application/json",
|
160
|
-
"Authorization": "Bearer
|
161
|
+
"Authorization": f"Bearer {self.token}",
|
161
162
|
},
|
162
163
|
)
|
163
164
|
|
@@ -191,7 +192,7 @@ class TestRolesDetailEndpoint(CustomTestCase):
|
|
191
192
|
follow_redirects=True,
|
192
193
|
headers={
|
193
194
|
"Content-Type": "application/json",
|
194
|
-
"Authorization": "Bearer
|
195
|
+
"Authorization": f"Bearer {self.token}",
|
195
196
|
},
|
196
197
|
)
|
197
198
|
|
@@ -203,7 +204,7 @@ class TestRolesDetailEndpoint(CustomTestCase):
|
|
203
204
|
follow_redirects=True,
|
204
205
|
headers={
|
205
206
|
"Content-Type": "application/json",
|
206
|
-
"Authorization": "Bearer
|
207
|
+
"Authorization": f"Bearer {self.token}",
|
207
208
|
},
|
208
209
|
)
|
209
210
|
|
@@ -246,7 +247,7 @@ class TestUserRolesListEndpoint(CustomTestCase):
|
|
246
247
|
follow_redirects=True,
|
247
248
|
headers={
|
248
249
|
"Content-Type": "application/json",
|
249
|
-
"Authorization": "Bearer
|
250
|
+
"Authorization": f"Bearer {self.token}",
|
250
251
|
},
|
251
252
|
)
|
252
253
|
self.assertEqual(200, response.status_code)
|
@@ -261,7 +262,7 @@ class TestUserRolesListEndpoint(CustomTestCase):
|
|
261
262
|
follow_redirects=True,
|
262
263
|
headers={
|
263
264
|
"Content-Type": "application/json",
|
264
|
-
"Authorization": "Bearer
|
265
|
+
"Authorization": f"Bearer {self.token}",
|
265
266
|
},
|
266
267
|
)
|
267
268
|
self.assertEqual(403, response.status_code)
|
@@ -431,7 +432,7 @@ class TestRolesModelMethods(CustomTestCase):
|
|
431
432
|
self.payload = {"name": "test_role"}
|
432
433
|
|
433
434
|
def test_user_role_delete_cascade(self):
|
434
|
-
payload = {"user_id": self.user}
|
435
|
+
payload = {"user_id": self.user.id}
|
435
436
|
self.token = self.create_user_with_role(ADMIN_ROLE)
|
436
437
|
self.cascade_delete(
|
437
438
|
self.url,
|
@@ -472,3 +473,40 @@ class TestRolesModelMethods(CustomTestCase):
|
|
472
473
|
self.token = self.create_user_with_role(ADMIN_ROLE)
|
473
474
|
idx = self.create_new_row(self.url, self.model, self.payload)
|
474
475
|
self.str_method(idx, "<Role test_role>")
|
476
|
+
|
477
|
+
def test_get_all_objects(self):
|
478
|
+
"""
|
479
|
+
Tests the get_all_objects method
|
480
|
+
"""
|
481
|
+
# We expect 4 roles to be present (from ROLES_MAP constant)
|
482
|
+
instances = RoleModel.get_all_objects().all()
|
483
|
+
self.assertEqual(len(instances), 4)
|
484
|
+
|
485
|
+
# Check that all the roles from ROLES_MAP are present
|
486
|
+
role_names = [role.name for role in instances]
|
487
|
+
expected_names = list(ROLES_MAP.values())
|
488
|
+
self.assertCountEqual(role_names, expected_names)
|
489
|
+
|
490
|
+
# Test offset parameter - should get all except the first role
|
491
|
+
instances = RoleModel.get_all_objects(offset=1).all()
|
492
|
+
self.assertEqual(len(instances), 3)
|
493
|
+
|
494
|
+
# Get the names of all roles except the first one
|
495
|
+
remaining_roles = [role.name for role in instances]
|
496
|
+
# The first role is skipped due to offset=1
|
497
|
+
first_role = RoleModel.get_all_objects().first().name
|
498
|
+
self.assertNotIn(first_role, remaining_roles)
|
499
|
+
|
500
|
+
# Test offset and limit parameters
|
501
|
+
instances = RoleModel.get_all_objects(offset=1, limit=1).all()
|
502
|
+
self.assertEqual(len(instances), 1)
|
503
|
+
|
504
|
+
# Verify that we get the second role when using offset=1 and limit=1
|
505
|
+
second_role = RoleModel.get_all_objects().all()[1]
|
506
|
+
self.assertEqual(instances[0].id, second_role.id)
|
507
|
+
self.assertEqual(instances[0].name, second_role.name)
|
508
|
+
|
509
|
+
# Test filtering by name
|
510
|
+
instances = RoleModel.get_all_objects(limit=1, name="admin").all()
|
511
|
+
self.assertEqual(len(instances), 1)
|
512
|
+
self.assertEqual(instances[0].name, "admin")
|
@@ -189,7 +189,7 @@ class TestNewSchemaEndpointOpen(CustomTestCase):
|
|
189
189
|
|
190
190
|
def test_get_all_schemas(self):
|
191
191
|
schemas = self.get_one_row(self.url, {}, 200, False, keys_to_check=["name"])
|
192
|
-
dags = [{"name": dag} for dag in ["solve_model_dag", "gc", "timer"]]
|
192
|
+
dags = [{"name": dag} for dag in ["solve_model_dag", "gc", "timer", "979073949072767"]]
|
193
193
|
|
194
194
|
self.assertEqual(dags, schemas)
|
195
195
|
|
@@ -81,7 +81,7 @@ class TestTablesListEndpoint(TestCase):
|
|
81
81
|
follow_redirects=True,
|
82
82
|
headers={
|
83
83
|
"Content-Type": "application/json",
|
84
|
-
"Authorization": "Bearer
|
84
|
+
"Authorization": f"Bearer {self.token}",
|
85
85
|
},
|
86
86
|
)
|
87
87
|
self.assertEqual(response.status_code, 200)
|
@@ -111,7 +111,7 @@ class TestTablesListEndpoint(TestCase):
|
|
111
111
|
follow_redirects=True,
|
112
112
|
headers={
|
113
113
|
"Content-Type": "application/json",
|
114
|
-
"Authorization": "Bearer
|
114
|
+
"Authorization": f"Bearer {self.token}",
|
115
115
|
},
|
116
116
|
query_string=dict(limit=3),
|
117
117
|
)
|
@@ -187,7 +187,7 @@ class TestTablesDetailEndpoint(TestCase):
|
|
187
187
|
follow_redirects=True,
|
188
188
|
headers={
|
189
189
|
"Content-Type": "application/json",
|
190
|
-
"Authorization": "Bearer
|
190
|
+
"Authorization": f"Bearer {self.token}",
|
191
191
|
},
|
192
192
|
)
|
193
193
|
|
@@ -204,7 +204,7 @@ class TestTablesDetailEndpoint(TestCase):
|
|
204
204
|
follow_redirects=True,
|
205
205
|
headers={
|
206
206
|
"Content-Type": "application/json",
|
207
|
-
"Authorization": "Bearer
|
207
|
+
"Authorization": f"Bearer {self.token}",
|
208
208
|
},
|
209
209
|
)
|
210
210
|
self.assertEqual(response.status_code, 400)
|
@@ -216,7 +216,7 @@ class TestTablesDetailEndpoint(TestCase):
|
|
216
216
|
follow_redirects=True,
|
217
217
|
headers={
|
218
218
|
"Content-Type": "application/json",
|
219
|
-
"Authorization": "Bearer
|
219
|
+
"Authorization": f"Bearer {self.token}",
|
220
220
|
},
|
221
221
|
)
|
222
222
|
self.assertEqual(response.status_code, 404)
|
@@ -270,7 +270,7 @@ class TestTablesEndpointAdmin(TestCase):
|
|
270
270
|
follow_redirects=True,
|
271
271
|
headers={
|
272
272
|
"Content-Type": "application/json",
|
273
|
-
"Authorization": "Bearer
|
273
|
+
"Authorization": f"Bearer {self.token}",
|
274
274
|
},
|
275
275
|
)
|
276
276
|
self.assertEqual(response.status_code, 403)
|
@@ -284,7 +284,7 @@ class TestTablesEndpointAdmin(TestCase):
|
|
284
284
|
follow_redirects=True,
|
285
285
|
headers={
|
286
286
|
"Content-Type": "application/json",
|
287
|
-
"Authorization": "Bearer
|
287
|
+
"Authorization": f"Bearer {self.token}",
|
288
288
|
},
|
289
289
|
)
|
290
290
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
"""
|
2
2
|
Unit test for the token endpoint
|
3
3
|
"""
|
4
|
+
|
4
5
|
import json
|
5
6
|
|
6
7
|
from flask import current_app
|
@@ -87,15 +88,28 @@ class TestUnexpiringToken(CustomTestCase):
|
|
87
88
|
def test_token_unexpiring(self):
|
88
89
|
auth = BIAuth()
|
89
90
|
|
91
|
+
# Generate token dynamically
|
90
92
|
token = auth.generate_token(1)
|
91
93
|
|
94
|
+
# Verify that the token decodes correctly
|
92
95
|
response = auth.decode_token(token)
|
93
|
-
self.assertEqual(response, {"user_id": 1})
|
94
|
-
|
95
|
-
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MDM1OTI1OTIsInN1YiI6MX0.Plvmi02FMfZOTn6bxArELEmDeyuP-2X794c5VtAFgCg"
|
96
96
|
|
97
|
-
|
98
|
-
self.
|
97
|
+
self.assertIn("iat", response)
|
98
|
+
self.assertIn("iss", response)
|
99
|
+
self.assertIn("sub", response)
|
100
|
+
self.assertEqual("testname", response["sub"])
|
101
|
+
# Don't use hardcoded token, generate another dynamic one
|
102
|
+
user = UserModel.get_one_user(1)
|
103
|
+
self.assertIsNotNone(user)
|
104
|
+
|
105
|
+
# Verify that another generated token also works
|
106
|
+
token2 = auth.generate_token(1)
|
107
|
+
response2 = auth.decode_token(token2)
|
108
|
+
|
109
|
+
self.assertIn("iat", response2)
|
110
|
+
self.assertIn("iss", response2)
|
111
|
+
self.assertIn("sub", response2)
|
112
|
+
self.assertEqual("testname", response2["sub"])
|
99
113
|
|
100
114
|
def test_user_not_valid(self):
|
101
115
|
auth = BIAuth()
|
@@ -1,8 +1,22 @@
|
|
1
|
+
"""
|
2
|
+
Unit tests for the user endpoints.
|
3
|
+
|
4
|
+
This module contains tests for the user-related functionalities, including:
|
5
|
+
- User authentication and authorization
|
6
|
+
- User role management
|
7
|
+
- Password handling and rotation
|
8
|
+
- User profile operations
|
9
|
+
|
10
|
+
All tests follow a consistent pattern of setting up test data,
|
11
|
+
executing operations, and verifying results against expected outcomes.
|
12
|
+
"""
|
13
|
+
|
1
14
|
import json
|
15
|
+
from datetime import datetime, timedelta, timezone
|
2
16
|
|
3
17
|
from flask import current_app
|
4
18
|
from flask_testing import TestCase
|
5
|
-
|
19
|
+
|
6
20
|
from cornflow.app import create_app
|
7
21
|
from cornflow.commands.access import access_init_command
|
8
22
|
from cornflow.commands.dag import register_deployed_dags_command_test
|
@@ -15,9 +29,8 @@ from cornflow.models import (
|
|
15
29
|
UserModel,
|
16
30
|
UserRoleModel,
|
17
31
|
)
|
18
|
-
|
19
|
-
from cornflow.shared.const import ADMIN_ROLE, PLANNER_ROLE, SERVICE_ROLE, VIEWER_ROLE
|
20
32
|
from cornflow.shared import db
|
33
|
+
from cornflow.shared.const import ADMIN_ROLE, SERVICE_ROLE, PLANNER_ROLE, VIEWER_ROLE
|
21
34
|
from cornflow.tests.const import (
|
22
35
|
CASE_PATH,
|
23
36
|
CASE_URL,
|
@@ -363,15 +376,18 @@ class TestUserEndpoint(TestCase):
|
|
363
376
|
|
364
377
|
def test_change_password_rotation(self):
|
365
378
|
current_app.config["PWD_ROTATION_TIME"] = 1 # in days
|
366
|
-
payload = {"pwd_last_change": (datetime.
|
367
|
-
|
379
|
+
payload = {"pwd_last_change": (datetime.now(timezone.utc) - timedelta(days=2))}
|
380
|
+
|
381
|
+
planner = UserModel.get_one_user(self.planner["id"])
|
382
|
+
planner.update(payload)
|
383
|
+
|
368
384
|
response = self.log_in(self.planner)
|
369
385
|
self.assertEqual(True, response.json["change_password"])
|
370
386
|
|
371
387
|
payload = {"password": "Newtestpassword1!"}
|
372
388
|
self.modify_info(self.planner, self.planner, payload)
|
373
389
|
self.planner.update(payload)
|
374
|
-
|
390
|
+
|
375
391
|
response = self.log_in(self.planner)
|
376
392
|
self.assertEqual(False, response.json["change_password"])
|
377
393
|
|
cornflow/tests/unit/tools.py
CHANGED
@@ -1,13 +1,78 @@
|
|
1
|
-
from unittest.mock import Mock
|
1
|
+
from unittest.mock import Mock, patch
|
2
|
+
from flask import Flask
|
3
|
+
import json
|
4
|
+
import os
|
5
|
+
|
6
|
+
from cornflow.shared.const import DATABRICKS_TERMINATE_STATE, DATABRICKS_FINISH_TO_STATE_MAP
|
7
|
+
|
8
|
+
|
9
|
+
def create_test_app():
|
10
|
+
app = Flask(__name__)
|
11
|
+
app.config["CORNFLOW_BACKEND"] = 2
|
12
|
+
return app
|
2
13
|
|
3
14
|
|
4
15
|
def patch_af_client(af_client_class):
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
16
|
+
with patch(
|
17
|
+
"cornflow.endpoints.execution.current_app.config"
|
18
|
+
) as mock_config:
|
19
|
+
mock_config.__getitem__.side_effect = lambda key: (
|
20
|
+
1 if key == "CORNFLOW_BACKEND" else {}
|
21
|
+
)
|
22
|
+
af_client_mock = Mock()
|
23
|
+
responses_mock = Mock()
|
24
|
+
responses_mock.json.return_value = {
|
25
|
+
"is_paused": False,
|
26
|
+
"dag_run_id": "12345",
|
27
|
+
"state": "success",
|
28
|
+
}
|
29
|
+
af_client_mock.is_alive.return_value = True
|
30
|
+
af_client_mock.get_orch_info.return_value = responses_mock
|
31
|
+
af_client_mock.run_workflow.return_value = responses_mock
|
32
|
+
af_client_mock.get_run_status.return_value = responses_mock
|
33
|
+
af_client_mock.set_dag_run_to_fail.return_value = None
|
34
|
+
af_client_class.from_config.return_value = af_client_mock
|
35
|
+
|
36
|
+
|
37
|
+
def patch_db_client(db_client_class):
|
38
|
+
mock_config = {"CORNFLOW_BACKEND": 2}
|
39
|
+
|
40
|
+
with patch(
|
41
|
+
"cornflow.endpoints.execution.current_app.config", mock_config
|
42
|
+
):
|
43
|
+
db_client_mock = Mock()
|
44
|
+
responses_mock = Mock()
|
45
|
+
responses_mock.json.return_value = {
|
46
|
+
"status": {"state": "SUCCESS", "termination_details": {"code": "SUCCESS"}}
|
47
|
+
}
|
48
|
+
response_run_workflow = Mock()
|
49
|
+
response_run_workflow.json.return_value = {
|
50
|
+
"run_id": 350148078719367,
|
51
|
+
"number_in_job": 350148078719367,
|
52
|
+
}
|
53
|
+
response_get_orch_info = Mock()
|
54
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
55
|
+
test_data_dir = os.path.join(os.path.dirname(current_dir), "data")
|
56
|
+
with open(
|
57
|
+
os.path.join(test_data_dir, "patch_databricks_get_orch_info.json")
|
58
|
+
) as f:
|
59
|
+
response_get_orch_info.json.return_value = json.load(f)
|
60
|
+
response_get_run_status = Mock()
|
61
|
+
with open(
|
62
|
+
os.path.join(test_data_dir, "patch_databricks_get_run_status.json")
|
63
|
+
) as f:
|
64
|
+
response_get_run_status.json.return_value = json.load(f)
|
65
|
+
state = response_get_run_status.json.return_value["status"]["state"]
|
66
|
+
if state == DATABRICKS_TERMINATE_STATE:
|
67
|
+
if (
|
68
|
+
response_get_run_status.json.return_value["status"]["termination_details"]["code"]
|
69
|
+
in DATABRICKS_FINISH_TO_STATE_MAP.keys()
|
70
|
+
):
|
71
|
+
response_get_run_status = response_get_run_status.json.return_value["status"]["termination_details"]["code"]
|
72
|
+
else:
|
73
|
+
response_get_run_status = "OTHER_FINISH_ERROR"
|
74
|
+
db_client_mock.is_alive.return_value = True
|
75
|
+
db_client_mock.get_orch_info.return_value = response_get_orch_info
|
76
|
+
db_client_mock.run_workflow.return_value = response_run_workflow
|
77
|
+
db_client_mock.get_run_status.return_value = response_get_run_status
|
78
|
+
db_client_class.from_config.return_value = db_client_mock
|
@@ -1,7 +1,7 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: cornflow
|
3
|
-
Version: 2.0.
|
4
|
-
Summary:
|
3
|
+
Version: 2.0.0a13
|
4
|
+
Summary: cornflow is an open source multi-solver optimization server with a REST API built using flask.
|
5
5
|
Home-page: https://github.com/baobabsoluciones/cornflow
|
6
6
|
Author: baobab soluciones
|
7
7
|
Author-email: cornflow@baobabsoluciones.es
|
@@ -12,9 +12,10 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
12
12
|
Requires-Python: >=3.9
|
13
13
|
Requires-Dist: alembic==1.9.2
|
14
14
|
Requires-Dist: apispec<=6.3.0
|
15
|
+
Requires-Dist: cachetools==5.3.3
|
15
16
|
Requires-Dist: click<=8.1.7
|
16
|
-
Requires-Dist: cornflow-client==2.0.
|
17
|
-
Requires-Dist: cryptography<=
|
17
|
+
Requires-Dist: cornflow-client==2.0.0a13
|
18
|
+
Requires-Dist: cryptography<=44.0.1
|
18
19
|
Requires-Dist: databricks-sdk==0.29.0
|
19
20
|
Requires-Dist: disposable-email-domains>=0.0.86
|
20
21
|
Requires-Dist: Flask==2.3.2
|
@@ -29,7 +30,7 @@ Requires-Dist: Flask-SQLAlchemy==2.5.1
|
|
29
30
|
Requires-Dist: gevent==23.9.1
|
30
31
|
Requires-Dist: greenlet<=2.0.2; python_version < "3.11"
|
31
32
|
Requires-Dist: greenlet==3.0.3; python_version >= "3.11"
|
32
|
-
Requires-Dist: gunicorn<=
|
33
|
+
Requires-Dist: gunicorn<=23.0.0
|
33
34
|
Requires-Dist: jsonpatch<=1.33
|
34
35
|
Requires-Dist: ldap3<=2.9.1
|
35
36
|
Requires-Dist: marshmallow<=3.20.2
|
@@ -50,7 +51,7 @@ Dynamic: requires-dist
|
|
50
51
|
Dynamic: requires-python
|
51
52
|
Dynamic: summary
|
52
53
|
|
53
|
-
|
54
|
+
cornflow
|
54
55
|
=========
|
55
56
|
|
56
57
|
.. image:: https://github.com/baobabsoluciones/cornflow/workflows/build/badge.svg?style=svg
|
@@ -70,13 +71,13 @@ Cornflow
|
|
70
71
|
|
71
72
|
.. image:: https://img.shields.io/badge/License-Apache2.0-blue
|
72
73
|
|
73
|
-
|
74
|
+
cornflow is an open source multi-solver optimization server with a REST API built using `flask <https://flask.palletsprojects.com>`_, `airflow <https://airflow.apache.org/>`_ and `pulp <https://coin-or.github.io/pulp/>`_.
|
74
75
|
|
75
|
-
While most deployment servers are based on the solving technique (MIP, CP, NLP, etc.),
|
76
|
+
While most deployment servers are based on the solving technique (MIP, CP, NLP, etc.), cornflow focuses on the optimization problems themselves. However, it does not impose any constraint on the type of problem and solution method to use.
|
76
77
|
|
77
|
-
With
|
78
|
+
With cornflow you can deploy a Traveling Salesman Problem solver next to a Knapsack solver or a Nurse Rostering Problem solver. As long as you describe the input and output data, you can upload any solution method for any problem and then use it with any data you want.
|
78
79
|
|
79
|
-
|
80
|
+
cornflow helps you formalize your problem by proposing development guidelines. It also provides a range of functionalities around your deployed solution method, namely:
|
80
81
|
|
81
82
|
* storage of users, instances, solutions and solution logs.
|
82
83
|
* deployment and maintenance of models, solvers and algorithms.
|
@@ -92,9 +93,9 @@ Cornflow helps you formalize your problem by proposing development guidelines. I
|
|
92
93
|
Installation instructions
|
93
94
|
-------------------------------
|
94
95
|
|
95
|
-
|
96
|
+
cornflow is tested with Ubuntu 20.04, python >= 3.8 and git.
|
96
97
|
|
97
|
-
Download the
|
98
|
+
Download the cornflow project and install requirements::
|
98
99
|
|
99
100
|
python3 -m venv venv
|
100
101
|
venv/bin/pip3 install cornflow
|
@@ -110,7 +111,7 @@ initialize the sqlite database::
|
|
110
111
|
flask create_admin_user -u cornflow -e cornflow_admin@admin.com -p cornflow_admin_password
|
111
112
|
|
112
113
|
|
113
|
-
activate the virtual environment and run
|
114
|
+
activate the virtual environment and run cornflow::
|
114
115
|
|
115
116
|
source venv/bin/activate
|
116
117
|
export FLASK_APP=cornflow.app
|
@@ -121,7 +122,7 @@ activate the virtual environment and run Cornflow::
|
|
121
122
|
export AIRFLOW_PWD=airflow_pwd
|
122
123
|
flask run
|
123
124
|
|
124
|
-
**
|
125
|
+
**cornflow needs a running installation of Airflow to operate and more configuration**. Check `the installation docs <https://baobabsoluciones.github.io/cornflow/main/install.html>`_ for more details on installing airflow, configuring the application and initializing the database.
|
125
126
|
|
126
127
|
Using cornflow to solve a PuLP model
|
127
128
|
---------------------------------------
|