cornflow 1.1.2__tar.gz → 1.1.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.1.2 → cornflow-1.1.5}/MANIFEST.in +2 -1
- cornflow-1.1.5/PKG-INFO +264 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/app.py +8 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/config.py +43 -5
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/login.py +86 -35
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/user.py +18 -2
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/shared/authentication/auth.py +10 -4
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/shared/exceptions.py +9 -8
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/custom_test_case.py +342 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_actions.py +46 -1
- cornflow-1.1.5/cornflow/tests/unit/test_alarms.py +87 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_apiview.py +45 -1
- cornflow-1.1.5/cornflow/tests/unit/test_application.py +60 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_cases.py +483 -5
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_cli.py +233 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_commands.py +230 -2
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_dags.py +139 -11
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_data_checks.py +134 -2
- cornflow-1.1.5/cornflow/tests/unit/test_log_in.py +511 -0
- cornflow-1.1.5/cornflow.egg-info/PKG-INFO +264 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow.egg-info/SOURCES.txt +2 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow.egg-info/entry_points.txt +0 -1
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow.egg-info/requires.txt +11 -11
- cornflow-1.1.5/requirements.txt +30 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/setup.py +2 -2
- cornflow-1.1.2/PKG-INFO +0 -228
- cornflow-1.1.2/cornflow/tests/unit/test_alarms.py +0 -39
- cornflow-1.1.2/cornflow/tests/unit/test_log_in.py +0 -33
- cornflow-1.1.2/cornflow.egg-info/PKG-INFO +0 -228
- {cornflow-1.1.2 → cornflow-1.1.5}/README.rst +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/airflow_config/__init__.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/airflow_config/airflow_local_settings.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/airflow_config/plugins/XCom/__init__.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/airflow_config/plugins/XCom/gce_xcom_backend.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/airflow_config/plugins/__init__.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/airflow_config/webserver_ldap.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/__init__.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/__init__.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/actions.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/arguments.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/config.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/migrations.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/permissions.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/roles.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/schemas.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/service.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/tools/__init__.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/tools/api_generator.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/tools/endpoint_tools.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/tools/models_tools.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/tools/schema_generator.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/tools/schemas_tools.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/tools/tools.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/users.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/utils.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/cli/views.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/commands/__init__.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/commands/access.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/commands/actions.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/commands/cleanup.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/commands/dag.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/commands/permissions.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/commands/roles.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/commands/schemas.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/commands/users.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/commands/views.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/__init__.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/action.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/alarms.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/apiview.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/case.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/dag.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/data_check.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/example_data.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/execution.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/health.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/instance.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/licenses.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/main_alarms.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/meta_resource.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/permission.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/roles.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/schemas.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/signup.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/tables.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/token.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/user.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/endpoints/user_role.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/gunicorn.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/README +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/alembic.ini +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/env.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/script.py.mako +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/versions/00757b557b02_.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/versions/1af47a419bbd_.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/versions/4aac5e0c6e66_.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/versions/7c3ea5ab5501_.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/versions/991b98e24225_.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/versions/a472b5ad50b7_.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/versions/c2db9409cb5f_.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/versions/c8a6c762e818_.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/versions/ca449af8034c_.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/versions/d0e0700dcd8e_.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/versions/d1b5be1f0549_.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/versions/e1a50dae1ac9_.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/versions/e937a5234ce4_.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/versions/ebdd955fcc5e_.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/migrations/versions/f3bee20314a2_.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/models/__init__.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/models/action.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/models/alarms.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/models/base_data_model.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/models/case.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/models/dag.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/models/dag_permissions.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/models/execution.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/models/instance.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/models/main_alarms.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/models/meta_models.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/models/permissions.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/models/role.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/models/user.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/models/user_role.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/models/view.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/__init__.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/action.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/alarms.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/case.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/common.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/dag.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/example_data.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/execution.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/health.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/instance.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/main_alarms.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/model_json.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/patch.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/permissions.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/query.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/role.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/schemas.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/solution_log.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/tables.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/user_role.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/schemas/view.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/shared/__init__.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/shared/authentication/__init__.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/shared/authentication/decorators.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/shared/authentication/ldap.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/shared/compress.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/shared/const.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/shared/email.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/shared/licenses.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/shared/log_config.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/shared/query_tools.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/shared/utils.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/shared/utils_tables.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/shared/validators.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/__init__.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/const.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/custom_liveServer.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/integration/__init__.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/integration/test_commands.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/integration/test_cornflowclient.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/ldap/__init__.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/ldap/test_ldap_authentication.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/__init__.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_example_data.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_executions.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_generate_from_schema.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_health.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_instances.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_instances_file.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_licenses.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_main_alarms.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_permissions.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_roles.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_schema_from_models.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_schemas.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_sign_up.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_tables.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_token.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/test_users.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow/tests/unit/tools.py +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow.egg-info/dependency_links.txt +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/cornflow.egg-info/top_level.txt +0 -0
- {cornflow-1.1.2 → cornflow-1.1.5}/setup.cfg +0 -0
cornflow-1.1.5/PKG-INFO
ADDED
@@ -0,0 +1,264 @@
|
|
1
|
+
Metadata-Version: 2.2
|
2
|
+
Name: cornflow
|
3
|
+
Version: 1.1.5
|
4
|
+
Summary: Cornflow is an open source multi-solver optimization server with a REST API built using flask.
|
5
|
+
Home-page: https://github.com/baobabsoluciones/cornflow
|
6
|
+
Author: baobab soluciones
|
7
|
+
Author-email: cornflow@baobabsoluciones.es
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
10
|
+
Classifier: Operating System :: OS Independent
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
12
|
+
Requires-Python: >=3.9
|
13
|
+
Requires-Dist: alembic==1.9.2
|
14
|
+
Requires-Dist: apispec<=6.3.0
|
15
|
+
Requires-Dist: click<=8.1.7
|
16
|
+
Requires-Dist: cornflow-client>=1.1.0
|
17
|
+
Requires-Dist: cryptography<=42.0.5
|
18
|
+
Requires-Dist: disposable-email-domains>=0.0.86
|
19
|
+
Requires-Dist: Flask==2.3.2
|
20
|
+
Requires-Dist: flask-apispec<=0.11.4
|
21
|
+
Requires-Dist: Flask-Bcrypt<=1.0.1
|
22
|
+
Requires-Dist: Flask-Compress<=1.14
|
23
|
+
Requires-Dist: flask-cors>=4.0.2
|
24
|
+
Requires-Dist: flask-inflate<=0.3
|
25
|
+
Requires-Dist: Flask-Migrate<=4.0.5
|
26
|
+
Requires-Dist: Flask-RESTful<=0.3.10
|
27
|
+
Requires-Dist: Flask-SQLAlchemy==2.5.1
|
28
|
+
Requires-Dist: gevent==23.9.1
|
29
|
+
Requires-Dist: greenlet<=2.0.2; python_version < "3.11"
|
30
|
+
Requires-Dist: greenlet==3.0.3; python_version >= "3.11"
|
31
|
+
Requires-Dist: gunicorn<=22.0.0
|
32
|
+
Requires-Dist: jsonpatch<=1.33
|
33
|
+
Requires-Dist: ldap3<=2.9.1
|
34
|
+
Requires-Dist: marshmallow<=3.20.2
|
35
|
+
Requires-Dist: PuLP<=2.9.0
|
36
|
+
Requires-Dist: psycopg2<=2.9.9
|
37
|
+
Requires-Dist: PyJWT<=2.8.0
|
38
|
+
Requires-Dist: pytups>=0.86.2
|
39
|
+
Requires-Dist: requests<=2.32.3
|
40
|
+
Requires-Dist: SQLAlchemy==1.3.21
|
41
|
+
Requires-Dist: webargs<=8.3.0
|
42
|
+
Requires-Dist: Werkzeug==3.0.6
|
43
|
+
Dynamic: author
|
44
|
+
Dynamic: author-email
|
45
|
+
Dynamic: classifier
|
46
|
+
Dynamic: description
|
47
|
+
Dynamic: home-page
|
48
|
+
Dynamic: requires-dist
|
49
|
+
Dynamic: requires-python
|
50
|
+
Dynamic: summary
|
51
|
+
|
52
|
+
Cornflow
|
53
|
+
=========
|
54
|
+
|
55
|
+
.. image:: https://github.com/baobabsoluciones/cornflow/workflows/build/badge.svg?style=svg
|
56
|
+
:target: https://github.com/baobabsoluciones/cornflow/actions
|
57
|
+
|
58
|
+
.. image:: https://github.com/baobabsoluciones/cornflow/workflows/docs/badge.svg?style=svg
|
59
|
+
:target: https://github.com/baobabsoluciones/cornflow/actions
|
60
|
+
|
61
|
+
.. image:: https://github.com/baobabsoluciones/cornflow/workflows/integration/badge.svg?style=svg
|
62
|
+
:target: https://github.com/baobabsoluciones/cornflow/actions
|
63
|
+
|
64
|
+
.. image:: https://img.shields.io/pypi/v/cornflow-client.svg?style=svg
|
65
|
+
:target: https://pypi.python.org/pypi/cornflow-client
|
66
|
+
|
67
|
+
.. image:: https://img.shields.io/pypi/pyversions/cornflow-client.svg?style=svg
|
68
|
+
:target: https://pypi.python.org/pypi/cornflow-client
|
69
|
+
|
70
|
+
.. image:: https://img.shields.io/badge/License-Apache2.0-blue
|
71
|
+
|
72
|
+
Cornflow is an open source multi-solver optimization server with a REST API built using `flask <https://flask.palletsprojects.com>`_, `airflow <https://airflow.apache.org/>`_ and `pulp <https://coin-or.github.io/pulp/>`_.
|
73
|
+
|
74
|
+
While most deployment servers are based on the solving technique (MIP, CP, NLP, etc.), Cornflow focuses on the optimization problems themselves. However, it does not impose any constraint on the type of problem and solution method to use.
|
75
|
+
|
76
|
+
With Cornflow you can deploy a Traveling Salesman Problem solver next to a Knapsack solver or a Nurse Rostering Problem solver. As long as you describe the input and output data, you can upload any solution method for any problem and then use it with any data you want.
|
77
|
+
|
78
|
+
Cornflow helps you formalize your problem by proposing development guidelines. It also provides a range of functionalities around your deployed solution method, namely:
|
79
|
+
|
80
|
+
* storage of users, instances, solutions and solution logs.
|
81
|
+
* deployment and maintenance of models, solvers and algorithms.
|
82
|
+
* scheduling of executions in remote machines.
|
83
|
+
* management of said executions: start, monitor, interrupt.
|
84
|
+
* centralizing of commercial licenses.
|
85
|
+
* scenario storage and comparison.
|
86
|
+
* user management, roles and groups.
|
87
|
+
|
88
|
+
|
89
|
+
.. contents:: **Table of Contents**
|
90
|
+
|
91
|
+
Installation instructions
|
92
|
+
-------------------------------
|
93
|
+
|
94
|
+
Cornflow is tested with Ubuntu 20.04, python >= 3.8 and git.
|
95
|
+
|
96
|
+
Download the Cornflow project and install requirements::
|
97
|
+
|
98
|
+
python3 -m venv venv
|
99
|
+
venv/bin/pip3 install cornflow
|
100
|
+
|
101
|
+
initialize the sqlite database::
|
102
|
+
|
103
|
+
source venv/bin/activate
|
104
|
+
export FLASK_APP=cornflow.app
|
105
|
+
export DATABASE_URL=sqlite:///cornflow.db
|
106
|
+
flask db upgrade
|
107
|
+
flask access_init
|
108
|
+
flask create_service_user -u airflow -e airflow_test@admin.com -p airflow_test_password
|
109
|
+
flask create_admin_user -u cornflow -e cornflow_admin@admin.com -p cornflow_admin_password
|
110
|
+
|
111
|
+
|
112
|
+
activate the virtual environment and run Cornflow::
|
113
|
+
|
114
|
+
source venv/bin/activate
|
115
|
+
export FLASK_APP=cornflow.app
|
116
|
+
export SECRET_KEY=THISNEEDSTOBECHANGED
|
117
|
+
export DATABASE_URL=sqlite:///cornflow.db
|
118
|
+
export AIRFLOW_URL=http://127.0.0.1:8080/
|
119
|
+
export AIRFLOW_USER=airflow_user
|
120
|
+
export AIRFLOW_PWD=airflow_pwd
|
121
|
+
flask run
|
122
|
+
|
123
|
+
**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.
|
124
|
+
|
125
|
+
Using cornflow to solve a PuLP model
|
126
|
+
---------------------------------------
|
127
|
+
|
128
|
+
We're going to test the cornflow server by using the `cornflow-client` and the `pulp` python package::
|
129
|
+
|
130
|
+
pip install cornflow-client pulp
|
131
|
+
|
132
|
+
Initialize the api client::
|
133
|
+
|
134
|
+
from cornflow_client import CornFlow
|
135
|
+
email = 'some_email@gmail.com'
|
136
|
+
pwd = 'Some_password1'
|
137
|
+
username = 'some_name'
|
138
|
+
client = CornFlow(url="http://127.0.0.1:5000")
|
139
|
+
|
140
|
+
Create a user::
|
141
|
+
|
142
|
+
config = dict(username=username, email=email, pwd=pwd)
|
143
|
+
client.sign_up(**config)
|
144
|
+
|
145
|
+
Log in::
|
146
|
+
|
147
|
+
client.login(username=username, pwd=pwd)
|
148
|
+
|
149
|
+
Prepare an instance::
|
150
|
+
|
151
|
+
import pulp
|
152
|
+
prob = pulp.LpProblem("test_export_dict_MIP", pulp.LpMinimize)
|
153
|
+
x = pulp.LpVariable("x", 0, 4)
|
154
|
+
y = pulp.LpVariable("y", -1, 1)
|
155
|
+
z = pulp.LpVariable("z", 0, None, pulp.LpInteger)
|
156
|
+
prob += x + 4 * y + 9 * z, "obj"
|
157
|
+
prob += x + y <= 5, "c1"
|
158
|
+
prob += x + z >= 10, "c2"
|
159
|
+
prob += -y + z == 7.5, "c3"
|
160
|
+
data = prob.to_dict()
|
161
|
+
insName = 'test_export_dict_MIP'
|
162
|
+
description = 'very small example'
|
163
|
+
|
164
|
+
Send instance::
|
165
|
+
|
166
|
+
instance = client.create_instance(data, name=insName, description=description, schema="solve_model_dag",)
|
167
|
+
|
168
|
+
Solve an instance::
|
169
|
+
|
170
|
+
config = dict(
|
171
|
+
solver = "PULP_CBC_CMD",
|
172
|
+
timeLimit = 10
|
173
|
+
)
|
174
|
+
execution = client.create_execution(
|
175
|
+
instance['id'], config, name='execution1', description='execution of a very small instance',
|
176
|
+
schema="solve_model_dag",
|
177
|
+
)
|
178
|
+
|
179
|
+
Check the status of an execution::
|
180
|
+
|
181
|
+
status = client.get_status(execution["id"])
|
182
|
+
print(status['state'])
|
183
|
+
# 1 means "finished correctly"
|
184
|
+
|
185
|
+
Retrieve a solution::
|
186
|
+
|
187
|
+
results = client.get_solution(execution['id'])
|
188
|
+
print(results['data'])
|
189
|
+
# returns a json with the solved pulp object
|
190
|
+
_vars, prob = pulp.LpProblem.from_dict(results['data'])
|
191
|
+
|
192
|
+
Retrieve the log of the solver::
|
193
|
+
|
194
|
+
log = client.get_log(execution['id'])
|
195
|
+
print(log['log'])
|
196
|
+
# json format of the solver log
|
197
|
+
|
198
|
+
Using cornflow to deploy a solution method
|
199
|
+
---------------------------------------------
|
200
|
+
|
201
|
+
To deploy a cornflow solution method, the following tasks need to be accomplished:
|
202
|
+
|
203
|
+
#. Create an Application for the new problem
|
204
|
+
#. Do a PR to a compatible repo linked to a server instance (e.g., like `this one <https://github.com/baobabsoluciones/cornflow>`_).
|
205
|
+
|
206
|
+
For more details on each part, check the `deployment guide <https://baobabsoluciones.github.io/cornflow/guides/deploy_solver.html>`_.
|
207
|
+
|
208
|
+
Using cornflow to solve a problem
|
209
|
+
-------------------------------------------
|
210
|
+
|
211
|
+
For this example we only need the cornflow_client package. We will test the graph-coloring demo defined `here <https://github.com/baobabsoluciones/cornflow-dags-public/tree/main/DAG/graph_coloring>`_. We will use the test server to solve it.
|
212
|
+
|
213
|
+
Initialize the api client::
|
214
|
+
|
215
|
+
from cornflow_client import CornFlow
|
216
|
+
email = 'readme@gmail.com'
|
217
|
+
pwd = 'some_password'
|
218
|
+
username = 'some_name'
|
219
|
+
client = CornFlow(url="https://devsm.cornflow.baobabsoluciones.app/")
|
220
|
+
client.login(username=username, pwd=pwd)
|
221
|
+
|
222
|
+
solve a graph coloring problem and get the solution::
|
223
|
+
|
224
|
+
data = dict(pairs=[dict(n1=0, n2=1), dict(n1=1, n2=2), dict(n1=1, n2=3)])
|
225
|
+
instance = client.create_instance(data, name='gc_4_1', description='very small gc problem', schema="graph_coloring")
|
226
|
+
config = dict()
|
227
|
+
execution = client.create_execution(
|
228
|
+
instance['id'], config, name='gc_4_1_exec', description='execution of very small gc problem',
|
229
|
+
schema="graph_coloring",
|
230
|
+
)
|
231
|
+
status = client.get_status(execution["id"])
|
232
|
+
print(status['state'])
|
233
|
+
solution = client.get_solution(execution["id"])
|
234
|
+
print(solution['data']['assignment'])
|
235
|
+
|
236
|
+
|
237
|
+
Running tests and coverage
|
238
|
+
------------------------------
|
239
|
+
|
240
|
+
Then you have to run the following commands::
|
241
|
+
|
242
|
+
export FLASK_ENV=testing
|
243
|
+
|
244
|
+
Finally you can run all the tests with the following command::
|
245
|
+
|
246
|
+
python -m unittest discover -s cornflow.tests
|
247
|
+
|
248
|
+
If you want to only run the unit tests (without a local airflow webserver)::
|
249
|
+
|
250
|
+
python -m unittest discover -s cornflow.tests.unit
|
251
|
+
|
252
|
+
If you want to only run the integration test with a local airflow webserver::
|
253
|
+
|
254
|
+
python -m unittest discover -s cornflow.tests.integration
|
255
|
+
|
256
|
+
After if you want to check the coverage report you need to run::
|
257
|
+
|
258
|
+
coverage run --source=./cornflow/ -m unittest discover -s=./cornflow/tests/
|
259
|
+
coverage report -m
|
260
|
+
|
261
|
+
or to get the html reports::
|
262
|
+
|
263
|
+
coverage html
|
264
|
+
|
@@ -1,6 +1,7 @@
|
|
1
1
|
"""
|
2
2
|
Main file with the creation of the app logic
|
3
3
|
"""
|
4
|
+
|
4
5
|
# Full imports
|
5
6
|
import os
|
6
7
|
import click
|
@@ -13,6 +14,8 @@ from flask_cors import CORS
|
|
13
14
|
from flask_migrate import Migrate
|
14
15
|
from flask_restful import Api
|
15
16
|
from logging.config import dictConfig
|
17
|
+
from werkzeug.middleware.dispatcher import DispatcherMiddleware
|
18
|
+
from werkzeug.exceptions import NotFound
|
16
19
|
|
17
20
|
# Module imports
|
18
21
|
from cornflow.commands import (
|
@@ -112,6 +115,11 @@ def create_app(env_name="development", dataconn=None):
|
|
112
115
|
app.cli.add_command(register_deployed_dags)
|
113
116
|
app.cli.add_command(register_dag_permissions)
|
114
117
|
|
118
|
+
if app.config["APPLICATION_ROOT"] != "/" and app.config["EXTERNAL_APP"] == 0:
|
119
|
+
app.wsgi_app = DispatcherMiddleware(
|
120
|
+
NotFound(), {app.config["APPLICATION_ROOT"]: app.wsgi_app}
|
121
|
+
)
|
122
|
+
|
115
123
|
return app
|
116
124
|
|
117
125
|
|
@@ -5,6 +5,12 @@ from apispec.ext.marshmallow import MarshmallowPlugin
|
|
5
5
|
|
6
6
|
|
7
7
|
class DefaultConfig(object):
|
8
|
+
"""
|
9
|
+
Default configuration class
|
10
|
+
"""
|
11
|
+
|
12
|
+
APPLICATION_ROOT = os.getenv("APPLICATION_ROOT", "/")
|
13
|
+
EXTERNAL_APP = int(os.getenv("EXTERNAL_APP", 0))
|
8
14
|
SERVICE_NAME = os.getenv("SERVICE_NAME", "Cornflow")
|
9
15
|
SECRET_TOKEN_KEY = os.getenv("SECRET_KEY")
|
10
16
|
SECRET_BI_KEY = os.getenv("SECRET_BI_KEY")
|
@@ -22,6 +28,11 @@ class DefaultConfig(object):
|
|
22
28
|
SIGNUP_ACTIVATED = int(os.getenv("SIGNUP_ACTIVATED", 1))
|
23
29
|
CORNFLOW_SERVICE_USER = os.getenv("CORNFLOW_SERVICE_USER", "service_user")
|
24
30
|
|
31
|
+
# If service user is allow to log with username and password
|
32
|
+
SERVICE_USER_ALLOW_PASSWORD_LOGIN = int(
|
33
|
+
os.getenv("SERVICE_USER_ALLOW_PASSWORD_LOGIN", 1)
|
34
|
+
)
|
35
|
+
|
25
36
|
# Open deployment (all dags accessible to all users)
|
26
37
|
OPEN_DEPLOYMENT = os.getenv("OPEN_DEPLOYMENT", 1)
|
27
38
|
|
@@ -84,14 +95,17 @@ class DefaultConfig(object):
|
|
84
95
|
|
85
96
|
|
86
97
|
class Development(DefaultConfig):
|
87
|
-
|
88
|
-
|
98
|
+
"""
|
99
|
+
Configuration class for development
|
100
|
+
"""
|
89
101
|
|
90
102
|
ENV = "development"
|
91
103
|
|
92
104
|
|
93
105
|
class Testing(DefaultConfig):
|
94
|
-
"""
|
106
|
+
"""
|
107
|
+
Configuration class for testing
|
108
|
+
"""
|
95
109
|
|
96
110
|
ENV = "testing"
|
97
111
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
@@ -109,8 +123,26 @@ class Testing(DefaultConfig):
|
|
109
123
|
LOG_LEVEL = int(os.getenv("LOG_LEVEL", 10))
|
110
124
|
|
111
125
|
|
126
|
+
class TestingOpenAuth(Testing):
|
127
|
+
"""
|
128
|
+
Configuration class for testing some edge cases with Open Auth login
|
129
|
+
"""
|
130
|
+
|
131
|
+
AUTH_TYPE = 0
|
132
|
+
|
133
|
+
|
134
|
+
class TestingApplicationRoot(Testing):
|
135
|
+
"""
|
136
|
+
Configuration class for testing with application root
|
137
|
+
"""
|
138
|
+
|
139
|
+
APPLICATION_ROOT = "/test"
|
140
|
+
|
141
|
+
|
112
142
|
class Production(DefaultConfig):
|
113
|
-
"""
|
143
|
+
"""
|
144
|
+
Configuration class for production
|
145
|
+
"""
|
114
146
|
|
115
147
|
ENV = "production"
|
116
148
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
@@ -121,4 +153,10 @@ class Production(DefaultConfig):
|
|
121
153
|
PROPAGATE_EXCEPTIONS = True
|
122
154
|
|
123
155
|
|
124
|
-
app_config = {
|
156
|
+
app_config = {
|
157
|
+
"development": Development,
|
158
|
+
"testing": Testing,
|
159
|
+
"production": Production,
|
160
|
+
"testing-oauth": TestingOpenAuth,
|
161
|
+
"testing-root": TestingApplicationRoot,
|
162
|
+
}
|
@@ -34,9 +34,11 @@ class LoginBaseEndpoint(BaseMetaResource):
|
|
34
34
|
"""
|
35
35
|
Base endpoint to perform a login action from a user
|
36
36
|
"""
|
37
|
+
|
37
38
|
def __init__(self):
|
38
39
|
super().__init__()
|
39
40
|
self.ldap_class = LDAPBase
|
41
|
+
self.user_role_association = UserRoleModel
|
40
42
|
|
41
43
|
def log_in(self, **kwargs):
|
42
44
|
"""
|
@@ -102,7 +104,9 @@ class LoginBaseEndpoint(BaseMetaResource):
|
|
102
104
|
raise InvalidCredentials()
|
103
105
|
user = self.data_model.get_one_object(username=username)
|
104
106
|
if not user:
|
105
|
-
current_app.logger.info(
|
107
|
+
current_app.logger.info(
|
108
|
+
f"LDAP user {username} does not exist and is created"
|
109
|
+
)
|
106
110
|
email = ldap_obj.get_user_email(username)
|
107
111
|
if not email:
|
108
112
|
email = ""
|
@@ -122,68 +126,115 @@ class LoginBaseEndpoint(BaseMetaResource):
|
|
122
126
|
|
123
127
|
except IntegrityError as e:
|
124
128
|
db.session.rollback()
|
125
|
-
current_app.logger.error(
|
129
|
+
current_app.logger.error(
|
130
|
+
f"Integrity error on user role assignment on log in: {e}"
|
131
|
+
)
|
126
132
|
except DBAPIError as e:
|
127
133
|
db.session.rollback()
|
128
|
-
current_app.logger.error(
|
134
|
+
current_app.logger.error(
|
135
|
+
f"Unknown error on user role assignment on log in: {e}"
|
136
|
+
)
|
129
137
|
|
130
138
|
return user
|
131
139
|
|
132
|
-
def auth_oid_authenticate(
|
140
|
+
def auth_oid_authenticate(
|
141
|
+
self, token: str = None, username: str = None, password: str = None
|
142
|
+
):
|
133
143
|
"""
|
134
|
-
Method in charge of performing the log in with the token issued by an Open ID provider
|
144
|
+
Method in charge of performing the log in with the token issued by an Open ID provider.
|
145
|
+
It has an exception and thus accepts username and password for service users if needed.
|
135
146
|
|
136
147
|
:param str token: the token that the user has obtained from the Open ID provider
|
148
|
+
:param str username: the username of the user to log in
|
149
|
+
:param str password: the password of the user to log in
|
137
150
|
:return: the user object or it raises an error if it has not been possible to log in
|
138
151
|
:rtype: :class:`UserModel`
|
139
152
|
"""
|
140
|
-
oid_provider = int(current_app.config["OID_PROVIDER"])
|
141
153
|
|
142
|
-
|
143
|
-
tenant_id = current_app.config["OID_TENANT_ID"]
|
144
|
-
issuer = current_app.config["OID_ISSUER"]
|
154
|
+
if token:
|
145
155
|
|
146
|
-
|
147
|
-
raise ConfigurationError("The OID provider configuration is not valid")
|
156
|
+
oid_provider = int(current_app.config["OID_PROVIDER"])
|
148
157
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
)
|
158
|
+
client_id = current_app.config["OID_CLIENT_ID"]
|
159
|
+
tenant_id = current_app.config["OID_TENANT_ID"]
|
160
|
+
issuer = current_app.config["OID_ISSUER"]
|
153
161
|
|
154
|
-
|
155
|
-
|
156
|
-
elif oid_provider == OID_NONE:
|
157
|
-
raise EndpointNotImplemented("The OID provider configuration is not valid")
|
158
|
-
else:
|
159
|
-
raise EndpointNotImplemented("The OID provider configuration is not valid")
|
162
|
+
if client_id is None or tenant_id is None or issuer is None:
|
163
|
+
raise ConfigurationError("The OID provider configuration is not valid")
|
160
164
|
|
161
|
-
|
165
|
+
if oid_provider == OID_AZURE:
|
166
|
+
decoded_token = self.auth_class().validate_oid_token(
|
167
|
+
token, client_id, tenant_id, issuer, oid_provider
|
168
|
+
)
|
162
169
|
|
163
|
-
|
170
|
+
elif oid_provider == OID_GOOGLE:
|
171
|
+
raise EndpointNotImplemented(
|
172
|
+
"The selected OID provider is not implemented"
|
173
|
+
)
|
174
|
+
elif oid_provider == OID_NONE:
|
175
|
+
raise EndpointNotImplemented(
|
176
|
+
"The OID provider configuration is not valid"
|
177
|
+
)
|
178
|
+
else:
|
179
|
+
raise EndpointNotImplemented(
|
180
|
+
"The OID provider configuration is not valid"
|
181
|
+
)
|
164
182
|
|
165
|
-
|
166
|
-
|
183
|
+
username = decoded_token["preferred_username"]
|
184
|
+
email = decoded_token.get("email", f"{username}@test.org")
|
185
|
+
first_name = decoded_token.get("given_name", "")
|
186
|
+
last_name = decoded_token.get("family_name", "")
|
167
187
|
|
168
|
-
|
188
|
+
user = self.data_model.get_one_object(username=username)
|
169
189
|
|
170
|
-
|
171
|
-
|
190
|
+
if not user:
|
191
|
+
current_app.logger.info(
|
192
|
+
f"OpenID user {username} does not exist and is created"
|
193
|
+
)
|
172
194
|
|
173
|
-
|
195
|
+
data = {
|
196
|
+
"username": username,
|
197
|
+
"email": email,
|
198
|
+
"first_name": first_name,
|
199
|
+
"last_name": last_name,
|
200
|
+
}
|
174
201
|
|
175
|
-
|
176
|
-
|
177
|
-
)
|
202
|
+
user = self.data_model(data=data)
|
203
|
+
user.save()
|
178
204
|
|
179
|
-
|
205
|
+
user_role = self.user_role_association(
|
206
|
+
{
|
207
|
+
"user_id": user.id,
|
208
|
+
"role_id": int(current_app.config["DEFAULT_ROLE"]),
|
209
|
+
}
|
210
|
+
)
|
180
211
|
|
181
|
-
|
212
|
+
user_role.save()
|
213
|
+
|
214
|
+
return user
|
215
|
+
elif (
|
216
|
+
username
|
217
|
+
and password
|
218
|
+
and current_app.config["SERVICE_USER_ALLOW_PASSWORD_LOGIN"] == 1
|
219
|
+
):
|
220
|
+
|
221
|
+
user = self.auth_db_authenticate(username, password)
|
222
|
+
|
223
|
+
if user.is_service_user():
|
224
|
+
return user
|
225
|
+
else:
|
226
|
+
raise InvalidUsage("Invalid request")
|
227
|
+
else:
|
228
|
+
raise InvalidUsage("Invalid request")
|
182
229
|
|
183
230
|
|
184
231
|
def check_last_password_change(user):
|
185
232
|
if user.pwd_last_change:
|
186
|
-
if
|
233
|
+
if (
|
234
|
+
user.pwd_last_change
|
235
|
+
+ timedelta(days=int(current_app.config["PWD_ROTATION_TIME"]))
|
236
|
+
< datetime.utcnow()
|
237
|
+
):
|
187
238
|
return True
|
188
239
|
return False
|
189
240
|
|
@@ -1,12 +1,14 @@
|
|
1
1
|
"""
|
2
2
|
This file contains the schemas used for the users defined in the application
|
3
3
|
"""
|
4
|
-
|
4
|
+
|
5
|
+
from marshmallow import fields, Schema, validates_schema, ValidationError
|
5
6
|
from .instance import InstanceSchema
|
6
7
|
|
7
8
|
|
8
9
|
class UserSchema(Schema):
|
9
10
|
""" """
|
11
|
+
|
10
12
|
id = fields.Int(dump_only=True)
|
11
13
|
first_name = fields.Str()
|
12
14
|
last_name = fields.Str()
|
@@ -66,9 +68,23 @@ class LoginEndpointRequest(Schema):
|
|
66
68
|
class LoginOpenAuthRequest(Schema):
|
67
69
|
"""
|
68
70
|
This is the schema used by the login endpoint with Open ID protocol
|
71
|
+
Validates that either a token is provided, or both username and password are present
|
69
72
|
"""
|
70
73
|
|
71
|
-
token = fields.Str(required=
|
74
|
+
token = fields.Str(required=False)
|
75
|
+
username = fields.Str(required=False)
|
76
|
+
password = fields.Str(required=False)
|
77
|
+
|
78
|
+
@validates_schema
|
79
|
+
def validate_fields(self, data, **kwargs):
|
80
|
+
if data.get("token") is None:
|
81
|
+
if not data.get("username") or not data.get("password"):
|
82
|
+
raise ValidationError(
|
83
|
+
"A token needs to be provided when using Open ID authentication"
|
84
|
+
)
|
85
|
+
else:
|
86
|
+
if data.get("username") or data.get("password"):
|
87
|
+
raise ValidationError("The login needs to be done with a token only")
|
72
88
|
|
73
89
|
|
74
90
|
class SignupRequest(Schema):
|
@@ -14,6 +14,8 @@ from datetime import datetime, timedelta
|
|
14
14
|
from flask import request, g, current_app, Request
|
15
15
|
from functools import wraps
|
16
16
|
from typing import Union, Tuple
|
17
|
+
|
18
|
+
from jwt import DecodeError
|
17
19
|
from werkzeug.datastructures import Headers
|
18
20
|
|
19
21
|
# Imports from internal modules
|
@@ -103,7 +105,8 @@ class Auth:
|
|
103
105
|
)
|
104
106
|
|
105
107
|
payload = {
|
106
|
-
"exp": datetime.utcnow()
|
108
|
+
"exp": datetime.utcnow()
|
109
|
+
+ timedelta(hours=float(current_app.config["TOKEN_DURATION"])),
|
107
110
|
"iat": datetime.utcnow(),
|
108
111
|
"sub": user_id,
|
109
112
|
}
|
@@ -314,7 +317,10 @@ class Auth:
|
|
314
317
|
:return: the key identifier
|
315
318
|
:rtype: str
|
316
319
|
"""
|
317
|
-
|
320
|
+
try:
|
321
|
+
headers = jwt.get_unverified_header(token)
|
322
|
+
except DecodeError as err:
|
323
|
+
raise InvalidCredentials("Token is not valid")
|
318
324
|
if not headers:
|
319
325
|
raise InvalidCredentials("Token is missing the headers")
|
320
326
|
try:
|
@@ -346,9 +352,9 @@ class Auth:
|
|
346
352
|
try:
|
347
353
|
response = requests.get(discovery_url)
|
348
354
|
response.raise_for_status()
|
349
|
-
except requests.exceptions.HTTPError
|
355
|
+
except requests.exceptions.HTTPError:
|
350
356
|
raise CommunicationError(
|
351
|
-
f"Error getting issuer discovery meta from {discovery_url}"
|
357
|
+
f"Error getting issuer discovery meta from {discovery_url}"
|
352
358
|
)
|
353
359
|
return response.json()
|
354
360
|
|