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
@@ -0,0 +1,103 @@
1
+ """
2
+ Unit tests for the get_resources function
3
+ """
4
+
5
+ import unittest
6
+ from unittest.mock import patch, MagicMock
7
+ from flask import Flask
8
+ from cornflow.endpoints import get_resources, resources
9
+ from cornflow.shared.const import CONDITIONAL_ENDPOINTS
10
+
11
+
12
+ class TestGetResources(unittest.TestCase):
13
+ """Test the get_resources function"""
14
+
15
+ def setUp(self):
16
+ self.app = Flask(__name__)
17
+ self.mock_signup_view = MagicMock()
18
+ self.mock_signup_view.view_class = MagicMock()
19
+ self.mock_login_view = MagicMock()
20
+ self.mock_login_view.view_class = MagicMock()
21
+
22
+ def test_returns_base_resources_when_no_conditional_endpoints(self):
23
+ """Test that get_resources returns base resources when no conditional endpoints are registered"""
24
+ with self.app.app_context():
25
+ with patch("flask.current_app.view_functions", {}):
26
+ result = get_resources()
27
+ self.assertEqual(result, resources)
28
+
29
+ def test_adds_conditional_endpoint_when_registered(self):
30
+ """Test that get_resources adds conditional endpoints when they are registered"""
31
+ with self.app.app_context():
32
+ mock_view_functions = {"signup": self.mock_signup_view}
33
+
34
+ with patch("flask.current_app.view_functions", mock_view_functions):
35
+ result = get_resources()
36
+
37
+ # Should have one more resource than base
38
+ self.assertEqual(len(result), len(resources) + 1)
39
+
40
+ # Check that signup endpoint was added correctly
41
+ signup_resource = next(
42
+ (r for r in result if r["endpoint"] == "signup"), None
43
+ )
44
+ self.assertIsNotNone(signup_resource)
45
+ self.assertEqual(
46
+ signup_resource["urls"], CONDITIONAL_ENDPOINTS["signup"]
47
+ )
48
+ self.assertEqual(
49
+ signup_resource["resource"], self.mock_signup_view.view_class
50
+ )
51
+
52
+ def test_adds_both_signup_and_login_when_registered(self):
53
+ """Test that get_resources adds both signup and login when both are registered"""
54
+ with self.app.app_context():
55
+ mock_view_functions = {
56
+ "signup": self.mock_signup_view,
57
+ "login": self.mock_login_view,
58
+ }
59
+
60
+ with patch("flask.current_app.view_functions", mock_view_functions):
61
+ result = get_resources()
62
+
63
+ # Should have two more resources than base
64
+ self.assertEqual(len(result), len(resources) + 2)
65
+
66
+ # Check that both endpoints were added correctly
67
+ signup_resource = next(
68
+ (r for r in result if r["endpoint"] == "signup"), None
69
+ )
70
+ login_resource = next(
71
+ (r for r in result if r["endpoint"] == "login"), None
72
+ )
73
+
74
+ self.assertIsNotNone(signup_resource)
75
+ self.assertIsNotNone(login_resource)
76
+ self.assertEqual(
77
+ signup_resource["urls"], CONDITIONAL_ENDPOINTS["signup"]
78
+ )
79
+ self.assertEqual(login_resource["urls"], CONDITIONAL_ENDPOINTS["login"])
80
+
81
+ def test_does_not_add_duplicate_endpoints(self):
82
+ """Test that get_resources does not add endpoints that already exist in base resources"""
83
+ with self.app.app_context():
84
+ mock_view_functions = {
85
+ "instance": MagicMock()
86
+ } # 'instance' already exists in base resources
87
+
88
+ with patch("flask.current_app.view_functions", mock_view_functions):
89
+ result = get_resources()
90
+ self.assertEqual(len(result), len(resources))
91
+
92
+ def test_ignores_non_conditional_endpoints(self):
93
+ """Test that get_resources ignores endpoints that are not in CONDITIONAL_ENDPOINTS"""
94
+ with self.app.app_context():
95
+ mock_view_functions = {"non_conditional_endpoint": MagicMock()}
96
+
97
+ with patch("flask.current_app.view_functions", mock_view_functions):
98
+ result = get_resources()
99
+ self.assertEqual(len(result), len(resources))
100
+
101
+
102
+ if __name__ == "__main__":
103
+ unittest.main()
@@ -1,10 +1,18 @@
1
1
  import os
