cornflow 1.0.6__tar.gz → 1.0.8a1__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 (180) hide show
  1. {cornflow-1.0.6/cornflow.egg-info → cornflow-1.0.8a1}/PKG-INFO +20 -7
  2. {cornflow-1.0.6 → cornflow-1.0.8a1}/README.rst +19 -6
  3. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/schemas.py +66 -55
  4. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/tools/schema_generator.py +3 -3
  5. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/execution.py +24 -20
  6. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/models/dag.py +2 -1
  7. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/models/execution.py +10 -0
  8. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/shared/validators.py +55 -1
  9. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/const.py +4 -0
  10. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_executions.py +58 -7
  11. cornflow-1.0.8a1/cornflow/tests/unit/test_licenses.py +45 -0
  12. cornflow-1.0.8a1/cornflow/tests/unit/test_schema_from_models.py +135 -0
  13. {cornflow-1.0.6 → cornflow-1.0.8a1/cornflow.egg-info}/PKG-INFO +20 -7
  14. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow.egg-info/SOURCES.txt +1 -0
  15. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow.egg-info/requires.txt +7 -2
  16. {cornflow-1.0.6 → cornflow-1.0.8a1}/setup.py +1 -1
  17. cornflow-1.0.6/cornflow/tests/unit/test_schema_from_models.py +0 -124
  18. {cornflow-1.0.6 → cornflow-1.0.8a1}/MANIFEST.in +0 -0
  19. {cornflow-1.0.6 → cornflow-1.0.8a1}/airflow_config/__init__.py +0 -0
  20. {cornflow-1.0.6 → cornflow-1.0.8a1}/airflow_config/plugins/XCom/__init__.py +0 -0
  21. {cornflow-1.0.6 → cornflow-1.0.8a1}/airflow_config/plugins/XCom/gce_xcom_backend.py +0 -0
  22. {cornflow-1.0.6 → cornflow-1.0.8a1}/airflow_config/plugins/__init__.py +0 -0
  23. {cornflow-1.0.6 → cornflow-1.0.8a1}/airflow_config/webserver_ldap.py +0 -0
  24. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/__init__.py +0 -0
  25. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/app.py +0 -0
  26. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/__init__.py +0 -0
  27. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/actions.py +0 -0
  28. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/arguments.py +0 -0
  29. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/config.py +0 -0
  30. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/migrations.py +0 -0
  31. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/permissions.py +0 -0
  32. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/roles.py +0 -0
  33. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/service.py +0 -0
  34. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/tools/__init__.py +0 -0
  35. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/tools/api_generator.py +0 -0
  36. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/tools/endpoint_tools.py +0 -0
  37. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/tools/models_tools.py +0 -0
  38. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/tools/schemas_tools.py +0 -0
  39. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/tools/tools.py +0 -0
  40. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/users.py +0 -0
  41. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/utils.py +0 -0
  42. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/cli/views.py +0 -0
  43. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/commands/__init__.py +0 -0
  44. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/commands/access.py +0 -0
  45. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/commands/actions.py +0 -0
  46. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/commands/cleanup.py +0 -0
  47. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/commands/dag.py +0 -0
  48. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/commands/permissions.py +0 -0
  49. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/commands/roles.py +0 -0
  50. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/commands/schemas.py +0 -0
  51. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/commands/users.py +0 -0
  52. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/commands/views.py +0 -0
  53. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/config.py +0 -0
  54. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/__init__.py +0 -0
  55. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/action.py +0 -0
  56. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/alarms.py +0 -0
  57. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/apiview.py +0 -0
  58. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/case.py +0 -0
  59. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/dag.py +0 -0
  60. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/data_check.py +0 -0
  61. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/example_data.py +0 -0
  62. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/health.py +0 -0
  63. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/instance.py +0 -0
  64. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/licenses.py +0 -0
  65. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/login.py +0 -0
  66. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/main_alarms.py +0 -0
  67. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/meta_resource.py +0 -0
  68. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/permission.py +0 -0
  69. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/roles.py +0 -0
  70. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/schemas.py +0 -0
  71. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/signup.py +0 -0
  72. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/tables.py +0 -0
  73. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/token.py +0 -0
  74. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/user.py +0 -0
  75. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/endpoints/user_role.py +0 -0
  76. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/gunicorn.py +0 -0
  77. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/README +0 -0
  78. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/alembic.ini +0 -0
  79. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/env.py +0 -0
  80. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/script.py.mako +0 -0
  81. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/versions/00757b557b02_.py +0 -0
  82. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/versions/1af47a419bbd_.py +0 -0
  83. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/versions/4aac5e0c6e66_.py +0 -0
  84. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/versions/7c3ea5ab5501_.py +0 -0
  85. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/versions/a472b5ad50b7_.py +0 -0
  86. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/versions/c2db9409cb5f_.py +0 -0
  87. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/versions/c8a6c762e818_.py +0 -0
  88. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/versions/ca449af8034c_.py +0 -0
  89. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/versions/d0e0700dcd8e_.py +0 -0
  90. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/versions/d1b5be1f0549_.py +0 -0
  91. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/versions/e1a50dae1ac9_.py +0 -0
  92. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/versions/e937a5234ce4_.py +0 -0
  93. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/versions/ebdd955fcc5e_.py +0 -0
  94. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/migrations/versions/f3bee20314a2_.py +0 -0
  95. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/models/__init__.py +0 -0
  96. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/models/action.py +0 -0
  97. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/models/alarms.py +0 -0
  98. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/models/base_data_model.py +0 -0
  99. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/models/case.py +0 -0
  100. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/models/dag_permissions.py +0 -0
  101. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/models/instance.py +0 -0
  102. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/models/main_alarms.py +0 -0
  103. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/models/meta_models.py +0 -0
  104. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/models/permissions.py +0 -0
  105. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/models/role.py +0 -0
  106. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/models/user.py +0 -0
  107. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/models/user_role.py +0 -0
  108. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/models/view.py +0 -0
  109. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/__init__.py +0 -0
  110. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/action.py +0 -0
  111. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/alarms.py +0 -0
  112. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/case.py +0 -0
  113. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/common.py +0 -0
  114. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/dag.py +0 -0
  115. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/example_data.py +0 -0
  116. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/execution.py +0 -0
  117. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/health.py +0 -0
  118. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/instance.py +0 -0
  119. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/main_alarms.py +0 -0
  120. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/model_json.py +0 -0
  121. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/patch.py +0 -0
  122. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/permissions.py +0 -0
  123. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/query.py +0 -0
  124. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/role.py +0 -0
  125. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/schemas.py +0 -0
  126. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/solution_log.py +0 -0
  127. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/tables.py +0 -0
  128. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/user.py +0 -0
  129. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/user_role.py +0 -0
  130. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/schemas/view.py +0 -0
  131. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/shared/__init__.py +0 -0
  132. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/shared/authentication/__init__.py +0 -0
  133. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/shared/authentication/auth.py +0 -0
  134. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/shared/authentication/decorators.py +0 -0
  135. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/shared/authentication/ldap.py +0 -0
  136. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/shared/compress.py +0 -0
  137. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/shared/const.py +0 -0
  138. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/shared/email.py +0 -0
  139. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/shared/exceptions.py +0 -0
  140. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/shared/licenses.py +0 -0
  141. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/shared/log_config.py +0 -0
  142. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/shared/query_tools.py +0 -0
  143. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/shared/utils.py +0 -0
  144. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/shared/utils_tables.py +0 -0
  145. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/__init__.py +0 -0
  146. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/custom_liveServer.py +0 -0
  147. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/custom_test_case.py +0 -0
  148. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/integration/__init__.py +0 -0
  149. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/integration/test_commands.py +0 -0
  150. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/integration/test_cornflowclient.py +0 -0
  151. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/ldap/__init__.py +0 -0
  152. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/ldap/test_ldap_authentication.py +0 -0
  153. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/__init__.py +0 -0
  154. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_actions.py +0 -0
  155. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_alarms.py +0 -0
  156. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_apiview.py +0 -0
  157. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_cases.py +0 -0
  158. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_cli.py +0 -0
  159. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_commands.py +0 -0
  160. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_dags.py +0 -0
  161. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_data_checks.py +0 -0
  162. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_example_data.py +0 -0
  163. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_generate_from_schema.py +0 -0
  164. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_health.py +0 -0
  165. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_instances.py +0 -0
  166. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_instances_file.py +0 -0
  167. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_log_in.py +0 -0
  168. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_main_alarms.py +0 -0
  169. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_permissions.py +0 -0
  170. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_roles.py +0 -0
  171. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_schemas.py +0 -0
  172. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_sign_up.py +0 -0
  173. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_tables.py +0 -0
  174. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_token.py +0 -0
  175. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/test_users.py +0 -0
  176. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow/tests/unit/tools.py +0 -0
  177. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow.egg-info/dependency_links.txt +0 -0
  178. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow.egg-info/entry_points.txt +0 -0
  179. {cornflow-1.0.6 → cornflow-1.0.8a1}/cornflow.egg-info/top_level.txt +0 -0
  180. {cornflow-1.0.6 → cornflow-1.0.8a1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.2
2
2
  Name: cornflow
3
- Version: 1.0.6
3
+ Version: 1.0.8a1
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
@@ -48,19 +48,33 @@ Description: Cornflow
48
48
  Installation instructions
49
49
  -------------------------------
50
50
 
51
- Cornflow is tested with Ubuntu 20.04, python >= 3.5 and git.
51
+ Cornflow is tested with Ubuntu 20.04, python >= 3.8 and git.
52
52
 
53
53
  Download the Cornflow project and install requirements::
54
54
 
55
- git clone git@github.com:baobabsoluciones/cornflow.git
56
- cd cornflow-server
57
55
  python3 -m venv venv
58
- venv/bin/pip3 install -r requirements-dev.txt
56
+ venv/bin/pip3 install cornflow
57
+
58
+ initialize the sqlite database::
59
+
60
+ source venv/bin/activate
61
+ export FLASK_APP=cornflow.app
62
+ export DATABASE_URL=sqlite:///cornflow.db
63
+ flask db upgrade
64
+ flask access_init
65
+ flask create_service_user -u airflow -e airflow_test@admin.com -p airflow_test_password
66
+ flask create_admin_user -u cornflow -e cornflow_admin@admin.com -p cornflow_admin_password
67
+
59
68
 
60
69
  activate the virtual environment and run Cornflow::
61
70
 
62
71
  source venv/bin/activate
63
72
  export FLASK_APP=cornflow.app
73
+ export SECRET_KEY=THISNEEDSTOBECHANGED
74
+ export DATABASE_URL=sqlite:///cornflow.db
75
+ export AIRFLOW_URL=http://127.0.0.1:8080/
76
+ export AIRFLOW_USER=airflow_user
77
+ export AIRFLOW_PWD=airflow_pwd
64
78
  flask run
65
79
 
66
80
  **Cornflow needs a running installation of Airflow to operate and more configuration**. Check `the installation docs <https://baobabsoluciones.github.io/cornflow/main/install.html>`_ for more details on installing airflow, configuring the application and initializing the database.
@@ -143,8 +157,7 @@ Description: Cornflow
143
157
 
144
158
  To deploy a cornflow solution method, the following tasks need to be accomplished:
145
159
 
146
- #. Create I/O schemas for the new problem (e.g., “TSP format”).
147
- #. Create a solve function (e.g., a 2-opt heuristic).
160
+ #. Create an Application for the new problem
148
161
  #. Do a PR to a compatible repo linked to a server instance (e.g., like `this one <https://github.com/baobabsoluciones/cornflow>`_).
149
162
 
150
163
  For more details on each part, check the `deployment guide <https://baobabsoluciones.github.io/cornflow/guides/deploy_solver.html>`_.
@@ -40,19 +40,33 @@ Cornflow helps you formalize your problem by proposing development guidelines. I
40
40
  Installation instructions
41
41
  -------------------------------
42
42
 
43
- Cornflow is tested with Ubuntu 20.04, python >= 3.5 and git.
43
+ Cornflow is tested with Ubuntu 20.04, python >= 3.8 and git.
44
44
 
45
45
  Download the Cornflow project and install requirements::
46
46
 
47
- git clone git@github.com:baobabsoluciones/cornflow.git
48
- cd cornflow-server
49
47
  python3 -m venv venv
50
- venv/bin/pip3 install -r requirements-dev.txt
48
+ venv/bin/pip3 install cornflow
49
+
50
+ initialize the sqlite database::
51
+
52
+ source venv/bin/activate
53
+ export FLASK_APP=cornflow.app
54
+ export DATABASE_URL=sqlite:///cornflow.db
55
+ flask db upgrade
56
+ flask access_init
57
+ flask create_service_user -u airflow -e airflow_test@admin.com -p airflow_test_password
58
+ flask create_admin_user -u cornflow -e cornflow_admin@admin.com -p cornflow_admin_password
59
+
51
60
 
52
61
  activate the virtual environment and run Cornflow::
53
62
 
54
63
  source venv/bin/activate
55
64
  export FLASK_APP=cornflow.app
65
+ export SECRET_KEY=THISNEEDSTOBECHANGED
66
+ export DATABASE_URL=sqlite:///cornflow.db
67
+ export AIRFLOW_URL=http://127.0.0.1:8080/
68
+ export AIRFLOW_USER=airflow_user
69
+ export AIRFLOW_PWD=airflow_pwd
56
70
  flask run
57
71
 
58
72
  **Cornflow needs a running installation of Airflow to operate and more configuration**. Check `the installation docs <https://baobabsoluciones.github.io/cornflow/main/install.html>`_ for more details on installing airflow, configuring the application and initializing the database.
@@ -135,8 +149,7 @@ Using cornflow to deploy a solution method
135
149
 
136
150
  To deploy a cornflow solution method, the following tasks need to be accomplished:
137
151
 
138
- #. Create I/O schemas for the new problem (e.g., “TSP format”).
139
- #. Create a solve function (e.g., a 2-opt heuristic).
152
+ #. Create an Application for the new problem
140
153
  #. Do a PR to a compatible repo linked to a server instance (e.g., like `this one <https://github.com/baobabsoluciones/cornflow>`_).
141
154
 
142
155
  For more details on each part, check the `deployment guide <https://baobabsoluciones.github.io/cornflow/guides/deploy_solver.html>`_.
@@ -25,7 +25,7 @@ def schemas():
25
25
 
26
26
  @schemas.command(
27
27
  name="generate_from_schema",
28
- help="Command to generate models, endpoints and schemas from a jsonschema"
28
+ help="Command to generate models, endpoints and schemas from a jsonschema",
29
29
  )
30
30
  @click.option(
31
31
  "--path", "-p", type=str, help="The absolute path to the JSONSchema", required=True
@@ -70,7 +70,13 @@ def schemas():
70
70
  required=False,
71
71
  )
72
72
  def generate_from_schema(
73
- path, app_name, output_path, remove_methods, one, endpoints_methods, endpoints_access
73
+ path,
74
+ app_name,
75
+ output_path,
76
+ remove_methods,
77
+ one,
78
+ endpoints_methods,
79
+ endpoints_access,
74
80
  ):
75
81
  """
76
82
  This method is executed for the command and creates all the files for the REST API from the provided JSONSchema
@@ -126,59 +132,64 @@ def generate_from_schema(
126
132
  output_path=output_path,
127
133
  options=methods_to_add,
128
134
  name_table=name_table,
129
- endpoints_access=dict_endpoints_access
135
+ endpoints_access=dict_endpoints_access,
130
136
  ).main()
131
137
 
132
138
 
133
- @schemas.command(name="schema_from_models", help="Command to generate a jsonschema from a set of models")
134
- @click.option(
135
- "--path",
136
- "-p",
137
- type=str,
138
- help="The absolute path to folder containing the models",
139
- required=True,
140
- )
141
- @click.option("--output-path", "-o", type=str, help="The output path", required=False)
142
- @click.option(
143
- "--ignore-files",
144
- "-i",
145
- type=str,
146
- help="Files that will be ignored (with the .py extension). "
147
- "__init__.py files are automatically ignored. Ex: 'instance.py'",
148
- multiple=True,
149
- required=False,
150
- )
151
- @click.option(
152
- "--leave-bases/--no-leave-bases",
153
- "-l/-nl",
154
- default=False,
155
- help="Use this option to leave the bases classes BaseDataModel, "
156
- "EmptyModel and TraceAttributes in the schema. By default, they will be deleted",
157
- )
158
- def schema_from_models(path, output_path, ignore_files, leave_bases):
159
- """
160
-
161
- :param str path: the path to the folder that contains the models
162
- :param output_path: the output path where the JSONSchema should be placed
163
- :param str ignore_files: files to be ignored.
164
- :param str leave_bases: if the JSONSchema should have abstract classes used as the base for other clases.
165
- :return: a click status code
166
- :rtype: int
167
- """
168
- path = path.replace("\\", "/")
169
- output = None
170
- if output_path:
171
- output = output_path.replace("\\", "/")
172
-
173
- if ignore_files:
174
- ignore_files = list(ignore_files)
175
-
176
- click.echo("Generating JSONSchema file from the REST API")
177
- click.echo(f"The path to the JSONSchema is {path}")
178
- click.echo(f"The output_path is {output}")
179
- click.echo(f"The ignore_files is {ignore_files}")
180
- click.echo(f"The leave_bases is {leave_bases}")
181
-
182
- SchemaGenerator(
183
- path, output_path=output, ignore_files=ignore_files, leave_bases=leave_bases
184
- ).main()
139
+ # @schemas.command(
140
+ # name="schema_from_models",
141
+ # help="Command to generate a jsonschema from a set of models",
142
+ # )
143
+ # @click.option(
144
+ # "--path",
145
+ # "-p",
146
+ # type=str,
147
+ # help="The absolute path to folder containing the models",
148
+ # required=True,
149
+ # )
150
+ # @click.option("--output-path", "-o", type=str, help="The output path", required=False)
151
+ # @click.option(
152
+ # "--ignore-files",
153
+ # "-i",
154
+ # type=str,
155
+ # help="Files that will be ignored (with the .py extension). "
156
+ # "__init__.py files are automatically ignored. Ex: 'instance.py'",
157
+ # multiple=True,
158
+ # required=False,
159
+ # )
160
+ # @click.option(
161
+ # "--leave-bases/--no-leave-bases",
162
+ # "-l/-nl",
163
+ # default=False,
164
+ # help="Use this option to leave the bases classes BaseDataModel, "
165
+ # "EmptyModel and TraceAttributes in the schema. By default, they will be deleted",
166
+ # )
167
+ # def schema_from_models(path, output_path, ignore_files, leave_bases):
168
+ # """
169
+ #
170
+ # :param str path: the path to the folder that contains the models
171
+ # :param output_path: the output path where the JSONSchema should be placed
172
+ # :param str ignore_files: files to be ignored.
173
+ # :param str leave_bases: if the JSONSchema should have abstract classes used as the base for other clases.
174
+ # :return: a click status code
175
+ # :rtype: int
176
+ # """
177
+ # path = path.replace("\\", "/")
178
+ # output = None
179
+ # if output_path:
180
+ # output = output_path.replace("\\", "/")
181
+ #
182
+ # if ignore_files:
183
+ # ignore_files = list(ignore_files)
184
+ #
185
+ # click.echo("Generating JSONSchema file from the REST API")
186
+ # click.echo(f"The path to the JSONSchema is {path}")
187
+ # click.echo(f"The output_path is {output}")
188
+ # click.echo(f"The ignore_files is {ignore_files}")
189
+ # click.echo(f"The leave_bases is {leave_bases}")
190
+ #
191
+ # SchemaGenerator(
192
+ # path, output_path=output, ignore_files=ignore_files, leave_bases=leave_bases
193
+ # ).main()
194
+ #
195
+ # return True
@@ -6,6 +6,8 @@ import json
6
6
  import importlib.util
7
7
  from distutils.dir_util import copy_tree
8
8
  from unittest.mock import MagicMock
9
+
10
+ import click
9
11
  from flask_sqlalchemy import SQLAlchemy
10
12
  import shutil
11
13
  from pytups import TupList, SuperDict
@@ -15,7 +17,6 @@ from sqlalchemy.sql.sqltypes import Integer
15
17
 
16
18
  class SchemaGenerator:
17
19
  def __init__(self, path, output_path=None, ignore_files=None, leave_bases=False):
18
-
19
20
  self.path = path
20
21
  self.tmp_path = os.path.join(os.getcwd(), "tmp_files")
21
22
  self.output_path = output_path or "./output_schema.json"
@@ -76,7 +77,6 @@ class SchemaGenerator:
76
77
  db = SQLAlchemy()
77
78
  try:
78
79
  for file_path, file_name in files:
79
-
80
80
  spec = importlib.util.spec_from_file_location(file_name, file_path)
81
81
  mod = importlib.util.module_from_spec(spec)
82
82
 
@@ -142,7 +142,7 @@ class SchemaGenerator:
142
142
 
143
143
  db.session.close()
144
144
  except Exception as err:
145
- print(err)
145
+ click.echo(err)
146
146
 
147
147
  def inherit(self):
148
148
  all_classes = set(self.parents.keys())
@@ -39,7 +39,11 @@ from cornflow.shared.const import (
39
39
  EXEC_STATE_QUEUED,
40
40
  )
41
41
  from cornflow.shared.exceptions import AirflowError, ObjectDoesNotExist, InvalidData
42
- from cornflow.shared.validators import json_schema_validate_as_string
42
+ from cornflow.shared.validators import (
43
+ json_schema_validate_as_string,
44
+ json_schema_extend_and_validate_as_string,
45
+ )
46
+
43
47
 
44
48
  class ExecutionEndpoint(BaseMetaResource):
45
49
  """
@@ -76,12 +80,10 @@ class ExecutionEndpoint(BaseMetaResource):
76
80
  ]
77
81
 
78
82
  running_executions = [
79
- execution for execution in executions
80
- if execution.state in [
81
- EXEC_STATE_RUNNING,
82
- EXEC_STATE_QUEUED,
83
- EXEC_STATE_UNKNOWN
84
- ]
83
+ execution
84
+ for execution in executions
85
+ if execution.state
86
+ in [EXEC_STATE_RUNNING, EXEC_STATE_QUEUED, EXEC_STATE_UNKNOWN]
85
87
  ]
86
88
 
87
89
  for execution in running_executions:
@@ -146,14 +148,6 @@ class ExecutionEndpoint(BaseMetaResource):
146
148
  user=self.get_user(), idx=execution.instance_id
147
149
  )
148
150
 
149
- if instance is None:
150
- err = "The instance to solve does not exist"
151
- raise ObjectDoesNotExist(
152
- error=err,
153
- log_txt=f"Error while user {self.get_user()} tries to create an execution "
154
- f"for instance {execution.instance_id}. " + err,
155
- )
156
-
157
151
  current_app.logger.debug(f"The request is: {request.args.get('run')}")
158
152
  # this allows testing without airflow interaction:
159
153
  if request.args.get("run", "1") == "0":
@@ -184,7 +178,9 @@ class ExecutionEndpoint(BaseMetaResource):
184
178
 
185
179
  # Validate config before running the dag
186
180
  config_schema = DeployedDAG.get_one_schema(config, schema, CONFIG_SCHEMA)
187
- config_errors = json_schema_validate_as_string(config_schema, kwargs["config"])
181
+ new_config, config_errors = json_schema_extend_and_validate_as_string(
182
+ config_schema, kwargs["config"]
183
+ )
188
184
  if config_errors:
189
185
  execution.update_state(
190
186
  EXEC_STATE_ERROR_START,
@@ -197,6 +193,8 @@ class ExecutionEndpoint(BaseMetaResource):
197
193
  log_txt=f"Error while user {self.get_user()} tries to create an execution. "
198
194
  f"Configuration data does not match the jsonschema.",
199
195
  )
196
+ elif new_config != kwargs["config"]:
197
+ execution.update_config(new_config)
200
198
 
201
199
  # Validate instance data before running the dag
202
200
  instance_schema = DeployedDAG.get_one_schema(config, schema, INSTANCE_SCHEMA)
@@ -326,7 +324,9 @@ class ExecutionRelaunchEndpoint(BaseMetaResource):
326
324
  }, 201
327
325
 
328
326
  # Validate config before running the dag
329
- config_schema = DeployedDAG.get_one_schema(config, kwargs["schema"], CONFIG_SCHEMA)
327
+ config_schema = DeployedDAG.get_one_schema(
328
+ config, kwargs["schema"], CONFIG_SCHEMA
329
+ )
330
330
  config_errors = json_schema_validate_as_string(config_schema, kwargs["config"])
331
331
  if config_errors:
332
332
  raise InvalidData(
@@ -444,14 +444,18 @@ class ExecutionDetailsEndpoint(ExecutionDetailsEndpointBase):
444
444
  schema = ExecutionModel.get_one_object(user=self.get_user(), idx=idx).schema
445
445
 
446
446
  if data.get("data") is not None and schema is not None:
447
- data_jsonschema = DeployedDAG.get_one_schema(config, schema, SOLUTION_SCHEMA)
448
- validation_errors = json_schema_validate_as_string(data_jsonschema, data["data"])
447
+ data_jsonschema = DeployedDAG.get_one_schema(
448
+ config, schema, SOLUTION_SCHEMA
449
+ )
450
+ validation_errors = json_schema_validate_as_string(
451
+ data_jsonschema, data["data"]
452
+ )
449
453
 
450
454
  if validation_errors:
451
455
  raise InvalidData(
452
456
  payload=dict(jsonschema_errors=validation_errors),
453
457
  log_txt=f"Error while user {self.get_user()} tries to edit execution {idx}. "
454
- f"Solution data does not match the jsonschema.",
458
+ f"Solution data does not match the jsonschema.",
455
459
  )
456
460
 
457
461
  current_app.logger.info(f"User {self.get_user()} edits execution {idx}")
@@ -71,7 +71,8 @@ class DeployedDAG(TraceAttributesModel):
71
71
  jsonschema = item.instance_checks_schema
72
72
  elif schema == SOLUTION_CHECKS_SCHEMA:
73
73
  jsonschema = item.solution_checks_schema
74
- else: # schema == CONFIG_SCHEMA
74
+ # schema == CONFIG_SCHEMA
75
+ else:
75
76
  jsonschema = item.config_schema
76
77
 
77
78
  if jsonschema is None:
@@ -99,6 +99,16 @@ class ExecutionModel(BaseDataModel):
99
99
  self.checks = None
100
100
  super().update(data)
101
101
 
102
+ def update_config(self, config: dict):
103
+ """
104
+ Method to update the config of the execution after extending with default values
105
+
106
+ :param dict config: The config to store
107
+ :return: nothing
108
+ """
109
+ self.config = config
110
+ super().update({})
111
+
102
112
  def update_state(self, code, message=None):
103
113
  """
104
114
  Method to update the state code and message of an execution
@@ -4,8 +4,9 @@ This file has several validators
4
4
  import re
5
5
  from typing import Tuple, Union
6
6
 
7
- from jsonschema import Draft7Validator
7
+ from jsonschema import Draft7Validator, validators
8
8
  from disposable_email_domains import blocklist
9
+ from jsonschema.protocols import Validator
9
10
 
10
11
 
11
12
  def is_special_character(character):
@@ -66,6 +67,27 @@ def check_email_pattern(email: str) -> Tuple[bool, Union[str, None]]:
66
67
  return True, None
67
68
 
68
69
 
70
+ def extend_with_default(validator_class):
71
+ """
72
+ Method to extend a validator, so it extends the data with the default values defined on the jsonschema
73
+ """
74
+ validate_properties = validator_class.VALIDATORS["properties"]
75
+
76
+ def set_defaults(validator, properties, instance, schema):
77
+ for prop, sub in properties.items():
78
+ if "default" in sub:
79
+ instance.setdefault(prop, sub["default"])
80
+ for error in validate_properties(
81
+ validator,
82
+ properties,
83
+ instance,
84
+ schema,
85
+ ):
86
+ yield error
87
+
88
+ return validators.extend(validator_class, {"properties": set_defaults})
89
+
90
+
69
91
  def json_schema_validate(schema: dict, data: dict) -> list:
70
92
  """
71
93
  Method to validate some data against a json schema
@@ -81,6 +103,23 @@ def json_schema_validate(schema: dict, data: dict) -> list:
81
103
  return []
82
104
 
83
105
 
106
+ def json_schema_extend_and_validate(schema: dict, data: dict) -> Tuple[dict, list]:
107
+ """
108
+ Method to validate som data, extend it with default values and give back the processed errors
109
+
110
+ :param dict schema: the json schema in dict format.
111
+ :param dict data: the data to validate in dict format
112
+ :return: a tuple with the data extended and the errors found
113
+ :rtype: tuple
114
+ """
115
+ data_cp = dict(data)
116
+ default_validator = extend_with_default(Draft7Validator)
117
+ validator = default_validator(schema)
118
+ if not validator.is_valid(data_cp):
119
+ return data_cp, [e for e in validator.iter_errors(data_cp)]
120
+ return data_cp, []
121
+
122
+
84
123
  def json_schema_validate_as_string(schema: dict, data: dict) -> list:
85
124
  """
86
125
  Method to validate some data against a json schema
@@ -91,3 +130,18 @@ def json_schema_validate_as_string(schema: dict, data: dict) -> list:
91
130
  :rtype: list
92
131
  """
93
132
  return [str(e) for e in json_schema_validate(schema, data)]
133
+
134
+
135
+ def json_schema_extend_and_validate_as_string(
136
+ schema: dict, data: dict
137
+ ) -> Tuple[dict, list]:
138
+ """
139
+ Method to extend the schema with default values and give back the processed error
140
+
141
+ :param dict schema: the json schema in dict format.
142
+ :param dict data: the data to validate in dict format
143
+ :return: a tuple with the data extended and the errors found
144
+ :rtype: tuple
145
+ """
146
+ data_cp, errors = json_schema_extend_and_validate(schema, data)
147
+ return data_cp, [str(e) for e in errors]
@@ -16,6 +16,8 @@ INSTANCE_GC_20 = _get_file("./data/gc_20_7.json")
16
16
  INSTANCE_FILE_FAIL = _get_file("./unit/test_instances.py")
17
17
 
18
18
  EXECUTION_PATH = _get_file("./data/new_execution.json")
19
+ BAD_EXECUTION_PATH = _get_file("./data/bad_execution.json")
20
+ EXECUTION_SOLUTION_PATH = _get_file("./data/new_execution_solution.json")
19
21
  EXECUTIONS_LIST = [EXECUTION_PATH, _get_file("./data/new_execution_2.json")]
20
22
  EXECUTION_URL = PREFIX + "/execution/"
21
23
  EXECUTION_URL_NORUN = EXECUTION_URL + "?run=0"
@@ -63,6 +65,8 @@ TABLES_URL = PREFIX + "/table/"
63
65
  ALARMS_URL = PREFIX + "/alarms/"
64
66
  MAIN_ALARMS_URL = PREFIX + "/main-alarms/"
65
67
 
68
+ LICENSES_URL = PREFIX + "/licences/"
69
+
66
70
  PUBLIC_DAGS = [
67
71
  "solve_model_dag",
68
72
  "gc",
@@ -16,6 +16,8 @@ from cornflow.tests.const import (
16
16
  EXECUTION_URL_NORUN,
17
17
  INSTANCE_URL,
18
18
  DAG_URL,
19
+ BAD_EXECUTION_PATH,
20
+ EXECUTION_SOLUTION_PATH,
19
21
  )
20
22
  from cornflow.tests.custom_test_case import CustomTestCase, BaseTestCases
21
23
  from cornflow.tests.unit.tools import patch_af_client
@@ -38,7 +40,9 @@ class TestExecutionsListEndpoint(BaseTestCases.ListFilters):
38
40
  return temp
39
41
 
40
42
  self.payload = load_file_fk(EXECUTION_PATH)
43
+ self.bad_payload = load_file_fk(BAD_EXECUTION_PATH)
41
44
  self.payloads = [load_file_fk(f) for f in EXECUTIONS_LIST]
45
+ self.solution = load_file_fk(EXECUTION_SOLUTION_PATH)
42
46
 
43
47
  def test_new_execution(self):
44
48
  self.create_new_row(self.url, self.model, payload=self.payload)
@@ -49,6 +53,55 @@ class TestExecutionsListEndpoint(BaseTestCases.ListFilters):
49
53
 
50
54
  self.create_new_row(EXECUTION_URL, self.model, payload=self.payload)
51
55
 
56
+ @patch("cornflow.endpoints.execution.Airflow")
57
+ def test_new_execution_bad_config(self, af_client_class):
58
+ patch_af_client(af_client_class)
59
+ response = self.create_new_row(
60
+ EXECUTION_URL,
61
+ self.model,
62
+ payload=self.bad_payload,
63
+ expected_status=400,
64
+ check_payload=False,
65
+ )
66
+ self.assertIn("error", response)
67
+ self.assertIn("jsonschema_errors", response)
68
+
69
+ @patch("cornflow.endpoints.execution.Airflow")
70
+ def test_new_execution_partial_config(self, af_client_class):
71
+ patch_af_client(af_client_class)
72
+ self.payload["config"].pop("solver")
73
+ response = self.create_new_row(
74
+ EXECUTION_URL, self.model, payload=self.payload, check_payload=False
75
+ )
76
+ self.assertIn("solver", response["config"])
77
+ self.assertEqual(response["config"]["solver"], "cbc")
78
+
79
+ @patch("cornflow.endpoints.execution.Airflow")
80
+ def test_new_execution_with_solution(self, af_client_class):
81
+ patch_af_client(af_client_class)
82
+ self.payload["data"] = self.solution
83
+ response = self.create_new_row(
84
+ EXECUTION_URL,
85
+ self.model,
86
+ payload=self.payload,
87
+ check_payload=False,
88
+ )
89
+
90
+ @patch("cornflow.endpoints.execution.Airflow")
91
+ def test_new_execution_with_solution_bad(self, af_client_class):
92
+ patch_af_client(af_client_class)
93
+ patch_af_client(af_client_class)
94
+ self.payload["data"] = {"message": "THIS IS NOT A VALID SOLUTION"}
95
+ response = self.create_new_row(
96
+ EXECUTION_URL,
97
+ self.model,
98
+ payload=self.payload,
99
+ check_payload=False,
100
+ expected_status=400,
101
+ )
102
+ self.assertIn("error", response)
103
+ self.assertIn("jsonschema_errors", response)
104
+
52
105
  def test_new_execution_no_instance(self):
53
106
  payload = dict(self.payload)
54
107
  payload["instance_id"] = "bad_id"
@@ -281,11 +334,7 @@ class TestExecutionsDetailEndpoint(
281
334
  def test_stop_execution(self, af_client_class):
282
335
  patch_af_client(af_client_class)
283
336
 
284
- idx = self.create_new_row(
285
- EXECUTION_URL,
286
- self.model,
287
- payload=self.payload
288
- )
337
+ idx = self.create_new_row(EXECUTION_URL, self.model, payload=self.payload)
289
338
 
290
339
  response = self.client.post(
291
340
  self.url + str(idx) + "/",
@@ -347,11 +396,13 @@ class TestExecutionsStatusEndpoint(TestExecutionsDetailEndpointMock):
347
396
  @patch("cornflow.endpoints.execution.Airflow")
348
397
  def test_get_one_status(self, af_client_class):
349
398
  patch_af_client(af_client_class)
350
-
399
+
351
400
  idx = self.create_new_row(EXECUTION_URL, self.model, self.payload)
352
401
  payload = dict(self.payload)
353
402
  payload["id"] = idx
354
- data = self.get_one_row(EXECUTION_URL + idx + "/status/", payload, check_payload=False)
403
+ data = self.get_one_row(
404
+ EXECUTION_URL + idx + "/status/", payload, check_payload=False
405
+ )
355
406
  self.assertEqual(data["state"], 1)
356
407
 
357
408
  @patch("cornflow.endpoints.execution.Airflow")
@@ -0,0 +1,45 @@
1
+ from cornflow.endpoints import LicensesEndpoint
2
+ from cornflow.tests.const import LICENSES_URL, _get_file
3
+ from cornflow.tests.custom_test_case import CustomTestCase
4
+
5
+
6
+ class TestLicensesListEndpoint(CustomTestCase):
7
+ @staticmethod
8
+ def read_requirements():
9
+ with open(_get_file("../../requirements.txt")) as req:
10
+ content = req.read()
11
+ requirements = content.split("\n")
12
+
13
+ requirements = [
14
+ r.split("=")[0].split(">")[0].split("<")[0].lower()
15
+ for r in requirements
16
+ if r != ""
17
+ ]
18
+ return requirements
19
+
20
+ def setUp(self):
21
+ super().setUp()
22
+ self.roles_with_access = LicensesEndpoint.ROLES_WITH_ACCESS
23
+ self.libraries = self.read_requirements()
24
+
25
+ def tearDown(self):
26
+ super().tearDown()
27
+
28
+ def test_get_licenses(self):
29
+ for role in self.roles_with_access:
30
+ self.token = self.create_user_with_role(role)
31
+ response = self.client.get(
32
+ LICENSES_URL,
33
+ follow_redirects=True,
34
+ headers={
35
+ "Content-Type": "application/json",
36
+ "Authorization": "Bearer " + self.token,
37
+ },
38
+ )
39
+
40
+ self.assertEqual(200, response.status_code)
41
+ self.assertIsInstance(response.json, list)
42
+ libraries = [k["library"].lower() for k in response.json]
43
+
44
+ for lib in self.libraries:
45
+ self.assertIn(lib, libraries)