cornflow 1.3.7__tar.gz → 1.3.8__tar.gz

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 (194) hide show
  1. {cornflow-1.3.7/cornflow.egg-info → cornflow-1.3.8}/PKG-INFO +2 -2
  2. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/service.py +14 -1
  3. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/config.py +1 -1
  4. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/execution.py +1 -1
  5. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/meta_resource.py +34 -1
  6. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/const.py +1 -1
  7. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/email.py +0 -1
  8. cornflow-1.3.8/cornflow/tests/unit/test_meta_resource.py +256 -0
  9. {cornflow-1.3.7 → cornflow-1.3.8/cornflow.egg-info}/PKG-INFO +2 -2
  10. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow.egg-info/SOURCES.txt +1 -0
  11. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow.egg-info/requires.txt +1 -1
  12. {cornflow-1.3.7 → cornflow-1.3.8}/requirements.txt +1 -1
  13. {cornflow-1.3.7 → cornflow-1.3.8}/setup.py +1 -1
  14. {cornflow-1.3.7 → cornflow-1.3.8}/MANIFEST.in +0 -0
  15. {cornflow-1.3.7 → cornflow-1.3.8}/README.rst +0 -0
  16. {cornflow-1.3.7 → cornflow-1.3.8}/airflow_config/__init__.py +0 -0
  17. {cornflow-1.3.7 → cornflow-1.3.8}/airflow_config/airflow_local_settings.py +0 -0
  18. {cornflow-1.3.7 → cornflow-1.3.8}/airflow_config/plugins/XCom/__init__.py +0 -0
  19. {cornflow-1.3.7 → cornflow-1.3.8}/airflow_config/plugins/XCom/gce_xcom_backend.py +0 -0
  20. {cornflow-1.3.7 → cornflow-1.3.8}/airflow_config/plugins/__init__.py +0 -0
  21. {cornflow-1.3.7 → cornflow-1.3.8}/airflow_config/webserver_ldap.py +0 -0
  22. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/__init__.py +0 -0
  23. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/app.py +0 -0
  24. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/__init__.py +0 -0
  25. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/actions.py +0 -0
  26. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/arguments.py +0 -0
  27. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/config.py +0 -0
  28. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/migrations.py +0 -0
  29. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/permissions.py +0 -0
  30. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/roles.py +0 -0
  31. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/schemas.py +0 -0
  32. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/tools/__init__.py +0 -0
  33. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/tools/api_generator.py +0 -0
  34. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/tools/endpoint_tools.py +0 -0
  35. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/tools/models_tools.py +0 -0
  36. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/tools/schema_generator.py +0 -0
  37. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/tools/schemas_tools.py +0 -0
  38. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/tools/tools.py +0 -0
  39. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/users.py +0 -0
  40. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/utils.py +0 -0
  41. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/views.py +0 -0
  42. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/__init__.py +0 -0
  43. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/access.py +0 -0
  44. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/actions.py +0 -0
  45. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/auxiliar.py +0 -0
  46. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/cleanup.py +0 -0
  47. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/dag.py +0 -0
  48. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/permissions.py +0 -0
  49. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/roles.py +0 -0
  50. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/schemas.py +0 -0
  51. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/users.py +0 -0
  52. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/views.py +0 -0
  53. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/__init__.py +0 -0
  54. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/action.py +0 -0
  55. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/alarms.py +0 -0
  56. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/apiview.py +0 -0
  57. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/case.py +0 -0
  58. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/dag.py +0 -0
  59. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/data_check.py +0 -0
  60. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/example_data.py +0 -0
  61. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/health.py +0 -0
  62. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/instance.py +0 -0
  63. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/licenses.py +0 -0
  64. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/login.py +0 -0
  65. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/main_alarms.py +0 -0
  66. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/permission.py +0 -0
  67. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/roles.py +0 -0
  68. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/schemas.py +0 -0
  69. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/signup.py +0 -0
  70. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/tables.py +0 -0
  71. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/token.py +0 -0
  72. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/user.py +0 -0
  73. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/user_role.py +0 -0
  74. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/gunicorn.py +0 -0
  75. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/README +0 -0
  76. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/alembic.ini +0 -0
  77. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/env.py +0 -0
  78. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/script.py.mako +0 -0
  79. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/00757b557b02_.py +0 -0
  80. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/1af47a419bbd_.py +0 -0
  81. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/4729cd156460_.py +0 -0
  82. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/4aac5e0c6e66_.py +0 -0
  83. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/509d280698e4_.py +0 -0
  84. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/7c3ea5ab5501_.py +0 -0
  85. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/991b98e24225_.py +0 -0
  86. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/999b98e24225.py +0 -0
  87. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/9cacf194fa1c_.py +0 -0
  88. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/a472b5ad50b7_.py +0 -0
  89. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/c2db9409cb5f_.py +0 -0
  90. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/c8a6c762e818_.py +0 -0
  91. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/ca449af8034c_.py +0 -0
  92. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/cef1df240b27_.py +0 -0
  93. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/d0e0700dcd8e_.py +0 -0
  94. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/d1b5be1f0549_.py +0 -0
  95. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/e1a50dae1ac9_.py +0 -0
  96. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/e937a5234ce4_.py +0 -0
  97. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/ebdd955fcc5e_.py +0 -0
  98. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/f3bee20314a2_.py +0 -0
  99. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/__init__.py +0 -0
  100. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/action.py +0 -0
  101. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/alarms.py +0 -0
  102. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/base_data_model.py +0 -0
  103. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/case.py +0 -0
  104. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/dag.py +0 -0
  105. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/dag_permissions.py +0 -0
  106. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/execution.py +0 -0
  107. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/instance.py +0 -0
  108. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/main_alarms.py +0 -0
  109. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/meta_models.py +0 -0
  110. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/permissions.py +0 -0
  111. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/role.py +0 -0
  112. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/user.py +0 -0
  113. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/user_role.py +0 -0
  114. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/view.py +0 -0
  115. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/__init__.py +0 -0
  116. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/action.py +0 -0
  117. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/alarms.py +0 -0
  118. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/case.py +0 -0
  119. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/common.py +0 -0
  120. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/dag.py +0 -0
  121. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/example_data.py +0 -0
  122. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/execution.py +0 -0
  123. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/health.py +0 -0
  124. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/instance.py +0 -0
  125. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/main_alarms.py +0 -0
  126. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/model_json.py +0 -0
  127. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/patch.py +0 -0
  128. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/permissions.py +0 -0
  129. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/query.py +0 -0
  130. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/role.py +0 -0
  131. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/schemas.py +0 -0
  132. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/solution_log.py +0 -0
  133. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/tables.py +0 -0
  134. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/user.py +0 -0
  135. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/user_role.py +0 -0
  136. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/view.py +0 -0
  137. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/__init__.py +0 -0
  138. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/authentication/__init__.py +0 -0
  139. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/authentication/auth.py +0 -0
  140. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/authentication/decorators.py +0 -0
  141. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/authentication/ldap.py +0 -0
  142. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/compress.py +0 -0
  143. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/exceptions.py +0 -0
  144. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/licenses.py +0 -0
  145. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/log_config.py +0 -0
  146. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/query_tools.py +0 -0
  147. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/utils.py +0 -0
  148. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/utils_tables.py +0 -0
  149. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/validators.py +0 -0
  150. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/__init__.py +0 -0
  151. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/base_test_execution.py +0 -0
  152. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/const.py +0 -0
  153. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/custom_liveServer.py +0 -0
  154. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/custom_test_case.py +0 -0
  155. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/integration/__init__.py +0 -0
  156. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/integration/test_commands.py +0 -0
  157. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/integration/test_cornflowclient.py +0 -0
  158. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/integration/test_data_checks_kpis.py +0 -0
  159. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/ldap/__init__.py +0 -0
  160. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/ldap/test_ldap_authentication.py +0 -0
  161. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/__init__.py +0 -0
  162. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_actions.py +0 -0
  163. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_alarms.py +0 -0
  164. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_apiview.py +0 -0
  165. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_application.py +0 -0
  166. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_cases.py +0 -0
  167. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_cli.py +0 -0
  168. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_commands.py +0 -0
  169. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_dags.py +0 -0
  170. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_data_checks.py +0 -0
  171. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_example_data.py +0 -0
  172. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_executions.py +0 -0
  173. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_external_role_creation.py +0 -0
  174. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_generate_from_schema.py +0 -0
  175. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_get_resources.py +0 -0
  176. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_health.py +0 -0
  177. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_instances.py +0 -0
  178. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_instances_file.py +0 -0
  179. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_licenses.py +0 -0
  180. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_log_in.py +0 -0
  181. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_main_alarms.py +0 -0
  182. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_permissions.py +0 -0
  183. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_roles.py +0 -0
  184. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_schema_from_models.py +0 -0
  185. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_schemas.py +0 -0
  186. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_sign_up.py +0 -0
  187. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_tables.py +0 -0
  188. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_token.py +0 -0
  189. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_users.py +0 -0
  190. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/tools.py +0 -0
  191. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow.egg-info/dependency_links.txt +0 -0
  192. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow.egg-info/entry_points.txt +0 -0
  193. {cornflow-1.3.7 → cornflow-1.3.8}/cornflow.egg-info/top_level.txt +0 -0
  194. {cornflow-1.3.7 → cornflow-1.3.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cornflow
3
- Version: 1.3.7
3
+ Version: 1.3.8
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.3.7
17
+ Requires-Dist: cornflow-client==1.3.8
18
18
  Requires-Dist: cryptography==48.0.1
19
19
  Requires-Dist: disposable-email-domains==0.0.162
20
20
  Requires-Dist: Flask==2.3.2
@@ -158,12 +158,25 @@ def _setup_environment_variables():
158
158
  os.environ["AIRFLOW_PWD"] = airflow_pwd
159
159
  os.environ["AIRFLOW_URL"] = airflow_url
160
160
  elif cornflow_backend == DATABRICKS_BACKEND:
161
+ required_databricks_vars = [
162
+ "DATABRICKS_HOST",
163
+ "DATABRICKS_CLIENT_SECRET",
164
+ "DATABRICKS_TOKEN_ENDPOINT",
165
+ "DATABRICKS_EP_CLUSTERS",
166
+ "DATABRICKS_CLIENT_ID",
167
+ ]
168
+ missing = [var for var in required_databricks_vars if not os.getenv(var)]
169
+ if missing:
170
+ sys.exit(
171
+ "FATAL: the Databricks backend requires the following environment "
172
+ f"variables to be set: {', '.join(missing)}"
173
+ )
161
174
  databricks_url = os.getenv("DATABRICKS_HOST")
162
175
  databricks_auth_secret = os.getenv("DATABRICKS_CLIENT_SECRET")
163
176
  databricks_token_endpoint = os.getenv("DATABRICKS_TOKEN_ENDPOINT")
164
177
  databricks_ep_clusters = os.getenv("DATABRICKS_EP_CLUSTERS")
165
178
  databricks_client_id = os.getenv("DATABRICKS_CLIENT_ID")
166
- databricks_health_path = os.getenv("DATABRICKS_HEALTH_PATH")
179
+ databricks_health_path = os.getenv("DATABRICKS_HEALTH_PATH", "default path")
167
180
  os.environ["DATABRICKS_HEALTH_PATH"] = databricks_health_path
168
181
  os.environ["DATABRICKS_HOST"] = databricks_url
169
182
  os.environ["DATABRICKS_CLIENT_SECRET"] = databricks_auth_secret
@@ -29,7 +29,7 @@ class DefaultConfig(object):
29
29
  AUTH_TYPE = int(os.getenv("AUTH_TYPE", AUTH_DB))
30
30
  DEFAULT_ROLE = int(os.getenv("DEFAULT_ROLE", PLANNER_ROLE))
31
31
  CORS_ORIGINS = os.getenv("CORS_ORIGINS", "*")
32
- SQLALCHEMY_TRACK_MODIFICATIONS = True
32
+ SQLALCHEMY_TRACK_MODIFICATIONS = False
33
33
  DEBUG = True
34
34
  TESTING = True
35
35
  LOG_LEVEL = int(os.getenv("LOG_LEVEL", 20))
@@ -407,7 +407,7 @@ class ExecutionRelaunchEndpoint(OrchestratorMixin):
407
407
  )