2
+ from unittest.mock import patch, MagicMock
2
3
 
3
4
  from cornflow.shared import db
4
5
 
5
6
  from cornflow.app import create_app
6
7
  from cornflow.commands import access_init_command
7
- from cornflow.shared.const import STATUS_HEALTHY, CORNFLOW_VERSION
8
+ from cornflow.shared.const import (
9
+ STATUS_HEALTHY,
10
+ STATUS_UNHEALTHY,
11
+ CORNFLOW_VERSION,
12
+ DATABRICKS_BACKEND,
13
+ AIRFLOW_BACKEND,
14
+ )
15
+ from cornflow.shared.exceptions import EndpointNotImplemented
8
16
  from cornflow.tests.const import HEALTH_URL
9
17
  from cornflow.tests.custom_test_case import CustomTestCase
10
18
 
@@ -28,10 +36,83 @@ class TestHealth(CustomTestCase):
28
36
  response = self.client.get(HEALTH_URL)
29
37
  self.assertEqual(200, response.status_code)
30
38
  cf_status = response.json["cornflow_status"]
31
- af_status = response.json["airflow_status"]
39
+ backend_status = response.json["backend_status"]
32
40
  cf_version = response.json["cornflow_version"]
33
41
  expected_version = CORNFLOW_VERSION
34
42
  self.assertEqual(str, type(cf_status))
35
- self.assertEqual(str, type(af_status))
43
+ self.assertEqual(str, type(backend_status))
36
44
  self.assertEqual(cf_status, STATUS_HEALTHY)
37
45
  self.assertEqual(cf_version, expected_version)
46
+
47
+ @patch("cornflow.endpoints.health.Databricks")
48
+ def test_health_databricks_backend_healthy(self, mock_databricks):
49
+ """Test health endpoint with Databricks backend when service is healthy"""
50
+ self.create_service_user()
51
+ os.environ["CORNFLOW_SERVICE_USER"] = "testuser4"
52
+
53
+ # Mock Databricks client
54
+ mock_db_client = MagicMock()
55
+ mock_db_client.is_alive.return_value = True
56
+ mock_databricks.from_config.return_value = mock_db_client
57
+
58
+ # Set Databricks backend
59
+ with self.app.app_context():
60
+ self.app.config["CORNFLOW_BACKEND"] = DATABRICKS_BACKEND
61
+
62
+ response = self.client.get(HEALTH_URL)
63
+
64
+ self.assertEqual(200, response.status_code)
65
+ cf_status = response.json["cornflow_status"]
66
+ backend_status = response.json["backend_status"]
67
+ cf_version = response.json["cornflow_version"]
68
+
69
+ self.assertEqual(cf_status, STATUS_HEALTHY)
70
+ self.assertEqual(backend_status, STATUS_HEALTHY)
71
+ self.assertEqual(cf_version, CORNFLOW_VERSION)
72
+
73
+ # Verify Databricks client was called
74
+ mock_databricks.from_config.assert_called_once()
75
+ mock_db_client.is_alive.assert_called_once()
76
+
77
+ @patch("cornflow.endpoints.health.Databricks")
78
+ def test_health_databricks_backend_unhealthy(self, mock_databricks):
79
+ """Test health endpoint with Databricks backend when service is unhealthy"""
80
+ self.create_service_user()
81
+ os.environ["CORNFLOW_SERVICE_USER"] = "testuser4"
82
+
83
+ # Mock Databricks client
84
+ mock_db_client = MagicMock()
85
+ mock_db_client.is_alive.return_value = False
86
+ mock_databricks.from_config.return_value = mock_db_client
87
+
88
+ # Set Databricks backend
89
+ with self.app.app_context():
90
+ self.app.config["CORNFLOW_BACKEND"] = DATABRICKS_BACKEND
91
+
92
+ response = self.client.get(HEALTH_URL)
93
+
94
+ self.assertEqual(200, response.status_code)
95
+ cf_status = response.json["cornflow_status"]
96
+ backend_status = response.json["backend_status"]
97
+
98
+ self.assertEqual(cf_status, STATUS_HEALTHY)
99
+ self.assertEqual(backend_status, STATUS_UNHEALTHY)
100
+
101
+ # Verify Databricks client was called
102
+ mock_databricks.from_config.assert_called_once()
103
+ mock_db_client.is_alive.assert_called_once()
104
+
105
+ def test_health_unsupported_backend(self):
106
+ """Test health endpoint with unsupported backend raises EndpointNotImplemented"""
107
+ self.create_service_user()
108
+ os.environ["CORNFLOW_SERVICE_USER"] = "testuser4"
109
+
110
+ # Set an unsupported backend value
111
+ with self.app.app_context():
112
+ self.app.config["CORNFLOW_BACKEND"] = 999 # Invalid backend value
113
+
114
+ response = self.client.get(HEALTH_URL)
115
+
116
+ self.assertEqual(501, response.status_code)
117
+ self.assertIn("error", response.json)
118
+ self.assertEqual(response.json["error"], "Endpoint not implemented")
@@ -5,7 +5,7 @@
5
5
  import json
