cornflow 1.0.11a1__tar.gz → 1.1.0a1__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 (183) hide show
  1. {cornflow-1.0.11a1/cornflow.egg-info → cornflow-1.1.0a1}/PKG-INFO +1 -1
  2. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/service.py +4 -0
  3. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/commands/__init__.py +1 -1
  4. cornflow-1.1.0a1/cornflow/commands/schemas.py +60 -0
  5. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/config.py +6 -0
  6. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/execution.py +2 -1
  7. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/login.py +16 -13
  8. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/user.py +2 -2
  9. cornflow-1.1.0a1/cornflow/migrations/versions/991b98e24225_.py +33 -0
  10. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/models/user.py +4 -0
  11. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/execution.py +8 -1
  12. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/solution_log.py +11 -5
  13. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/user.py +3 -0
  14. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/shared/authentication/auth.py +1 -1
  15. cornflow-1.1.0a1/cornflow/shared/licenses.py +39 -0
  16. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/custom_test_case.py +17 -3
  17. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/integration/test_cornflowclient.py +20 -14
  18. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_cases.py +95 -6
  19. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_dags.py +48 -1
  20. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_example_data.py +3 -0
  21. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_executions.py +98 -8
  22. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_instances.py +43 -5
  23. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_main_alarms.py +8 -8
  24. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_schemas.py +12 -1
  25. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_token.py +17 -0
  26. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_users.py +16 -0
  27. {cornflow-1.0.11a1 → cornflow-1.1.0a1/cornflow.egg-info}/PKG-INFO +1 -1
  28. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow.egg-info/SOURCES.txt +1 -0
  29. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow.egg-info/requires.txt +1 -1
  30. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/setup.py +1 -1
  31. cornflow-1.0.11a1/cornflow/commands/schemas.py +0 -29
  32. cornflow-1.0.11a1/cornflow/shared/licenses.py +0 -76
  33. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/MANIFEST.in +0 -0
  34. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/README.rst +0 -0
  35. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/airflow_config/__init__.py +0 -0
  36. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/airflow_config/airflow_local_settings.py +0 -0
  37. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/airflow_config/plugins/XCom/__init__.py +0 -0
  38. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/airflow_config/plugins/XCom/gce_xcom_backend.py +0 -0
  39. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/airflow_config/plugins/__init__.py +0 -0
  40. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/airflow_config/webserver_ldap.py +0 -0
  41. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/__init__.py +0 -0
  42. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/app.py +0 -0
  43. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/__init__.py +0 -0
  44. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/actions.py +0 -0
  45. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/arguments.py +0 -0
  46. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/config.py +0 -0
  47. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/migrations.py +0 -0
  48. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/permissions.py +0 -0
  49. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/roles.py +0 -0
  50. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/schemas.py +0 -0
  51. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/tools/__init__.py +0 -0
  52. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/tools/api_generator.py +0 -0
  53. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/tools/endpoint_tools.py +0 -0
  54. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/tools/models_tools.py +0 -0
  55. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/tools/schema_generator.py +0 -0
  56. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/tools/schemas_tools.py +0 -0
  57. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/tools/tools.py +0 -0
  58. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/users.py +0 -0
  59. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/utils.py +0 -0
  60. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/cli/views.py +0 -0
  61. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/commands/access.py +0 -0
  62. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/commands/actions.py +0 -0
  63. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/commands/cleanup.py +0 -0
  64. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/commands/dag.py +0 -0
  65. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/commands/permissions.py +0 -0
  66. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/commands/roles.py +0 -0
  67. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/commands/users.py +0 -0
  68. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/commands/views.py +0 -0
  69. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/__init__.py +0 -0
  70. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/action.py +0 -0
  71. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/alarms.py +0 -0
  72. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/apiview.py +0 -0
  73. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/case.py +0 -0
  74. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/dag.py +0 -0
  75. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/data_check.py +0 -0
  76. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/example_data.py +0 -0
  77. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/health.py +0 -0
  78. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/instance.py +0 -0
  79. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/licenses.py +0 -0
  80. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/main_alarms.py +0 -0
  81. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/meta_resource.py +0 -0
  82. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/permission.py +0 -0
  83. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/roles.py +0 -0
  84. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/schemas.py +0 -0
  85. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/signup.py +0 -0
  86. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/tables.py +0 -0
  87. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/token.py +0 -0
  88. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/endpoints/user_role.py +0 -0
  89. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/gunicorn.py +0 -0
  90. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/README +0 -0
  91. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/alembic.ini +0 -0
  92. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/env.py +0 -0
  93. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/script.py.mako +0 -0
  94. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/versions/00757b557b02_.py +0 -0
  95. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/versions/1af47a419bbd_.py +0 -0
  96. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/versions/4aac5e0c6e66_.py +0 -0
  97. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/versions/7c3ea5ab5501_.py +0 -0
  98. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/versions/a472b5ad50b7_.py +0 -0
  99. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/versions/c2db9409cb5f_.py +0 -0
  100. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/versions/c8a6c762e818_.py +0 -0
  101. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/versions/ca449af8034c_.py +0 -0
  102. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/versions/d0e0700dcd8e_.py +0 -0
  103. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/versions/d1b5be1f0549_.py +0 -0
  104. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/versions/e1a50dae1ac9_.py +0 -0
  105. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/versions/e937a5234ce4_.py +0 -0
  106. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/versions/ebdd955fcc5e_.py +0 -0
  107. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/migrations/versions/f3bee20314a2_.py +0 -0
  108. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/models/__init__.py +0 -0
  109. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/models/action.py +0 -0
  110. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/models/alarms.py +0 -0
  111. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/models/base_data_model.py +0 -0
  112. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/models/case.py +0 -0
  113. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/models/dag.py +0 -0
  114. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/models/dag_permissions.py +0 -0
  115. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/models/execution.py +0 -0
  116. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/models/instance.py +0 -0
  117. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/models/main_alarms.py +0 -0
  118. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/models/meta_models.py +0 -0
  119. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/models/permissions.py +0 -0
  120. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/models/role.py +0 -0
  121. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/models/user_role.py +0 -0
  122. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/models/view.py +0 -0
  123. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/__init__.py +0 -0
  124. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/action.py +0 -0
  125. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/alarms.py +0 -0
  126. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/case.py +0 -0
  127. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/common.py +0 -0
  128. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/dag.py +0 -0
  129. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/example_data.py +0 -0
  130. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/health.py +0 -0
  131. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/instance.py +0 -0
  132. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/main_alarms.py +0 -0
  133. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/model_json.py +0 -0
  134. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/patch.py +0 -0
  135. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/permissions.py +0 -0
  136. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/query.py +0 -0
  137. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/role.py +0 -0
  138. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/schemas.py +0 -0
  139. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/tables.py +0 -0
  140. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/user_role.py +0 -0
  141. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/schemas/view.py +0 -0
  142. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/shared/__init__.py +0 -0
  143. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/shared/authentication/__init__.py +0 -0
  144. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/shared/authentication/decorators.py +0 -0
  145. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/shared/authentication/ldap.py +0 -0
  146. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/shared/compress.py +0 -0
  147. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/shared/const.py +0 -0
  148. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/shared/email.py +0 -0
  149. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/shared/exceptions.py +0 -0
  150. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/shared/log_config.py +0 -0
  151. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/shared/query_tools.py +0 -0
  152. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/shared/utils.py +0 -0
  153. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/shared/utils_tables.py +0 -0
  154. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/shared/validators.py +0 -0
  155. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/__init__.py +0 -0
  156. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/const.py +0 -0
  157. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/custom_liveServer.py +0 -0
  158. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/integration/__init__.py +0 -0
  159. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/integration/test_commands.py +0 -0
  160. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/ldap/__init__.py +0 -0
  161. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/ldap/test_ldap_authentication.py +0 -0
  162. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/__init__.py +0 -0
  163. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_actions.py +0 -0
  164. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_alarms.py +0 -0
  165. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_apiview.py +0 -0
  166. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_cli.py +0 -0
  167. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_commands.py +0 -0
  168. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_data_checks.py +0 -0
  169. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_generate_from_schema.py +0 -0
  170. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_health.py +0 -0
  171. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_instances_file.py +0 -0
  172. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_licenses.py +0 -0
  173. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_log_in.py +0 -0
  174. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_permissions.py +0 -0
  175. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_roles.py +0 -0
  176. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_schema_from_models.py +0 -0
  177. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_sign_up.py +0 -0
  178. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/test_tables.py +0 -0
  179. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow/tests/unit/tools.py +0 -0
  180. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow.egg-info/dependency_links.txt +0 -0
  181. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow.egg-info/entry_points.txt +0 -0
  182. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/cornflow.egg-info/top_level.txt +0 -0
  183. {cornflow-1.0.11a1 → cornflow-1.1.0a1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.2
2
2
  Name: cornflow
3
- Version: 1.0.11a1
3
+ Version: 1.1.0a1
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,6 +14,7 @@ from cornflow.commands import (
14
14
  register_deployed_dags_command,
15
15
  register_dag_permissions_command,
16
16
  update_schemas_command,
17
+ update_dag_registry_command,
17
18
  )
18
19
  from cornflow.shared.const import AUTH_DB, ADMIN_ROLE, SERVICE_ROLE
19
20
  from cornflow.shared import db
@@ -211,6 +212,9 @@ def init_cornflow_service():
211
212
  )