408
408
 
409
409
  # If the execution is still running or queued, raise an error
410
- if execution.state == 0 or execution.state == -7:
410
+ if execution.state in (EXEC_STATE_RUNNING, EXEC_STATE_QUEUED):
411
411
  return {"message": "This execution is still running"}, 400
412
412
 
413
413
  # this allows testing without airflow interaction:
@@ -111,16 +111,49 @@ class BaseMetaResource(Resource, MethodResource):
111
111
  return {"message": "Created correctly"}, 201
112
112
 
113
113
  def post_bulk_update(self, data, trace_field="user_id"):
114
- """"""
114
+ """
115
+ Method to POST a bulk of objects, updating the ones that already exist
116
+ and creating the ones that do not.
117
+
118
+ The rows that already exist are looked up with the fields declared on
119
+ `self.unique`, regardless of their `deleted_at` value, so a soft deleted
120
+ row is matched too. On the models that implement the soft delete (the
121
+ ones with a `deleted_at` column) a match that was deleted is reactivated,
122
+ clearing its `deleted_at` before applying the update, so it becomes
123
+ visible again on the reads.
124
+
125
+ If no row matches on `self.unique` but the model has soft delete and the
126
+ payload carries an `id`, the row is looked up by that id as a fallback.
127
+ A soft deleted row found this way is reactivated the same way as a
128
+ regular match. If no row is found, or the row found is not deleted, a
129
+ new instance is created.
130
+
131
+ :param dict data: a dictionary with key 'data' that holds a list with all the
132
+ objects that are going to be created or updated
133
+ :param str trace_field: the field that tracks the user that created the object
134
+ :return: a message and a status code of the operation
135
+ """
115
136
  data = [
116
137
  {**el, **{trace_field: self.get_user_id()}} for el in dict(data)["data"]
117
138
  ]