6
6
 
7
7
  # Imports from internal modules
8
- from cornflow.models import MainAlarmsModel, AlarmsModel
8
+ from cornflow.models import MainAlarmsModel
9
9
  from cornflow.tests.const import MAIN_ALARMS_URL, ALARMS_URL
10
10
  from cornflow.tests.custom_test_case import CustomTestCase
11
11
 
@@ -251,7 +251,8 @@ class TestUserRolesListEndpoint(CustomTestCase):
251
251
  },
252
252
  )
253
253
  self.assertEqual(200, response.status_code)
254
- self.assertEqual(self.payload, response.json)
254
+ self.assertCountEqual(self.payload, response.json)
255
+
255
256
 
256
257
  def test_get_user_roles_not_authorized_user(self):
257
258
  for role in ROLES_MAP:
@@ -81,7 +81,7 @@
81
81
  # }
82
82
  # required_instance = {"id", "name", "data_hash"}
83
83
  # foreign_keys = [
84
- # ("permission_dag", "dag_id", "deployed_dags.id"),
84
+ # ("permission_dag", "dag_id", "deployed_workflows.id"),
85
85
  # ("permission_dag", "user_id", "users.id"),
86
86
  # ("permission_view", "action_id", "actions.id"),
87
87
  # ("permission_view", "api_view_id", "api_view.id"),
@@ -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
 
@@ -1,18 +1,25 @@
1
1
  """
2
2
  Unit test for the sign-up endpoint
3
3
  """
4
- from cornflow.commands import access_init_command
5
- from cornflow.commands.dag import register_deployed_dags_command_test
6
4
 
7
5
  # Import from libraries
8
- from flask_testing import TestCase
6
+
7
+ # Import from internal modules
8
+
9
9
  import json
10
+ from unittest.mock import patch
11
+
12
+ # Import from libraries
13
+ from flask_testing import TestCase
10
14
 
11
15
  # Import from internal modules
12
16
  from cornflow.app import create_app
13
- from cornflow.models import UserModel, UserRoleModel
14
- from cornflow.shared.const import PLANNER_ROLE
17
+ from cornflow.commands import access_init_command
18
+ from cornflow.commands.dag import register_deployed_dags_command_test
19
+ from cornflow.models import UserModel, UserRoleModel, ViewModel, PermissionViewRoleModel
15
20
  from cornflow.shared import db
21
+ from cornflow.shared.authentication import Auth
22
+ from cornflow.shared.const import PLANNER_ROLE, ADMIN_ROLE, POST_ACTION, NO_SIGNUP, SIGNUP_WITH_AUTH
16
23
  from cornflow.tests.const import SIGNUP_URL
17
24
 
18
25
 
@@ -95,7 +102,7 @@ class TestSignUp(TestCase):
95
102
  class TestSignUpDeactivated(TestCase):
96
103
  def create_app(self):
97
104
  app = create_app("testing")
98
- app.config["SIGNUP_ACTIVATED"] = 0
105
+ app.config["SIGNUP_ACTIVATED"] = NO_SIGNUP
99
106
  return app
100
107
 
