daitum-test 0.5.2__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.
- daitum_test/__init__.py +107 -0
- daitum_test/data.py +253 -0
- daitum_test/errors.py +59 -0
- daitum_test/evaluator.py +314 -0
- daitum_test/model.py +247 -0
- daitum_test/plugin.py +54 -0
- daitum_test/py.typed +0 -0
- daitum_test/result.py +310 -0
- daitum_test/run.py +215 -0
- daitum_test-0.5.2.dist-info/METADATA +129 -0
- daitum_test-0.5.2.dist-info/RECORD +15 -0
- daitum_test-0.5.2.dist-info/WHEEL +5 -0
- daitum_test-0.5.2.dist-info/entry_points.txt +2 -0
- daitum_test-0.5.2.dist-info/licenses/LICENSE +190 -0
- daitum_test-0.5.2.dist-info/top_level.txt +1 -0
daitum_test/__init__.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Copyright 2026 Daitum
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
daitum_test - Test Daitum models from Python
|
|
17
|
+
============================================
|
|
18
|
+
|
|
19
|
+
Load a model, feed it data, run it, and make plain assertions about the results — tables,
|
|
20
|
+
named values, and diagnostics — locally, without the platform. Use it to unit-test a formula
|
|
21
|
+
against crafted inputs, check an optimisation result, assert an output table has no error
|
|
22
|
+
cells, or compare several scenarios in one run.
|
|
23
|
+
|
|
24
|
+
.. code-block:: python
|
|
25
|
+
|
|
26
|
+
from daitum_test import Evaluator, Model, Run
|
|
27
|
+
|
|
28
|
+
model = Model.from_archive("MyModel.zip")
|
|
29
|
+
evaluator = Evaluator()
|
|
30
|
+
|
|
31
|
+
result = evaluator.evaluate(model, runs=[
|
|
32
|
+
Run(scenario="Initial", id="high-demand", mode="OPTIMISER",
|
|
33
|
+
tables={"Items": "fixtures/items_high_demand.csv"},
|
|
34
|
+
named_values={"Budget": 50_000}),
|
|
35
|
+
])
|
|
36
|
+
|
|
37
|
+
run = result["high-demand"]
|
|
38
|
+
assert run.errors == []
|
|
39
|
+
assert run.named_value("TotalCost") <= 50_000
|
|
40
|
+
assert run.table("Output").row_count >= 1
|
|
41
|
+
assert run.table("Output").error_cells() == []
|
|
42
|
+
|
|
43
|
+
Load a model with :meth:`Model.from_archive`, :meth:`Model.from_directory`, or
|
|
44
|
+
:meth:`Model.from_definition` (daitum-core builders work directly). Feed data as CSV files or
|
|
45
|
+
inline rows — cells are typed from the model's own field definitions. A pytest plugin provides
|
|
46
|
+
a ready-made ``evaluator`` fixture.
|
|
47
|
+
|
|
48
|
+
Install with ``pip install daitum-test`` (no runtime dependencies beyond the standard
|
|
49
|
+
library). Evaluation runs against the Daitum model engine, which is obtained separately and
|
|
50
|
+
located via the ``EVALUATOR_JAR`` environment variable, with a Java 25+ runtime available; see
|
|
51
|
+
the README. Each evaluation loads the whole model once, so batch a module's runs into a single
|
|
52
|
+
``evaluate`` call (e.g. from a module-scoped fixture) rather than calling per test.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
from daitum_test.data import ERROR, INDEX_KEY, REF_KEY
|
|
56
|
+
from daitum_test.errors import (
|
|
57
|
+
ContractError,
|
|
58
|
+
DaitumTestError,
|
|
59
|
+
DataConversionError,
|
|
60
|
+
EvaluatorCrashError,
|
|
61
|
+
EvaluatorNotFoundError,
|
|
62
|
+
ModelInspectionError,
|
|
63
|
+
UnknownFieldError,
|
|
64
|
+
UnknownNamedValueError,
|
|
65
|
+
UnknownRunError,
|
|
66
|
+
UnknownTableError,
|
|
67
|
+
)
|
|
68
|
+
from daitum_test.evaluator import Evaluator
|
|
69
|
+
from daitum_test.model import Model
|
|
70
|
+
from daitum_test.result import (
|
|
71
|
+
Diagnostic,
|
|
72
|
+
ErrorCell,
|
|
73
|
+
EvaluationResult,
|
|
74
|
+
RunResult,
|
|
75
|
+
TableResult,
|
|
76
|
+
ValidationReport,
|
|
77
|
+
is_error_cell,
|
|
78
|
+
)
|
|
79
|
+
from daitum_test.run import Options, Run, TableLike
|
|
80
|
+
|
|
81
|
+
__all__ = [
|
|
82
|
+
"ERROR",
|
|
83
|
+
"INDEX_KEY",
|
|
84
|
+
"REF_KEY",
|
|
85
|
+
"ContractError",
|
|
86
|
+
"DaitumTestError",
|
|
87
|
+
"DataConversionError",
|
|
88
|
+
"Diagnostic",
|
|
89
|
+
"ErrorCell",
|
|
90
|
+
"EvaluationResult",
|
|
91
|
+
"Evaluator",
|
|
92
|
+
"EvaluatorCrashError",
|
|
93
|
+
"EvaluatorNotFoundError",
|
|
94
|
+
"Model",
|
|
95
|
+
"ModelInspectionError",
|
|
96
|
+
"Options",
|
|
97
|
+
"Run",
|
|
98
|
+
"RunResult",
|
|
99
|
+
"TableLike",
|
|
100
|
+
"TableResult",
|
|
101
|
+
"UnknownFieldError",
|
|
102
|
+
"UnknownNamedValueError",
|
|
103
|
+
"UnknownRunError",
|
|
104
|
+
"UnknownTableError",
|
|
105
|
+
"ValidationReport",
|
|
106
|
+
"is_error_cell",
|
|
107
|
+
]
|
daitum_test/data.py
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Copyright 2026 Daitum
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Conversion of Python data into the evaluator's JSON encodings.
|
|
16
|
+
|
|
17
|
+
Encodings in the request contract:
|
|
18
|
+
|
|
19
|
+
- **Table rows** are plain JSON values (``"#ERROR"`` marks an error cell; a missing field
|
|
20
|
+
is null).
|
|
21
|
+
- **Named values** are typed objects: ``{"@type": "DECIMAL", "value": 0.5}``.
|
|
22
|
+
- **Object references** (``OBJECT`` / ``OBJECT_ARRAY`` fields) are the **row index** of the
|
|
23
|
+
referenced row within the referenced table, as supplied in the same request (0-based). The
|
|
24
|
+
harness resolves a natural key (the referenced table's key-column value) to that index for
|
|
25
|
+
you.
|
|
26
|
+
- **Maps** (``*_MAP`` fields) are objects keyed by the referenced row index as a string:
|
|
27
|
+
``{"0": true, "1": false}``. The harness resolves natural keys here too.
|
|
28
|
+
|
|
29
|
+
CSV files are converted using the model's declared field types — never by guessing from the
|
|
30
|
+
cell text (``"0123"`` silently becoming ``123`` would corrupt STRING data).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import csv
|
|
34
|
+
import datetime
|
|
35
|
+
from decimal import Decimal
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
from typing import Any
|
|
38
|
+
|
|
39
|
+
from daitum_test.errors import DataConversionError
|
|
40
|
+
|
|
41
|
+
ERROR = "#ERROR"
|
|
42
|
+
"""Sentinel for an error cell in input rows."""
|
|
43
|
+
|
|
44
|
+
#: Escape hatch: wrap a cell as ``{"@ref": key}`` to force resolution by natural key (no
|
|
45
|
+
#: row-index fallback) — use when a key is an integer and might be mistaken for a row index.
|
|
46
|
+
REF_KEY = "@ref"
|
|
47
|
+
|
|
48
|
+
#: Escape hatch: wrap a cell as ``{"@index": n}`` to pass an explicit 0-based row index
|
|
49
|
+
#: without any key lookup.
|
|
50
|
+
INDEX_KEY = "@index"
|
|
51
|
+
|
|
52
|
+
# Python type → evaluator DataType, used when the model's declared type is unknown.
|
|
53
|
+
# bool must be checked before int (bool subclasses int).
|
|
54
|
+
_PYTHON_TYPE_NAMES = (
|
|
55
|
+
(bool, "BOOLEAN"),
|
|
56
|
+
(int, "INTEGER"),
|
|
57
|
+
(float, "DECIMAL"),
|
|
58
|
+
(Decimal, "DECIMAL"),
|
|
59
|
+
(str, "STRING"),
|
|
60
|
+
(datetime.datetime, "DATETIME"), # before date (datetime subclasses date)
|
|
61
|
+
(datetime.date, "DATE"),
|
|
62
|
+
(datetime.time, "TIME"),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def convert_cell(value: Any) -> Any:
|
|
67
|
+
"""Convert one Python value to the plain JSON cell encoding."""
|
|
68
|
+
if isinstance(value, (datetime.datetime, datetime.date, datetime.time)):
|
|
69
|
+
return value.isoformat()
|
|
70
|
+
if isinstance(value, Decimal):
|
|
71
|
+
return float(value)
|
|
72
|
+
if value is None or isinstance(value, (bool, int, float, str)):
|
|
73
|
+
return value
|
|
74
|
+
if isinstance(value, (list, tuple)):
|
|
75
|
+
return [convert_cell(v) for v in value]
|
|
76
|
+
raise DataConversionError(f"Cannot encode {value!r} ({type(value).__name__}) as a table cell.")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class ReferenceResolver:
|
|
80
|
+
"""Resolves natural keys of referenced tables to the 0-based row indices the evaluator
|
|
81
|
+
expects for OBJECT / MAP fields.
|
|
82
|
+
|
|
83
|
+
A row's index is its position in the referenced table **as supplied in the same request**
|
|
84
|
+
(an override's rows if the table is overridden, else the model's base scenario rows). The
|
|
85
|
+
resolver is built from exactly those rows, so the indices it emits match what the evaluator
|
|
86
|
+
assigns when it loads them.
|
|
87
|
+
|
|
88
|
+
A bare value is treated as a natural key and looked up. If the key is not found **and** it
|
|
89
|
+
is an integer, it falls back to being used as a row index directly — so integer-keyed
|
|
90
|
+
tables resolve by key, while a hand-supplied row index still works where there is no
|
|
91
|
+
ambiguity. Use ``{"@ref": k}`` to force a key lookup (no fallback) or ``{"@index": n}`` to
|
|
92
|
+
force a raw row index.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(self, key_indexes: dict[str, dict[Any, int]]):
|
|
96
|
+
self._key_indexes = key_indexes
|
|
97
|
+
|
|
98
|
+
def _lookup(self, table: str, key: Any, *, allow_index_fallback: bool) -> int:
|
|
99
|
+
if isinstance(key, bool): # bool is an int subclass; never a key or index
|
|
100
|
+
raise DataConversionError(f"A boolean is not a valid reference into {table!r}.")
|
|
101
|
+
index = self._key_indexes.get(table, {})
|
|
102
|
+
if key in index:
|
|
103
|
+
return index[key]
|
|
104
|
+
if allow_index_fallback and isinstance(key, int):
|
|
105
|
+
return key # no row has this key, but a bare int is a usable row index
|
|
106
|
+
known = ", ".join(repr(k) for k in list(index)[:10])
|
|
107
|
+
raise DataConversionError(
|
|
108
|
+
f"Cannot resolve reference {key!r} into table {table!r}: no row with that key. "
|
|
109
|
+
f"Known keys: {known or '(the table has no rows in this run)'}. Provide the "
|
|
110
|
+
f"referenced table's rows in the same run, or pass {{'@index': n}} for a row index."
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def resolve(self, table: str, value: Any) -> int:
|
|
114
|
+
"""Row index for a cell value: a bare key (with int-index fallback), ``{"@ref": k}``
|
|
115
|
+
(key lookup only), or ``{"@index": n}`` (raw row index)."""
|
|
116
|
+
if isinstance(value, dict) and set(value) == {INDEX_KEY}:
|
|
117
|
+
n = value[INDEX_KEY]
|
|
118
|
+
if not isinstance(n, int) or isinstance(n, bool):
|
|
119
|
+
raise DataConversionError(f"{INDEX_KEY} requires an integer row index, got {n!r}.")
|
|
120
|
+
return n
|
|
121
|
+
if isinstance(value, dict) and set(value) == {REF_KEY}:
|
|
122
|
+
return self._lookup(table, value[REF_KEY], allow_index_fallback=False)
|
|
123
|
+
return self._lookup(table, value, allow_index_fallback=True)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def encode_object_cell(
|
|
127
|
+
value: Any, table: str, resolver: ReferenceResolver, *, is_array: bool
|
|
128
|
+
) -> Any:
|
|
129
|
+
"""Encode an OBJECT / OBJECT_ARRAY cell to row index/indices (None passes through)."""
|
|
130
|
+
if value is None or value == ERROR:
|
|
131
|
+
return value
|
|
132
|
+
if is_array:
|
|
133
|
+
if not isinstance(value, (list, tuple)):
|
|
134
|
+
raise DataConversionError(
|
|
135
|
+
f"{table!r} object-array field expects a list of keys, got {value!r}."
|
|
136
|
+
)
|
|
137
|
+
return [resolver.resolve(table, key) for key in value]
|
|
138
|
+
return resolver.resolve(table, value)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def encode_map_cell(value: Any, table: str, resolver: ReferenceResolver) -> Any:
|
|
142
|
+
"""Encode a map cell ``{key: v}`` to ``{"<row index>": v}`` (None passes through).
|
|
143
|
+
|
|
144
|
+
Map keys are natural keys of the referenced table (integer keys resolve by key too).
|
|
145
|
+
"""
|
|
146
|
+
if value is None or value == ERROR:
|
|
147
|
+
return value
|
|
148
|
+
if not isinstance(value, dict):
|
|
149
|
+
raise DataConversionError(
|
|
150
|
+
f"{table!r} map field expects a dict of key→value, got {value!r}."
|
|
151
|
+
)
|
|
152
|
+
return {str(resolver.resolve(table, key)): convert_cell(v) for key, v in value.items()}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _convert_csv_cell(table: str, field: str, data_type: str, text: str) -> Any:
|
|
156
|
+
if text == ERROR:
|
|
157
|
+
return ERROR
|
|
158
|
+
try:
|
|
159
|
+
if data_type == "INTEGER":
|
|
160
|
+
return int(text)
|
|
161
|
+
if data_type == "DECIMAL":
|
|
162
|
+
return float(text)
|
|
163
|
+
if data_type == "BOOLEAN":
|
|
164
|
+
lowered = text.lower()
|
|
165
|
+
if lowered not in ("true", "false"):
|
|
166
|
+
raise ValueError(f"expected 'true' or 'false', got {text!r}")
|
|
167
|
+
return lowered == "true"
|
|
168
|
+
if data_type in ("STRING", "DATE", "DATETIME", "TIME"):
|
|
169
|
+
return text # dates are ISO-8601 strings; the evaluator validates them
|
|
170
|
+
if data_type == "OBJECT":
|
|
171
|
+
# A single reference: the raw natural key, resolved to a row index later.
|
|
172
|
+
return text
|
|
173
|
+
except ValueError as exc:
|
|
174
|
+
raise DataConversionError(
|
|
175
|
+
f"{table}[{field}]: cannot convert {text!r} to {data_type}: {exc}"
|
|
176
|
+
) from exc
|
|
177
|
+
raise DataConversionError(
|
|
178
|
+
f"{table}[{field}] has type {data_type}, which is not supported from CSV "
|
|
179
|
+
"(object-array and map fields use commas ambiguously); pass inline rows for this "
|
|
180
|
+
"table instead."
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def csv_rows(path: Path, field_types: dict[str, str], table: str = "") -> list[dict[str, Any]]:
|
|
185
|
+
"""Read a CSV file into typed JSON rows using the model's declared field types.
|
|
186
|
+
|
|
187
|
+
An empty cell omits the field from the row (null per the contract); an unknown header
|
|
188
|
+
fails immediately, before any JVM is paid for.
|
|
189
|
+
"""
|
|
190
|
+
table = table or path.stem
|
|
191
|
+
# utf-8-sig transparently strips a BOM, common in Excel-exported CSVs.
|
|
192
|
+
with open(path, newline="", encoding="utf-8-sig") as handle:
|
|
193
|
+
reader = csv.DictReader(handle)
|
|
194
|
+
headers = reader.fieldnames or []
|
|
195
|
+
unknown = [header for header in headers if header not in field_types]
|
|
196
|
+
if unknown:
|
|
197
|
+
raise DataConversionError(
|
|
198
|
+
f"{path}: unknown field(s) {unknown} for table {table!r}. "
|
|
199
|
+
f"Model fields: {', '.join(sorted(field_types))}"
|
|
200
|
+
)
|
|
201
|
+
rows = []
|
|
202
|
+
for row in reader:
|
|
203
|
+
converted = {
|
|
204
|
+
field: _convert_csv_cell(table, field, field_types[field], text)
|
|
205
|
+
for field, text in row.items()
|
|
206
|
+
if text is not None and text != ""
|
|
207
|
+
}
|
|
208
|
+
rows.append(converted)
|
|
209
|
+
return rows
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _infer_type(value: Any) -> str:
|
|
213
|
+
for python_type, name in _PYTHON_TYPE_NAMES:
|
|
214
|
+
if isinstance(value, python_type):
|
|
215
|
+
return name
|
|
216
|
+
raise DataConversionError(
|
|
217
|
+
f"Cannot encode {value!r} ({type(value).__name__}) as a named value; pass a dict "
|
|
218
|
+
'with an explicit "@type" instead.'
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def encode_named_value(value: Any, declared_type: str | None = None) -> dict[str, Any]:
|
|
223
|
+
"""Encode a Python value as the evaluator's ``{"@type": ..., "value": ...}`` object.
|
|
224
|
+
|
|
225
|
+
The model's declared type wins when known: the Python value is coerced to it (e.g. ``40``
|
|
226
|
+
for a DECIMAL named value becomes ``40.0``). A dict already carrying ``"@type"`` passes
|
|
227
|
+
through verbatim as an escape hatch for types this module does not model.
|
|
228
|
+
"""
|
|
229
|
+
if isinstance(value, dict):
|
|
230
|
+
if "@type" in value:
|
|
231
|
+
return value
|
|
232
|
+
raise DataConversionError(
|
|
233
|
+
f'A dict named value must carry "@type"; got keys {sorted(value)}.'
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
if isinstance(value, (list, tuple)):
|
|
237
|
+
if not value:
|
|
238
|
+
if declared_type is None:
|
|
239
|
+
raise DataConversionError(
|
|
240
|
+
"Cannot infer the element type of an empty list; the model's declared "
|
|
241
|
+
"type is required."
|
|
242
|
+
)
|
|
243
|
+
element_type = declared_type
|
|
244
|
+
else:
|
|
245
|
+
element_type = declared_type or f"{_infer_type(value[0])}_ARRAY"
|
|
246
|
+
return {"@type": element_type, "value": [convert_cell(v) for v in value]}
|
|
247
|
+
|
|
248
|
+
type_name = declared_type or _infer_type(value)
|
|
249
|
+
converted = convert_cell(value)
|
|
250
|
+
# Coerce numerics to the declared type so 40 for a DECIMAL becomes 40.0.
|
|
251
|
+
if type_name == "DECIMAL" and isinstance(converted, int) and not isinstance(converted, bool):
|
|
252
|
+
converted = float(converted)
|
|
253
|
+
return {"@type": type_name, "value": converted}
|
daitum_test/errors.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Copyright 2026 Daitum
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Exception hierarchy for daitum-test."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DaitumTestError(Exception):
|
|
19
|
+
"""Base class for all daitum-test errors."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EvaluatorNotFoundError(DaitumTestError):
|
|
23
|
+
"""The evaluator jar or a usable Java runtime could not be located."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ContractError(DaitumTestError):
|
|
27
|
+
"""The evaluator rejected the request (exit 2), or it was invalid pre-flight.
|
|
28
|
+
|
|
29
|
+
This signals a bug in daitum-test or bad caller input — never a model error.
|
|
30
|
+
Model errors are returned on the :class:`~daitum_test.result.EvaluationResult`.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class EvaluatorCrashError(DaitumTestError):
|
|
35
|
+
"""The evaluator crashed unexpectedly (exit 3 or an unrecognised exit code)."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ModelInspectionError(DaitumTestError):
|
|
39
|
+
"""The model definition could not be read, so typed conversion is unavailable."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class DataConversionError(DaitumTestError):
|
|
43
|
+
"""Input data could not be converted to the evaluator's JSON encoding."""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class UnknownRunError(DaitumTestError):
|
|
47
|
+
"""A run id was not present in the evaluation response."""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class UnknownTableError(DaitumTestError):
|
|
51
|
+
"""A table id was not present in a run's results."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class UnknownFieldError(DaitumTestError):
|
|
55
|
+
"""A field id was not present in a table's results."""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class UnknownNamedValueError(DaitumTestError):
|
|
59
|
+
"""A named value was not present in a run's results."""
|