118
139
 
140
+ has_soft_delete = hasattr(self.data_model, "deleted_at")
141
+
119
142
  instances = []
120
143
  for el in data:
121
144
  temp_el = dict(SuperDict(el).kfilter(lambda v: v in self.unique))
122
145
  temp_instance = self.data_model.query.filter_by(**temp_el).first()
146
+ if (
147
+ temp_instance is None
148
+ and has_soft_delete
149
+ and el.get("id") is not None
150
+ ):
151
+ candidate = self.data_model.query.filter_by(id=el.get("id")).first()
152
+ if candidate is not None and candidate.deleted_at is not None:
153
+ temp_instance = candidate
123
154
  if temp_instance is not None:
155
+ if has_soft_delete and temp_instance.deleted_at is not None:
156
+ temp_instance.deleted_at = None
124
157
  temp_instance.pre_update(el)
125
158
  instances.append(temp_instance)
126
159
  else:
@@ -7,7 +7,7 @@ AIRFLOW_BACKEND = 1
7
7
  DATABRICKS_BACKEND = 2
8
8
 
9
9
 
10
- CORNFLOW_VERSION = "1.3.7"
10
+ CORNFLOW_VERSION = "1.3.8"
11
11
  INTERNAL_TOKEN_ISSUER = "cornflow"