101
108
  def setUp(self):
@@ -120,3 +127,171 @@ class TestSignUpDeactivated(TestCase):
120
127
  )
121
128
 
122
129
  self.assertEqual(response.status_code, 400)
130
+
131
+
132
+ class TestSignUpAuthenticated(TestCase):
133
+ """Test the authenticated signup endpoint (SIGNUP_ACTIVATED=SIGNUP_WITH_AUTH)"""
134
+
135
+ def create_app(self):
136
+ with patch("cornflow.config.Testing.SIGNUP_ACTIVATED", SIGNUP_WITH_AUTH):
137
+ app = create_app("testing")
138
+ return app
139
+
140
+ def setUp(self):
141
+ db.create_all()
142
+ access_init_command(verbose=False)
143
+ register_deployed_dags_command_test(verbose=False)
144
+
145
+ # Create test data
146
+ self.data = {
147
+ "username": "testname",
148
+ "email": "test@test.com",
149
+ "password": "Testpassword1!",
150
+ }
151
+
152
+ # Create an admin user for testing
153
+ self.admin_user = UserModel(
154
+ {
155
+ "username": "admin",
156
+ "email": "admin@test.com",
157
+ "password": "Adminpassword1!",
158
+ }
159
+ )
160
+ self.admin_user.save()
161
+
162
+ # Assign admin role to the user
163
+ admin_role = UserRoleModel(
164
+ {"user_id": self.admin_user.id, "role_id": ADMIN_ROLE}
165
+ )
166
+ admin_role.save()
167
+
168
+ # Create a regular user for testing
169
+ self.regular_user = UserModel(
170
+ {
171
+ "username": "regular",
172
+ "email": "regular@test.com",
173
+ "password": "Regularpassword1!",
174
+ }
175
+ )
176
+ self.regular_user.save()
177
+
178
+ # Assign planner role to the user
179
+ planner_role = UserRoleModel(
180
+ {"user_id": self.regular_user.id, "role_id": PLANNER_ROLE}
181
+ )
182
+ planner_role.save()
183
+
184
+ def tearDown(self):
185
+ db.session.remove()
186
+ db.drop_all()
187
+
188
+ def get_auth_token(self, user):
189
+ """Helper method to get authentication token for a user"""
190
+ auth = Auth()
191
+ return auth.generate_token(user.id)
192
+
193
+ def test_authenticated_signup_admin_can_register(self):
194
+ """Test that admin users can register new users"""
195
+ payload = self.data
196
+ admin_token = self.get_auth_token(self.admin_user)
197
+
198
+ response = self.client.post(
199
+ SIGNUP_URL,
200
+ data=json.dumps(payload),
201
+ follow_redirects=True,
202
+ headers={
203
+ "Content-Type": "application/json",
204
+ "Authorization": f"Bearer {admin_token}",
205
+ },
206
+ )
207
+
208
+ self.assertEqual(201, response.status_code)
209
+ self.assertEqual(str, type(response.json["token"]))
210
+ self.assertEqual(int, type(response.json["id"]))
211
+ self.assertEqual(
212
+ PLANNER_ROLE,
213
+ UserRoleModel.query.filter_by(user_id=response.json["id"]).first().role_id,
214
+ )
215
+ self.assertNotEqual(None, UserModel.get_one_user_by_email(self.data["email"]))
216
+
217
+ def test_authenticated_signup_regular_user_cannot_register(self):
218
+ """Test that regular users cannot register new users"""
219
+ payload = self.data
220
+ regular_token = self.get_auth_token(self.regular_user)
221
+
222
+ response = self.client.post(
223
+ SIGNUP_URL,
224
+ data=json.dumps(payload),
225
+ follow_redirects=True,
226
+ headers={
227
+ "Content-Type": "application/json",
228
+ "Authorization": f"Bearer {regular_token}",
229
+ },
230
+ )
231
+
232
+ # Should return 403 Forbidden due to insufficient permissions
233
+ self.assertEqual(403, response.status_code)
234
+ self.assertIn("error", response.json)
235
+
236
+ def test_authenticated_signup_no_auth_header_fails(self):
237
+ """Test that signup fails without authentication header"""
238
+ payload = self.data
239
+
240
+ response = self.client.post(
241
+ SIGNUP_URL,
242
+ data=json.dumps(payload),
243
+ follow_redirects=True,
244
+ headers={"Content-Type": "application/json"},
245
+ )
246
+
247
+ # Should return 400 Bad Request due to missing authorization
248
+ self.assertEqual(400, response.status_code)
249
+ self.assertIn("error", response.json)
250
+
251
+ def test_authenticated_signup_invalid_token_fails(self):
252
+ """Test that signup fails with invalid token"""
253
+ payload = self.data
254
+
255
+ response = self.client.post(
256
+ SIGNUP_URL,
257
+ data=json.dumps(payload),
258
+ follow_redirects=True,
259
+ headers={
260
+ "Content-Type": "application/json",
261
+ "Authorization": "Bearer invalid_token",
262
+ },
263
+ )
264
+
265
+ # Should return 400 Bad Request due to invalid token
266
+ self.assertEqual(400, response.status_code)
267
+ self.assertIn("error", response.json)
268
+
269
+ def test_signup_permissions_are_registered_in_database(self):
270
+ """Test that signup permissions are properly registered in the database"""
271
+ # Check that the signup endpoint exists in the views table
272
+ signup_view = ViewModel.query.filter_by(name="signup").first()
273
+ self.assertIsNotNone(
274
+ signup_view, "Signup endpoint should be registered in views table"
275
+ )
276
+ self.assertEqual("/signup/", signup_view.url_rule)
277
+
278
+ # Check that admin role has permissions for signup endpoint
279
+ admin_permissions = PermissionViewRoleModel.query.filter_by(
280
+ role_id=ADMIN_ROLE, api_view_id=signup_view.id
281
+ ).all()
282
+
283
+ # Admin should have POST permission for signup endpoint
284
+ self.assertGreater(
285
+ len(admin_permissions),
286
+ 0,
287
+ "Admin should have permissions for signup endpoint",
288
+ )
289
+
290
+ # Verify that the permission is for POST action
291
+ post_permission = PermissionViewRoleModel.query.filter_by(
292
+ role_id=ADMIN_ROLE, api_view_id=signup_view.id, action_id=POST_ACTION
293
+ ).first()
294
+
295
+ self.assertIsNotNone(
296
+ post_permission, "Admin should have POST permission for signup endpoint"
297
+ )
@@ -1,13 +1,96 @@
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 (
7
+ DATABRICKS_TERMINATE_STATE,
8
+ DATABRICKS_FINISH_TO_STATE_MAP,
9
+ )
10
+
11
+
12
+ def create_test_app():
13
+ app = Flask(__name__)
14
+ app.config["CORNFLOW_BACKEND"] = 2
15
+ return app
2
16
 