212
213
  register_dag_permissions_command(open_deployment, verbose=True)
213
214
  update_schemas_command(airflow_url, airflow_user, airflow_pwd, verbose=True)
215
+ update_dag_registry_command(
216
+ airflow_url, airflow_user, airflow_pwd, verbose=True
217
+ )
214
218
 
215
219
  os.system(
216
220
  f"/usr/local/bin/gunicorn -c python:cornflow.gunicorn "
@@ -6,7 +6,7 @@ from .permissions import (
6
6
  register_dag_permissions_command,
7
7
  )
8
8
  from .roles import register_roles_command
9
- from .schemas import update_schemas_command
9
+ from .schemas import update_schemas_command, update_dag_registry_command
10
10
  from .users import (
11
11
  create_user_with_role,
12
12
  create_service_user_command,
@@ -0,0 +1,60 @@
1
+ def update_schemas_command(url, user, pwd, verbose: bool = False):
2
+ import time
3
+ from flask import current_app
4
+
5
+ from cornflow_client.airflow.api import Airflow
6
+
7
+ af_client = Airflow(url, user, pwd)
8
+ max_attempts = 20
9
+ attempts = 0
10
+ while not af_client.is_alive() and attempts < max_attempts:
11
+ attempts += 1
12
+ if verbose == 1:
13
+ current_app.logger.info(f"Airflow is not reachable (attempt {attempts})")
14
+ time.sleep(15)
15
+
16
+ if not af_client.is_alive():
17
+ if verbose == 1:
18
+ current_app.logger.info("Airflow is not reachable")
19
+ return False
20
+
21
+ response = af_client.update_schemas()
22
+ if response.status_code == 200:
23
+ if verbose:
24
+ current_app.logger.info("DAGs schemas updated")
25
+ else:
26
+ if verbose:
27
+ current_app.logger.info("The DAGs schemas were not updated properly")
28
+
29
+ return True
30
+
31
+
32
+ def update_dag_registry_command(url, user, pwd, verbose: bool = False):
33
+ import time
34
+ from flask import current_app
35
+
36
+ from cornflow_client.airflow.api import Airflow
37
+
38
+ af_client = Airflow(url, user, pwd)
39
+ max_attempts = 20
40
+ attempts = 0
41
+ while not af_client.is_alive() and attempts < max_attempts:
42
+ attempts += 1
43
+ if verbose == 1:
44
+ current_app.logger.info(f"Airflow is not reachable (attempt {attempts})")
45
+ time.sleep(15)
46
+
47
+ if not af_client.is_alive():
48
+ if verbose == 1:
49
+ current_app.logger.info("Airflow is not reachable")
50
+ return False
51
+
52
+ response = af_client.update_dag_registry()
53
+ if response.status_code == 200:
54
+ if verbose:
55
+ current_app.logger.info("DAGs schemas updated on cornflow")
56
+ else:
57
+ if verbose:
58
+ current_app.logger.info("The DAGs schemas were not updated properly")
59
+
60
+ return True
@@ -76,6 +76,12 @@ class DefaultConfig(object):
76
76
  # Alarms endpoints
77
77
  ALARMS_ENDPOINTS = os.getenv("CF_ALARMS_ENDPOINT", 0)
78
78
 
79
+ # Token duration in hours
80
+ TOKEN_DURATION = os.getenv("TOKEN_DURATION", 24)
81
+
82
+ # Password rotation time in days
83
+ PWD_ROTATION_TIME = os.getenv("PWD_ROTATION_TIME", 120)
84
+
79
85
 
80
86
  class Development(DefaultConfig):
81
87
 
@@ -24,6 +24,7 @@ from cornflow.schemas.execution import (
24
24
  ExecutionEditRequest,
25
25
  QueryFiltersExecution,
26
26
  ReLaunchExecutionRequest,
27
+ ExecutionDetailsWithIndicatorsAndLogResponse
27
28
  )
28
29
  from cornflow.shared.authentication import Auth, authenticate
29
30
  from cornflow.shared.compress import compressed
@@ -58,7 +59,7 @@ class ExecutionEndpoint(BaseMetaResource):
58
59
 
59
60
  @doc(description="Get all executions", tags=["Executions"])
60
61
  @authenticate(auth_class=Auth())
61
- @marshal_with(ExecutionDetailsEndpointWithIndicatorsResponse(many=True))
62
+ @marshal_with(ExecutionDetailsWithIndicatorsAndLogResponse(many=True))
62
63
  @use_kwargs(QueryFiltersExecution, location="query")
63
64
  def get(self, **kwargs):
64
65
  """
@@ -6,10 +6,11 @@ External endpoint for the user to login to the cornflow webserver
6
6
  from flask import current_app
7
7
  from flask_apispec import use_kwargs, doc
8
8
  from sqlalchemy.exc import IntegrityError, DBAPIError
9
+ from datetime import datetime, timedelta
9
10
 
10
11
  # Import from internal modules
11
12
  from cornflow.endpoints.meta_resource import BaseMetaResource
12
- from cornflow.models import PermissionsDAG, UserModel, UserRoleModel
13
+ from cornflow.models import UserModel, UserRoleModel
13
14
  from cornflow.schemas.user import LoginEndpointRequest, LoginOpenAuthRequest
14
15
  from cornflow.shared import db
15
16
  from cornflow.shared.authentication import Auth, LDAPBase
@@ -47,9 +48,11 @@ class LoginBaseEndpoint(BaseMetaResource):
47
48
  :rtype: dict
48
49
  """
49
50
  auth_type = current_app.config["AUTH_TYPE"]
51
+ response = {}
50
52
 
51
53
  if auth_type == AUTH_DB:
52
54
  user = self.auth_db_authenticate(**kwargs)
55
+ response.update({"change_password": check_last_password_change(user)})
53
56
  elif auth_type == AUTH_LDAP:
54
57
  user = self.auth_ldap_authenticate(**kwargs)
55
58
  elif auth_type == AUTH_OID:
@@ -62,7 +65,9 @@ class LoginBaseEndpoint(BaseMetaResource):
62
65
  except Exception as e:
63
66
  raise InvalidUsage(f"Error in generating user token: {str(e)}", 400)
64
67
 
65
- return {"token": token, "id": user.id}, 200
68
+ response.update({"token": token, "id": user.id})
69
+
70
+ return response, 200
66
71
 
67
72
  def auth_db_authenticate(self, username, password):
68
73
  """
@@ -176,6 +181,13 @@ class LoginBaseEndpoint(BaseMetaResource):
176
181
  return user
177
182
 
178
183
 
184
+ def check_last_password_change(user):
185
+ if user.pwd_last_change:
186
+ if user.pwd_last_change + timedelta(days=int(current_app.config["PWD_ROTATION_TIME"])) < datetime.utcnow():
187
+ return True
188
+ return False
189
+
190
+
179
191
  class LoginEndpoint(LoginBaseEndpoint):
180
192
  """
181
193
  Endpoint used to do the login to the cornflow webserver
@@ -198,11 +210,7 @@ class LoginEndpoint(LoginBaseEndpoint):
198
210
  :rtype: Tuple(dict, integer)
199
211
  """
200
212
 
201
- content, status = self.log_in(**kwargs)
202
- if int(current_app.config["OPEN_DEPLOYMENT"]) == 1:
203
- PermissionsDAG.delete_all_permissions_from_user(content["id"])
204
- PermissionsDAG.add_all_permissions_to_user(content["id"])
205
- return content, status
213
+ return self.log_in(**kwargs)
206
214
 
207
215
 
208
216
  class LoginOpenAuthEndpoint(LoginBaseEndpoint):
@@ -218,9 +226,4 @@ class LoginOpenAuthEndpoint(LoginBaseEndpoint):
218
226
  @use_kwargs(LoginOpenAuthRequest, location="json")
219
227
  def post(self, **kwargs):
220
228
  """ """
221
-
222
- content, status = self.log_in(**kwargs)
223
- if int(current_app.config["OPEN_DEPLOYMENT"]) == 1:
224
- PermissionsDAG.delete_all_permissions_from_user(content["id"])
225
- PermissionsDAG.add_all_permissions_to_user(content["id"])
226
- return content, status
229
+ return self.log_in(**kwargs)
@@ -170,7 +170,7 @@ class UserDetailsEndpoint(BaseMetaResource):
170
170
  f"To edit a user, go to the OID provider.",
171
171
  )
172
172
 
173
- if data.get("password"):
173
+ if data.get("password") is not None:
174
174
  check, msg = check_password_pattern(data.get("password"))
175
175
  if not check:
176
176
  raise InvalidCredentials(
@@ -179,7 +179,7 @@ class UserDetailsEndpoint(BaseMetaResource):
179
179
  f"The new password is not valid.",
180
180
  )
181
181
 
182
- if data.get("email"):
182
+ if data.get("email") is not None:
183
183
  check, msg = check_email_pattern(data.get("email"))
184
184
  if not check:
185
185
  raise InvalidCredentials(
@@ -0,0 +1,33 @@
1
+ """
2
+ Added pwd_last_change column to users table
3
+
4
+ Revision ID: 991b98e24225
5
+ Revises: ebdd955fcc5e
6
+ Create Date: 2024-01-31 19:17:18.009264
7
+
8
+ """
9
+ from alembic import op
10
+ import sqlalchemy as sa
11
+
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "991b98e24225"
15
+ down_revision = "ebdd955fcc5e"
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade():
21
+ # ### commands auto generated by Alembic - please adjust! ###
22
+ with op.batch_alter_table("users", schema=None) as batch_op:
23
+ batch_op.add_column(sa.Column("pwd_last_change", sa.DateTime(), nullable=True))
24
+
25
+ # ### end Alembic commands ###
26
+
27
+
28
+ def downgrade():
29
+ # ### commands auto generated by Alembic - please adjust! ###
30
+ with op.batch_alter_table("users", schema=None) as batch_op:
31
+ batch_op.drop_column("pwd_last_change")
32
+
33
+ # ### end Alembic commands ###
@@ -4,6 +4,7 @@ This file contains the UserModel
4
4
  # Imports from external libraries
5
5
  import random
6
6
  import string
7
+ from datetime import datetime
7
8
 
8
9
  # Imports from internal modules
9
10
  from cornflow.models.meta_models import TraceAttributesModel
@@ -51,6 +52,7 @@ class UserModel(TraceAttributesModel):
51
52
  last_name = db.Column(db.String(128), nullable=True)
52
53
  username = db.Column(db.String(128), nullable=False, unique=True)
53
54
  password = db.Column(db.String(128), nullable=True)
55
+ pwd_last_change = db.Column(db.DateTime, nullable=True)
54
56
  email = db.Column(db.String(128), nullable=False, unique=True)
55
57
 
56
58
  user_roles = db.relationship(
@@ -93,6 +95,7 @@ class UserModel(TraceAttributesModel):
93
95
  self.first_name = data.get("first_name")
94
96
  self.last_name = data.get("last_name")
95
97
  self.username = data.get("username")
98
+ self.pwd_last_change = datetime.utcnow()
96
99
  # TODO: handle better None passwords that can be found when using ldap
97
100
  check_pass, msg = check_password_pattern(data.get("password"))
98
101
  if check_pass:
@@ -123,6 +126,7 @@ class UserModel(TraceAttributesModel):
123
126
  if new_password:
124
127
  new_password = self.__generate_hash(new_password)
125
128
  data["password"] = new_password
129
+ data["pwd_last_change"] = datetime.utcnow()
126
130
  super().update(data)
127
131
 
128
132
  def comes_from_external_provider(self):
@@ -4,7 +4,7 @@ from marshmallow import fields, Schema, validate
4
4
  # Imports from internal modules
5
5
  from cornflow.shared.const import MIN_EXECUTION_STATUS_CODE, MAX_EXECUTION_STATUS_CODE
6
6
  from .common import QueryFilters, BaseDataEndpointResponse
7
- from .solution_log import LogSchema
7
+ from .solution_log import LogSchema, BasicLogSchema
8
8
 
9
9
 
10
10
  class QueryFiltersExecution(QueryFilters):
@@ -114,6 +114,12 @@ class ExecutionDetailsEndpointWithIndicatorsResponse(ExecutionDetailsEndpointRes
114
114
  indicators = fields.Method("get_indicators")
115
115
 
116
116
 
117
+ class ExecutionDetailsWithIndicatorsAndLogResponse(
118
+ ExecutionDetailsEndpointWithIndicatorsResponse
119
+ ):
120
+ log = fields.Nested(BasicLogSchema, attribute="log_json")
121
+
122
+
117
123
  class ExecutionStatusEndpointResponse(Schema):
118
124
  id = fields.Str()
119
125
  state = fields.Int()
@@ -129,6 +135,7 @@ class ExecutionStatusEndpointUpdate(Schema):
129
135
  class ExecutionDataEndpointResponse(ExecutionDetailsEndpointResponse):
130
136
  data = fields.Raw()
131
137
  checks = fields.Raw()
138
+ log = fields.Nested(BasicLogSchema, attribute="log_json")
132
139
 
133
140
 
134
141
  class ExecutionLogEndpointResponse(ExecutionDetailsEndpointWithIndicatorsResponse):
@@ -1,4 +1,4 @@
1
- from marshmallow import fields, Schema
1
+ from marshmallow import fields, Schema, EXCLUDE
2
2
 
3
3
  options = dict(required=True, allow_none=True)
4
4
  log_options = dict(required=False, allow_none=True)
@@ -49,10 +49,18 @@ class FirstSolution(Schema):
49
49
  CutsBestBound = fields.Float(**options)
50
50
 
51
51
 
52
- class LogSchema(Schema):
52
+ class BasicLogSchema(Schema):
53
+ status = fields.Str(**log_options)
54
+ status_code = fields.Int(**log_options)
55
+ sol_code = fields.Int(**log_options)
56
+
57
+
58
+ class LogSchema(BasicLogSchema):
59
+ class Meta:
60
+ unknown = EXCLUDE
61
+
53
62
  version = fields.Str(**log_options)
54
63
  solver = fields.Str(**log_options)
55
- status = fields.Str(**log_options)
56
64
  best_bound = fields.Float(**log_options)
57
65
  best_solution = fields.Float(**log_options)
58
66
  gap = fields.Float(**log_options)
@@ -63,8 +71,6 @@ class LogSchema(Schema):
63
71
  presolve = fields.Nested(PresolveSchema, **log_options)
64
72
  first_relaxed = fields.Float(**log_options)
65
73
  first_solution = fields.Nested(FirstSolution, **log_options)
66
- status_code = fields.Int(**log_options)
67
- sol_code = fields.Int(**log_options)
68
74
  nodes = fields.Int(**log_options)
69
75
  progress = fields.Nested(ProgressSchema, required=False)
70
76
  cut_info = fields.Raw(**log_options)
@@ -25,6 +25,7 @@ class UserEndpointResponse(Schema):
25
25
  last_name = fields.Str()
26
26
  email = fields.Str()
27
27
  created_at = fields.Str()
28
+ pwd_last_change = fields.Str()
28
29
 
29
30
 
30
31
  class UserDetailsEndpointResponse(Schema):
@@ -33,6 +34,7 @@ class UserDetailsEndpointResponse(Schema):
33
34
  last_name = fields.Str()
34
35
  username = fields.Str()
35
36
  email = fields.Str()
37
+ pwd_last_change = fields.Str()
36
38
 
37
39
 
38
40
  class TokenEndpointResponse(Schema):
@@ -49,6 +51,7 @@ class UserEditRequest(Schema):
49
51
  last_name = fields.Str(required=False)
50
52
  email = fields.Str(required=False)
51
53
  password = fields.Str(required=False)
54
+ pwd_last_change = fields.DateTime(required=False)
52
55
 
53
56
 
54
57
  class LoginEndpointRequest(Schema):
@@ -103,7 +103,7 @@ class Auth:
103
103
  )
104
104
 
105
105
  payload = {
106
- "exp": datetime.utcnow() + timedelta(days=1),
106
+ "exp": datetime.utcnow() + timedelta(hours=float(current_app.config["TOKEN_DURATION"])),
107
107
  "iat": datetime.utcnow(),
108
108
  "sub": user_id,
109
109
  }
@@ -0,0 +1,39 @@
1
+ import importlib.metadata as metadata
2
+
3
+
4
+ def get_info(name, pkg):
5
+ """
6
+ Search information in a package metadata.
7
+ The expected format of the line is "name: info"
8
+ This function searches for the name and returns the info.
9
+
10
+ :param name: name to be searched.
11
+ :param pkg: a dictionary representing the package metadata.
12
+ :return: the info part of the line for the given name.
13
+ """
14
+ if name in pkg:
15
+ return pkg[name]
16
+ return f"({name} not found)"
17
+
18
+
19
+ def get_licenses_summary():
20
+ """
21
+ Get a list of dicts with licenses and library information.
22
+
23
+ :return: a list of dicts with library, license, version, author, description, home page and license text.
24
+ """
25
+ license_list = []
26
+ for pkg in sorted(metadata.distributions(), key=lambda x: x.metadata['Name'].lower()):
27
+ pkg_metadata = dict(pkg.metadata.items())
28
+ license_list += [
29
+ {
30
+ "library": get_info("Name", pkg_metadata),
31
+ "license": get_info("License", pkg_metadata),
32
+ "version": get_info("Version", pkg_metadata),
33
+ "author": get_info("Author", pkg_metadata),
34
+ "description": get_info("Summary", pkg_metadata),
35
+ "home page": get_info("Home-page", pkg_metadata),
36
+ }
37
+ ]
38
+
39
+ return license_list
@@ -5,6 +5,9 @@ This file contains the different custom test classes used to generalize the unit
5
5
  # Import from libraries
6
6
  import logging as log
7
7
  from datetime import datetime, timedelta
8
+
9
+ from typing import List
10
+
8
11
  from flask import current_app
9
12
  from flask_testing import TestCase
10
13
  import json
@@ -27,7 +30,6 @@ from cornflow.tests.const import (
27
30
  TOKEN_URL,
28
31
  )
29
32
 
30
-
31
33
  try:
32
34
  date_from_str = datetime.fromisoformat
33
35
  except:
@@ -172,7 +174,9 @@ class CustomTestCase(TestCase):
172
174
  self.assertEqual(getattr(row, key), payload[key])
173
175
  return row.id
174
176
 
175
- def get_rows(self, url, data, token=None, check_data=True):
177
+ def get_rows(
178
+ self, url, data, token=None, check_data=True, keys_to_check: List[str] = None
179
+ ):
176
180
  token = token or self.token
177
181
 
178
182
  codes = [
@@ -187,6 +191,8 @@ class CustomTestCase(TestCase):
187
191
  if check_data:
188
192
  for i in range(len(data)):
189
193
  self.assertEqual(rows_data[i]["id"], codes[i])
194
+ if keys_to_check:
195
+ self.assertCountEqual(list(rows_data[i].keys()), keys_to_check)
190
196
  for key in self.get_keys_to_check(data[i]):
191
197
  self.assertIn(key, rows_data[i])
192
198
  if key in data[i]:
@@ -199,7 +205,13 @@ class CustomTestCase(TestCase):
199
205
  return payload.keys()
200
206
 
201
207
  def get_one_row(
202
- self, url, payload, expected_status=200, check_payload=True, token=None
208
+ self,
209
+ url,
210
+ payload,
211
+ expected_status=200,
212
+ check_payload=True,
213
+ token=None,
214
+ keys_to_check: List[str] = None,
203
215
  ):
204
216
  token = token or self.token
205
217
 
@@ -210,6 +222,8 @@ class CustomTestCase(TestCase):
210
222
  self.assertEqual(expected_status, row.status_code)
211
223
  if not check_payload:
212
224
  return row.json
225
+ if keys_to_check:
226
+ self.assertCountEqual(list(row.json.keys()), keys_to_check)
213
227
  self.assertEqual(row.json["id"], payload["id"])
214
228
  for key in self.get_keys_to_check(payload):
215
229
  self.assertIn(key, row.json)
@@ -35,6 +35,24 @@ class TestCornflowClientBasic(CustomTestCaseLive):
35
35
  super().setUp()
36
36
  self.items_to_check = ["name", "description"]
37
37
 
38
+ def check_status_evolution(self, execution, end_state=EXEC_STATE_CORRECT):
39
+ statuses = [execution["state"]]
40
+ while end_state not in statuses and len(statuses) < 100:
41
+ time.sleep(1)
42
+ status = self.client.get_status(execution["id"])
43
+ statuses.append(status["state"])
44
+
45
+ self.assertIn(EXEC_STATE_QUEUED, statuses)
46
+ self.assertIn(EXEC_STATE_RUNNING, statuses)
47
+ self.assertIn(end_state, statuses)
48
+
49
+ queued_idx = statuses.index(EXEC_STATE_QUEUED)
50
+ running_idx = statuses.index(EXEC_STATE_RUNNING)
51
+ end_state_idx = statuses.index(end_state)
52
+
53
+ self.assertLess(queued_idx, running_idx)
54
+ self.assertLess(running_idx, end_state_idx)
55
+
38
56
  def create_new_instance_file(self, mps_file):
39
57
  name = "test_instance1"
40
58
  description = "description123"
@@ -141,7 +159,6 @@ class TestCornflowClientBasic(CustomTestCaseLive):
141
159
 
142
160
 
143
161
  class TestCornflowClientOpen(TestCornflowClientBasic):
144
-
145
162
  # TODO: user management
146
163
  # TODO: infeasible execution
147
164
 
@@ -242,7 +259,6 @@ class TestCornflowClientOpen(TestCornflowClientBasic):
242
259
  self.client.create_instance(**payload)
243
260
 
244
261
  def test_new_instance_with_schema_good(self):
245
-
246
262
  payload = load_file(INSTANCE_PATH)
247
263
  payload["schema"] = "solve_model_dag"
248
264
  self.create_new_instance_payload(payload)
@@ -335,23 +351,13 @@ class TestCornflowClientAdmin(TestCornflowClientBasic):
335
351
 
336
352
  def test_status_solving(self):
337
353
  execution = self.create_instance_and_execution()
338
- time.sleep(10)
339
- status = self.client.get_status(execution["id"])
340
- self.assertEqual(status["state"], EXEC_STATE_CORRECT)
354
+ self.check_status_evolution(execution, EXEC_STATE_CORRECT)
341
355
 
342
356
  def test_status_solving_timer(self):
343
357
  execution = self.create_timer_instance_and_execution(10)
344
- status = self.client.get_status(execution["id"])
345
- self.assertEqual(status["state"], EXEC_STATE_QUEUED)
346
- time.sleep(5)
347
- status = self.client.get_status(execution["id"])
348
- self.assertEqual(status["state"], EXEC_STATE_RUNNING)
349
- time.sleep(12)
350
- status = self.client.get_status(execution["id"])
351
- self.assertEqual(status["state"], EXEC_STATE_CORRECT)
358
+ self.check_status_evolution(execution, EXEC_STATE_CORRECT)
352
359
 
353
360
  def test_manual_execution(self):
354
-
355
361
  instance_payload = load_file(INSTANCE_PATH)
356
362
  one_instance = self.create_new_instance_payload(instance_payload)
357
363
  name = "test_execution_name_123"