python-jsonlogic 0.0.1__tar.gz → 0.1.0__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.
Files changed (51) hide show
  1. python_jsonlogic-0.1.0/PKG-INFO +141 -0
  2. python_jsonlogic-0.1.0/README.rst +96 -0
  3. {python-jsonlogic-0.0.1 → python_jsonlogic-0.1.0}/pyproject.toml +9 -3
  4. python_jsonlogic-0.1.0/src/jsonlogic/__init__.py +3 -0
  5. python_jsonlogic-0.1.0/src/jsonlogic/_compat.py +12 -0
  6. python_jsonlogic-0.1.0/src/jsonlogic/core.py +132 -0
  7. python_jsonlogic-0.1.0/src/jsonlogic/evaluation/__init__.py +5 -0
  8. python_jsonlogic-0.1.0/src/jsonlogic/evaluation/evaluation_context.py +74 -0
  9. python_jsonlogic-0.1.0/src/jsonlogic/evaluation/evaluation_settings.py +104 -0
  10. python_jsonlogic-0.1.0/src/jsonlogic/evaluation/utils.py +62 -0
  11. python_jsonlogic-0.1.0/src/jsonlogic/json_schema/__init__.py +184 -0
  12. python_jsonlogic-0.1.0/src/jsonlogic/json_schema/types.py +370 -0
  13. python_jsonlogic-0.1.0/src/jsonlogic/operators/__init__.py +53 -0
  14. python_jsonlogic-0.1.0/src/jsonlogic/operators/operators.py +359 -0
  15. {python-jsonlogic-0.0.1 → python_jsonlogic-0.1.0}/src/jsonlogic/registry.py +55 -11
  16. python_jsonlogic-0.1.0/src/jsonlogic/resolving.py +201 -0
  17. python_jsonlogic-0.1.0/src/jsonlogic/typechecking/__init__.py +15 -0
  18. python_jsonlogic-0.1.0/src/jsonlogic/typechecking/diagnostics.py +37 -0
  19. python_jsonlogic-0.1.0/src/jsonlogic/typechecking/typecheck_context.py +69 -0
  20. python_jsonlogic-0.1.0/src/jsonlogic/typechecking/typecheck_settings.py +212 -0
  21. python_jsonlogic-0.1.0/src/jsonlogic/typechecking/utils.py +49 -0
  22. python_jsonlogic-0.1.0/src/jsonlogic/typing.py +50 -0
  23. python_jsonlogic-0.1.0/src/jsonlogic/utils.py +47 -0
  24. python_jsonlogic-0.1.0/src/python_jsonlogic.egg-info/PKG-INFO +141 -0
  25. python_jsonlogic-0.1.0/src/python_jsonlogic.egg-info/SOURCES.txt +32 -0
  26. python_jsonlogic-0.1.0/src/python_jsonlogic.egg-info/requires.txt +3 -0
  27. python_jsonlogic-0.1.0/tests/test_json_logic_expression.py +9 -0
  28. python_jsonlogic-0.1.0/tests/test_registry.py +107 -0
  29. python_jsonlogic-0.1.0/tests/test_resolving.py +140 -0
  30. python-jsonlogic-0.0.1/PKG-INFO +0 -66
  31. python-jsonlogic-0.0.1/README.rst +0 -21
  32. python-jsonlogic-0.0.1/src/jsonlogic/__init__.py +0 -0
  33. python-jsonlogic-0.0.1/src/jsonlogic/_compat.py +0 -8
  34. python-jsonlogic-0.0.1/src/jsonlogic/core.py +0 -73
  35. python-jsonlogic-0.0.1/src/jsonlogic/json_schema/__init__.py +0 -77
  36. python-jsonlogic-0.0.1/src/jsonlogic/json_schema/resolvers.py +0 -51
  37. python-jsonlogic-0.0.1/src/jsonlogic/json_schema/types.py +0 -130
  38. python-jsonlogic-0.0.1/src/jsonlogic/operators/__init__.py +0 -24
  39. python-jsonlogic-0.0.1/src/jsonlogic/operators/base.py +0 -0
  40. python-jsonlogic-0.0.1/src/jsonlogic/operators/operators.py +0 -159
  41. python-jsonlogic-0.0.1/src/jsonlogic/operators/typechecking.py +0 -16
  42. python-jsonlogic-0.0.1/src/jsonlogic/typing.py +0 -17
  43. python-jsonlogic-0.0.1/src/jsonlogic/utils.py +0 -14
  44. python-jsonlogic-0.0.1/src/python_jsonlogic.egg-info/PKG-INFO +0 -66
  45. python-jsonlogic-0.0.1/src/python_jsonlogic.egg-info/SOURCES.txt +0 -22
  46. python-jsonlogic-0.0.1/src/python_jsonlogic.egg-info/requires.txt +0 -3
  47. {python-jsonlogic-0.0.1 → python_jsonlogic-0.1.0}/LICENSE +0 -0
  48. {python-jsonlogic-0.0.1 → python_jsonlogic-0.1.0}/setup.cfg +0 -0
  49. {python-jsonlogic-0.0.1 → python_jsonlogic-0.1.0}/src/jsonlogic/py.typed +0 -0
  50. {python-jsonlogic-0.0.1 → python_jsonlogic-0.1.0}/src/python_jsonlogic.egg-info/dependency_links.txt +0 -0
  51. {python-jsonlogic-0.0.1 → python_jsonlogic-0.1.0}/src/python_jsonlogic.egg-info/top_level.txt +0 -0
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.1
2
+ Name: python-jsonlogic
3
+ Version: 0.1.0
4
+ Summary: An extensible and sane implementation of JsonLogic
5
+ Author-email: Victorien <contact@vctrn.dev>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 Victorien
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Classifier: Development Status :: 4 - Beta
29
+ Classifier: Operating System :: OS Independent
30
+ Classifier: Programming Language :: Python :: 3
31
+ Classifier: Programming Language :: Python :: 3 :: Only
32
+ Classifier: Programming Language :: Python :: 3.8
33
+ Classifier: Programming Language :: Python :: 3.9
34
+ Classifier: Programming Language :: Python :: 3.10
35
+ Classifier: Programming Language :: Python :: 3.11
36
+ Classifier: Programming Language :: Python :: 3.12
37
+ Classifier: Typing :: Typed
38
+ Classifier: License :: OSI Approved :: MIT License
39
+ Classifier: Intended Audience :: Developers
40
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
41
+ Requires-Python: >=3.8
42
+ Description-Content-Type: text/x-rst
43
+ License-File: LICENSE
44
+ Requires-Dist: typing-extensions>=4.10.0; python_version < "3.13"
45
+
46
+ ================
47
+ python-jsonlogic
48
+ ================
49
+
50
+ |Pythons| |PyPI| |Ruff|
51
+
52
+ .. |Pythons| image:: https://img.shields.io/pypi/pyversions/python-jsonlogic.svg
53
+ :alt: Supported Python versions
54
+ :target: https://pypi.org/project/python-jsonlogic/
55
+
56
+ .. |PyPI| image:: https://img.shields.io/pypi/v/python-jsonlogic.svg
57
+ :alt: PyPI - Version
58
+ :target: https://pypi.org/project/python-jsonlogic/
59
+
60
+ .. |Ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
61
+ :alt: PyPI - Version
62
+ :target: https://github.com/astral-sh/ruff
63
+
64
+ ``python-jsonlogic`` is an extensible and sane implementation of `JsonLogic`_, making use of the `JSON Schema`_ specification.
65
+
66
+ .. _`JSON Schema`: https://json-schema.org/
67
+
68
+ Motivation
69
+ ----------
70
+
71
+ While the `JsonLogic`_ format can be great to serialize logic, it lacks a formal specification
72
+ and some aspects are unclear/unspecified:
73
+
74
+ * `operators <https://jsonlogic.com/operations.html>`_ arguments can take any value. For instance,
75
+ `comparison operators <https://jsonlogic.com/operations.html#---and->`_ are said to work with "numeric" values,
76
+ however the `JavaScript playground <https://jsonlogic.com/play.html>`_ doesn't validate inputs. It is
77
+ also convenient to allow such comparison operators for date and datetime objects as well.
78
+ * Operators `accessing data <https://jsonlogic.com/operations.html#accessing-data>`_ use a dot-like notation,
79
+ which is ambiguous when dealing with keys such as ``my.key``.
80
+ * Operators such as `map <https://jsonlogic.com/operations.html#map-reduce-and-filter>`_ provides their own data scope,
81
+ making it impossible to access higher-level data inside the operator expression.
82
+
83
+ For these reasons, ``python-jsonlogic`` provides a way to typecheck your JSON Logic expressions at "compile" time,
84
+ before applying input data to them.
85
+
86
+ .. _`JsonLogic`: https://jsonlogic.com/
87
+
88
+ Installation
89
+ ------------
90
+
91
+ From PyPI:
92
+
93
+ .. code:: bash
94
+
95
+ pip install python-jsonlogic
96
+
97
+ The library can be imported from the ``jsonlogic`` module.
98
+
99
+ Usage
100
+ -----
101
+
102
+ .. code-block:: python
103
+
104
+ # 1. Create or use an already existing operator registry:
105
+ from jsonlogic.operators import operator_registry
106
+
107
+ # 2. Parse the JSON Logic expression:
108
+ from jsonlogic import JSONLogicExpression
109
+
110
+ expr = JSONLogicExpression.from_json({"map": [
111
+ [1, 2],
112
+ {"*": [{"var": ""}, {"var": "/my_int@1"}]},
113
+ ]})
114
+
115
+ # 3. Create an operator tree:
116
+ root_op = expr.as_operator_tree(operator_registry)
117
+
118
+ # 4. Typecheck the expression:
119
+ from jsonlogic.typechecking import typecheck
120
+
121
+ typ, diagnostics = typecheck(
122
+ root_op,
123
+ data_schema={
124
+ "type": "object",
125
+ "properties": {
126
+ "my_int": {"type": "integer"}
127
+ },
128
+ }
129
+ )
130
+ print(typ)
131
+ #> ArrayType(IntegerType())
132
+
133
+ # 5. Evaluate with data:
134
+ from jsonlogic.evaluation import evaluate
135
+ value = evaluate(
136
+ root_op,
137
+ data={"my_int": 2},
138
+ data_schema=None,
139
+ )
140
+ print(value)
141
+ #> [2, 4]
@@ -0,0 +1,96 @@
1
+ ================
2
+ python-jsonlogic
3
+ ================
4
+
5
+ |Pythons| |PyPI| |Ruff|
6
+
7
+ .. |Pythons| image:: https://img.shields.io/pypi/pyversions/python-jsonlogic.svg
8
+ :alt: Supported Python versions
9
+ :target: https://pypi.org/project/python-jsonlogic/
10
+
11
+ .. |PyPI| image:: https://img.shields.io/pypi/v/python-jsonlogic.svg
12
+ :alt: PyPI - Version
13
+ :target: https://pypi.org/project/python-jsonlogic/
14
+
15
+ .. |Ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
16
+ :alt: PyPI - Version
17
+ :target: https://github.com/astral-sh/ruff
18
+
19
+ ``python-jsonlogic`` is an extensible and sane implementation of `JsonLogic`_, making use of the `JSON Schema`_ specification.
20
+
21
+ .. _`JSON Schema`: https://json-schema.org/
22
+
23
+ Motivation
24
+ ----------
25
+
26
+ While the `JsonLogic`_ format can be great to serialize logic, it lacks a formal specification
27
+ and some aspects are unclear/unspecified:
28
+
29
+ * `operators <https://jsonlogic.com/operations.html>`_ arguments can take any value. For instance,
30
+ `comparison operators <https://jsonlogic.com/operations.html#---and->`_ are said to work with "numeric" values,
31
+ however the `JavaScript playground <https://jsonlogic.com/play.html>`_ doesn't validate inputs. It is
32
+ also convenient to allow such comparison operators for date and datetime objects as well.
33
+ * Operators `accessing data <https://jsonlogic.com/operations.html#accessing-data>`_ use a dot-like notation,
34
+ which is ambiguous when dealing with keys such as ``my.key``.
35
+ * Operators such as `map <https://jsonlogic.com/operations.html#map-reduce-and-filter>`_ provides their own data scope,
36
+ making it impossible to access higher-level data inside the operator expression.
37
+
38
+ For these reasons, ``python-jsonlogic`` provides a way to typecheck your JSON Logic expressions at "compile" time,
39
+ before applying input data to them.
40
+
41
+ .. _`JsonLogic`: https://jsonlogic.com/
42
+
43
+ Installation
44
+ ------------
45
+
46
+ From PyPI:
47
+
48
+ .. code:: bash
49
+
50
+ pip install python-jsonlogic
51
+
52
+ The library can be imported from the ``jsonlogic`` module.
53
+
54
+ Usage
55
+ -----
56
+
57
+ .. code-block:: python
58
+
59
+ # 1. Create or use an already existing operator registry:
60
+ from jsonlogic.operators import operator_registry
61
+
62
+ # 2. Parse the JSON Logic expression:
63
+ from jsonlogic import JSONLogicExpression
64
+
65
+ expr = JSONLogicExpression.from_json({"map": [
66
+ [1, 2],
67
+ {"*": [{"var": ""}, {"var": "/my_int@1"}]},
68
+ ]})
69
+
70
+ # 3. Create an operator tree:
71
+ root_op = expr.as_operator_tree(operator_registry)
72
+
73
+ # 4. Typecheck the expression:
74
+ from jsonlogic.typechecking import typecheck
75
+
76
+ typ, diagnostics = typecheck(
77
+ root_op,
78
+ data_schema={
79
+ "type": "object",
80
+ "properties": {
81
+ "my_int": {"type": "integer"}
82
+ },
83
+ }
84
+ )
85
+ print(typ)
86
+ #> ArrayType(IntegerType())
87
+
88
+ # 5. Evaluate with data:
89
+ from jsonlogic.evaluation import evaluate
90
+ value = evaluate(
91
+ root_op,
92
+ data={"my_int": 2},
93
+ data_schema=None,
94
+ )
95
+ print(value)
96
+ #> [2, 4]
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "python-jsonlogic"
7
7
  description = "An extensible and sane implementation of JsonLogic"