3
17
 
4
18
  def patch_af_client(af_client_class):
5
- af_client_mock = Mock()
6
- responses_mock = Mock()
7
- responses_mock.json.return_value = {"is_paused": False, "dag_run_id": "12345", "state": "success"}
8
- af_client_mock.is_alive.return_value = True
9
- af_client_mock.get_dag_info.return_value = responses_mock
10
- af_client_mock.run_dag.return_value = responses_mock
11
- af_client_mock.get_dag_run_status.return_value = responses_mock
12
- af_client_mock.set_dag_run_to_fail.return_value = None
13
- af_client_class.from_config.return_value = af_client_mock
19
+ with patch("cornflow.endpoints.execution.current_app.config") as mock_config:
20
+ mock_config.__getitem__.side_effect = lambda key: (
21
+ 1 if key == "CORNFLOW_BACKEND" else {}
22
+ )
23
+ af_client_mock = Mock()
24
+ responses_mock = Mock()
25
+ responses_mock.json.return_value = {
26
+ "is_paused": False,
27
+ "dag_run_id": "12345",
28
+ "state": "success",
29
+ }
30
+ af_client_mock.is_alive.return_value = True
31
+ af_client_mock.get_workflow_info.return_value = responses_mock
32
+
33
+ # Configurar get_workflow_info
34
+ schema_info_mock = Mock()
35
+ schema_info_mock.json.return_value = {"is_paused": False}
36
+ af_client_mock.get_workflow_info.return_value = schema_info_mock
37
+
38
+ # Configurar run_workflow
39
+ run_response_mock = Mock()
40
+ run_response_mock.json.return_value = {"dag_run_id": "12345"}
41
+ af_client_mock.run_workflow.return_value = run_response_mock
42
+
43
+ # Configurar get_run_status
44
+ status_mock = Mock()
45
+ status_mock.json.return_value = {"state": "success"}
46
+ af_client_mock.get_run_status.return_value = status_mock
47
+
48
+ af_client_mock.run_dag.return_value = responses_mock
49
+ af_client_mock.set_dag_run_to_fail.return_value = None
50
+ af_client_class.from_config.return_value = af_client_mock
51
+
52
+
53
+ def patch_db_client(db_client_class):
54
+ mock_config = {"CORNFLOW_BACKEND": 2}
55
+
56
+ with patch("cornflow.endpoints.execution.current_app.config", mock_config):
57
+ db_client_mock = Mock()
58
+ responses_mock = Mock()
59
+ responses_mock.json.return_value = {
60
+ "status": {"state": "SUCCESS", "termination_details": {"code": "SUCCESS"}}
61
+ }
62
+ response_run_workflow = Mock()
63
+ response_run_workflow.json.return_value = {
64
+ "run_id": 350148078719367,
65
+ "number_in_job": 350148078719367,
66
+ }
67
+ response_get_workflow_info = Mock()
68
+ current_dir = os.path.dirname(os.path.abspath(__file__))
69
+ test_data_dir = os.path.join(os.path.dirname(current_dir), "data")
70
+ with open(
71
+ os.path.join(test_data_dir, "patch_databricks_get_orch_info.json")
72
+ ) as f:
73
+ response_get_workflow_info.json.return_value = json.load(f)
74
+ response_get_run_status = Mock()
75
+ with open(
76
+ os.path.join(test_data_dir, "patch_databricks_get_run_status.json")
77
+ ) as f:
78
+ response_get_run_status.json.return_value = json.load(f)
79
+ state = response_get_run_status.json.return_value["status"]["state"]
80
+ if state == DATABRICKS_TERMINATE_STATE:
81
+ if (
82
+ response_get_run_status.json.return_value["status"][
83
+ "termination_details"
84
+ ]["code"]
85
+ in DATABRICKS_FINISH_TO_STATE_MAP.keys()
86
+ ):
87
+ response_get_run_status = response_get_run_status.json.return_value[
88
+ "status"
89
+ ]["termination_details"]["code"]
90
+ else:
91
+ response_get_run_status = "OTHER_FINISH_ERROR"
92
+ db_client_mock.is_alive.return_value = True
93
+ db_client_mock.get_workflow_info.return_value = response_get_workflow_info
94
+ db_client_mock.run_workflow.return_value = response_run_workflow
95
+ db_client_mock.get_run_status.return_value = response_get_run_status
96
+ db_client_class.from_config.return_value = db_client_mock
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cornflow
3
- Version: 1.2.3a5
3
+ Version: 1.3.0rc1
4
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
@@ -14,7 +14,7 @@ Requires-Dist: alembic==1.9.2
14
14
  Requires-Dist: apispec<=6.3.0
15
15
  Requires-Dist: cachetools==5.3.3
16
16
  Requires-Dist: click<=8.1.7
17
- Requires-Dist: cornflow-client>=1.2.3.a5
17
+ Requires-Dist: cornflow-client==1.3.0rc1
18
18
  Requires-Dist: cryptography<=44.0.1
19
19
  Requires-Dist: disposable-email-domains>=0.0.86
20
20
  Requires-Dist: Flask==2.3.2
@@ -37,7 +37,7 @@ Requires-Dist: PuLP<=2.9.0
37
37
  Requires-Dist: psycopg2<=2.9.9
38
38
  Requires-Dist: PyJWT<=2.8.0
39
39
  Requires-Dist: pytups>=0.86.2
40
- Requires-Dist: requests<=2.32.3
40
+ Requires-Dist: requests<=2.32.4
41
41
  Requires-Dist: SQLAlchemy==1.3.21
42
42
  Requires-Dist: webargs<=8.3.0
43
43
  Requires-Dist: Werkzeug==3.0.6