cornflow 1.3.7__tar.gz → 1.3.8__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.7/cornflow.egg-info → cornflow-1.3.8}/PKG-INFO +2 -2
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/service.py +14 -1
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/config.py +1 -1
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/execution.py +1 -1
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/meta_resource.py +34 -1
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/const.py +1 -1
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/email.py +0 -1
- cornflow-1.3.8/cornflow/tests/unit/test_meta_resource.py +256 -0
- {cornflow-1.3.7 → cornflow-1.3.8/cornflow.egg-info}/PKG-INFO +2 -2
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow.egg-info/SOURCES.txt +1 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow.egg-info/requires.txt +1 -1
- {cornflow-1.3.7 → cornflow-1.3.8}/requirements.txt +1 -1
- {cornflow-1.3.7 → cornflow-1.3.8}/setup.py +1 -1
- {cornflow-1.3.7 → cornflow-1.3.8}/MANIFEST.in +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/README.rst +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/airflow_config/__init__.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/airflow_config/airflow_local_settings.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/airflow_config/plugins/XCom/__init__.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/airflow_config/plugins/XCom/gce_xcom_backend.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/airflow_config/plugins/__init__.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/airflow_config/webserver_ldap.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/__init__.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/app.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/__init__.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/actions.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/arguments.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/config.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/migrations.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/permissions.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/roles.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/schemas.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/tools/__init__.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/tools/api_generator.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/tools/endpoint_tools.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/tools/models_tools.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/tools/schema_generator.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/tools/schemas_tools.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/tools/tools.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/users.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/utils.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/cli/views.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/__init__.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/access.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/actions.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/auxiliar.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/cleanup.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/dag.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/permissions.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/roles.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/schemas.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/users.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/commands/views.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/__init__.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/action.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/alarms.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/apiview.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/case.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/dag.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/data_check.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/example_data.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/health.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/instance.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/licenses.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/login.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/main_alarms.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/permission.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/roles.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/schemas.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/signup.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/tables.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/token.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/user.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/endpoints/user_role.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/gunicorn.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/README +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/alembic.ini +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/env.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/script.py.mako +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/00757b557b02_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/1af47a419bbd_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/4729cd156460_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/4aac5e0c6e66_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/509d280698e4_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/7c3ea5ab5501_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/991b98e24225_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/999b98e24225.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/9cacf194fa1c_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/a472b5ad50b7_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/c2db9409cb5f_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/c8a6c762e818_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/ca449af8034c_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/cef1df240b27_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/d0e0700dcd8e_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/d1b5be1f0549_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/e1a50dae1ac9_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/e937a5234ce4_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/ebdd955fcc5e_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/migrations/versions/f3bee20314a2_.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/__init__.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/action.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/alarms.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/base_data_model.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/case.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/dag.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/dag_permissions.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/execution.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/instance.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/main_alarms.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/meta_models.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/permissions.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/role.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/user.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/user_role.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/models/view.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/__init__.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/action.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/alarms.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/case.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/common.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/dag.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/example_data.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/execution.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/health.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/instance.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/main_alarms.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/model_json.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/patch.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/permissions.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/query.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/role.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/schemas.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/solution_log.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/tables.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/user.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/user_role.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/schemas/view.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/__init__.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/authentication/__init__.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/authentication/auth.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/authentication/decorators.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/authentication/ldap.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/compress.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/exceptions.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/licenses.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/log_config.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/query_tools.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/utils.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/utils_tables.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/shared/validators.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/__init__.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/base_test_execution.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/const.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/custom_liveServer.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/custom_test_case.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/integration/__init__.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/integration/test_commands.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/integration/test_cornflowclient.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/integration/test_data_checks_kpis.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/ldap/__init__.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/ldap/test_ldap_authentication.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/__init__.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_actions.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_alarms.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_apiview.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_application.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_cases.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_cli.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_commands.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_dags.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_data_checks.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_example_data.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_executions.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_external_role_creation.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_generate_from_schema.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_get_resources.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_health.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_instances.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_instances_file.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_licenses.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_log_in.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_main_alarms.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_permissions.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_roles.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_schema_from_models.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_schemas.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_sign_up.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_tables.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_token.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/test_users.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow/tests/unit/tools.py +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow.egg-info/dependency_links.txt +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow.egg-info/entry_points.txt +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/cornflow.egg-info/top_level.txt +0 -0
- {cornflow-1.3.7 → cornflow-1.3.8}/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.8
|
|
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.8
|
|
18
18
|
Requires-Dist: cryptography==48.0.1
|
|
19
19
|
Requires-Dist: disposable-email-domains==0.0.162
|
|
20
20
|
Requires-Dist: Flask==2.3.2
|
|
@@ -158,12 +158,25 @@ def _setup_environment_variables():
|
|
|
158
158
|
os.environ["AIRFLOW_PWD"] = airflow_pwd
|
|
159
159
|
os.environ["AIRFLOW_URL"] = airflow_url
|
|
160
160
|
elif cornflow_backend == DATABRICKS_BACKEND:
|
|
161
|
+
required_databricks_vars = [
|
|
162
|
+
"DATABRICKS_HOST",
|
|
163
|
+
"DATABRICKS_CLIENT_SECRET",
|
|
164
|
+
"DATABRICKS_TOKEN_ENDPOINT",
|
|
165
|
+
"DATABRICKS_EP_CLUSTERS",
|
|
166
|
+
"DATABRICKS_CLIENT_ID",
|
|
167
|
+
]
|
|
168
|
+
missing = [var for var in required_databricks_vars if not os.getenv(var)]
|
|
169
|
+
if missing:
|
|
170
|
+
sys.exit(
|
|
171
|
+
"FATAL: the Databricks backend requires the following environment "
|
|
172
|
+
f"variables to be set: {', '.join(missing)}"
|
|
173
|
+
)
|
|
161
174
|
databricks_url = os.getenv("DATABRICKS_HOST")
|
|
162
175
|
databricks_auth_secret = os.getenv("DATABRICKS_CLIENT_SECRET")
|
|
163
176
|
databricks_token_endpoint = os.getenv("DATABRICKS_TOKEN_ENDPOINT")
|
|
164
177
|
databricks_ep_clusters = os.getenv("DATABRICKS_EP_CLUSTERS")
|
|
165
178
|
databricks_client_id = os.getenv("DATABRICKS_CLIENT_ID")
|
|
166
|
-
databricks_health_path = os.getenv("DATABRICKS_HEALTH_PATH")
|
|
179
|
+
databricks_health_path = os.getenv("DATABRICKS_HEALTH_PATH", "default path")
|
|
167
180
|
os.environ["DATABRICKS_HEALTH_PATH"] = databricks_health_path
|
|
168
181
|
os.environ["DATABRICKS_HOST"] = databricks_url
|
|
169
182
|
os.environ["DATABRICKS_CLIENT_SECRET"] = databricks_auth_secret
|
|
@@ -29,7 +29,7 @@ class DefaultConfig(object):
|
|
|
29
29
|
AUTH_TYPE = int(os.getenv("AUTH_TYPE", AUTH_DB))
|
|
30
30
|
DEFAULT_ROLE = int(os.getenv("DEFAULT_ROLE", PLANNER_ROLE))
|
|
31
31
|
CORS_ORIGINS = os.getenv("CORS_ORIGINS", "*")
|
|
32
|
-
SQLALCHEMY_TRACK_MODIFICATIONS =
|
|
32
|
+
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
|
33
33
|
DEBUG = True
|
|
34
34
|
TESTING = True
|
|
35
35
|
LOG_LEVEL = int(os.getenv("LOG_LEVEL", 20))
|
|
@@ -407,7 +407,7 @@ class ExecutionRelaunchEndpoint(OrchestratorMixin):
|
|
|
407
407
|
)
|
|
408
408
|
|
|
409
409
|
# If the execution is still running or queued, raise an error
|
|
410
|
-
if execution.state
|
|
410
|
+
if execution.state in (EXEC_STATE_RUNNING, EXEC_STATE_QUEUED):
|
|
411
411
|
return {"message": "This execution is still running"}, 400
|
|
412
412
|
|
|
413
413
|
# this allows testing without airflow interaction:
|
|
@@ -111,16 +111,49 @@ class BaseMetaResource(Resource, MethodResource):
|
|
|
111
111
|
return {"message": "Created correctly"}, 201
|
|
112
112
|
|
|
113
113
|
def post_bulk_update(self, data, trace_field="user_id"):
|
|
114
|
-
"""
|
|
114
|
+
"""
|
|
115
|
+
Method to POST a bulk of objects, updating the ones that already exist
|
|
116
|
+
and creating the ones that do not.
|
|
117
|
+
|
|
118
|
+
The rows that already exist are looked up with the fields declared on
|
|
119
|
+
`self.unique`, regardless of their `deleted_at` value, so a soft deleted
|
|
120
|
+
row is matched too. On the models that implement the soft delete (the
|
|
121
|
+
ones with a `deleted_at` column) a match that was deleted is reactivated,
|
|
122
|
+
clearing its `deleted_at` before applying the update, so it becomes
|
|
123
|
+
visible again on the reads.
|
|
124
|
+
|
|
125
|
+
If no row matches on `self.unique` but the model has soft delete and the
|
|
126
|
+
payload carries an `id`, the row is looked up by that id as a fallback.
|
|
127
|
+
A soft deleted row found this way is reactivated the same way as a
|
|
128
|
+
regular match. If no row is found, or the row found is not deleted, a
|
|
129
|
+
new instance is created.
|
|
130
|
+
|
|
131
|
+
:param dict data: a dictionary with key 'data' that holds a list with all the
|
|
132
|
+
objects that are going to be created or updated
|
|
133
|
+
:param str trace_field: the field that tracks the user that created the object
|
|
134
|
+
:return: a message and a status code of the operation
|
|
135
|
+
"""
|
|
115
136
|
data = [
|
|
116
137
|
{**el, **{trace_field: self.get_user_id()}} for el in dict(data)["data"]
|
|
117
138
|
]
|
|
118
139
|
|
|
140
|
+
has_soft_delete = hasattr(self.data_model, "deleted_at")
|
|
141
|
+
|
|
119
142
|
instances = []
|
|
120
143
|
for el in data:
|
|
121
144
|
temp_el = dict(SuperDict(el).kfilter(lambda v: v in self.unique))
|
|
122
145
|
temp_instance = self.data_model.query.filter_by(**temp_el).first()
|
|
146
|
+
if (
|
|
147
|
+
temp_instance is None
|
|
148
|
+
and has_soft_delete
|
|
149
|
+
and el.get("id") is not None
|
|
150
|
+
):
|
|
151
|
+
candidate = self.data_model.query.filter_by(id=el.get("id")).first()
|
|
152
|
+
if candidate is not None and candidate.deleted_at is not None:
|
|
153
|
+
temp_instance = candidate
|
|
123
154
|
if temp_instance is not None:
|
|
155
|
+
if has_soft_delete and temp_instance.deleted_at is not None:
|
|
156
|
+
temp_instance.deleted_at = None
|
|
124
157
|
temp_instance.pre_update(el)
|
|
125
158
|
instances.append(temp_instance)
|
|
126
159
|
else:
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for the BaseMetaResource class.
|
|
3
|
+
|
|
4
|
+
This module contains tests for the logic that all the resources share, and that no
|
|
5
|
+
endpoint of cornflow exposes on its own: the bulk update. The endpoints that use it
|
|
6
|
+
are the ones that the cli generates (`put_bulk`), so the resource is built here the
|
|
7
|
+
same way `cli/tools/endpoint_tools.py` builds it.
|
|
8
|
+
|
|
9
|
+
Classes
|
|
10
|
+
-------
|
|
11
|
+
TestPostBulkUpdate
|
|
12
|
+
Tests for the bulk update on a model with soft delete
|
|
13
|
+
TestPostBulkUpdateNoSoftDelete
|
|
14
|
+
Tests for the bulk update on a model without soft delete
|
|
15
|
+
TestPostBulkUpdateClientId
|
|
16
|
+
Tests for the bulk update on the models that take the id from the payload
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# Imports from internal modules
|
|
20
|
+
from cornflow.endpoints.meta_resource import BaseMetaResource
|
|
21
|
+
from cornflow.models import AlarmsModel, RoleModel, ViewModel
|
|
22
|
+
from cornflow.shared.exceptions import InvalidData
|
|
23
|
+
from cornflow.tests.custom_test_case import CustomTestCase
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BulkUpdateTestCase(CustomTestCase):
|
|
27
|
+
"""
|
|
28
|
+
Base test case that builds a resource on top of a model and sends rows to its
|
|
29
|
+
bulk update.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
# the model the resource works on, set by each test case
|
|
33
|
+
data_model = None
|
|
34
|
+
|
|
35
|
+
def setUp(self):
|
|
36
|
+
super().setUp()
|
|
37
|
+
self.model = self.data_model
|
|
38
|
+
self.resource = BaseMetaResource()
|
|
39
|
+
self.resource.data_model = self.data_model
|
|
40
|
+
# the id is what the generated endpoints use as unique
|
|
41
|
+
self.resource.unique = ["id"]
|
|
42
|
+
# the bulk update tracks the user that sends the rows, but there is no request
|
|
43
|
+
# on these tests, so the user is given directly
|
|
44
|
+
self.resource.get_user_id = lambda: self.user.id
|
|
45
|
+
|
|
46
|
+
def bulk_update(self, rows):
|
|
47
|
+
"""
|
|
48
|
+
Sends a list of rows to the bulk update of the resource
|
|
49
|
+
|
|
50
|
+
:param list rows: the rows to create or update
|
|
51
|
+
:return: the message and the status code returned by the resource
|
|
52
|
+
:rtype: Tuple(dict, integer)
|
|
53
|
+
"""
|
|
54
|
+
return self.resource.post_bulk_update(data={"data": rows})
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TestPostBulkUpdate(BulkUpdateTestCase):
|
|
58
|
+
"""
|
|
59
|
+
Test cases for the bulk update on :class:`AlarmsModel`, a model with soft delete
|
|
60
|
+
whose id is given by the database.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
data_model = AlarmsModel
|
|
64
|
+
|
|
65
|
+
def create_alarm(self, name, criticality=1, description="Description"):
|
|
66
|
+
"""
|
|
67
|
+
Creates one alarm directly on the database
|
|
68
|
+
|
|
69
|
+
:param str name: the name of the alarm
|
|
70
|
+
:param float criticality: the criticality of the alarm
|
|
71
|
+
:param str description: the description of the alarm
|
|
72
|
+
:return: the created alarm
|
|
73
|
+
:rtype: :class:`AlarmsModel`
|
|
74
|
+
"""
|
|
75
|
+
alarm = AlarmsModel(
|
|
76
|
+
{"name": name, "criticality": criticality, "description": description}
|
|
77
|
+
)
|
|
78
|
+
alarm.save()
|
|
79
|
+
return alarm
|
|
80
|
+
|
|
81
|
+
def test_update_existing_row(self):
|
|
82
|
+
"""
|
|
83
|
+
A row that exists and is not deleted gets updated
|
|
84
|
+
"""
|
|
85
|
+
alarm = self.create_alarm("Alarm 1")
|
|
86
|
+
|
|
87
|
+
_, status = self.bulk_update(
|
|
88
|
+
[
|
|
89
|
+
{
|
|
90
|
+
"id": alarm.id,
|
|
91
|
+
"name": "Alarm 1 edited",
|
|
92
|
+
"criticality": 2,
|
|
93
|
+
"description": "Description",
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
self.assertEqual(201, status)
|
|
99
|
+
self.assertEqual(1, AlarmsModel.query.count())
|
|
100
|
+
self.assertEqual(
|
|
101
|
+
"Alarm 1 edited", AlarmsModel.get_one_object(idx=alarm.id).name
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def test_new_row_is_created(self):
|
|
105
|
+
"""
|
|
106
|
+
A row whose id does not exist yet gets created, and the rows that are already
|
|
107
|
+
on the table are left alone
|
|
108
|
+
"""
|
|
109
|
+
kept = self.create_alarm("Alarm 1", description="Keep me")
|
|
110
|
+
|
|
111
|
+
_, status = self.bulk_update(
|
|
112
|
+
[
|
|
113
|
+
{
|
|
114
|
+
"id": kept.id + 100,
|
|
115
|
+
"name": "Alarm 2",
|
|
116
|
+
"criticality": 1,
|
|
117
|
+
"description": "Description",
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
self.assertEqual(201, status)
|
|
123
|
+
self.assertEqual(2, AlarmsModel.get_all_objects().count())
|
|
124
|
+
self.assertEqual("Keep me", AlarmsModel.get_one_object(idx=kept.id).description)
|
|
125
|
+
|
|
126
|
+
def test_soft_deleted_row_is_reactivated(self):
|
|
127
|
+
"""
|
|
128
|
+
A row that was soft deleted and is matched by the unique fields is
|
|
129
|
+
reactivated: its deleted_at is cleared and it is updated with the data sent,
|
|
130
|
+
instead of a new row being created
|
|
131
|
+
"""
|
|
132
|
+
self.resource.unique = ["name"]
|
|
133
|
+
deleted = self.create_alarm("Alarm 1", description="Deleted one")
|
|
134
|
+
deleted.disable()
|
|
135
|
+
|
|
136
|
+
# the row is not visible on the reads any more
|
|
137
|
+
self.assertEqual(0, AlarmsModel.get_all_objects().count())
|
|
138
|
+
|
|
139
|
+
_, status = self.bulk_update(
|
|
140
|
+
[{"name": "Alarm 1", "criticality": 2, "description": "Resent"}]
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
self.assertEqual(201, status)
|
|
144
|
+
reactivated = AlarmsModel.query.get(deleted.id)
|
|
145
|
+
self.assertIsNone(reactivated.deleted_at)
|
|
146
|
+
self.assertEqual("Resent", reactivated.description)
|
|
147
|
+
# and it is visible on the reads again
|
|
148
|
+
visible = AlarmsModel.get_all_objects().all()
|
|
149
|
+
self.assertEqual(1, len(visible))
|
|
150
|
+
self.assertEqual(deleted.id, visible[0].id)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class TestPostBulkUpdateNoSoftDelete(BulkUpdateTestCase):
|
|
154
|
+
"""
|
|
155
|
+
Test cases for the bulk update on :class:`ViewModel`, that inherits from
|
|
156
|
+
EmptyBaseModel and therefore has no deleted_at column.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
data_model = ViewModel
|
|
160
|
+
|
|
161
|
+
def test_update_existing_row(self):
|
|
162
|
+
"""
|
|
163
|
+
A model without soft delete keeps updating the row that already exists
|
|
164
|
+
"""
|
|
165
|
+
view = ViewModel.query.first()
|
|
166
|
+
|
|
167
|
+
_, status = self.bulk_update(
|
|
168
|
+
[
|
|
169
|
+
{
|
|
170
|
+
"id": view.id,
|
|
171
|
+
"name": view.name,
|
|
172
|
+
"url_rule": view.url_rule,
|
|
173
|
+
"description": "edited description",
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
self.assertEqual(201, status)
|
|
179
|
+
self.assertEqual("edited description", ViewModel.query.get(view.id).description)
|
|
180
|
+
|
|
181
|
+
def test_new_row_is_created(self):
|
|
182
|
+
"""
|
|
183
|
+
A model without soft delete keeps creating the rows that do not exist
|
|
184
|
+
"""
|
|
185
|
+
count = ViewModel.query.count()
|
|
186
|
+
|
|
187
|
+
_, status = self.bulk_update(
|
|
188
|
+
[{"id": 10000, "name": "brand new view", "url_rule": "/brand-new/"}]
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
self.assertEqual(201, status)
|
|
192
|
+
self.assertEqual(count + 1, ViewModel.query.count())
|
|
193
|
+
self.assertEqual(1, ViewModel.query.filter_by(name="brand new view").count())
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class TestPostBulkUpdateClientId(BulkUpdateTestCase):
|
|
197
|
+
"""
|
|
198
|
+
Test cases for the bulk update on the models that take the id from the payload,
|
|
199
|
+
the same way the models that the cli generates do.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
data_model = RoleModel
|
|
203
|
+
|
|
204
|
+
def test_id_of_a_soft_deleted_row_is_reactivated(self):
|
|
205
|
+
"""
|
|
206
|
+
The id of a soft deleted row can be given again to the bulk update: the row
|
|
207
|
+
that holds it is reactivated and updated with the data sent, instead of
|
|
208
|
+
failing on the primary key or being left deleted
|
|
209
|
+
"""
|
|
210
|
+
deleted = RoleModel({"id": 99, "name": "a role"})
|
|
211
|
+
deleted.save()
|
|
212
|
+
deleted.disable()
|
|
213
|
+
|
|
214
|
+
_, status = self.bulk_update([{"id": 99, "name": "a role resent"}])
|
|
215
|
+
|
|
216
|
+
self.assertEqual(201, status)
|
|
217
|
+
reactivated = RoleModel.query.get(99)
|
|
218
|
+
self.assertIsNone(reactivated.deleted_at)
|
|
219
|
+
self.assertEqual("a role resent", reactivated.name)
|
|
220
|
+
# and it is visible on the reads again
|
|
221
|
+
self.assertIsNotNone(RoleModel.get_one_object(idx=99))
|
|
222
|
+
|
|
223
|
+
def test_id_of_a_live_row_is_not_rejected(self):
|
|
224
|
+
"""
|
|
225
|
+
An id held by a row that is not deleted is not the case that is rejected: it
|
|
226
|
+
keeps failing on the primary key, the way it did before
|
|
227
|
+
"""
|
|
228
|
+
live = RoleModel({"id": 98, "name": "a live role"})
|
|
229
|
+
live.save()
|
|
230
|
+
self.resource.unique = ["name"]
|
|
231
|
+
|
|
232
|
+
with self.assertRaises(InvalidData):
|
|
233
|
+
self.bulk_update([{"id": 98, "name": "another role"}])
|
|
234
|
+
|
|
235
|
+
self.assertEqual("a live role", RoleModel.query.get(98).name)
|
|
236
|
+
|
|
237
|
+
def test_id_of_a_soft_deleted_row_is_reactivated_when_unique_differs(self):
|
|
238
|
+
"""
|
|
239
|
+
When the unique fields do not match a soft deleted row, its id can still be
|
|
240
|
+
given again to the bulk update: the row that holds it is reactivated and
|
|
241
|
+
updated with the data sent, instead of failing on the primary key or a new
|
|
242
|
+
row being created
|
|
243
|
+
"""
|
|
244
|
+
self.resource.unique = ["name"]
|
|
245
|
+
deleted = RoleModel({"id": 97, "name": "a role"})
|
|
246
|
+
deleted.save()
|
|
247
|
+
deleted.disable()
|
|
248
|
+
|
|
249
|
+
_, status = self.bulk_update([{"id": 97, "name": "a role renamed"}])
|
|
250
|
+
|
|
251
|
+
self.assertEqual(201, status)
|
|
252
|
+
reactivated = RoleModel.query.get(97)
|
|
253
|
+
self.assertIsNone(reactivated.deleted_at)
|
|
254
|
+
self.assertEqual("a role renamed", reactivated.name)
|
|
255
|
+
# and it is visible on the reads again
|
|
256
|
+
self.assertIsNotNone(RoleModel.get_one_object(idx=97))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cornflow
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.8
|
|
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.8
|
|
18
18
|
Requires-Dist: cryptography==48.0.1
|
|
19
19
|
Requires-Dist: disposable-email-domains==0.0.162
|
|
20
20
|
Requires-Dist: Flask==2.3.2
|
|
@@ -180,6 +180,7 @@ cornflow/tests/unit/test_instances_file.py
|
|
|
180
180
|
cornflow/tests/unit/test_licenses.py
|
|
181
181
|
cornflow/tests/unit/test_log_in.py
|
|
182
182
|
cornflow/tests/unit/test_main_alarms.py
|
|
183
|
+
cornflow/tests/unit/test_meta_resource.py
|
|
183
184
|
cornflow/tests/unit/test_permissions.py
|
|
184
185
|
cornflow/tests/unit/test_roles.py
|
|
185
186
|
cornflow/tests/unit/test_schema_from_models.py
|
|
@@ -9,7 +9,7 @@ with open("requirements.txt", "r") as fh:
|
|
|
9
9
|
|
|
10
10
|
setuptools.setup(
|
|
11
11
|
name="cornflow",
|
|
12
|
-
version="1.3.
|
|
12
|
+
version="1.3.8",
|
|
13
13
|
author="baobab soluciones",
|
|
14
14
|
author_email="cornflow@baobabsoluciones.es",
|
|
15
15
|
description="cornflow is an open source multi-solver optimization server with a REST API built using flask.",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|