cornflow 1.0.7__py3-none-any.whl → 1.0.8a1__py3-none-any.whl
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/cli/schemas.py +66 -55
- cornflow/cli/tools/schema_generator.py +3 -3
- cornflow/endpoints/execution.py +24 -20
- cornflow/models/dag.py +2 -1
- cornflow/models/execution.py +10 -0
- cornflow/shared/validators.py +55 -1
- cornflow/tests/const.py +4 -0
- cornflow/tests/unit/test_executions.py +58 -7
- cornflow/tests/unit/test_licenses.py +45 -0
- cornflow/tests/unit/test_schema_from_models.py +135 -124
- {cornflow-1.0.7.dist-info → cornflow-1.0.8a1.dist-info}/METADATA +23 -9
- {cornflow-1.0.7.dist-info → cornflow-1.0.8a1.dist-info}/RECORD +15 -14
- {cornflow-1.0.7.dist-info → cornflow-1.0.8a1.dist-info}/WHEEL +0 -0
- {cornflow-1.0.7.dist-info → cornflow-1.0.8a1.dist-info}/entry_points.txt +0 -0
- {cornflow-1.0.7.dist-info → cornflow-1.0.8a1.dist-info}/top_level.txt +0 -0
cornflow/cli/schemas.py
CHANGED
@@ -25,7 +25,7 @@ def schemas():
|
|
25
25
|
|
26
26
|
@schemas.command(
|
27
27
|
name="generate_from_schema",
|
28
|
-
help="Command to generate models, endpoints and schemas from a jsonschema"
|
28
|
+
help="Command to generate models, endpoints and schemas from a jsonschema",
|
29
29
|
)
|
30
30
|
@click.option(
|
31
31
|
"--path", "-p", type=str, help="The absolute path to the JSONSchema", required=True
|
@@ -70,7 +70,13 @@ def schemas():
|
|
70
70
|
required=False,
|
71
71
|
)
|
72
72
|
def generate_from_schema(
|
73
|
-
path,
|
73
|
+
path,
|
74
|
+
app_name,
|
75
|
+
output_path,
|
76
|
+
remove_methods,
|
77
|
+
one,
|
78
|
+
endpoints_methods,
|
79
|
+
endpoints_access,
|
74
80
|
):
|
75
81
|
"""
|
76
82
|
This method is executed for the command and creates all the files for the REST API from the provided JSONSchema
|
@@ -126,59 +132,64 @@ def generate_from_schema(
|
|
126
132
|
output_path=output_path,
|
127
133
|
options=methods_to_add,
|
128
134
|
name_table=name_table,
|
129
|
-
endpoints_access=dict_endpoints_access
|
135
|
+
endpoints_access=dict_endpoints_access,
|
130
136
|
).main()
|
131
137
|
|
132
138
|
|
133
|
-
@schemas.command(
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
139
|
+
# @schemas.command(
|
140
|
+
# name="schema_from_models",
|
141
|
+
# help="Command to generate a jsonschema from a set of models",
|
142
|
+
# )
|
143
|
+
# @click.option(
|
144
|
+
# "--path",
|
145
|
+
# "-p",
|
146
|
+
# type=str,
|
147
|
+
# help="The absolute path to folder containing the models",
|
148
|
+
# required=True,
|
149
|
+
# )
|
150
|
+
# @click.option("--output-path", "-o", type=str, help="The output path", required=False)
|
151
|
+
# @click.option(
|
152
|
+
# "--ignore-files",
|
153
|
+
# "-i",
|
154
|
+
# type=str,
|
155
|
+
# help="Files that will be ignored (with the .py extension). "
|
156
|
+
# "__init__.py files are automatically ignored. Ex: 'instance.py'",
|
157
|
+
# multiple=True,
|
158
|
+
# required=False,
|
159
|
+
# )
|
160
|
+
# @click.option(
|
161
|
+
# "--leave-bases/--no-leave-bases",
|
162
|
+
# "-l/-nl",
|
163
|
+
# default=False,
|
164
|
+
# help="Use this option to leave the bases classes BaseDataModel, "
|
165
|
+
# "EmptyModel and TraceAttributes in the schema. By default, they will be deleted",
|
166
|
+
# )
|
167
|
+
# def schema_from_models(path, output_path, ignore_files, leave_bases):
|
168
|
+
# """
|
169
|
+
#
|
170
|
+
# :param str path: the path to the folder that contains the models
|
171
|
+
# :param output_path: the output path where the JSONSchema should be placed
|
172
|
+
# :param str ignore_files: files to be ignored.
|
173
|
+
# :param str leave_bases: if the JSONSchema should have abstract classes used as the base for other clases.
|
174
|
+
# :return: a click status code
|
175
|
+
# :rtype: int
|
176
|
+
# """
|
177
|
+
# path = path.replace("\\", "/")
|
178
|
+
# output = None
|
179
|
+
# if output_path:
|
180
|
+
# output = output_path.replace("\\", "/")
|
181
|
+
#
|
182
|
+
# if ignore_files:
|
183
|
+
# ignore_files = list(ignore_files)
|
184
|
+
#
|
185
|
+
# click.echo("Generating JSONSchema file from the REST API")
|
186
|
+
# click.echo(f"The path to the JSONSchema is {path}")
|
187
|
+
# click.echo(f"The output_path is {output}")
|
188
|
+
# click.echo(f"The ignore_files is {ignore_files}")
|
189
|
+
# click.echo(f"The leave_bases is {leave_bases}")
|
190
|
+
#
|
191
|
+
# SchemaGenerator(
|
192
|
+
# path, output_path=output, ignore_files=ignore_files, leave_bases=leave_bases
|
193
|
+
# ).main()
|
194
|
+
#
|
195
|
+
# return True
|
@@ -6,6 +6,8 @@ import json
|
|
6
6
|
import importlib.util
|
7
7
|
from distutils.dir_util import copy_tree
|
8
8
|
from unittest.mock import MagicMock
|
9
|
+
|
10
|
+
import click
|
9
11
|
from flask_sqlalchemy import SQLAlchemy
|
10
12
|
import shutil
|
11
13
|
from pytups import TupList, SuperDict
|
@@ -15,7 +17,6 @@ from sqlalchemy.sql.sqltypes import Integer
|
|
15
17
|
|
16
18
|
class SchemaGenerator:
|
17
19
|
def __init__(self, path, output_path=None, ignore_files=None, leave_bases=False):
|
18
|
-
|
19
20
|
self.path = path
|
20
21
|
self.tmp_path = os.path.join(os.getcwd(), "tmp_files")
|
21
22
|
self.output_path = output_path or "./output_schema.json"
|
@@ -76,7 +77,6 @@ class SchemaGenerator:
|
|
76
77
|
db = SQLAlchemy()
|
77
78
|
try:
|
78
79
|
for file_path, file_name in files:
|
79
|
-
|
80
80
|
spec = importlib.util.spec_from_file_location(file_name, file_path)
|
81
81
|
mod = importlib.util.module_from_spec(spec)
|
82
82
|
|
@@ -142,7 +142,7 @@ class SchemaGenerator:
|
|
142
142
|
|
143
143
|
db.session.close()
|
144
144
|
except Exception as err:
|
145
|
-
|
145
|
+
click.echo(err)
|
146
146
|
|
147
147
|
def inherit(self):
|
148
148
|
all_classes = set(self.parents.keys())
|
cornflow/endpoints/execution.py
CHANGED
@@ -39,7 +39,11 @@ from cornflow.shared.const import (
|
|
39
39
|
EXEC_STATE_QUEUED,
|
40
40
|
)
|
41
41
|
from cornflow.shared.exceptions import AirflowError, ObjectDoesNotExist, InvalidData
|
42
|
-
from cornflow.shared.validators import
|
42
|
+
from cornflow.shared.validators import (
|
43
|
+
json_schema_validate_as_string,
|
44
|
+
json_schema_extend_and_validate_as_string,
|
45
|
+
)
|
46
|
+
|
43
47
|
|
44
48
|
class ExecutionEndpoint(BaseMetaResource):
|
45
49
|
"""
|
@@ -76,12 +80,10 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
76
80
|
]
|
77
81
|
|
78
82
|
running_executions = [
|
79
|
-
execution
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
EXEC_STATE_UNKNOWN
|
84
|
-
]
|
83
|
+
execution
|
84
|
+
for execution in executions
|
85
|
+
if execution.state
|
86
|
+
in [EXEC_STATE_RUNNING, EXEC_STATE_QUEUED, EXEC_STATE_UNKNOWN]
|
85
87
|
]
|
86
88
|
|
87
89
|
for execution in running_executions:
|
@@ -146,14 +148,6 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
146
148
|
user=self.get_user(), idx=execution.instance_id
|
147
149
|
)
|
148
150
|
|
149
|
-
if instance is None:
|
150
|
-
err = "The instance to solve does not exist"
|
151
|
-
raise ObjectDoesNotExist(
|
152
|
-
error=err,
|
153
|
-
log_txt=f"Error while user {self.get_user()} tries to create an execution "
|
154
|
-
f"for instance {execution.instance_id}. " + err,
|
155
|
-
)
|
156
|
-
|
157
151
|
current_app.logger.debug(f"The request is: {request.args.get('run')}")
|
158
152
|
# this allows testing without airflow interaction:
|
159
153
|
if request.args.get("run", "1") == "0":
|
@@ -184,7 +178,9 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
184
178
|
|
185
179
|
# Validate config before running the dag
|
186
180
|
config_schema = DeployedDAG.get_one_schema(config, schema, CONFIG_SCHEMA)
|
187
|
-
config_errors =
|
181
|
+
new_config, config_errors = json_schema_extend_and_validate_as_string(
|
182
|
+
config_schema, kwargs["config"]
|
183
|
+
)
|
188
184
|
if config_errors:
|
189
185
|
execution.update_state(
|
190
186
|
EXEC_STATE_ERROR_START,
|
@@ -197,6 +193,8 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
197
193
|
log_txt=f"Error while user {self.get_user()} tries to create an execution. "
|
198
194
|
f"Configuration data does not match the jsonschema.",
|
199
195
|
)
|
196
|
+
elif new_config != kwargs["config"]:
|
197
|
+
execution.update_config(new_config)
|
200
198
|
|
201
199
|
# Validate instance data before running the dag
|
202
200
|
instance_schema = DeployedDAG.get_one_schema(config, schema, INSTANCE_SCHEMA)
|
@@ -326,7 +324,9 @@ class ExecutionRelaunchEndpoint(BaseMetaResource):
|
|
326
324
|
}, 201
|
327
325
|
|
328
326
|
# Validate config before running the dag
|
329
|
-
config_schema = DeployedDAG.get_one_schema(
|
327
|
+
config_schema = DeployedDAG.get_one_schema(
|
328
|
+
config, kwargs["schema"], CONFIG_SCHEMA
|
329
|
+
)
|
330
330
|
config_errors = json_schema_validate_as_string(config_schema, kwargs["config"])
|
331
331
|
if config_errors:
|
332
332
|
raise InvalidData(
|
@@ -444,14 +444,18 @@ class ExecutionDetailsEndpoint(ExecutionDetailsEndpointBase):
|
|
444
444
|
schema = ExecutionModel.get_one_object(user=self.get_user(), idx=idx).schema
|
445
445
|
|
446
446
|
if data.get("data") is not None and schema is not None:
|
447
|
-
data_jsonschema = DeployedDAG.get_one_schema(
|
448
|
-
|
447
|
+
data_jsonschema = DeployedDAG.get_one_schema(
|
448
|
+
config, schema, SOLUTION_SCHEMA
|
449
|
+
)
|
450
|
+
validation_errors = json_schema_validate_as_string(
|
451
|
+
data_jsonschema, data["data"]
|
452
|
+
)
|
449
453
|
|
450
454
|
if validation_errors:
|
451
455
|
raise InvalidData(
|
452
456
|
payload=dict(jsonschema_errors=validation_errors),
|
453
457
|
log_txt=f"Error while user {self.get_user()} tries to edit execution {idx}. "
|
454
|
-
|
458
|
+
f"Solution data does not match the jsonschema.",
|
455
459
|
)
|
456
460
|
|
457
461
|
current_app.logger.info(f"User {self.get_user()} edits execution {idx}")
|
cornflow/models/dag.py
CHANGED
@@ -71,7 +71,8 @@ class DeployedDAG(TraceAttributesModel):
|
|
71
71
|
jsonschema = item.instance_checks_schema
|
72
72
|
elif schema == SOLUTION_CHECKS_SCHEMA:
|
73
73
|
jsonschema = item.solution_checks_schema
|
74
|
-
|
74
|
+
# schema == CONFIG_SCHEMA
|
75
|
+
else:
|
75
76
|
jsonschema = item.config_schema
|
76
77
|
|
77
78
|
if jsonschema is None:
|
cornflow/models/execution.py
CHANGED
@@ -99,6 +99,16 @@ class ExecutionModel(BaseDataModel):
|
|
99
99
|
self.checks = None
|
100
100
|
super().update(data)
|
101
101
|
|
102
|
+
def update_config(self, config: dict):
|
103
|
+
"""
|
104
|
+
Method to update the config of the execution after extending with default values
|
105
|
+
|
106
|
+
:param dict config: The config to store
|
107
|
+
:return: nothing
|
108
|
+
"""
|
109
|
+
self.config = config
|
110
|
+
super().update({})
|
111
|
+
|
102
112
|
def update_state(self, code, message=None):
|
103
113
|
"""
|
104
114
|
Method to update the state code and message of an execution
|
cornflow/shared/validators.py
CHANGED
@@ -4,8 +4,9 @@ This file has several validators
|
|
4
4
|
import re
|
5
5
|
from typing import Tuple, Union
|
6
6
|
|
7
|
-
from jsonschema import Draft7Validator
|
7
|
+
from jsonschema import Draft7Validator, validators
|
8
8
|
from disposable_email_domains import blocklist
|
9
|
+
from jsonschema.protocols import Validator
|
9
10
|
|
10
11
|
|
11
12
|
def is_special_character(character):
|
@@ -66,6 +67,27 @@ def check_email_pattern(email: str) -> Tuple[bool, Union[str, None]]:
|
|
66
67
|
return True, None
|
67
68
|
|
68
69
|
|
70
|
+
def extend_with_default(validator_class):
|
71
|
+
"""
|
72
|
+
Method to extend a validator, so it extends the data with the default values defined on the jsonschema
|
73
|
+
"""
|
74
|
+
validate_properties = validator_class.VALIDATORS["properties"]
|
75
|
+
|
76
|
+
def set_defaults(validator, properties, instance, schema):
|
77
|
+
for prop, sub in properties.items():
|
78
|
+
if "default" in sub:
|
79
|
+
instance.setdefault(prop, sub["default"])
|
80
|
+
for error in validate_properties(
|
81
|
+
validator,
|
82
|
+
properties,
|
83
|
+
instance,
|
84
|
+
schema,
|
85
|
+
):
|
86
|
+
yield error
|
87
|
+
|
88
|
+
return validators.extend(validator_class, {"properties": set_defaults})
|
89
|
+
|
90
|
+
|
69
91
|
def json_schema_validate(schema: dict, data: dict) -> list:
|
70
92
|
"""
|
71
93
|
Method to validate some data against a json schema
|
@@ -81,6 +103,23 @@ def json_schema_validate(schema: dict, data: dict) -> list:
|
|
81
103
|
return []
|
82
104
|
|
83
105
|
|
106
|
+
def json_schema_extend_and_validate(schema: dict, data: dict) -> Tuple[dict, list]:
|
107
|
+
"""
|
108
|
+
Method to validate som data, extend it with default values and give back the processed errors
|
109
|
+
|
110
|
+
:param dict schema: the json schema in dict format.
|
111
|
+
:param dict data: the data to validate in dict format
|
112
|
+
:return: a tuple with the data extended and the errors found
|
113
|
+
:rtype: tuple
|
114
|
+
"""
|
115
|
+
data_cp = dict(data)
|
116
|
+
default_validator = extend_with_default(Draft7Validator)
|
117
|
+
validator = default_validator(schema)
|
118
|
+
if not validator.is_valid(data_cp):
|
119
|
+
return data_cp, [e for e in validator.iter_errors(data_cp)]
|
120
|
+
return data_cp, []
|
121
|
+
|
122
|
+
|
84
123
|
def json_schema_validate_as_string(schema: dict, data: dict) -> list:
|
85
124
|
"""
|
86
125
|
Method to validate some data against a json schema
|
@@ -91,3 +130,18 @@ def json_schema_validate_as_string(schema: dict, data: dict) -> list:
|
|
91
130
|
:rtype: list
|
92
131
|
"""
|
93
132
|
return [str(e) for e in json_schema_validate(schema, data)]
|
133
|
+
|
134
|
+
|
135
|
+
def json_schema_extend_and_validate_as_string(
|
136
|
+
schema: dict, data: dict
|
137
|
+
) -> Tuple[dict, list]:
|
138
|
+
"""
|
139
|
+
Method to extend the schema with default values and give back the processed error
|
140
|
+
|
141
|
+
:param dict schema: the json schema in dict format.
|
142
|
+
:param dict data: the data to validate in dict format
|
143
|
+
:return: a tuple with the data extended and the errors found
|
144
|
+
:rtype: tuple
|
145
|
+
"""
|
146
|
+
data_cp, errors = json_schema_extend_and_validate(schema, data)
|
147
|
+
return data_cp, [str(e) for e in errors]
|
cornflow/tests/const.py
CHANGED
@@ -16,6 +16,8 @@ INSTANCE_GC_20 = _get_file("./data/gc_20_7.json")
|
|
16
16
|
INSTANCE_FILE_FAIL = _get_file("./unit/test_instances.py")
|
17
17
|
|
18
18
|
EXECUTION_PATH = _get_file("./data/new_execution.json")
|
19
|
+
BAD_EXECUTION_PATH = _get_file("./data/bad_execution.json")
|
20
|
+
EXECUTION_SOLUTION_PATH = _get_file("./data/new_execution_solution.json")
|
19
21
|
EXECUTIONS_LIST = [EXECUTION_PATH, _get_file("./data/new_execution_2.json")]
|
20
22
|
EXECUTION_URL = PREFIX + "/execution/"
|
21
23
|
EXECUTION_URL_NORUN = EXECUTION_URL + "?run=0"
|
@@ -63,6 +65,8 @@ TABLES_URL = PREFIX + "/table/"
|
|
63
65
|
ALARMS_URL = PREFIX + "/alarms/"
|
64
66
|
MAIN_ALARMS_URL = PREFIX + "/main-alarms/"
|
65
67
|
|
68
|
+
LICENSES_URL = PREFIX + "/licences/"
|
69
|
+
|
66
70
|
PUBLIC_DAGS = [
|
67
71
|
"solve_model_dag",
|
68
72
|
"gc",
|
@@ -16,6 +16,8 @@ from cornflow.tests.const import (
|
|
16
16
|
EXECUTION_URL_NORUN,
|
17
17
|
INSTANCE_URL,
|
18
18
|
DAG_URL,
|
19
|
+
BAD_EXECUTION_PATH,
|
20
|
+
EXECUTION_SOLUTION_PATH,
|
19
21
|
)
|
20
22
|
from cornflow.tests.custom_test_case import CustomTestCase, BaseTestCases
|
21
23
|
from cornflow.tests.unit.tools import patch_af_client
|
@@ -38,7 +40,9 @@ class TestExecutionsListEndpoint(BaseTestCases.ListFilters):
|
|
38
40
|
return temp
|
39
41
|
|
40
42
|
self.payload = load_file_fk(EXECUTION_PATH)
|
43
|
+
self.bad_payload = load_file_fk(BAD_EXECUTION_PATH)
|
41
44
|
self.payloads = [load_file_fk(f) for f in EXECUTIONS_LIST]
|
45
|
+
self.solution = load_file_fk(EXECUTION_SOLUTION_PATH)
|
42
46
|
|
43
47
|
def test_new_execution(self):
|
44
48
|
self.create_new_row(self.url, self.model, payload=self.payload)
|
@@ -49,6 +53,55 @@ class TestExecutionsListEndpoint(BaseTestCases.ListFilters):
|
|
49
53
|
|
50
54
|
self.create_new_row(EXECUTION_URL, self.model, payload=self.payload)
|
51
55
|
|
56
|
+
@patch("cornflow.endpoints.execution.Airflow")
|
57
|
+
def test_new_execution_bad_config(self, af_client_class):
|
58
|
+
patch_af_client(af_client_class)
|
59
|
+
response = self.create_new_row(
|
60
|
+
EXECUTION_URL,
|
61
|
+
self.model,
|
62
|
+
payload=self.bad_payload,
|
63
|
+
expected_status=400,
|
64
|
+
check_payload=False,
|
65
|
+
)
|
66
|
+
self.assertIn("error", response)
|
67
|
+
self.assertIn("jsonschema_errors", response)
|
68
|
+
|
69
|
+
@patch("cornflow.endpoints.execution.Airflow")
|
70
|
+
def test_new_execution_partial_config(self, af_client_class):
|
71
|
+
patch_af_client(af_client_class)
|
72
|
+
self.payload["config"].pop("solver")
|
73
|
+
response = self.create_new_row(
|
74
|
+
EXECUTION_URL, self.model, payload=self.payload, check_payload=False
|
75
|
+
)
|
76
|
+
self.assertIn("solver", response["config"])
|
77
|
+
self.assertEqual(response["config"]["solver"], "cbc")
|
78
|
+
|
79
|
+
@patch("cornflow.endpoints.execution.Airflow")
|
80
|
+
def test_new_execution_with_solution(self, af_client_class):
|
81
|
+
patch_af_client(af_client_class)
|
82
|
+
self.payload["data"] = self.solution
|
83
|
+
response = self.create_new_row(
|
84
|
+
EXECUTION_URL,
|
85
|
+
self.model,
|
86
|
+
payload=self.payload,
|
87
|
+
check_payload=False,
|
88
|
+
)
|
89
|
+
|
90
|
+
@patch("cornflow.endpoints.execution.Airflow")
|
91
|
+
def test_new_execution_with_solution_bad(self, af_client_class):
|
92
|
+
patch_af_client(af_client_class)
|
93
|
+
patch_af_client(af_client_class)
|
94
|
+
self.payload["data"] = {"message": "THIS IS NOT A VALID SOLUTION"}
|
95
|
+
response = self.create_new_row(
|
96
|
+
EXECUTION_URL,
|
97
|
+
self.model,
|
98
|
+
payload=self.payload,
|
99
|
+
check_payload=False,
|
100
|
+
expected_status=400,
|
101
|
+
)
|
102
|
+
self.assertIn("error", response)
|
103
|
+
self.assertIn("jsonschema_errors", response)
|
104
|
+
|
52
105
|
def test_new_execution_no_instance(self):
|
53
106
|
payload = dict(self.payload)
|
54
107
|
payload["instance_id"] = "bad_id"
|
@@ -281,11 +334,7 @@ class TestExecutionsDetailEndpoint(
|
|
281
334
|
def test_stop_execution(self, af_client_class):
|
282
335
|
patch_af_client(af_client_class)
|
283
336
|
|
284
|
-
idx = self.create_new_row(
|
285
|
-
EXECUTION_URL,
|
286
|
-
self.model,
|
287
|
-
payload=self.payload
|
288
|
-
)
|
337
|
+
idx = self.create_new_row(EXECUTION_URL, self.model, payload=self.payload)
|
289
338
|
|
290
339
|
response = self.client.post(
|
291
340
|
self.url + str(idx) + "/",
|
@@ -347,11 +396,13 @@ class TestExecutionsStatusEndpoint(TestExecutionsDetailEndpointMock):
|
|
347
396
|
@patch("cornflow.endpoints.execution.Airflow")
|
348
397
|
def test_get_one_status(self, af_client_class):
|
349
398
|
patch_af_client(af_client_class)
|
350
|
-
|
399
|
+
|
351
400
|
idx = self.create_new_row(EXECUTION_URL, self.model, self.payload)
|
352
401
|
payload = dict(self.payload)
|
353
402
|
payload["id"] = idx
|
354
|
-
data = self.get_one_row(
|
403
|
+
data = self.get_one_row(
|
404
|
+
EXECUTION_URL + idx + "/status/", payload, check_payload=False
|
405
|
+
)
|
355
406
|
self.assertEqual(data["state"], 1)
|
356
407
|
|
357
408
|
@patch("cornflow.endpoints.execution.Airflow")
|
@@ -0,0 +1,45 @@
|
|
1
|
+
from cornflow.endpoints import LicensesEndpoint
|
2
|
+
from cornflow.tests.const import LICENSES_URL, _get_file
|
3
|
+
from cornflow.tests.custom_test_case import CustomTestCase
|
4
|
+
|
5
|
+
|
6
|
+
class TestLicensesListEndpoint(CustomTestCase):
|
7
|
+
@staticmethod
|
8
|
+
def read_requirements():
|
9
|
+
with open(_get_file("../../requirements.txt")) as req:
|
10
|
+
content = req.read()
|
11
|
+
requirements = content.split("\n")
|
12
|
+
|
13
|
+
requirements = [
|
14
|
+
r.split("=")[0].split(">")[0].split("<")[0].lower()
|
15
|
+
for r in requirements
|
16
|
+
if r != ""
|
17
|
+
]
|
18
|
+
return requirements
|
19
|
+
|
20
|
+
def setUp(self):
|
21
|
+
super().setUp()
|
22
|
+
self.roles_with_access = LicensesEndpoint.ROLES_WITH_ACCESS
|
23
|
+
self.libraries = self.read_requirements()
|
24
|
+
|
25
|
+
def tearDown(self):
|
26
|
+
super().tearDown()
|
27
|
+
|
28
|
+
def test_get_licenses(self):
|
29
|
+
for role in self.roles_with_access:
|
30
|
+
self.token = self.create_user_with_role(role)
|
31
|
+
response = self.client.get(
|
32
|
+
LICENSES_URL,
|
33
|
+
follow_redirects=True,
|
34
|
+
headers={
|
35
|
+
"Content-Type": "application/json",
|
36
|
+
"Authorization": "Bearer " + self.token,
|
37
|
+
},
|
38
|
+
)
|
39
|
+
|
40
|
+
self.assertEqual(200, response.status_code)
|
41
|
+
self.assertIsInstance(response.json, list)
|
42
|
+
libraries = [k["library"].lower() for k in response.json]
|
43
|
+
|
44
|
+
for lib in self.libraries:
|
45
|
+
self.assertIn(lib, libraries)
|
@@ -1,124 +1,135 @@
|
|
1
|
-
import
|
2
|
-
import
|
3
|
-
import
|
4
|
-
|
5
|
-
from click.testing import CliRunner
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
1
|
+
# import json
|
2
|
+
# import os
|
3
|
+
# import unittest
|
4
|
+
#
|
5
|
+
# from click.testing import CliRunner
|
6
|
+
# from flask_testing import TestCase
|
7
|
+
#
|
8
|
+
# from cornflow.app import create_app
|
9
|
+
# from cornflow.cli import cli
|
10
|
+
# from cornflow.shared import db
|
11
|
+
#
|
12
|
+
# path_to_tests = os.path.dirname(os.path.abspath(__file__))
|
13
|
+
#
|
14
|
+
#
|
15
|
+
# class SchemaFromModelsTests(TestCase):
|
16
|
+
# def create_app(self):
|
17
|
+
# app = create_app("testing")
|
18
|
+
# return app
|
19
|
+
#
|
20
|
+
# def setUp(self):
|
21
|
+
# db.create_all()
|
22
|
+
# self.models_path = self._get_path("../../models")
|
23
|
+
# print(self.models_path)
|
24
|
+
# self.output_path = self._get_path(os.path.join(os.getcwd(), "test_output.json"))
|
25
|
+
# print(self.output_path)
|
26
|
+
#
|
27
|
+
# @staticmethod
|
28
|
+
# def import_schema(path):
|
29
|
+
# with open(path, "r") as fd:
|
30
|
+
# schema = json.load(fd)
|
31
|
+
# return schema
|
32
|
+
#
|
33
|
+
# @staticmethod
|
34
|
+
# def _get_path(rel_path):
|
35
|
+
# return os.path.join(path_to_tests, rel_path)
|
36
|
+
#
|
37
|
+
# def tearDown(self):
|
38
|
+
# db.session.remove()
|
39
|
+
# db.drop_all()
|
40
|
+
# if os.path.exists(self.output_path):
|
41
|
+
# os.remove(self.output_path)
|
42
|
+
#
|
43
|
+
# def test_base(self):
|
44
|
+
# runner = CliRunner()
|
45
|
+
# result = runner.invoke(
|
46
|
+
# cli,
|
47
|
+
# [
|
48
|
+
# "schemas",
|
49
|
+
# "schema_from_models",
|
50
|
+
# "-p",
|
51
|
+
# self.models_path,
|
52
|
+
# "-o",
|
53
|
+
# self.output_path,
|
54
|
+
# ],
|
55
|
+
# )
|
56
|
+
#
|
57
|
+
# self.assertEqual(result.exit_code, True)
|
58
|
+
#
|
59
|
+
# schema = self.import_schema(self._get_path(self.output_path))
|
60
|
+
#
|
61
|
+
# tables = {
|
62
|
+
# "instances": {
|
63
|
+
# "id": "string",
|
64
|
+
# "data": "object",
|
65
|
+
# "checks": "object",
|
66
|
+
# "name": "string",
|
67
|
+
# "description": "string",
|
68
|
+
# },
|
69
|
+
# "actions": {"id": "integer", "name": "string"},
|
70
|
+
# "permission_dag": {
|
71
|
+
# "id": "integer",
|
72
|
+
# "dag_id": "string",
|
73
|
+
# "user_id": "integer",
|
74
|
+
# },
|
75
|
+
# "permission_view": {
|
76
|
+
# "id": "integer",
|
77
|
+
# "action_id": "integer",
|
78
|
+
# "api_view_id": "integer",
|
79
|
+
# "role_id": "integer",
|
80
|
+
# },
|
81
|
+
# }
|
82
|
+
# required_instance = {"id", "name", "data_hash"}
|
83
|
+
# foreign_keys = [
|
84
|
+
# ("permission_dag", "dag_id", "deployed_dags.id"),
|
85
|
+
# ("permission_dag", "user_id", "users.id"),
|
86
|
+
# ("permission_view", "action_id", "actions.id"),
|
87
|
+
# ("permission_view", "api_view_id", "api_view.id"),
|
88
|
+
# ]
|
89
|
+
# for tab_name, tab_checks in tables.items():
|
90
|
+
# # All tables exist
|
91
|
+
# self.assertIn(tab_name, schema["properties"])
|
92
|
+
# # The properties have correct types
|
93
|
+
# for prop, type_prop in tab_checks.items():
|
94
|
+
# table_props = schema["properties"][tab_name]["items"]["properties"]
|
95
|
+
# self.assertIn(prop, table_props)
|
96
|
+
# self.assertIn("type", table_props.get(prop, {}).keys())
|
97
|
+
# self.assertEqual(
|
98
|
+
# type_prop, table_props.get(prop, {}).get("type", "null")
|
99
|
+
# )
|
100
|
+
# # The foreign keys are correct
|
101
|
+
# for tab, key, foreign_key in foreign_keys:
|
102
|
+
# self.assertIn(
|
103
|
+
# "foreign_key", schema["properties"][tab]["items"]["properties"][key]
|
104
|
+
# )
|
105
|
+
# self.assertEqual(
|
106
|
+
# schema["properties"][tab]["items"]["properties"][key]["foreign_key"],
|
107
|
+
# foreign_key,
|
108
|
+
# )
|
109
|
+
# # The required property is correct
|
110
|
+
# self.assertEqual(
|
111
|
+
# required_instance,
|
112
|
+
# set(schema["properties"]["instances"]["items"]["required"]),
|
113
|
+
# )
|
114
|
+
#
|
115
|
+
# def test_ignore(self):
|
116
|
+
# runner = CliRunner()
|
117
|
+
# result = runner.invoke(
|
118
|
+
# cli,
|
119
|
+
# [
|
120
|
+
# "schemas",
|
121
|
+
# "schema_from_models",
|
122
|
+
# "-p",
|
123
|
+
# self.models_path,
|
124
|
+
# "-o",
|
125
|
+
# self.output_path,
|
126
|
+
# "-i",
|
127
|
+
# "instance.py",
|
128
|
+
# ],
|
129
|
+
# )
|
130
|
+
#
|
131
|
+
# self.assertEqual(result.exit_code, True)
|
132
|
+
#
|
133
|
+
# schema = self.import_schema(self.output_path)
|
134
|
+
#
|
135
|
+
# self.assertNotIn("instances", schema["properties"].keys())
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: cornflow
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.8a1
|
4
4
|
Summary: Cornflow is an open source multi-solver optimization server with a REST API built using flask.
|
5
5
|
Home-page: https://github.com/baobabsoluciones/cornflow
|
6
6
|
Author: baobab soluciones
|
@@ -15,7 +15,7 @@ Requires-Python: >=3.8
|
|
15
15
|
Requires-Dist: alembic ==1.9.2
|
16
16
|
Requires-Dist: apispec <=6.2.0
|
17
17
|
Requires-Dist: click <=8.1.3
|
18
|
-
Requires-Dist: cornflow-client
|
18
|
+
Requires-Dist: cornflow-client ==1.0.16a1
|
19
19
|
Requires-Dist: cryptography <=39.0.2
|
20
20
|
Requires-Dist: disposable-email-domains >=0.0.86
|
21
21
|
Requires-Dist: Flask ==2.3.2
|
@@ -28,7 +28,6 @@ Requires-Dist: Flask-Migrate <=4.0.4
|
|
28
28
|
Requires-Dist: Flask-RESTful <=0.3.9
|
29
29
|
Requires-Dist: Flask-SQLAlchemy ==2.5.1
|
30
30
|
Requires-Dist: gevent ==23.9.1
|
31
|
-
Requires-Dist: greenlet <=2.0.2
|
32
31
|
Requires-Dist: gunicorn <=20.1.0
|
33
32
|
Requires-Dist: jsonpatch <=1.32
|
34
33
|
Requires-Dist: ldap3 <=2.9.1
|
@@ -41,6 +40,8 @@ Requires-Dist: requests <=2.29.0
|
|
41
40
|
Requires-Dist: SQLAlchemy ==1.3.21
|
42
41
|
Requires-Dist: webargs <=8.2.0
|
43
42
|
Requires-Dist: Werkzeug <=2.3.3
|
43
|
+
Requires-Dist: greenlet <=2.0.2 ; python_version < "3.11"
|
44
|
+
Requires-Dist: greenlet ==3.0.0 ; python_version >= "3.11"
|
44
45
|
|
45
46
|
Cornflow
|
46
47
|
=========
|
@@ -84,19 +85,33 @@ Cornflow helps you formalize your problem by proposing development guidelines. I
|
|
84
85
|
Installation instructions
|
85
86
|
-------------------------------
|
86
87
|
|
87
|
-
Cornflow is tested with Ubuntu 20.04, python >= 3.
|
88
|
+
Cornflow is tested with Ubuntu 20.04, python >= 3.8 and git.
|
88
89
|
|
89
90
|
Download the Cornflow project and install requirements::
|
90
91
|
|
91
|
-
git clone git@github.com:baobabsoluciones/cornflow.git
|
92
|
-
cd cornflow-server
|
93
92
|
python3 -m venv venv
|
94
|
-
venv/bin/pip3 install
|
93
|
+
venv/bin/pip3 install cornflow
|
94
|
+
|
95
|
+
initialize the sqlite database::
|
96
|
+
|
97
|
+
source venv/bin/activate
|
98
|
+
export FLASK_APP=cornflow.app
|
99
|
+
export DATABASE_URL=sqlite:///cornflow.db
|
100
|
+
flask db upgrade
|
101
|
+
flask access_init
|
102
|
+
flask create_service_user -u airflow -e airflow_test@admin.com -p airflow_test_password
|
103
|
+
flask create_admin_user -u cornflow -e cornflow_admin@admin.com -p cornflow_admin_password
|
104
|
+
|
95
105
|
|
96
106
|
activate the virtual environment and run Cornflow::
|
97
107
|
|
98
108
|
source venv/bin/activate
|
99
109
|
export FLASK_APP=cornflow.app
|
110
|
+
export SECRET_KEY=THISNEEDSTOBECHANGED
|
111
|
+
export DATABASE_URL=sqlite:///cornflow.db
|
112
|
+
export AIRFLOW_URL=http://127.0.0.1:8080/
|
113
|
+
export AIRFLOW_USER=airflow_user
|
114
|
+
export AIRFLOW_PWD=airflow_pwd
|
100
115
|
flask run
|
101
116
|
|
102
117
|
**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.
|
@@ -179,8 +194,7 @@ Using cornflow to deploy a solution method
|
|
179
194
|
|
180
195
|
To deploy a cornflow solution method, the following tasks need to be accomplished:
|
181
196
|
|
182
|
-
#. Create
|
183
|
-
#. Create a solve function (e.g., a 2-opt heuristic).
|
197
|
+
#. Create an Application for the new problem
|
184
198
|
#. Do a PR to a compatible repo linked to a server instance (e.g., like `this one <https://github.com/baobabsoluciones/cornflow>`_).
|
185
199
|
|
186
200
|
For more details on each part, check the `deployment guide <https://baobabsoluciones.github.io/cornflow/guides/deploy_solver.html>`_.
|
@@ -14,7 +14,7 @@ cornflow/cli/config.py,sha256=_7Y6tDo5uu4riymkzMYHnTR9IYxBG_FsjwmB84Du90U,1148
|
|
14
14
|
cornflow/cli/migrations.py,sha256=Stc8H99rG8vgo3yRJcck11zBY_EA4WqyVybglfl8zJE,1624
|
15
15
|
cornflow/cli/permissions.py,sha256=4KXKysH4g8YYQIZcPuXFS2g0xEErp-e8I_FAqMGaV7U,1006
|
16
16
|
cornflow/cli/roles.py,sha256=NFG__qrlyOT0h4L4nwo9FSV4DKjGtMVh3gwiJxwM37w,411
|
17
|
-
cornflow/cli/schemas.py,sha256=
|
17
|
+
cornflow/cli/schemas.py,sha256=sxuJOZf12SBZAXDiAYNPB-n9LSxzSwkB3xyhgS_4K9A,6086
|
18
18
|
cornflow/cli/service.py,sha256=yhQT860oXmQFU_maHHL276tYubKUs6YcwheWv7x7oh0,9135
|
19
19
|
cornflow/cli/users.py,sha256=QvvFJw_tjgYB4GjwXmGTAH_FT_Z6nATg9gmkCbcyLJ8,748
|
20
20
|
cornflow/cli/utils.py,sha256=0tF41gTt6LL9XGOizTQg2GXuOXbqLg6gapCr-HWjJ0Q,733
|
@@ -23,7 +23,7 @@ cornflow/cli/tools/__init__.py,sha256=JtyhYfUfirw78BV1mCsdeY0W25fDPWTmZhNBWdDh0w
|
|
23
23
|
cornflow/cli/tools/api_generator.py,sha256=7ZEEGBdL9Anbj5gnPm3m_eHQm0ehz7Y7YaD952mGh58,16344
|
24
24
|
cornflow/cli/tools/endpoint_tools.py,sha256=Xnv-IQWx6szgMzyD7r3unV050S-J7CxVR5p_cMHMO30,12640
|
25
25
|
cornflow/cli/tools/models_tools.py,sha256=GGqO8fmksA37jhP_xm1dMMAJRZ8_k9j-0V2J05hJAog,5398
|
26
|
-
cornflow/cli/tools/schema_generator.py,sha256=
|
26
|
+
cornflow/cli/tools/schema_generator.py,sha256=3zfxQUej8BwPwmrGttjpK0R_oSnXIJQWOqoott-E78E,7557
|
27
27
|
cornflow/cli/tools/schemas_tools.py,sha256=56VQfsUC6b1Hm5Ran1X6X4QF4HfTxrcOjWlGUCL7BAw,2245
|
28
28
|
cornflow/cli/tools/tools.py,sha256=Qm0X-wHN12vXYJNRHONGjqDZewwXyXy4R_j4UT_XMLs,929
|
29
29
|
cornflow/commands/__init__.py,sha256=E-IgJGF9NNYVhyWYh5ZYP6o0QucWVpJ9N2sbSxFkDmI,515
|
@@ -44,7 +44,7 @@ cornflow/endpoints/case.py,sha256=80Fpv9p8mwIXzjQFuyq1PnPTz3RaOUk932sCUfw7yGA,18
|
|
44
44
|
cornflow/endpoints/dag.py,sha256=MRthA2pnZCAFfoPbHCLDW2j1BsQ3WdjRGC17Szl4b28,10390
|
45
45
|
cornflow/endpoints/data_check.py,sha256=ZyYR84IT9snjXxUrQfrlv_RzOec_AYeTsijuHYdLAcA,16496
|
46
46
|
cornflow/endpoints/example_data.py,sha256=fzolSYl1sYCSCR4Ctr7QD9JM3NdxCjBiiQLVxCPCbJg,2441
|
47
|
-
cornflow/endpoints/execution.py,sha256=
|
47
|
+
cornflow/endpoints/execution.py,sha256=TQ-JtgFtYXVXJIbVs12qxWwGigfLrg9PzdTP0fK6k4g,27951
|
48
48
|
cornflow/endpoints/health.py,sha256=TWmWjKdQOoDzpqwcfksuaAGOLIb2idxzPQcGMWrdkCY,1610
|
49
49
|
cornflow/endpoints/instance.py,sha256=WAnloocXFxSW4vunBJo3CIHx4NzC_0GPJh5bj3ETd9U,11615
|
50
50
|
cornflow/endpoints/licenses.py,sha256=82hHWGYvVIiyw9mlwGtMwJMDJ-ShHOi9rvuM6KvfE4U,873
|
@@ -82,9 +82,9 @@ cornflow/models/action.py,sha256=8MYzQ2qX5bG0zk28OufypzThkR7AU1J1el-5ABoTurg,120
|
|
82
82
|
cornflow/models/alarms.py,sha256=R_g3tkWNSJaAG4gSvthgJlyrueY9VDuIZPoVHk5lDvU,1682
|
83
83
|
cornflow/models/base_data_model.py,sha256=WykAv5_t5-xTP--ajYniIyZYN-ahqpRKLlW6pG3ghGg,5467
|
84
84
|
cornflow/models/case.py,sha256=CW1UvsrBvKK4ilQ5bWaqPwZbF0P3A1M_910E495nndA,7202
|
85
|
-
cornflow/models/dag.py,sha256=
|
85
|
+
cornflow/models/dag.py,sha256=DhHpBqJXrLzPoVSyrS_rYI7BotdzITopJDKsql3mQnQ,2930
|
86
86
|
cornflow/models/dag_permissions.py,sha256=QkhPxSLKxH5elIMdf-rnRtV_CEZBQDFFKUOWv15RgwI,1716
|
87
|
-
cornflow/models/execution.py,sha256=
|
87
|
+
cornflow/models/execution.py,sha256=Q0b_m9ACJbgw0lhQ6gkzqY3CjBxRmHO2uU5G75y7wGo,5974
|
88
88
|
cornflow/models/instance.py,sha256=JKQndz413eGw6J9HwzyIynVQrqtS4egrJY0_jTBuwZM,3788
|
89
89
|
cornflow/models/main_alarms.py,sha256=9S-Ohr2kYFFWB0HomrpSdDIoUr85Eu1rt90Om_Pa8VY,1748
|
90
90
|
cornflow/models/meta_models.py,sha256=qeliGdpw0_q0GCeZzansF-09Ay5pueaT-QQPVPZ5aj4,12000
|
@@ -125,13 +125,13 @@ cornflow/shared/log_config.py,sha256=FM2ajjp2MB4BlFbUHklnWInT7-LLjtrqQ0mo3k_HRmE
|
|
125
125
|
cornflow/shared/query_tools.py,sha256=6yGLCWjv-I2a_ZU4A0IymyJq67fZPZdRcCGOGQQpSXg,1199
|
126
126
|
cornflow/shared/utils.py,sha256=g2ZsD3SwsqIHXZ7GWVAVB0F9gX7mT9dQkPgR2Ahsh6M,860
|
127
127
|
cornflow/shared/utils_tables.py,sha256=A7bjpt7Metkb0FP7tKXMqOkak2fgi3O9dejYvoJBpb0,2236
|
128
|
-
cornflow/shared/validators.py,sha256=
|
128
|
+
cornflow/shared/validators.py,sha256=aFKAAJ2diElUA8WdDyCcXJI6r3FV7HFVzoOTC6t4f8Y,4803
|
129
129
|
cornflow/shared/authentication/__init__.py,sha256=cJIChk5X6hbA_16usEvfHr8g4JDFI6WKo0GPVOOiYHA,137
|
130
130
|
cornflow/shared/authentication/auth.py,sha256=VuM8kmjSi2G_4BR9sop6vn13SFEHiixFBOhB_Pm196M,16840
|
131
131
|
cornflow/shared/authentication/decorators.py,sha256=_QpwOU1kYzpaK85Dl0Btdj5hG8Ps47PFgySp_gqhlgk,1276
|
132
132
|
cornflow/shared/authentication/ldap.py,sha256=QfdC2X_ZMcIJabKC5pYWDGMhS5pIOJJvdZXuuiruq-M,4853
|
133
133
|
cornflow/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
134
|
-
cornflow/tests/const.py,sha256=
|
134
|
+
cornflow/tests/const.py,sha256=_5BYFGN42Xg0PXMR8UU5DBL6dYmYn5rgRBgPyptrKso,2499
|
135
135
|
cornflow/tests/custom_liveServer.py,sha256=I_0YNrcKIwVmRov3zCQMWwcCWkMe5V246Hpa4gS8AZE,3079
|
136
136
|
cornflow/tests/custom_test_case.py,sha256=3nHflrRoBXSaws0XSdbBaV21mTYL8LzDJs6wOyFBdmQ,24773
|
137
137
|
cornflow/tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -149,24 +149,25 @@ cornflow/tests/unit/test_commands.py,sha256=QwGHTOxBOwiIYYQg8wcmSR11lKQk0I8Ltr3s
|
|
149
149
|
cornflow/tests/unit/test_dags.py,sha256=Vww3_TDmHUnPvX2BJac6fXMp4y4UOWqnkYuzdVkBa4k,9034
|
150
150
|
cornflow/tests/unit/test_data_checks.py,sha256=VjB3AAQOHlqnaRT2jI9L2mNLDAcda6llpiZWkW7nnkk,5471
|
151
151
|
cornflow/tests/unit/test_example_data.py,sha256=uaF66FpFNHAgHYH_6RH7Ylu1A6JcBU_qT7pNwS5IMOk,1518
|
152
|
-
cornflow/tests/unit/test_executions.py,sha256=
|
152
|
+
cornflow/tests/unit/test_executions.py,sha256=tNzkeOgZIdS-11FXNXLODIdfHCKZ5B1I4XuvyN8irj0,15333
|
153
153
|
cornflow/tests/unit/test_generate_from_schema.py,sha256=L1EdnASbDJ8SjrX1V4WnUKKwV0sRTwVnNYnxSpyeSeQ,15376
|
154
154
|
cornflow/tests/unit/test_health.py,sha256=0E0HXMb63_Z8drbLZdxnJwtTbQyaZS9ZEHut6qsDbh8,1033
|
155
155
|
cornflow/tests/unit/test_instances.py,sha256=FWNxeP9LMkeCAoniEHioSFN2W_4yqbI-69pAMLkh-sY,9650
|
156
156
|
cornflow/tests/unit/test_instances_file.py,sha256=zXxSlOM_MMkFvpWNX-iatD40xoIAOGQkinCLf1txb0M,1986
|
157
|
+
cornflow/tests/unit/test_licenses.py,sha256=9vYkJZw7Ubza2_pvpgrbyiat6hpLsglgoCPdOzdammo,1494
|
157
158
|
cornflow/tests/unit/test_log_in.py,sha256=zwVCNO0sGQhpVcUaJnh8cVv2z-qPEYCdI98y61CNyfE,979
|
158
159
|
cornflow/tests/unit/test_main_alarms.py,sha256=H1LWQETlSQMsC6Rb3BB9ds7xL705t9aiPyp4gCWMwDQ,1970
|
159
160
|
cornflow/tests/unit/test_permissions.py,sha256=4mLj3GI0Bvhy927eXu_RyAmK8i2XD7raYc6W8lyAO04,8782
|
160
161
|
cornflow/tests/unit/test_roles.py,sha256=xZ3TohL_sv1ZBPvHv_nnYSsKEhBlrzIchx9soaTb5Ow,16581
|
161
|
-
cornflow/tests/unit/test_schema_from_models.py,sha256=
|
162
|
+
cornflow/tests/unit/test_schema_from_models.py,sha256=7IfycOGO3U06baX8I-OPJfu-3ZAn5cv8RCdj9wvalMk,4421
|
162
163
|
cornflow/tests/unit/test_schemas.py,sha256=nX78H0QhG3fThnw8CqS7EhDyuXWwMiA0pXVy52qhlUk,7177
|
163
164
|
cornflow/tests/unit/test_sign_up.py,sha256=-i6VO9z1FwqRHFvaSrpWAzOZx6qa8mHUEmmsjuMXjn8,3481
|
164
165
|
cornflow/tests/unit/test_tables.py,sha256=dY55YgaCkyqwJnqn0LbZHNeXBoL4ZxXWwKkCoTF4WVE,8947
|
165
166
|
cornflow/tests/unit/test_token.py,sha256=r0hzozPTlYmmWsxbFXQ5QovXIg5sES6b2--2dzlEreI,2068
|
166
167
|
cornflow/tests/unit/test_users.py,sha256=_GGnT3ltVEDWJcBUulUflhEx2eKIjbSFi74zY211k1I,21677
|
167
168
|
cornflow/tests/unit/tools.py,sha256=ag3sWv2WLi498R1GL5AOUnXqSsszD3UugzLZLC5NqAw,585
|
168
|
-
cornflow-1.0.
|
169
|
-
cornflow-1.0.
|
170
|
-
cornflow-1.0.
|
171
|
-
cornflow-1.0.
|
172
|
-
cornflow-1.0.
|
169
|
+
cornflow-1.0.8a1.dist-info/METADATA,sha256=7i-UJ_t05Rzs6f3Zji_yrLcyP-r0Hx-brHryZtOYa5A,9404
|
170
|
+
cornflow-1.0.8a1.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
171
|
+
cornflow-1.0.8a1.dist-info/entry_points.txt,sha256=r5wKLHpuyVLMUIZ5I29_tpqYf-RuP-3w_8DhFi8_blQ,47
|
172
|
+
cornflow-1.0.8a1.dist-info/top_level.txt,sha256=Qj9kLFJW1PLb-ZV2s_aCkQ-Wi5W6KC6fFR-LTBrx-rU,24
|
173
|
+
cornflow-1.0.8a1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|