cornflow 1.3.4rc2__tar.gz → 1.3.5__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.3.4rc2/cornflow.egg-info → cornflow-1.3.5}/PKG-INFO +2 -2
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/commands/auxiliar.py +2 -2
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/commands/dag.py +3 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/config.py +11 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/__init__.py +4 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/data_check.py +2 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/execution.py +205 -9
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/schemas.py +1 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/user_role.py +12 -2
- cornflow-1.3.5/cornflow/migrations/versions/4729cd156460_.py +38 -0
- cornflow-1.3.5/cornflow/migrations/versions/9cacf194fa1c_.py +40 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/models/dag.py +5 -1
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/models/execution.py +70 -2
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/dag.py +2 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/execution.py +24 -3
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/schemas.py +1 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/shared/const.py +27 -1
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/const.py +2 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_cli.py +5 -5
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_dags.py +2 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_data_checks.py +39 -0
- cornflow-1.3.5/cornflow/tests/unit/test_executions.py +494 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_roles.py +30 -17
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_schemas.py +7 -1
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_users.py +93 -1
- {cornflow-1.3.4rc2 → cornflow-1.3.5/cornflow.egg-info}/PKG-INFO +2 -2
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow.egg-info/SOURCES.txt +2 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow.egg-info/requires.txt +1 -1
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/requirements.txt +1 -1
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/setup.py +1 -1
- cornflow-1.3.4rc2/cornflow/tests/unit/test_executions.py +0 -124
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/MANIFEST.in +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/README.rst +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/airflow_config/__init__.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/airflow_config/airflow_local_settings.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/airflow_config/plugins/XCom/__init__.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/airflow_config/plugins/XCom/gce_xcom_backend.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/airflow_config/plugins/__init__.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/airflow_config/webserver_ldap.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/__init__.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/app.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/__init__.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/actions.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/arguments.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/config.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/migrations.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/permissions.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/roles.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/schemas.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/service.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/tools/__init__.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/tools/api_generator.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/tools/endpoint_tools.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/tools/models_tools.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/tools/schema_generator.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/tools/schemas_tools.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/tools/tools.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/users.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/utils.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/cli/views.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/commands/__init__.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/commands/access.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/commands/actions.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/commands/cleanup.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/commands/permissions.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/commands/roles.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/commands/schemas.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/commands/users.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/commands/views.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/action.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/alarms.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/apiview.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/case.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/dag.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/example_data.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/health.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/instance.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/licenses.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/login.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/main_alarms.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/meta_resource.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/permission.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/roles.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/signup.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/tables.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/token.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/endpoints/user.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/gunicorn.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/README +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/alembic.ini +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/env.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/script.py.mako +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/00757b557b02_.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/1af47a419bbd_.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/4aac5e0c6e66_.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/509d280698e4_.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/7c3ea5ab5501_.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/991b98e24225_.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/999b98e24225.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/a472b5ad50b7_.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/c2db9409cb5f_.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/c8a6c762e818_.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/ca449af8034c_.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/cef1df240b27_.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/d0e0700dcd8e_.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/d1b5be1f0549_.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/e1a50dae1ac9_.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/e937a5234ce4_.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/ebdd955fcc5e_.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/migrations/versions/f3bee20314a2_.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/models/__init__.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/models/action.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/models/alarms.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/models/base_data_model.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/models/case.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/models/dag_permissions.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/models/instance.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/models/main_alarms.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/models/meta_models.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/models/permissions.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/models/role.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/models/user.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/models/user_role.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/models/view.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/__init__.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/action.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/alarms.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/case.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/common.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/example_data.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/health.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/instance.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/main_alarms.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/model_json.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/patch.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/permissions.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/query.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/role.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/solution_log.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/tables.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/user.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/user_role.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/schemas/view.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/shared/__init__.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/shared/authentication/__init__.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/shared/authentication/auth.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/shared/authentication/decorators.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/shared/authentication/ldap.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/shared/compress.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/shared/email.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/shared/exceptions.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/shared/licenses.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/shared/log_config.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/shared/query_tools.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/shared/utils.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/shared/utils_tables.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/shared/validators.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/__init__.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/base_test_execution.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/custom_liveServer.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/custom_test_case.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/integration/__init__.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/integration/test_commands.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/integration/test_cornflowclient.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/integration/test_data_checks_kpis.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/ldap/__init__.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/ldap/test_ldap_authentication.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/__init__.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_actions.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_alarms.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_apiview.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_application.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_cases.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_commands.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_example_data.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_external_role_creation.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_generate_from_schema.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_get_resources.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_health.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_instances.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_instances_file.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_licenses.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_log_in.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_main_alarms.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_permissions.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_schema_from_models.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_sign_up.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_tables.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/test_token.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow/tests/unit/tools.py +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow.egg-info/dependency_links.txt +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow.egg-info/entry_points.txt +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/cornflow.egg-info/top_level.txt +0 -0
- {cornflow-1.3.4rc2 → cornflow-1.3.5}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cornflow
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.5
|
|
4
4
|
Summary: cornflow is an open source multi-solver optimization server with a REST API built using flask.
|
|
5
5
|
Home-page: https://github.com/baobabsoluciones/cornflow
|
|
6
6
|
Author: baobab soluciones
|
|
@@ -14,7 +14,7 @@ Requires-Dist: alembic==1.9.2
|
|
|
14
14
|
Requires-Dist: apispec==6.3.0
|
|
15
15
|
Requires-Dist: cachetools==5.3.3
|
|
16
16
|
Requires-Dist: click==8.1.7
|
|
17
|
-
Requires-Dist: cornflow-client==1.3.
|
|
17
|
+
Requires-Dist: cornflow-client==1.3.5
|
|
18
18
|
Requires-Dist: cryptography==46.0.5
|
|
19
19
|
Requires-Dist: disposable-email-domains==0.0.162
|
|
20
20
|
Requires-Dist: Flask==2.3.2
|
|
@@ -87,8 +87,8 @@ def get_new_roles_to_add(extra_permissions, resources_roles_with_access):
|
|
|
87
87
|
|
|
88
88
|
for role_id in roles_with_access:
|
|
89
89
|
if role_id not in existing_roles:
|
|
90
|
-
if role_id in
|
|
91
|
-
# Create
|
|
90
|
+
if role_id in ROLES_MAP:
|
|
91
|
+
# Create role with its predefined name from ROLES_MAP
|
|
92
92
|
role_name = ROLES_MAP[role_id]
|
|
93
93
|
new_role = RoleModel(
|
|
94
94
|
{
|
|
@@ -49,6 +49,7 @@ def register_deployed_dags_command(
|
|
|
49
49
|
"instance_checks_schema": schemas[dag["dag_id"]]["instance_checks"],
|
|
50
50
|
"solution_checks_schema": schemas[dag["dag_id"]]["solution_checks"],
|
|
51
51
|
"config_schema": schemas[dag["dag_id"]]["config"],
|
|
52
|
+
"kpis_schema": schemas[dag["dag_id"]]["kpis"],
|
|
52
53
|
}
|
|
53
54
|
)
|
|
54
55
|
for dag in dag_list
|
|
@@ -92,6 +93,7 @@ def register_deployed_dags_command_test(dags: list = None, verbose: bool = False
|
|
|
92
93
|
"solution_schema": get_pulp_jsonschema(),
|
|
93
94
|
"instance_checks_schema": dict(),
|
|
94
95
|
"solution_checks_schema": dict(),
|
|
96
|
+
"kpis_schema": dict(),
|
|
95
97
|
"config_schema": get_empty_schema(solvers=["cbc", "PULP_CBC_CMD"]),
|
|
96
98
|
}
|
|
97
99
|
)
|
|
@@ -104,6 +106,7 @@ def register_deployed_dags_command_test(dags: list = None, verbose: bool = False
|
|
|
104
106
|
"solution_schema": dict(),
|
|
105
107
|
"instance_checks_schema": dict(),
|
|
106
108
|
"solution_checks_schema": dict(),
|
|
109
|
+
"kpis_schema": dict(),
|
|
107
110
|
"config_schema": dict(),
|
|
108
111
|
}
|
|
109
112
|
)
|
|
@@ -13,6 +13,7 @@ from cornflow.shared.const import (
|
|
|
13
13
|
from apispec import APISpec
|
|
14
14
|
from apispec.ext.marshmallow import MarshmallowPlugin
|
|
15
15
|
|
|
16
|
+
|
|
16
17
|
class DefaultConfig(object):
|
|
17
18
|
"""
|
|
18
19
|
Default configuration class
|
|
@@ -110,6 +111,16 @@ class DefaultConfig(object):
|
|
|
110
111
|
# Alarms endpoints
|
|
111
112
|
ALARMS_ENDPOINTS = os.getenv("CF_ALARMS_ENDPOINT", 0)
|
|
112
113
|
|
|
114
|
+
# Execution files
|
|
115
|
+
EXECUTION_FILES = int(os.getenv("EXECUTION_FILES", 0))
|
|
116
|
+
execution_files_path = os.path.join(os.getcwd(), "execution_files")
|
|
117
|
+
EXECUTION_FILES_PATH = os.getenv("EXECUTION_FILES_PATH", execution_files_path)
|
|
118
|
+
# Cleanup frequency (in days). By default, execution files that are older than 90 days will be deleted.
|
|
119
|
+
# If 0: no executions files will ever be deleted.
|
|
120
|
+
EXECUTION_FILES_CLEANUP_FREQUENCY = int(
|
|
121
|
+
os.getenv("EXECUTION_FILES_CLEANUP_FREQUENCY", 90)
|
|
122
|
+
)
|
|
123
|
+
|
|
113
124
|
# Token duration in hours
|
|
114
125
|
TOKEN_DURATION = os.getenv("TOKEN_DURATION", 24)
|
|
115
126
|
|
|
@@ -39,6 +39,8 @@ from .execution import (
|
|
|
39
39
|
ExecutionDataEndpoint,
|
|
40
40
|
ExecutionLogEndpoint,
|
|
41
41
|
ExecutionRelaunchEndpoint,
|
|
42
|
+
ExecutionFilesEndpoint,
|
|
43
|
+
ExecutionFilesCleanupEndpoint
|
|
42
44
|
)
|
|
43
45
|
from .health import HealthEndpoint
|
|
44
46
|
from .instance import (
|
|
@@ -128,6 +130,8 @@ resources = [
|
|
|
128
130
|
endpoint="execution-relaunch",
|
|
129
131
|
),
|
|
130
132
|
dict(resource=ExecutionEndpoint, urls="/execution/", endpoint="execution"),
|
|
133
|
+
dict(resource=ExecutionFilesEndpoint, urls="/execution/files/<string:idx>/", endpoint="execution-files"),
|
|
134
|
+
dict(resource=ExecutionFilesCleanupEndpoint, urls="/execution/files/cleanup/", endpoint="execution-files-cleanup"),
|
|
131
135
|
dict(resource=DAGDetailEndpoint, urls="/dag/<string:idx>/", endpoint="dag"),
|
|
132
136
|
dict(resource=DAGEndpointManual, urls="/dag/", endpoint="dag-manual"),
|
|
133
137
|
dict(
|
|
@@ -232,6 +232,7 @@ class DataCheckInstanceEndpoint(BaseMetaResource):
|
|
|
232
232
|
instance_id=instance.id,
|
|
233
233
|
name=f"data_check_instance_{instance.name}",
|
|
234
234
|
schema=instance.schema,
|
|
235
|
+
checks_and_kpis_only=True,
|
|
235
236
|
)
|
|
236
237
|
schema = instance.schema
|
|
237
238
|
|
|
@@ -337,6 +338,7 @@ class DataCheckCaseKPIsEndpoint(BaseMetaResource):
|
|
|
337
338
|
instance_id=instance.id,
|
|
338
339
|
name=f"data_check_case_{case.name}",
|
|
339
340
|
schema=schema,
|
|
341
|
+
checks_and_kpis_only=True,
|
|
340
342
|
)
|
|
341
343
|
if case.solution is not None:
|
|
342
344
|
validation_schema = schema
|
|
@@ -4,18 +4,23 @@ or check the status of an ongoing one
|
|
|
4
4
|
These endpoints hve different access url, but manage the same data entities
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
#
|
|
8
|
-
from
|
|
7
|
+
# Imports from external libraries
|
|
8
|
+
from datetime import datetime, timedelta, timezone
|
|
9
|
+
from flask import request, current_app, make_response, send_file
|
|
10
|
+
from flask_apispec import marshal_with, use_kwargs, doc
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import time
|
|
14
|
+
import zipfile
|
|
9
15
|
|
|
16
|
+
# Import from cornflow-client
|
|
17
|
+
from cornflow_client.airflow.api import Airflow
|
|
10
18
|
from cornflow_client.databricks.api import Databricks
|
|
11
19
|
from cornflow_client.constants import INSTANCE_SCHEMA, CONFIG_SCHEMA, SOLUTION_SCHEMA
|
|
12
|
-
from flask import request, current_app
|
|
13
|
-
from flask_apispec import marshal_with, use_kwargs, doc
|
|
14
20
|
|
|
15
21
|
# Import from internal modules
|
|
16
22
|
from cornflow.endpoints.meta_resource import BaseMetaResource
|
|
17
23
|
from cornflow.models import InstanceModel, DeployedWorkflow, ExecutionModel
|
|
18
|
-
from cornflow.shared.const import config_orchestrator
|
|
19
24
|
from cornflow.schemas.execution import (
|
|
20
25
|
ExecutionDetailsEndpointResponse,
|
|
21
26
|
ExecutionDetailsEndpointWithIndicatorsResponse,
|
|
@@ -28,14 +33,15 @@ from cornflow.schemas.execution import (
|
|
|
28
33
|
QueryFiltersExecution,
|
|
29
34
|
ReLaunchExecutionRequest,
|
|
30
35
|
ExecutionDetailsWithIndicatorsAndLogResponse,
|
|
36
|
+
ExecutionFilesPostRequest,
|
|
31
37
|
)
|
|
32
38
|
from cornflow.shared.authentication import Auth, authenticate
|
|
33
39
|
from cornflow.shared.compress import compressed
|
|
34
40
|
from cornflow.shared.const import (
|
|
41
|
+
config_orchestrator,
|
|
42
|
+
SERVICE_ROLE,
|
|
35
43
|
AIRFLOW_BACKEND,
|
|
36
44
|
DATABRICKS_BACKEND,
|
|
37
|
-
)
|
|
38
|
-
from cornflow.shared.const import (
|
|
39
45
|
AIRFLOW_ERROR_MSG,
|
|
40
46
|
AIRFLOW_NOT_REACHABLE_MSG,
|
|
41
47
|
DAG_PAUSED_MSG,
|
|
@@ -49,6 +55,9 @@ from cornflow.shared.const import (
|
|
|
49
55
|
DATABRICKS_TO_STATE_MAP,
|
|
50
56
|
EXEC_STATE_STOPPED,
|
|
51
57
|
EXEC_STATE_QUEUED,
|
|
58
|
+
EXECUTION_FILES_STATUS_OK,
|
|
59
|
+
EXECUTION_FILES_STATUS_DELETED,
|
|
60
|
+
EXECUTION_FILES_STATUS_MESSAGE_DICT,
|
|
52
61
|
)
|
|
53
62
|
|
|
54
63
|
from cornflow.shared.exceptions import (
|
|
@@ -57,6 +66,7 @@ from cornflow.shared.exceptions import (
|
|
|
57
66
|
ObjectDoesNotExist,
|
|
58
67
|
InvalidData,
|
|
59
68
|
EndpointNotImplemented,
|
|
69
|
+
InvalidUsage,
|
|
60
70
|
)
|
|
61
71
|
from cornflow.shared.validators import (
|
|
62
72
|
json_schema_validate_as_string,
|
|
@@ -142,14 +152,16 @@ class ExecutionEndpoint(OrchestratorMixin):
|
|
|
142
152
|
created by the authenticated user) and a integer with the HTTP status code
|
|
143
153
|
:rtype: Tuple(dict, integer)
|
|
144
154
|
"""
|
|
145
|
-
|
|
155
|
+
checks_and_kpis = kwargs.pop("checks_and_kpis", False)
|
|
156
|
+
executions = self.get_list(
|
|
157
|
+
user=self.get_user(), checks_and_kpis=checks_and_kpis, **kwargs
|
|
158
|
+
)
|
|
146
159
|
current_app.logger.info(f"User {self.get_user()} gets list of executions")
|
|
147
160
|
|
|
148
161
|
executions = [
|
|
149
162
|
execution
|
|
150
163
|
for execution in executions
|
|
151
164
|
if not execution.config.get("checks_only", False)
|
|
152
|
-
and not execution.config.get("checks_and_kpis_only", False)
|
|
153
165
|
]
|
|
154
166
|
|
|
155
167
|
running_executions = [
|
|
@@ -754,6 +766,190 @@ class ExecutionLogEndpoint(ExecutionDetailsEndpointBase):
|
|
|
754
766
|
return self.get_detail(user=self.get_user(), idx=idx)
|
|
755
767
|
|
|
756
768
|
|
|
769
|
+
class ExecutionFilesEndpointMixin(ExecutionDetailsEndpointBase):
|
|
770
|
+
"""
|
|
771
|
+
Endpoint used to handle the execution files.
|
|
772
|
+
"""
|
|
773
|
+
|
|
774
|
+
ROLES_WITH_ACCESS = [SERVICE_ROLE]
|
|
775
|
+
|
|
776
|
+
@staticmethod
|
|
777
|
+
def check_execution_files_active(func):
|
|
778
|
+
def wrapper(self, *args, **kwargs):
|
|
779
|
+
"""
|
|
780
|
+
If execution files are not enabled, return an Exception
|
|
781
|
+
"""
|
|
782
|
+
if not current_app.config["EXECUTION_FILES"]:
|
|
783
|
+
raise EndpointNotImplemented(
|
|
784
|
+
"Execution files are not active for this deployment"
|
|
785
|
+
)
|
|
786
|
+
return func(self, *args, **kwargs)
|
|
787
|
+
|
|
788
|
+
return wrapper
|
|
789
|
+
|
|
790
|
+
@staticmethod
|
|
791
|
+
def _get_execution_files_path(execution_idx):
|
|
792
|
+
"""
|
|
793
|
+
Generate execution files path.
|
|
794
|
+
"""
|
|
795
|
+
return os.path.join(
|
|
796
|
+
current_app.config["EXECUTION_FILES_PATH"], f"{execution_idx}.zip"
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
class ExecutionFilesEndpoint(ExecutionFilesEndpointMixin):
|
|
801
|
+
@doc(description="Get execution files", tags=["Executions"], inherit=False)
|
|
802
|
+
@authenticate(auth_class=Auth())
|
|
803
|
+
@ExecutionFilesEndpointMixin.check_execution_files_active
|
|
804
|
+
def get(self, idx):
|
|
805
|
+
"""
|
|
806
|
+
Get the execution files for a specific execution.
|
|
807
|
+
:param str idx: ID of the execution.
|
|
808
|
+
"""
|
|
809
|
+
current_app.logger.info(
|
|
810
|
+
f"User {self.get_user()} gets execution files for execution {idx}"
|
|
811
|
+
)
|
|
812
|
+
execution = self.data_model.get_one_object(user=self.get_user(), idx=idx)
|
|
813
|
+
if execution is None:
|
|
814
|
+
raise ObjectDoesNotExist(
|
|
815
|
+
log_txt=f"Error while user {self.get_user()} tries to get the files of execution {idx}. "
|
|
816
|
+
f"The execution does not exist."
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
execution_files_status = execution.execution_files_status
|
|
820
|
+
if execution_files_status != EXECUTION_FILES_STATUS_OK:
|
|
821
|
+
return {
|
|
822
|
+
"status": execution_files_status,
|
|
823
|
+
"error": EXECUTION_FILES_STATUS_MESSAGE_DICT[execution_files_status],
|
|
824
|
+
}, 400
|
|
825
|
+
|
|
826
|
+
zip_file_path = self._get_execution_files_path(idx)
|
|
827
|
+
if not os.path.exists(zip_file_path):
|
|
828
|
+
# Files were not found. We try indicating to the front to re-generate them.
|
|
829
|
+
execution.update({"execution_files_status": EXECUTION_FILES_STATUS_DELETED})
|
|
830
|
+
return {
|
|
831
|
+
"status": EXECUTION_FILES_STATUS_DELETED,
|
|
832
|
+
"error": EXECUTION_FILES_STATUS_MESSAGE_DICT[
|
|
833
|
+
EXECUTION_FILES_STATUS_DELETED
|
|
834
|
+
],
|
|
835
|
+
}, 400
|
|
836
|
+
|
|
837
|
+
# Generate response
|
|
838
|
+
response = make_response(
|
|
839
|
+
send_file(
|
|
840
|
+
zip_file_path,
|
|
841
|
+
mimetype="application/zip",
|
|
842
|
+
as_attachment=True,
|
|
843
|
+
download_name=f'{execution.name}_{time.strftime("%Y%m%d-%H%M%S")}.zip',
|
|
844
|
+
)
|
|
845
|
+
)
|
|
846
|
+
response.headers["X-Message"] = EXECUTION_FILES_STATUS_MESSAGE_DICT[
|
|
847
|
+
execution_files_status
|
|
848
|
+
]
|
|
849
|
+
response.headers["X-Status-Code"] = "200"
|
|
850
|
+
return response
|
|
851
|
+
|
|
852
|
+
@doc(description="Save an execution's files", tags=["Executions"], inherit=False)
|
|
853
|
+
@authenticate(auth_class=Auth())
|
|
854
|
+
@ExecutionFilesEndpointMixin.check_execution_files_active
|
|
855
|
+
def post(self, idx):
|
|
856
|
+
"""
|
|
857
|
+
Save the execution files for a specific execution.
|
|
858
|
+
:param str idx: ID of the execution.
|
|
859
|
+
:param kwargs: dict with status
|
|
860
|
+
"""
|
|
861
|
+
current_app.logger.info(
|
|
862
|
+
f"User {self.get_user()} creates execution files for execution {idx}"
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
# Check request data format
|
|
866
|
+
request_data = request.values.to_dict()
|
|
867
|
+
request_data = ExecutionFilesPostRequest().load(request_data)
|
|
868
|
+
|
|
869
|
+
execution_files_status = request_data["execution_files_status"]
|
|
870
|
+
|
|
871
|
+
execution = self.data_model.get_one_object(user=self.get_user(), idx=idx)
|
|
872
|
+
if execution is None:
|
|
873
|
+
raise ObjectDoesNotExist(
|
|
874
|
+
log_txt=f"Error while user {self.get_user()} tries to save the files of execution {idx}. "
|
|
875
|
+
f"The execution does not exist."
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
if execution_files_status != EXECUTION_FILES_STATUS_OK:
|
|
879
|
+
execution.update({"execution_files_status": execution_files_status})
|
|
880
|
+
return {"message": "Execution files status saved correctly"}, 200
|
|
881
|
+
|
|
882
|
+
file = request.files.get("execution_file")
|
|
883
|
+
if file is None:
|
|
884
|
+
raise InvalidUsage(
|
|
885
|
+
"Execution file status was 'OK' but not file was provided."
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
if not file.filename.lower().endswith(".zip") or not zipfile.is_zipfile(
|
|
889
|
+
file.stream
|
|
890
|
+
):
|
|
891
|
+
raise InvalidUsage("The execution file must be a valid .zip file")
|
|
892
|
+
|
|
893
|
+
file.stream.seek(0)
|
|
894
|
+
|
|
895
|
+
zip_file_path = self._get_execution_files_path(idx)
|
|
896
|
+
|
|
897
|
+
if not os.path.exists(current_app.config["EXECUTION_FILES_PATH"]):
|
|
898
|
+
os.makedirs(current_app.config["EXECUTION_FILES_PATH"])
|
|
899
|
+
|
|
900
|
+
file.save(zip_file_path)
|
|
901
|
+
execution.update({"execution_files_status": execution_files_status})
|
|
902
|
+
|
|
903
|
+
return {"message": "Execution files saved correctly"}, 200
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
class ExecutionFilesCleanupEndpoint(ExecutionFilesEndpointMixin):
|
|
907
|
+
@doc(description="Clean old executions files", tags=["Executions"], inherit=False)
|
|
908
|
+
@authenticate(auth_class=Auth())
|
|
909
|
+
@ExecutionFilesEndpointMixin.check_execution_files_active
|
|
910
|
+
def delete(self):
|
|
911
|
+
"""
|
|
912
|
+
Clean old execution files.
|
|
913
|
+
"""
|
|
914
|
+
current_app.logger.info(f"User {self.get_user()} runs execution files cleanup")
|
|
915
|
+
|
|
916
|
+
execution_files_path = current_app.config["EXECUTION_FILES_PATH"]
|
|
917
|
+
cleanup_frequency = current_app.config["EXECUTION_FILES_CLEANUP_FREQUENCY"]
|
|
918
|
+
if cleanup_frequency == 0:
|
|
919
|
+
raise EndpointNotImplemented(
|
|
920
|
+
"Execution files cleanup is deactivated server-side."
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
if not os.path.exists(execution_files_path):
|
|
924
|
+
return {"message": "No execution files found"}, 200
|
|
925
|
+
|
|
926
|
+
nb_deleted_files = 0
|
|
927
|
+
for file in os.listdir(execution_files_path):
|
|
928
|
+
execution_id = file.replace(".zip", "")
|
|
929
|
+
full_file_path = os.path.join(execution_files_path, file)
|
|
930
|
+
|
|
931
|
+
execution = self.data_model.get_one_object(
|
|
932
|
+
user=self.get_user(), idx=execution_id
|
|
933
|
+
)
|
|
934
|
+
today = datetime.now(timezone.utc)
|
|
935
|
+
if execution is None or (
|
|
936
|
+
execution.updated_at.replace(tzinfo=timezone.utc)
|
|
937
|
+
<= today - timedelta(days=cleanup_frequency)
|
|
938
|
+
):
|
|
939
|
+
try:
|
|
940
|
+
os.remove(full_file_path)
|
|
941
|
+
nb_deleted_files += 1
|
|
942
|
+
if execution is not None:
|
|
943
|
+
execution.update(
|
|
944
|
+
{"execution_files_status": EXECUTION_FILES_STATUS_DELETED}
|
|
945
|
+
)
|
|
946
|
+
except Exception as err:
|
|
947
|
+
current_app.logger.error(
|
|
948
|
+
f"Error deleting execution file {file}: {err}"
|
|
949
|
+
)
|
|
950
|
+
return {"message": f"{nb_deleted_files} files were deleted."}, 200
|
|
951
|
+
|
|
952
|
+
|
|
757
953
|
# region aux_functions
|
|
758
954
|
|
|
759
955
|
|
|
@@ -70,6 +70,7 @@ class SchemaDetailsEndpoint(BaseMetaResource):
|
|
|
70
70
|
"solution": deployed_dag.solution_schema,
|
|
71
71
|
"instance_checks": deployed_dag.instance_checks_schema,
|
|
72
72
|
"solution_checks": deployed_dag.solution_checks_schema,
|
|
73
|
+
"kpis": deployed_dag.kpis_schema,
|
|
73
74
|
"config": deployed_dag.config_schema,
|
|
74
75
|
"name": dag_name,
|
|
75
76
|
}, 200
|
|
@@ -18,6 +18,10 @@ from cornflow.shared.exceptions import EndpointNotImplemented, ObjectAlreadyExis
|
|
|
18
18
|
|
|
19
19
|
class UserRoleListEndpoint(BaseMetaResource):
|
|
20
20
|
ROLES_WITH_ACCESS = [ADMIN_ROLE]
|
|
21
|
+
# Other roles can get their own roles, but not all roles
|
|
22
|
+
# This is because the admin role is the only one that can get all roles
|
|
23
|
+
# and the other roles can only get their own roles
|
|
24
|
+
|
|
21
25
|
DESCRIPTION = (
|
|
22
26
|
"Endpoint to get the list of roles assigned to users and create new assignments"
|
|
23
27
|
)
|
|
@@ -34,16 +38,22 @@ class UserRoleListEndpoint(BaseMetaResource):
|
|
|
34
38
|
API method to get the assigned roles to the users defined in the application.
|
|
35
39
|
It requires authentication to be passed in the form of a token that has to be linked to
|
|
36
40
|
an existing session (login) made by a user.
|
|
41
|
+
Admin users receive every assignment; other users receive only their own.
|
|
37
42
|
|
|
38
43
|
:return: A dictionary with the response (data of the user assigned roles or an error message)
|
|
39
44
|
and an integer with the HTTP status code.
|
|
40
45
|
:rtype: Tuple(dict, integer)
|
|
41
46
|
|
|
42
47
|
"""
|
|
48
|
+
if self.is_admin():
|
|
49
|
+
current_app.logger.info(
|
|
50
|
+
f"User {self.get_user()} gets all user roles assignments"
|
|
51
|
+
)
|
|
52
|
+
return self.get_list()
|
|
43
53
|
current_app.logger.info(
|
|
44
|
-
f"User {self.get_user()} gets
|
|
54
|
+
f"User {self.get_user()} gets their own user role assignments"
|
|
45
55
|
)
|
|
46
|
-
return self.get_list()
|
|
56
|
+
return self.get_list(user_id=self.get_user_id())
|
|
47
57
|
|
|
48
58
|
@doc(description="Creates a new role assignment", tags=["User roles"])
|
|
49
59
|
@authenticate(auth_class=Auth())
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""empty message
|
|
2
|
+
|
|
3
|
+
Revision ID: 4729cd156460
|
|
4
|
+
Revises: 9cacf194fa1c
|
|
5
|
+
Create Date: 2026-05-07 14:58:53.461691
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from alembic import op
|
|
10
|
+
import sqlalchemy as sa
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# revision identifiers, used by Alembic.
|
|
14
|
+
revision = "4729cd156460"
|
|
15
|
+
down_revision = "9cacf194fa1c"
|
|
16
|
+
branch_labels = None
|
|
17
|
+
depends_on = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade():
|
|
21
|
+
with op.batch_alter_table("executions", schema=None) as batch_op:
|
|
22
|
+
batch_op.add_column(
|
|
23
|
+
sa.Column("execution_files_status", sa.SmallInteger(), nullable=True)
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
op.execute(
|
|
27
|
+
'update "executions" set execution_files_status = 0 where execution_files_status is null'
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
with op.batch_alter_table("executions", schema=None) as batch_op:
|
|
31
|
+
batch_op.alter_column(
|
|
32
|
+
"execution_files_status", existing_type=sa.SmallInteger(), nullable=False
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def downgrade():
|
|
37
|
+
with op.batch_alter_table("executions", schema=None) as batch_op:
|
|
38
|
+
batch_op.drop_column("execution_files_status")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Add kpis_schema to deployed_workflows, add checks_and_kpis_only to execution
|
|
3
|
+
|
|
4
|
+
Revision ID: 9cacf194fa1c
|
|
5
|
+
Revises: 509d280698e4
|
|
6
|
+
Create Date: 2026-04-13 09:44:59.661880
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from alembic import op
|
|
11
|
+
import sqlalchemy as sa
|
|
12
|
+
from sqlalchemy.dialects import postgresql
|
|
13
|
+
|
|
14
|
+
# revision identifiers, used by Alembic.
|
|
15
|
+
revision = "9cacf194fa1c"
|
|
16
|
+
down_revision = "509d280698e4"
|
|
17
|
+
branch_labels = None
|
|
18
|
+
depends_on = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def upgrade():
|
|
22
|
+
with op.batch_alter_table("deployed_workflows", schema=None) as batch_op:
|
|
23
|
+
batch_op.add_column(
|
|
24
|
+
sa.Column(
|
|
25
|
+
"kpis_schema", postgresql.JSON(astext_type=sa.Text()), nullable=True
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
with op.batch_alter_table("executions", schema=None) as batch_op:
|
|
30
|
+
batch_op.add_column(
|
|
31
|
+
sa.Column("checks_and_kpis_only", sa.Boolean(), nullable=True)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def downgrade():
|
|
36
|
+
with op.batch_alter_table("executions", schema=None) as batch_op:
|
|
37
|
+
batch_op.drop_column("checks_and_kpis_only")
|
|
38
|
+
|
|
39
|
+
with op.batch_alter_table("deployed_workflows", schema=None) as batch_op:
|
|
40
|
+
batch_op.drop_column("kpis_schema")
|
|
@@ -7,6 +7,7 @@ from cornflow_client.constants import (
|
|
|
7
7
|
SOLUTION_SCHEMA,
|
|
8
8
|
INSTANCE_CHECKS_SCHEMA,
|
|
9
9
|
SOLUTION_CHECKS_SCHEMA,
|
|
10
|
+
KPIS_SCHEMA,
|
|
10
11
|
)
|
|
11
12
|
from sqlalchemy.dialects.postgresql import TEXT, JSON
|
|
12
13
|
|
|
@@ -29,6 +30,7 @@ class DeployedWorkflow(TraceAttributesModel):
|
|
|
29
30
|
config_schema = db.Column(JSON, nullable=True)
|
|
30
31
|
instance_checks_schema = db.Column(JSON, nullable=True)
|
|
31
32
|
solution_checks_schema = db.Column(JSON, nullable=True)
|
|
33
|
+
kpis_schema = db.Column(JSON, nullable=True)
|
|
32
34
|
|
|
33
35
|
dag_permissions = db.relationship(
|
|
34
36
|
"PermissionsDAG",
|
|
@@ -46,6 +48,7 @@ class DeployedWorkflow(TraceAttributesModel):
|
|
|
46
48
|
self.instance_checks_schema = data.get("instance_checks_schema", None)
|
|
47
49
|
self.solution_checks_schema = data.get("solution_checks_schema", None)
|
|
48
50
|
self.config_schema = data.get("config_schema", None)
|
|
51
|
+
self.kpis_schema = data.get("kpis_schema", None)
|
|
49
52
|
|
|
50
53
|
def __repr__(self):
|
|
51
54
|
return f"<DAG {self.id}>"
|
|
@@ -70,7 +73,8 @@ class DeployedWorkflow(TraceAttributesModel):
|
|
|
70
73
|
jsonschema = item.instance_checks_schema
|
|
71
74
|
elif schema == SOLUTION_CHECKS_SCHEMA:
|
|
72
75
|
jsonschema = item.solution_checks_schema
|
|
73
|
-
|
|
76
|
+
elif schema == KPIS_SCHEMA:
|
|
77
|
+
jsonschema = item.kpis_schema
|
|
74
78
|
else:
|
|
75
79
|
jsonschema = item.config_schema
|
|
76
80
|
|
|
@@ -3,13 +3,22 @@ Model for the executions
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
# Import from libraries
|
|
6
|
+
from flask import current_app
|
|
6
7
|
import hashlib
|
|
7
8
|
from sqlalchemy.dialects.postgresql import JSON, TEXT
|
|
9
|
+
from sqlalchemy import desc, or_
|
|
10
|
+
from sqlalchemy.sql.expression import false
|
|
8
11
|
|
|
9
12
|
# Imports from internal modules
|
|
10
13
|
from cornflow.models.base_data_model import BaseDataModel
|
|
11
14
|
from cornflow.shared import db
|
|
12
|
-
from cornflow.shared.const import
|
|
15
|
+
from cornflow.shared.const import (
|
|
16
|
+
DEFAULT_EXECUTION_CODE,
|
|
17
|
+
EXECUTION_STATE_MESSAGE_DICT,
|
|
18
|
+
EXECUTION_FILES_STATUS_NOT_GENERATED,
|
|
19
|
+
EXECUTION_FILES_STATUS_OK,
|
|
20
|
+
EXECUTION_FILES_STATUS_NOT_UP_TO_DATE,
|
|
21
|
+
)
|
|
13
22
|
|
|
14
23
|
|
|
15
24
|
class ExecutionModel(BaseDataModel):
|
|
@@ -70,6 +79,10 @@ class ExecutionModel(BaseDataModel):
|
|
|
70
79
|
)
|
|
71
80
|
kpis = db.Column(JSON, nullable=True)
|
|
72
81
|
last_run_checks_and_kpis = db.Column(db.Boolean, nullable=True)
|
|
82
|
+
checks_and_kpis_only = db.Column(db.Boolean, nullable=True)
|
|
83
|
+
execution_files_status = db.Column(
|
|
84
|
+
db.SmallInteger, default=EXECUTION_FILES_STATUS_NOT_GENERATED, nullable=False
|
|
85
|
+
)
|
|
73
86
|
|
|
74
87
|
def __init__(self, data):
|
|
75
88
|
super().__init__(data)
|
|
@@ -92,6 +105,10 @@ class ExecutionModel(BaseDataModel):
|
|
|
92
105
|
self.log_json = data.get("log_json")
|
|
93
106
|
self.kpis = data.get("kpis")
|
|
94
107
|
self.last_run_checks_and_kpis = data.get("last_run_checks_and_kpis", False)
|
|
108
|
+
self.checks_and_kpis_only = data.get("checks_and_kpis_only", False)
|
|
109
|
+
self.execution_files_status = data.get(
|
|
110
|
+
"execution_files_status", EXECUTION_FILES_STATUS_NOT_GENERATED
|
|
111
|
+
)
|
|
95
112
|
|
|
96
113
|
def update(self, data):
|
|
97
114
|
"""
|
|
@@ -101,10 +118,12 @@ class ExecutionModel(BaseDataModel):
|
|
|
101
118
|
:return: None
|
|
102
119
|
:rtype: None
|
|
103
120
|
"""
|
|
104
|
-
# Delete the checks and KPIs if the data has been modified since they are probably not valid anymore
|
|
121
|
+
# Delete the checks and KPIs if the data has been modified since they are probably not valid anymore.
|
|
105
122
|
if "data" in data.keys():
|
|
106
123
|
self.checks = None
|
|
107
124
|
self.kpis = None
|
|
125
|
+
if self.execution_files_status == EXECUTION_FILES_STATUS_OK:
|
|
126
|
+
self.execution_files_status = EXECUTION_FILES_STATUS_NOT_UP_TO_DATE
|
|
108
127
|
super().update(data)
|
|
109
128
|
|
|
110
129
|
def update_config(self, config: dict):
|
|
@@ -142,6 +161,55 @@ class ExecutionModel(BaseDataModel):
|
|
|
142
161
|
self.log_text = txt
|
|
143
162
|
super().update({})
|
|
144
163
|
|
|
164
|
+
@classmethod
|
|
165
|
+
def get_all_objects(
|
|
166
|
+
cls,
|
|
167
|
+
user,
|
|
168
|
+
schema=None,
|
|
169
|
+
creation_date_gte=None,
|
|
170
|
+
creation_date_lte=None,
|
|
171
|
+
checks_and_kpis=False,
|
|
172
|
+
offset=0,
|
|
173
|
+
limit=10,
|
|
174
|
+
):
|
|
175
|
+
"""
|
|
176
|
+
Query to get all objects from a user
|
|
177
|
+
:param UserModel user: User object.
|
|
178
|
+
:param string schema: data_schema to filter (dag)
|
|
179
|
+
:param string creation_date_gte: created_at needs to be larger or equal to this
|
|
180
|
+
:param string creation_date_lte: created_at needs to be smaller or equal to this
|
|
181
|
+
:param int offset: query offset for pagination
|
|
182
|
+
:param bool checks_and_kpis: include checks and kpis executions
|
|
183
|
+
:param int limit: query size limit
|
|
184
|
+
:return: The objects
|
|
185
|
+
:rtype: list(:class:`BaseDataModel`)
|
|
186
|
+
"""
|
|
187
|
+
query = cls.query.filter(cls.deleted_at == None)
|
|
188
|
+
user_access = int(current_app.config["USER_ACCESS_ALL_OBJECTS"])
|
|
189
|
+
if (
|
|
190
|
+
user is not None
|
|
191
|
+
and not user.is_admin()
|
|
192
|
+
and not user.is_service_user()
|
|
193
|
+
and user_access == 0
|
|
194
|
+
):
|
|
195
|
+
query = query.filter(cls.user_id == user.id)
|
|
196
|
+
|
|
197
|
+
if schema:
|
|
198
|
+
query = query.filter(cls.schema == schema)
|
|
199
|
+
if creation_date_gte:
|
|
200
|
+
query = query.filter(cls.created_at >= creation_date_gte)
|
|
201
|
+
if creation_date_lte:
|
|
202
|
+
query = query.filter(cls.created_at <= creation_date_lte)
|
|
203
|
+
if not checks_and_kpis:
|
|
204
|
+
query = query.filter(
|
|
205
|
+
or_(
|
|
206
|
+
cls.checks_and_kpis_only == false(),
|
|
207
|
+
cls.checks_and_kpis_only.is_(None),
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return query.order_by(desc(cls.created_at)).offset(offset).limit(limit).all()
|
|
212
|
+
|
|
145
213
|
def __repr__(self):
|
|
146
214
|
"""
|
|
147
215
|
Method to represent the class :class:`ExecutionModel`
|
|
@@ -11,6 +11,7 @@ class DeployedDAGSchema(Schema):
|
|
|
11
11
|
config_schema = fields.Raw(required=True, allow_none=False)
|
|
12
12
|
instance_checks_schema = fields.Raw(required=True, allow_none=False)
|
|
13
13
|
solution_checks_schema = fields.Raw(required=True, allow_none=False)
|
|
14
|
+
kpis_schema = fields.Raw(required=True, allow_none=False)
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class DeployedDAGEditSchema(Schema):
|
|
@@ -20,3 +21,4 @@ class DeployedDAGEditSchema(Schema):
|
|
|
20
21
|
config_schema = fields.Raw(required=False)
|
|
21
22
|
instance_checks_schema = fields.Raw(required=True, allow_none=False)
|
|
22
23
|
solution_checks_schema = fields.Raw(required=True, allow_none=False)
|
|
24
|
+
kpis_schema = fields.Raw(required=True, allow_none=False)
|