12
12
 
13
13
  # endpoints responses for health check
@@ -31,7 +31,6 @@ def get_email(text: str, subject: str, sender: str, receiver: str):
31
31
 
32
32
  t = MIMEText(body, "html")
33
33
 
34
- email.attach(t)
35
34
  email.attach(t)
36
35
 
37
36
  return email.as_string()
@@ -0,0 +1,256 @@
1
+ """
2
+ Unit tests for the BaseMetaResource class.
3
+
4
+ This module contains tests for the logic that all the resources share, and that no
5
+ endpoint of cornflow exposes on its own: the bulk update. The endpoints that use it
6
+ are the ones that the cli generates (`put_bulk`), so the resource is built here the
7
+ same way `cli/tools/endpoint_tools.py` builds it.
8
+
9
+ Classes
10
+ -------
11
+ TestPostBulkUpdate
12
+ Tests for the bulk update on a model with soft delete
13
+ TestPostBulkUpdateNoSoftDelete
14
+ Tests for the bulk update on a model without soft delete
15
+ TestPostBulkUpdateClientId
16
+ Tests for the bulk update on the models that take the id from the payload
17
+ """
18
+
19
+ # Imports from internal modules
20
+ from cornflow.endpoints.meta_resource import BaseMetaResource
21
+ from cornflow.models import AlarmsModel, RoleModel, ViewModel
22
+ from cornflow.shared.exceptions import InvalidData
23
+ from cornflow.tests.custom_test_case import CustomTestCase
24
+
25
+
26
+ class BulkUpdateTestCase(CustomTestCase):
27
+ """
28
+ Base test case that builds a resource on top of a model and sends rows to its
29
+ bulk update.
30
+ """
31
+
32
+ # the model the resource works on, set by each test case
33
+ data_model = None
34
+
35
+ def setUp(self):
36
+ super().setUp()
37
+ self.model = self.data_model
38
+ self.resource = BaseMetaResource()
39
+ self.resource.data_model = self.data_model
40
+ # the id is what the generated endpoints use as unique
41
+ self.resource.unique = ["id"]
42
+ # the bulk update tracks the user that sends the rows, but there is no request
43
+ # on these tests, so the user is given directly
44
+ self.resource.get_user_id = lambda: self.user.id
45
+
46
+ def bulk_update(self, rows):
47
+ """
48
+ Sends a list of rows to the bulk update of the resource
49
+
50
+ :param list rows: the rows to create or update
51
+ :return: the message and the status code returned by the resource
52
+ :rtype: Tuple(dict, integer)
53
+ """
54
+ return self.resource.post_bulk_update(data={"data": rows})
55
+
56
+
57
+ class TestPostBulkUpdate(BulkUpdateTestCase):
58
+ """
59
+ Test cases for the bulk update on :class:`AlarmsModel`, a model with soft delete
60
+ whose id is given by the database.
61
+ """
62
+
63
+ data_model = AlarmsModel
64
+
65
+ def create_alarm(self, name, criticality=1, description="Description"):
66
+ """
67
+ Creates one alarm directly on the database
68
+
69
+ :param str name: the name of the alarm
70
+ :param float criticality: the criticality of the alarm
71
+ :param str description: the description of the alarm
72
+ :return: the created alarm
73
+ :rtype: :class:`AlarmsModel`
74
+ """
75
+ alarm = AlarmsModel(
76
+ {"name": name, "criticality": criticality, "description": description}
77
+ )
78
+ alarm.save()
79
+ return alarm
80
+
81
+ def test_update_existing_row(self):
82
+ """
83
+ A row that exists and is not deleted gets updated
84
+ """
85
+ alarm = self.create_alarm("Alarm 1")
86
+
87
+ _, status = self.bulk_update(
88
+ [
89
+ {
90
+ "id": alarm.id,
91
+ "name": "Alarm 1 edited",
92
+ "criticality": 2,
93
+ "description": "Description",
94
+ }
95
+ ]
96
+ )
97
+
98
+ self.assertEqual(201, status)
99
+ self.assertEqual(1, AlarmsModel.query.count())
100
+ self.assertEqual(
101
+ "Alarm 1 edited", AlarmsModel.get_one_object(idx=alarm.id).name
102
+ )
103
+
104
+ def test_new_row_is_created(self):
105
+ """
106
+ A row whose id does not exist yet gets created, and the rows that are already
107
+ on the table are left alone
108
+ """
109
+ kept = self.create_alarm("Alarm 1", description="Keep me")
110
+
111
+ _, status = self.bulk_update(
112
+ [
113
+ {
114
+ "id": kept.id + 100,
115
+ "name": "Alarm 2",
116
+ "criticality": 1,
117
+ "description": "Description",
118
+ }
119
+ ]
120
+ )
121
+
122
+ self.assertEqual(201, status)
123
+ self.assertEqual(2, AlarmsModel.get_all_objects().count())
124
+ self.assertEqual("Keep me", AlarmsModel.get_one_object(idx=kept.id).description)
125
+
126
+ def test_soft_deleted_row_is_reactivated(self):
127
+ """
128
+ A row that was soft deleted and is matched by the unique fields is
129
+ reactivated: its deleted_at is cleared and it is updated with the data sent,
130
+ instead of a new row being created
131
+ """
132
+ self.resource.unique = ["name"]
133
+ deleted = self.create_alarm("Alarm 1", description="Deleted one")
134
+ deleted.disable()
135
+
136
+ # the row is not visible on the reads any more
137
+ self.assertEqual(0, AlarmsModel.get_all_objects().count())
138
+
139
+ _, status = self.bulk_update(
140
+ [{"name": "Alarm 1", "criticality": 2, "description": "Resent"}]
141
+ )
142
+
143
+ self.assertEqual(201, status)
144
+ reactivated = AlarmsModel.query.get(deleted.id)
145
+ self.assertIsNone(reactivated.deleted_at)
146
+ self.assertEqual("Resent", reactivated.description)
147
+ # and it is visible on the reads again
148
+ visible = AlarmsModel.get_all_objects().all()
149
+ self.assertEqual(1, len(visible))
150
+ self.assertEqual(deleted.id, visible[0].id)
151
+
152
+
153
+ class TestPostBulkUpdateNoSoftDelete(BulkUpdateTestCase):
154
+ """
155
+ Test cases for the bulk update on :class:`ViewModel`, that inherits from
156
+ EmptyBaseModel and therefore has no deleted_at column.
157
+ """
158
+
159
+ data_model = ViewModel
160
+
161
+ def test_update_existing_row(self):
162
+ """
163
+ A model without soft delete keeps updating the row that already exists
164
+ """
165
+ view = ViewModel.query.first()
166
+
167
+ _, status = self.bulk_update(
168
+ [
169
+ {
170
+ "id": view.id,
171
+ "name": view.name,
172
+ "url_rule": view.url_rule,
173
+ "description": "edited description",
174
+ }
175
+ ]
176
+ )
177
+
178
+ self.assertEqual(201, status)
179
+ self.assertEqual("edited description", ViewModel.query.get(view.id).description)
180
+
181
+ def test_new_row_is_created(self):
182
+ """
183
+ A model without soft delete keeps creating the rows that do not exist
184
+ """
185
+ count = ViewModel.query.count()
186
+
187
+ _, status = self.bulk_update(
188
+ [{"id": 10000, "name": "brand new view", "url_rule": "/brand-new/"}]
189
+ )
190
+
191
+ self.assertEqual(201, status)
192
+ self.assertEqual(count + 1, ViewModel.query.count())
193
+ self.assertEqual(1, ViewModel.query.filter_by(name="brand new view").count())
194
+
195
+
196
+ class TestPostBulkUpdateClientId(BulkUpdateTestCase):
197
+ """
198
+ Test cases for the bulk update on the models that take the id from the payload,
199
+ the same way the models that the cli generates do.
200
+ """
201
+
202
+ data_model = RoleModel
203
+
204
+ def test_id_of_a_soft_deleted_row_is_reactivated(self):
205
+ """
206
+ The id of a soft deleted row can be given again to the bulk update: the row
207
+ that holds it is reactivated and updated with the data sent, instead of
208
+ failing on the primary key or being left deleted
209
+ """
210
+ deleted = RoleModel({"id": 99, "name": "a role"})
211
+ deleted.save()
212
+ deleted.disable()
213
+
214
+ _, status = self.bulk_update([{"id": 99, "name": "a role resent"}])
215
+
216
+ self.assertEqual(201, status)
217
+ reactivated = RoleModel.query.get(99)
218
+ self.assertIsNone(reactivated.deleted_at)
219
+ self.assertEqual("a role resent", reactivated.name)
220
+ # and it is visible on the reads again
221
+ self.assertIsNotNone(RoleModel.get_one_object(idx=99))
222
+
223
+ def test_id_of_a_live_row_is_not_rejected(self):
224
+ """
225
+ An id held by a row that is not deleted is not the case that is rejected: it
226
+ keeps failing on the primary key, the way it did before
227
+ """
228
+ live = RoleModel({"id": 98, "name": "a live role"})
229
+ live.save()
230
+ self.resource.unique = ["name"]
231
+
232
+ with self.assertRaises(InvalidData):
233
+ self.bulk_update([{"id": 98, "name": "another role"}])
234
+
235
+ self.assertEqual("a live role", RoleModel.query.get(98).name)
236
+
237
+ def test_id_of_a_soft_deleted_row_is_reactivated_when_unique_differs(self):
238
+ """
239
+ When the unique fields do not match a soft deleted row, its id can still be
240
+ given again to the bulk update: the row that holds it is reactivated and
241
+ updated with the data sent, instead of failing on the primary key or a new
242
+ row being created
243
+ """
244
+ self.resource.unique = ["name"]
245
+ deleted = RoleModel({"id": 97, "name": "a role"})
246
+ deleted.save()
247
+ deleted.disable()
248
+
249
+ _, status = self.bulk_update([{"id": 97, "name": "a role renamed"}])
250
+
251
+ self.assertEqual(201, status)
252
+ reactivated = RoleModel.query.get(97)
253
+ self.assertIsNone(reactivated.deleted_at)
254
+ self.assertEqual("a role renamed", reactivated.name)
255
+ # and it is visible on the reads again
256
+ self.assertIsNotNone(RoleModel.get_one_object(idx=97))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cornflow
3
- Version: 1.3.7
3
+ Version: 1.3.8
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.3.7
17
+ Requires-Dist: cornflow-client==1.3.8
18
18
  Requires-Dist: cryptography==48.0.1