8
- version = "0.0.1"
8
+ version = "0.1.0"
9
9
  readme = "README.rst"
10
10
  authors = [
11
11
  {name = "Victorien", email = "contact@vctrn.dev"}
@@ -28,7 +28,7 @@ classifiers = [
28
28
  license = {file = "LICENSE"}
29
29
  requires-python = ">=3.8"
30
30
  dependencies = [
31
- "typing-extensions>=4.6.0; python_version < '3.12'",
31
+ "typing-extensions>=4.10.0; python_version < '3.13'",
32
32
  ]
33
33
 
34
34
  [tool.setuptools]
@@ -46,6 +46,7 @@ src = ["src"]
46
46
  target-version = "py38"
47
47
 
48
48
  [tool.ruff.lint]
49
+ typing-modules = ["_compat"]
49
50
  preview = true
50
51
  explicit-preview-rules = true
51
52
  select = [
@@ -61,14 +62,19 @@ select = [
61
62
  "B", # flake8-bugbear
62
63
  "C4", # flake8-comprehensions
63
64
  "T10", # flake8-debugger
65
+ "FA", # flake8-future-annotations
64
66
  "PIE", # flake8-pie
65
67
  "T20", # flake8-print
66
68
  "RSE", # flake8-raise
67
69
  "PTH", # flake8-use-pathlib
68
70
  ]
69
71
  ignore = [
70
- "PLR2004"
72
+ "PLR2004",
73
+ "PLR0911",
71
74
  ]
72
75
 
73
76
  [tool.ruff.lint.isort]
74
77
  known-first-party = ["jsonlogic"]
78
+
79
+ [tool.pytest.ini_options]
80
+ pythonpath = "src"
@@ -0,0 +1,3 @@
1
+ from .core import JSONLogicExpression, JSONLogicSyntaxError, Operator
2
+
3
+ __all__ = ("JSONLogicExpression", "JSONLogicSyntaxError", "Operator")
@@ -0,0 +1,12 @@
1
+ import sys
2
+
3
+ if sys.version_info >= (3, 13):
4
+ from types import NoneType
5
+ from typing import Self, TypeAlias, TypeIs, TypeVarTuple, Unpack
6
+ else:
7
+ from typing_extensions import Self, TypeAlias, TypeIs, TypeVarTuple, Unpack
8
+
9
+ NoneType = type(None)
10
+
11
+
12
+ __all__ = ("NoneType", "Self", "TypeAlias", "TypeIs", "TypeVarTuple", "Unpack")
@@ -0,0 +1,132 @@
1
+ """Base structures of the library. The two classes defined, :class:`Operator` and :class:`JSONLogicExpression`,
2
+ can be extended to provide extra functionality.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from abc import ABC, abstractmethod
8
+ from dataclasses import dataclass, field
9
+ from typing import TYPE_CHECKING, Any
10
+
11
+ from ._compat import Self, TypeAlias
12
+ from .json_schema.types import AnyType, JSONSchemaType
13
+ from .typing import JSON, JSONLogicPrimitive, JSONObject, OperatorArgument
14
+
15
+ if TYPE_CHECKING:
16
+ # This is a hack to make Pylance think `TypeAlias` comes from `typing`
17
+ from typing import TypeAlias
18
+
19
+ from .evaluation import EvaluationContext
20
+ from .registry import OperatorRegistry
21
+ from .typechecking import TypecheckContext
22
+
23
+
24
+ @dataclass
25
+ class Operator(ABC):
26
+ """The base class for all operators."""
27
+
28
+ operator: str = field(repr=False)
29
+ """The string representation of the operator."""
30
+
31
+ # metadata: Any | None = None
32
+ # """Extra metadata for this operator.
33
+
34
+ # For any exception encountered, this will be included.
35
+ # """
36
+
37
+ @classmethod
38
+ @abstractmethod
39
+ def from_expression(cls, operator: str, arguments: list[OperatorArgument]) -> Self:
40
+ """Return an instance of the operator from the list of provided arguments.
41
+
42
+ Args:
43
+ operator: The ID of the operator, as provided by the :class:`~jsonlogic.registry.OperatorRegistry`.
44
+ arguments: The list of the arguments for this operator. Subclasses are responsible
45
+ for checking the correct number of arguments and optionally the types.
46
+ """
47
+
48
+ @abstractmethod
49
+ def evaluate(self, context: EvaluationContext) -> Any:
50
+ """Evaluate the operator with the provided data."""
51
+
52
+ def typecheck(self, context: TypecheckContext) -> JSONSchemaType:
53
+ """Typecheck the operator (and all children) given the data schema."""
54
+
55
+ return AnyType()
56
+
57
+
58
+ class JSONLogicSyntaxError(Exception):
59
+ """A syntax error when building an operator tree from a :class:`JSONLogicExpression`."""
60
+
61
+ def __init__(self, message: str, /) -> None:
62
+ self.message = message
63
+
64
+
65
+ ExprArgument: TypeAlias = "JSONLogicPrimitive | JSONLogicExpression | list[ExprArgument]"
66
+
67
+ NormalizedExpression: TypeAlias = "dict[str, list[ExprArgument]]"
68
+
69
+
70
+ @dataclass
71
+ class JSONLogicExpression:
72
+ """A parsed and normalized JSON Logic expression.
73
+
74
+ The underlying structure of an expression is a single item dictionary,
75
+ mapping the operator key to a list of arguments.
76
+
77
+ All JSON Logic expressions should be instantiated using the :meth:`from_json` constructor::
78
+
79
+ expr = JSONLogicExpression.from_json({"op": ...})
80
+ """
81
+
82
+ expression: NormalizedExpression
83
+
84
+ @classmethod
85
+ def _parse_impl(cls, json: JSON) -> ExprArgument:
86
+ if isinstance(json, dict):
87
+ return cls.from_json(json)
88
+ if isinstance(json, list):
89
+ return [cls._parse_impl(s) for s in json]
90
+ return json
91
+
92
+ @classmethod
93
+ def from_json(cls, json: JSONObject) -> Self:
94
+ """Build a JSON Logic expression from JSON data.
95
+
96
+ Operator arguments are recursively normalized to a :class:`list`::
97
+
98
+ expr = JSONLogicExpression.from_json({"var": "varname"})
99
+ assert expr.expression == {"var": ["varname"]}
100
+ """
101
+ if not isinstance(json, dict):
102
+ raise ValueError("The root node of the expression must be a dict")
103
+
104
+ operator, op_args = next(iter(json.items()))
105
+ if not isinstance(op_args, list):
106
+ op_args = [op_args]
107
+
108
+ return cls({operator: [cls._parse_impl(arg) for arg in op_args]})
109
+
110
+ def _as_op_impl(self, op_arg: ExprArgument, operator_registry: OperatorRegistry) -> OperatorArgument:
111
+ if isinstance(op_arg, JSONLogicExpression):
112
+ return op_arg.as_operator_tree(operator_registry)
113
+ if isinstance(op_arg, list):
114
+ return [self._as_op_impl(sub_arg, operator_registry) for sub_arg in op_arg]
115
+ return op_arg
116
+
117
+ def as_operator_tree(self, operator_registry: OperatorRegistry) -> Operator:
118
+ """Return a recursive tree of operators, using the provided registry as a reference.
119
+
120
+ Args:
121
+ operator_registry: The registry to use to resolve operator IDs.
122
+
123
+ Returns:
124
+ An :class:`Operator` instance.
125
+ """
126
+ if not isinstance(self.expression, dict):
127
+ return self.expression
128
+
129
+ op_id, op_args = next(iter(self.expression.items()))
130
+ OperatorCls = operator_registry.get(op_id)
131
+
132
+ return OperatorCls.from_expression(op_id, [self._as_op_impl(op_arg, operator_registry) for op_arg in op_args])
@@ -0,0 +1,5 @@
1
+ from .evaluation_context import EvaluationContext
2
+ from .evaluation_settings import EvaluationSettings
3
+ from .utils import evaluate, get_value
4
+
5
+ __all__ = ("EvaluationContext", "EvaluationSettings", "evaluate", "get_value")
@@ -0,0 +1,74 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Literal, overload
4
+
5
+ from jsonlogic.json_schema import cast_from_schema
6
+ from jsonlogic.resolving import resolve_data, resolve_json_schema
7
+ from jsonlogic.typing import JSON
8
+ from jsonlogic.utils import DataStack
9
+
10
+ from .evaluation_settings import EvaluationSettings, EvaluationSettingsDict
11
+
12
+
13
+ class EvaluationContext:
14
+ """A context object used when evaluating operators.
15
+
16
+ When evaluating an :class:`~jsonlogic.core.Operator`, an instance of this
17
+ class should be used.
18
+
19
+ .. code-block:: pycon
20
+
21
+ >>> expr = JSONLogicExpression.from_json({"var": "/a_date"})
22
+ >>> root_op = expr.as_operator_tree(operator_registry)
23
+ >>> context = EvaluationContext(
24
+ ... data={"a_date": "1970-01-01"},
25
+ ... data_schema={
26
+ ... "type": "object",
27
+ ... "properties": {
28
+ ... "a_date": {"type": "string", "format": "date"},
29
+ ... },
30
+ ... },
31
+ ... )
32
+ >>> root_op.evaluate(context)
33
+ datetime.date(1970, 1, 1)
34
+
35
+ Args:
36
+ root_data: The root data available during evaluation.
37
+ data_schema: The matching JSON Schema describing the root data. This should be the same JSON Schema
38
+ used during typechecking (see :paramref:`~jsonlogic.typechecking.TypecheckContext.root_data_schema`).
39
+ settings: Settings to be used when evaluating an :class:`~jsonlogic.core.Operator`.
40
+ See :class:`EvaluationSettings` for the available settings and default values.
41
+ """
42
+
43
+ def __init__(
44
+ self, root_data: JSON, data_schema: dict[str, Any] | None = None, settings: EvaluationSettingsDict | None = None
45
+ ) -> None:
46
+ self.data_stack = DataStack((root_data, data_schema))
47
+ self.settings = EvaluationSettings.from_dict(settings) if settings is not None else EvaluationSettings()
48
+
49
+ @overload
50
+ def resolve_variable(self, reference: str, *, bare: Literal[True]) -> JSON: ...
51
+
52
+ @overload
53
+ def resolve_variable(self, reference: str, *, bare: Literal[False] = ...) -> Any: ...
54
+
55
+ def resolve_variable(self, reference: str, *, bare: bool = False) -> JSON | Any:
56
+ """Resolve a variable given the string reference pointing to it.
57
+
58
+ The format of the reference should match the reference parser defined
59
+ in the :class:`EvaluationSettings`.
60
+
61
+ Args:
62
+ reference: The string reference of the variable.
63
+ bare: Whether the resolved value should be casted to a specific Python
64
+ type according to the matching JSON Schema. Note that this will only
65
+ be possible if a :paramref:`~EvaluationContext.data_schema` was provided.
66
+ """
67
+ parsed_reference, scope = self.settings.reference_parser(reference)
68
+ root_data, root_schema = self.data_stack.get(scope)
69
+ bare_value = resolve_data(parsed_reference, root_data)
70
+ if bare or root_schema is None:
71
+ return bare_value
72
+
73
+ schema = resolve_json_schema(parsed_reference, root_schema)
74
+ return cast_from_schema(bare_value, schema, self.settings.variable_casts)
@@ -0,0 +1,104 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import date, datetime
5
+ from typing import Any, Callable, TypedDict
6
+
7
+ from jsonlogic._compat import Self
8
+ from jsonlogic.resolving import PointerReferenceParser, ReferenceParser
9
+
10
+
11
+ def _d_variable_casts() -> dict[str, Callable[[str], Any]]:
12
+ return {
13
+ "date": date.fromisoformat,
14
+ "date-time": datetime.fromisoformat,
15
+ }
16
+
17
+
18
+ @dataclass
19
+ class EvaluationSettings:
20
+ """Settings used when evaluating an :class:`~jsonlogic.core.Operator`."""
21
+
22
+ reference_parser: ReferenceParser = field(default_factory=PointerReferenceParser)
23
+ """A reference parser instance to use when resolving variables.
24
+
25
+ Default: :class:`~jsonlogic.resolving.PointerReferenceParser`.
26
+ """
27
+
28
+ variable_casts: dict[str, Callable[[str], Any]] = field(default_factory=_d_variable_casts)
29
+ """A mapping between `JSON Schema formats`_ and their corresponding conversion callable.
30
+
31
+ When an operator reads variables from the provided data (such as the ``"var"`` operator),
32
+ such variables of type :class:`str` may be converted to a specific Python type if
33
+ the corresponding JSON Schema of the data was provided during evaluation.
34
+
35
+ This setting is analogous to the :attr:`~jsonlogic.typechecking.TypecheckSettings.variable_casts`
36
+ configuration of the :class:`~jsonlogic.typechecking.TypecheckSettings` class.
37
+
38
+ Default: :python:`{"date": date.fromisoformat, "date-time": datetime.fromisoformat}`.
39
+
40
+ .. _JSON Schema formats: https://json-schema.org/understanding-json-schema/reference/string#built-in-formats
41
+ """
42
+
43
+ literal_casts: list[Callable[[str], Any]] = field(default_factory=list)
44
+ """A list of conversion callables to try when encountering a literal string value during evaluation.
45
+
46
+ When a literal string value is encountered in a JSON Logic expression, it might be
47
+ beneficial to convert it to a specific Python type.
48
+
49
+ This setting is analogous to the :attr:`~jsonlogic.typechecking.TypecheckSettings.literal_casts`
50
+ configuration of the :class:`~jsonlogic.typechecking.TypecheckSettings` class.
51
+
52
+ Default: :python:`[]` (no cast).
53
+
54
+ .. warning::
55
+
56
+ The order in which the conversion callables are defined matters. Each
57
+ callable will be applied one after the other until no exception is raised.
58
+ """
59
+
60
+ @classmethod
61
+ def from_dict(cls, dct: EvaluationSettingsDict, /) -> Self:
62
+ return cls(**dct)
63
+
64
+
65
+ class EvaluationSettingsDict(TypedDict, total=False):
66
+ """Settings used when evaluating an :class:`~jsonlogic.core.Operator`."""
67
+
68
+ reference_parser: ReferenceParser
69
+ """A reference parser instance to use when resolving variables.
70
+
71
+ Default: :class:`~jsonlogic.resolving.PointerReferenceParser`.
72
+ """
73
+
74
+ variable_casts: dict[str, Callable[[str], Any]]
75
+ """A mapping between `JSON Schema formats`_ and their corresponding conversion callable.
76
+
77
+ When an operator reads variables from the provided data (such as the ``"var"`` operator),
78
+ such variables of type :class:`str` may be converted to a specific Python type if
79
+ the corresponding JSON Schema of the data was provided during evaluation.
80
+
81
+ This setting is analogous to the :attr:`~jsonlogic.typechecking.TypecheckSettings.variable_casts`
82
+ configuration of the :class:`~jsonlogic.typechecking.TypecheckSettings` class.
83
+
84
+ Default: :python:`{"date": date.fromisoformat, "date-time": datetime.fromisoformat}`.
85
+
86
+ .. _JSON Schema formats: https://json-schema.org/understanding-json-schema/reference/string#built-in-formats
87
+ """
88
+
89
+ literal_casts: list[Callable[[str], Any]]
90
+ """A list of conversion callables to try when encountering a literal string value during evaluation.
91
+
92
+ When a literal string value is encountered in a JSON Logic expression, it might be
93
+ beneficial to convert it to a specific Python type.
94
+
95
+ This setting is analogous to the :attr:`~jsonlogic.typechecking.TypecheckSettings.literal_casts`
96
+ configuration of the :class:`~jsonlogic.typechecking.TypecheckSettings` class.
97
+
98
+ Default: :python:`[]` (no cast).
99
+
100
+ .. warning::
101
+
102
+ The order in which the conversion callables are defined matters. Each
103
+ callable will be applied one after the other until no exception is raised.
104
+ """
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable
4
+
5
+ from jsonlogic.core import Operator
6
+ from jsonlogic.typing import JSON, JSONLogicPrimitive, OperatorArgument
7
+
8
+ from .evaluation_context import EvaluationContext
9
+ from .evaluation_settings import EvaluationSettingsDict
10
+
11
+
12
+ def evaluate(
13
+ operator: Operator, data: JSON, data_schema: dict[str, Any] | None, settings: EvaluationSettingsDict | None = None
14
+ ) -> Any:
15
+ """Helper function to evaluate an :class:`~jsonlogic.core.Operator`.
16
+
17
+ Args:
18
+ operator: The operator to evaluate.
19
+ data: The root data available during evaluation.
20
+ data_schema: The matching JSON Schema describing the root data. This should be the same JSON Schema
21
+ used during typechecking (see :paramref:`~jsonlogic.typechecking.TypecheckContext.root_data_schema`).
22
+ settings: Settings to be used when evaluating an :class:`~jsonlogic.core.Operator`.
23
+ See :class:`EvaluationSettings` for the available settings and default values.
24
+ Returns:
25
+ The evaluated value.
26
+ """
27
+ context = EvaluationContext(data, data_schema, settings)
28
+ return operator.evaluate(context)
29
+
30
+
31
+ # Function analogous to :func:`jsonlogic.json_schema.from_value`
32
+ def _cast_value(value: JSONLogicPrimitive, literal_casts: list[Callable[[str], Any]]) -> Any:
33
+ if isinstance(value, str):
34
+ for func in literal_casts:
35
+ try:
36
+ casted_value = func(value)
37
+ except Exception:
38
+ pass
39
+ else:
40
+ return casted_value
41
+
42
+ if not isinstance(value, list):
43
+ return value
44
+
45
+ return [_cast_value(subval, literal_casts) for subval in value]
46
+
47
+
48
+ def get_value(obj: OperatorArgument, context: EvaluationContext) -> Any:
49
+ """Get the value of an operator argument.
50
+
51
+ Args:
52
+ obj: the object to evaluate. If this is an :class:`~jsonlogic.core.Operator`,
53
+ it is evaluated and the value is returned. Otherwise, it must be a
54
+ :data:`~jsonlogic.typing.JSONLogicPrimitive`, and the type is inferred from
55
+ the actual value according to the :attr:`~TypecheckSettings.literal_casts` setting.
56
+ context: The typecheck context.
57
+ """
58
+ if isinstance(obj, Operator):
59
+ return obj.evaluate(context)
60
+ if isinstance(obj, list):
61
+ return [get_value(sub_obj, context) for sub_obj in obj]
62
+ return _cast_value(obj, context.settings.literal_casts)