-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add test suite for evaluation Docs are up to date, some clarifications are also made
- Loading branch information
Showing
14 changed files
with
505 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
Evaluation | ||
========== | ||
|
||
Once our JSON Logic expression has been typechecked [#f1]_, it can be evaluated | ||
using the utility :func:`~jsonlogic.evaluation.evaluate` function: | ||
|
||
.. code-block:: python | ||
from jsonlogic import JSONLogicExpression, Operator | ||
from jsonlogic.evaluation import evaluate | ||
from jsonlogic.operators import operator_registry | ||
expr = JSONLogicExpression.from_json({">": [{"var": "my_int"}, 2]}) | ||
root_op = expr.as_operator_tree(operator_registry) | ||
assert isinstance(root_op, Operator) | ||
return_value = evaluate( | ||
root_op, | ||
data={"my_int": 1}, | ||
data_schema=None, | ||
settings={ # Optional | ||
"variable_casts": {...}, | ||
}, | ||
) | ||
assert return_value is False | ||
This function returns the evaluated expression result. Because the implementation | ||
of the typechecking functionnality is based on the `JSON Schema`_ specification, | ||
we assume the provided :paramref:`~jsonlogic.evaluation.evaluate.data` argument | ||
is JSON data. When string variables are of a specific format, The | ||
:paramref:`~jsonlogic.evaluation.evaluate.data_schema` argument is used to | ||
know what is the corresponding format: | ||
|
||
.. code-block:: python | ||
expr = JSONLogicExpression.from_json({ | ||
">": [ | ||
{"var": "a_date_var"}, | ||
"2020-01-01", | ||
] | ||
}) | ||
root_op = expr.as_operator_tree(operator_registry) | ||
return_value = evaluate( | ||
root_op, | ||
data={"a_date_var": "2024-01-01"}, | ||
data_schema={ # Use the same schema used during typechecking. | ||
"type": "object", | ||
"properties": { | ||
"a_date_var": {"type": "string", "format": "date"}, | ||
}, | ||
}, | ||
settings={ | ||
"literal_casts": [date.fromisoformat], | ||
}, | ||
) | ||
assert return_value is True | ||
During evaluation, variables are resolved and casted to a specific type | ||
(see :meth:`~jsonlogic.evaluation.EvaluationContext.resolve_variable`) | ||
according to the provided JSON Schema. | ||
|
||
.. note:: | ||
|
||
If you are dealing with already converted data, you can pass :data:`None` | ||
to the :paramref:`~jsonlogic.evaluation.evaluate.data_schema` argument. | ||
This way, no variable cast will be performed during evaluation. | ||
|
||
Evaluation settings | ||
------------------- | ||
|
||
Most of the available evaluation settings are analogous to the typechecking settings. | ||
You can refer to the API documentation of the :class:`~jsonlogic.evaluation.EvaluationSettings` | ||
class for more details. | ||
|
||
.. _`JSON Schema`: https://json-schema.org/ | ||
|
||
.. rubric:: footnotes | ||
|
||
.. [#f1] Of course you can skip this step and evaluate the expression directly. | ||
Do note that no runtime exception will be caught during evaluation of operators. | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
from .evaluation_context import EvaluationContext | ||
from .evaluation_settings import EvaluationSettings | ||
from .utils import evaluate, get_value | ||
|
||
__all__ = ("EvaluationContext", "EvaluationSettings") | ||
__all__ = ("EvaluationContext", "EvaluationSettings", "evaluate", "get_value") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Any, Callable | ||
|
||
from jsonlogic.core import Operator | ||
from jsonlogic.typing import JSON, JSONLogicPrimitive, OperatorArgument | ||
|
||
from .evaluation_context import EvaluationContext | ||
from .evaluation_settings import EvaluationSettingsDict | ||
|
||
|
||
def evaluate( | ||
operator: Operator, data: JSON, data_schema: dict[str, Any] | None, settings: EvaluationSettingsDict | None = None | ||
) -> Any: | ||
"""Helper function to evaluate an :class:`~jsonlogic.core.Operator`. | ||
Args: | ||
operator: The operator to evaluate. | ||
data: The root data available during evaluation. | ||
data_schema: The matching JSON Schema describing the root data. This should be the same JSON Schema | ||
used during typechecking (see :paramref:`~jsonlogic.typechecking.TypecheckContext.root_data_schema`). | ||
settings: Settings to be used when evaluating an :class:`~jsonlogic.core.Operator`. | ||
See :class:`EvaluationSettings` for the available settings and default values. | ||
Returns: | ||
The evaluated value. | ||
""" | ||
context = EvaluationContext(data, data_schema, settings) | ||
return operator.evaluate(context) | ||
|
||
|
||
# Function analogous to :func:`jsonlogic.json_schema.from_value` | ||
def _cast_value(value: JSONLogicPrimitive, literal_casts: list[Callable[[str], Any]]) -> Any: | ||
if isinstance(value, str): | ||
for func in literal_casts: | ||
try: | ||
casted_value = func(value) | ||
except Exception: | ||
pass | ||
else: | ||
return casted_value | ||
|
||
if not isinstance(value, list): | ||
return value | ||
|
||
return [_cast_value(subval, literal_casts) for subval in value] | ||
|
||
|
||
def get_value(obj: OperatorArgument, context: EvaluationContext) -> Any: | ||
"""Get the value of an operator argument. | ||
Args: | ||
obj: the object to evaluate. If this is an :class:`~jsonlogic.core.Operator`, | ||
it is evaluated and the value is returned. Otherwise, it must be a | ||
:data:`~jsonlogic.typing.JSONLogicPrimitive`, and the type is inferred from | ||
the actual value according to the :attr:`~TypecheckSettings.literal_casts` setting. | ||
context: The typecheck context. | ||
""" | ||
if isinstance(obj, Operator): | ||
return obj.evaluate(context) | ||
return _cast_value(obj, context.settings.literal_casts) |
Oops, something went wrong.