19
19
  Requires-Dist: disposable-email-domains==0.0.162
20
20
  Requires-Dist: Flask==2.3.2
@@ -180,6 +180,7 @@ cornflow/tests/unit/test_instances_file.py
180
180
  cornflow/tests/unit/test_licenses.py
181
181
  cornflow/tests/unit/test_log_in.py
182
182
  cornflow/tests/unit/test_main_alarms.py
183
+ cornflow/tests/unit/test_meta_resource.py
183
184
  cornflow/tests/unit/test_permissions.py
184
185
  cornflow/tests/unit/test_roles.py
185
186
  cornflow/tests/unit/test_schema_from_models.py
@@ -2,7 +2,7 @@ alembic==1.9.2
2
2
  apispec==6.3.0
3
3
  cachetools==5.3.3
4
4
  click==8.1.7
5
- cornflow-client==1.3.7
5
+ cornflow-client==1.3.8
6
6
  cryptography==48.0.1
7
7
  disposable-email-domains==0.0.162
8
8
  Flask==2.3.2
@@ -2,7 +2,7 @@ alembic==1.9.2
2
2
  apispec==6.3.0
3
3
  cachetools==5.3.3
4
4
  click==8.1.7
5
- cornflow-client==1.3.7
5
+ cornflow-client==1.3.8
6
6
  cryptography==48.0.1
7
7
  disposable-email-domains==0.0.162
8
8
  Flask==2.3.2
@@ -9,7 +9,7 @@ with open("requirements.txt", "r") as fh:
9
9
 
10
10
  setuptools.setup(
11
11
  name="cornflow",
12
- version="1.3.7",
12
+ version="1.3.8",
13
13
  author="baobab soluciones",
14
14
  author_email="cornflow@baobabsoluciones.es",
15
15
  description="cornflow is an open source multi-solver optimization server with a REST API built using flask.",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes