cornflow 1.0.7__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.
- {cornflow-1.0.7/cornflow.egg-info → cornflow-1.0.8a1}/PKG-INFO +20 -7
- {cornflow-1.0.7 → cornflow-1.0.8a1}/README.rst +19 -6
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/schemas.py +66 -55
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/tools/schema_generator.py +3 -3
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/execution.py +24 -20
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/models/dag.py +2 -1
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/models/execution.py +10 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/shared/validators.py +55 -1
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/const.py +4 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_executions.py +58 -7
- cornflow-1.0.8a1/cornflow/tests/unit/test_licenses.py +45 -0
- cornflow-1.0.8a1/cornflow/tests/unit/test_schema_from_models.py +135 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1/cornflow.egg-info}/PKG-INFO +20 -7
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow.egg-info/SOURCES.txt +1 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow.egg-info/requires.txt +7 -2
- {cornflow-1.0.7 → cornflow-1.0.8a1}/setup.py +1 -1
- cornflow-1.0.7/cornflow/tests/unit/test_schema_from_models.py +0 -124
- {cornflow-1.0.7 → cornflow-1.0.8a1}/MANIFEST.in +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/airflow_config/__init__.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/airflow_config/plugins/XCom/__init__.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/airflow_config/plugins/XCom/gce_xcom_backend.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/airflow_config/plugins/__init__.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/airflow_config/webserver_ldap.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/__init__.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/app.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/__init__.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/actions.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/arguments.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/config.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/migrations.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/permissions.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/roles.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/service.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/tools/__init__.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/tools/api_generator.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/tools/endpoint_tools.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/tools/models_tools.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/tools/schemas_tools.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/tools/tools.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/users.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/utils.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/cli/views.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/commands/__init__.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/commands/access.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/commands/actions.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/commands/cleanup.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/commands/dag.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/commands/permissions.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/commands/roles.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/commands/schemas.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/commands/users.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/commands/views.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/config.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/__init__.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/action.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/alarms.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/apiview.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/case.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/dag.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/data_check.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/example_data.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/health.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/instance.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/licenses.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/login.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/main_alarms.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/meta_resource.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/permission.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/roles.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/schemas.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/signup.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/tables.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/token.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/user.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/endpoints/user_role.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/gunicorn.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/README +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/alembic.ini +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/env.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/script.py.mako +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/versions/00757b557b02_.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/versions/1af47a419bbd_.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/versions/4aac5e0c6e66_.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/versions/7c3ea5ab5501_.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/versions/a472b5ad50b7_.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/versions/c2db9409cb5f_.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/versions/c8a6c762e818_.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/versions/ca449af8034c_.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/versions/d0e0700dcd8e_.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/versions/d1b5be1f0549_.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/versions/e1a50dae1ac9_.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/versions/e937a5234ce4_.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/versions/ebdd955fcc5e_.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/migrations/versions/f3bee20314a2_.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/models/__init__.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/models/action.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/models/alarms.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/models/base_data_model.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/models/case.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/models/dag_permissions.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/models/instance.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/models/main_alarms.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/models/meta_models.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/models/permissions.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/models/role.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/models/user.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/models/user_role.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/models/view.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/__init__.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/action.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/alarms.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/case.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/common.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/dag.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/example_data.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/execution.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/health.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/instance.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/main_alarms.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/model_json.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/patch.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/permissions.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/query.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/role.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/schemas.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/solution_log.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/tables.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/user.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/user_role.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/schemas/view.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/shared/__init__.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/shared/authentication/__init__.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/shared/authentication/auth.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/shared/authentication/decorators.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/shared/authentication/ldap.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/shared/compress.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/shared/const.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/shared/email.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/shared/exceptions.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/shared/licenses.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/shared/log_config.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/shared/query_tools.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/shared/utils.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/shared/utils_tables.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/__init__.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/custom_liveServer.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/custom_test_case.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/integration/__init__.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/integration/test_commands.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/integration/test_cornflowclient.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/ldap/__init__.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/ldap/test_ldap_authentication.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/__init__.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_actions.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_alarms.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_apiview.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_cases.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_cli.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_commands.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_dags.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_data_checks.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_example_data.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_generate_from_schema.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_health.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_instances.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_instances_file.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_log_in.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_main_alarms.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_permissions.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_roles.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_schemas.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_sign_up.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_tables.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_token.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/test_users.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow/tests/unit/tools.py +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow.egg-info/dependency_links.txt +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow.egg-info/entry_points.txt +0 -0
- {cornflow-1.0.7 → cornflow-1.0.8a1}/cornflow.egg-info/top_level.txt +0 -0
- {cornflow-1.0.7 → 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.
|
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.
|
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
|
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
|
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.
|
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
|
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
|
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,
|
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(
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
-
|
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
|
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
|
80
|
-
|
81
|
-
|
82
|
-
|
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 =
|
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(
|
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(
|
448
|
-
|
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
|
-
|
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
|
-
|
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(
|
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)
|