jsonlogic-py 0.1__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.
- jsonlogic_py-0.1/LICENSE.txt +7 -0
- jsonlogic_py-0.1/PKG-INFO +42 -0
- jsonlogic_py-0.1/README.md +20 -0
- jsonlogic_py-0.1/pyproject.toml +30 -0
- jsonlogic_py-0.1/requirements-dev.txt +5 -0
- jsonlogic_py-0.1/requirements.txt +0 -0
- jsonlogic_py-0.1/setup.cfg +4 -0
- jsonlogic_py-0.1/src/jsonlogic/__init__.py +128 -0
- jsonlogic_py-0.1/src/jsonlogic/classes.py +8 -0
- jsonlogic_py-0.1/src/jsonlogic/errors.py +9 -0
- jsonlogic_py-0.1/src/jsonlogic/jsontypes.py +89 -0
- jsonlogic_py-0.1/src/jsonlogic/operations.py +90 -0
- jsonlogic_py-0.1/src/jsonlogic_py.egg-info/PKG-INFO +42 -0
- jsonlogic_py-0.1/src/jsonlogic_py.egg-info/SOURCES.txt +18 -0
- jsonlogic_py-0.1/src/jsonlogic_py.egg-info/dependency_links.txt +1 -0
- jsonlogic_py-0.1/src/jsonlogic_py.egg-info/requires.txt +7 -0
- jsonlogic_py-0.1/src/jsonlogic_py.egg-info/top_level.txt +1 -0
- jsonlogic_py-0.1/test/test_classes.py +66 -0
- jsonlogic_py-0.1/test/test_jsonlogic.py +126 -0
- jsonlogic_py-0.1/test/test_types.py +85 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2023-2024 Lawrence Livermore National Laboratory
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: jsonlogic-py
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: A Python package that emits JSON Logic
|
|
5
|
+
Author: Peter Pirkelbauer, Seth Bromberger
|
|
6
|
+
Project-URL: Homepage, https://github.com/MetallData/jsonlogic
|
|
7
|
+
Project-URL: Issues, https://github.com/MetallData/jsonlogic/issues
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE.txt
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest; extra == "dev"
|
|
18
|
+
Requires-Dist: coverage; extra == "dev"
|
|
19
|
+
Requires-Dist: json-logic-qubit; extra == "dev"
|
|
20
|
+
Requires-Dist: flake8; extra == "dev"
|
|
21
|
+
Requires-Dist: mypy; extra == "dev"
|
|
22
|
+
|
|
23
|
+
## jsonlogic.py - JSON Logic expression generator
|
|
24
|
+
|
|
25
|
+
This package provides functionality to express JSON Logic using standard Python datastructures.
|
|
26
|
+
|
|
27
|
+
An example:
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
>>> from jsonlogic import Variable
|
|
31
|
+
>>> v1 = Variable('var1')
|
|
32
|
+
>>> v2 = Variable('var2')
|
|
33
|
+
>>> e = (v1 < v2)
|
|
34
|
+
>>> print(e)
|
|
35
|
+
{"<": [{"var": "var1"}, {"var": "var2"}]}
|
|
36
|
+
>>> print (v1 < 3)
|
|
37
|
+
{"<": [{"var": "var1"}, 3]}
|
|
38
|
+
>>> print ( (v1 < 3) & (v1 > v2))
|
|
39
|
+
{"and": [{"<": [{"var": "var1"}, 3]}, {">": [{"var": "var1"}, {"var": "var2"}]}]}
|
|
40
|
+
>>> print ( (v1 < 3) & ~(v1 > v2))) # ~ is "not"
|
|
41
|
+
{"and": [{"<": [{"var": "v1"}, 3]}, {"not": [{">": [{"var": "v1"}, {"var": "v2"}]}]}]}
|
|
42
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
## jsonlogic.py - JSON Logic expression generator
|
|
2
|
+
|
|
3
|
+
This package provides functionality to express JSON Logic using standard Python datastructures.
|
|
4
|
+
|
|
5
|
+
An example:
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
>>> from jsonlogic import Variable
|
|
9
|
+
>>> v1 = Variable('var1')
|
|
10
|
+
>>> v2 = Variable('var2')
|
|
11
|
+
>>> e = (v1 < v2)
|
|
12
|
+
>>> print(e)
|
|
13
|
+
{"<": [{"var": "var1"}, {"var": "var2"}]}
|
|
14
|
+
>>> print (v1 < 3)
|
|
15
|
+
{"<": [{"var": "var1"}, 3]}
|
|
16
|
+
>>> print ( (v1 < 3) & (v1 > v2))
|
|
17
|
+
{"and": [{"<": [{"var": "var1"}, 3]}, {">": [{"var": "var1"}, {"var": "var2"}]}]}
|
|
18
|
+
>>> print ( (v1 < 3) & ~(v1 > v2))) # ~ is "not"
|
|
19
|
+
{"and": [{"<": [{"var": "v1"}, 3]}, {"not": [{">": [{"var": "v1"}, {"var": "v2"}]}]}]}
|
|
20
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools >= 68.0.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "jsonlogic-py"
|
|
7
|
+
version = "0.1"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Peter Pirkelbauer"}, { name="Seth Bromberger"}
|
|
10
|
+
]
|
|
11
|
+
description="A Python package that emits JSON Logic"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
dynamic = ["dependencies", "optional-dependencies"]
|
|
14
|
+
requires-python = ">=3.10"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent"
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.dynamic]
|
|
24
|
+
dependencies = {file = ["requirements.txt"]}
|
|
25
|
+
optional-dependencies = {dev = {file = ["requirements-dev.txt"] }}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/MetallData/jsonlogic"
|
|
30
|
+
Issues = "https://github.com/MetallData/jsonlogic/issues"
|
|
File without changes
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
""" JSONLogic emitters """
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import Any
|
|
5
|
+
import json
|
|
6
|
+
from .operations import Operation, jl_operations
|
|
7
|
+
from .errors import JsonLogicArgumentError
|
|
8
|
+
from .classes import Entity
|
|
9
|
+
from .jsontypes import PyJsonType, deduce_type
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Operand(Entity):
|
|
13
|
+
"""An abstract base class representing an JSONLogic operand"""
|
|
14
|
+
|
|
15
|
+
def __new__(cls, *_, **__):
|
|
16
|
+
for dunder, op in jl_operations.items():
|
|
17
|
+
setattr(cls, dunder, lambda self, *x, o=op: Expression(o, self, *x))
|
|
18
|
+
return super().__new__(cls)
|
|
19
|
+
|
|
20
|
+
def prepare(self) -> Any:
|
|
21
|
+
"""prepares the structure for json by converting it into something that can be dumped"""
|
|
22
|
+
raise NotImplementedError()
|
|
23
|
+
|
|
24
|
+
def to_json(self) -> str:
|
|
25
|
+
"""represents the object as JSON"""
|
|
26
|
+
return json.dumps(self.prepare())
|
|
27
|
+
|
|
28
|
+
def __str__(self):
|
|
29
|
+
return self.to_json()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Literal(Operand):
|
|
33
|
+
"""A JSONLogic literal."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, val: PyJsonType | Literal, docstr: str | None = None):
|
|
36
|
+
super().__init__()
|
|
37
|
+
self._rawval: PyJsonType = val._rawval if isinstance(val, Literal) else val
|
|
38
|
+
|
|
39
|
+
self.type = deduce_type(val)
|
|
40
|
+
if docstr is not None:
|
|
41
|
+
self.__doc__ = docstr
|
|
42
|
+
|
|
43
|
+
def __repr__(self):
|
|
44
|
+
return str(self.type)
|
|
45
|
+
|
|
46
|
+
def __str__(self):
|
|
47
|
+
return str(self.type)
|
|
48
|
+
|
|
49
|
+
def prepare(self) -> PyJsonType:
|
|
50
|
+
return self.type.prepare()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Variable(Operand):
|
|
54
|
+
"""A JSONLogic variable"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, var: str, docstr: str | None = None):
|
|
57
|
+
super().__init__()
|
|
58
|
+
self.var = var
|
|
59
|
+
if docstr is not None:
|
|
60
|
+
self.__doc__ = docstr
|
|
61
|
+
|
|
62
|
+
def prepare(self):
|
|
63
|
+
return {"var": self.var}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Expression(Operand):
|
|
67
|
+
"""A JSONLogic expression"""
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
op: Operation,
|
|
72
|
+
o1: Variable | Expression,
|
|
73
|
+
*on: Operand | PyJsonType,
|
|
74
|
+
):
|
|
75
|
+
super().__init__()
|
|
76
|
+
# if op is None then this expression is just a native Selector held in o1.
|
|
77
|
+
self.op = op
|
|
78
|
+
self.o1 = o1
|
|
79
|
+
if op.arity is not None and len(on) != op.arity - 1:
|
|
80
|
+
raise JsonLogicArgumentError(
|
|
81
|
+
f"incorrect number of arguments for {op}: wanted {op.arity}, got {len(on) + 1}"
|
|
82
|
+
)
|
|
83
|
+
# add the remaining variables, casting them to Literals if they're not Variables, Expressions, or Literals.
|
|
84
|
+
self.on = tuple(Literal(o) if not isinstance(o, Operand) else o for o in on)
|
|
85
|
+
|
|
86
|
+
def prepare(self):
|
|
87
|
+
return {
|
|
88
|
+
str(self.op): [self.o1.prepare()]
|
|
89
|
+
+ list(x.prepare() if isinstance(x, Operand) else x for x in self.on)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# class Type(ABC):
|
|
94
|
+
# """The abstract base class for all JSONLogic Types"""
|
|
95
|
+
|
|
96
|
+
# def __init__(self, value):
|
|
97
|
+
# _value = value
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# class Number(Type, float):
|
|
101
|
+
# """Type representing a JSONLogic number"""
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# class String(Type, str):
|
|
105
|
+
# """Type representing a JSONLogic string"""
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# class Object(Type, dict):
|
|
109
|
+
# """Type representing a JSONLogic object (map)"""
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# class Bool(Type):
|
|
113
|
+
# """Type representing a JSONLogic boolean"""
|
|
114
|
+
|
|
115
|
+
# def __init__(self, value):
|
|
116
|
+
# self.
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# class Array(Type, list):
|
|
120
|
+
# """Type representing a JSONLogic array (list)"""
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# class Null(Type, NoneType):
|
|
124
|
+
# """Type representing a JSONLogic null"""
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# class _Any(Type, Any):
|
|
128
|
+
# """Type representing any JSONLogic type"""
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# pylint: disable=too-few-public-methods
|
|
2
|
+
|
|
3
|
+
""" JSON typing classes and functions """
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
from abc import ABC
|
|
7
|
+
import warnings
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
PyJsonType = int | float | bool | str | list | dict
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class JsonType(ABC):
|
|
15
|
+
"""The abstract base class for JSON types"""
|
|
16
|
+
|
|
17
|
+
typename = "UNDEFINED"
|
|
18
|
+
|
|
19
|
+
def __init__(self, val):
|
|
20
|
+
self.val = val
|
|
21
|
+
|
|
22
|
+
def __str__(self):
|
|
23
|
+
return f"[{self.typename}]{self.val}"
|
|
24
|
+
|
|
25
|
+
def prepare(self) -> PyJsonType:
|
|
26
|
+
"""Prepares the type for JSON serialization"""
|
|
27
|
+
raise NotImplementedError("class has not defined prepare()")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class JsonNumber(JsonType):
|
|
31
|
+
"""A JSON Number type"""
|
|
32
|
+
|
|
33
|
+
typename = "Number"
|
|
34
|
+
|
|
35
|
+
def prepare(self):
|
|
36
|
+
return int(self.val) if isinstance(self.val, int) else float(self.val)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class JsonBool(JsonType):
|
|
40
|
+
"""A JSON Boolean type"""
|
|
41
|
+
|
|
42
|
+
typename = "Boolean"
|
|
43
|
+
|
|
44
|
+
def prepare(self) -> bool:
|
|
45
|
+
return bool(self.val)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class JsonStr(JsonType):
|
|
49
|
+
"""A JSON String type"""
|
|
50
|
+
|
|
51
|
+
typename = "String"
|
|
52
|
+
|
|
53
|
+
def prepare(self) -> str:
|
|
54
|
+
return str(self.val)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class JsonArray(JsonType):
|
|
58
|
+
"""A JSON Array type"""
|
|
59
|
+
|
|
60
|
+
typename = "Array"
|
|
61
|
+
|
|
62
|
+
def prepare(self) -> list:
|
|
63
|
+
return list(self.val)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class JsonObj(JsonType):
|
|
67
|
+
"""A JSON Object type"""
|
|
68
|
+
|
|
69
|
+
typename = "Object"
|
|
70
|
+
|
|
71
|
+
def prepare(self) -> dict:
|
|
72
|
+
return dict(self.val)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def deduce_type(val: Any) -> JsonType:
|
|
76
|
+
"""Given a value, try to deduce the json datatype. Default is string."""
|
|
77
|
+
if isinstance(val, bool):
|
|
78
|
+
return JsonBool(val)
|
|
79
|
+
if isinstance(val, (float, int)):
|
|
80
|
+
return JsonNumber(val)
|
|
81
|
+
if isinstance(val, list):
|
|
82
|
+
return JsonArray(val)
|
|
83
|
+
if isinstance(val, dict):
|
|
84
|
+
return JsonObj(val)
|
|
85
|
+
if not isinstance(val, str):
|
|
86
|
+
warnings.warn(
|
|
87
|
+
f"cannot deduce type of {val} ({type(val)}); assuming string", UserWarning
|
|
88
|
+
)
|
|
89
|
+
return JsonStr(str(val))
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Copyright 2020 Lawrence Livermore National Security, LLC and other CLIPPy Project Developers.
|
|
2
|
+
# See the top-level COPYRIGHT file for details.
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
""" Holds the expression building code. """
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Callable
|
|
12
|
+
from .classes import Entity
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Operation(Entity):
|
|
17
|
+
"""A JSONLogic operation"""
|
|
18
|
+
|
|
19
|
+
op: str
|
|
20
|
+
arity: int | None # None means unlimited
|
|
21
|
+
|
|
22
|
+
def __repr__(self):
|
|
23
|
+
return self.op
|
|
24
|
+
|
|
25
|
+
def __str__(self):
|
|
26
|
+
return str(self.op)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_jl_lt = Operation("<", 2)
|
|
30
|
+
_jl_le = Operation("<=", 2)
|
|
31
|
+
_jl_eq = Operation("==", 2)
|
|
32
|
+
_jl_ne = Operation("!=", 2)
|
|
33
|
+
_jl_gt = Operation(">", 2)
|
|
34
|
+
_jl_ge = Operation(">=", 2)
|
|
35
|
+
_jl_add = Operation("+", 2)
|
|
36
|
+
_jl_sub = Operation("-", 2)
|
|
37
|
+
_jl_mul = Operation("*", 2)
|
|
38
|
+
_jl_matmul = Operation("@", 2)
|
|
39
|
+
_jl_truediv = Operation("/", 2)
|
|
40
|
+
_jl_floordiv = Operation("//", 2)
|
|
41
|
+
_jl_mod = Operation("%", 2)
|
|
42
|
+
_jl_divmod = Operation("divmod", 2)
|
|
43
|
+
_jl_pow = Operation("**", 2)
|
|
44
|
+
_jl_lshift = Operation("<<", 2)
|
|
45
|
+
_jl_rshift = Operation(">>", 2)
|
|
46
|
+
_jl_and = Operation("and", 2) # single &
|
|
47
|
+
_jl_not = Operation("not", 1)
|
|
48
|
+
_jl_xor = Operation("^", 2)
|
|
49
|
+
_jl_or = Operation("or", 2) # single |
|
|
50
|
+
# _jl_contains = Operation():
|
|
51
|
+
# raise NotImplementedError("syntax a in b is not supported. Use b.contains(a) instead.")
|
|
52
|
+
# # will not work when written as "x in set",
|
|
53
|
+
# # b/c the in-operator always converts the result to bool
|
|
54
|
+
# # https://stackoverflow.com/questions/38542543/functionality-of-python-in-vs-contains
|
|
55
|
+
# # https://bugs.python.org/issue16011
|
|
56
|
+
# # return Expression("in", o, self)
|
|
57
|
+
|
|
58
|
+
# # to be modeled after Pandas' str.contains
|
|
59
|
+
_jl_contains = Operation("in", 2)
|
|
60
|
+
_jl_regex = Operation("regex", 2)
|
|
61
|
+
|
|
62
|
+
# string and array concatenation
|
|
63
|
+
_jl_cat = Operation("cat", None)
|
|
64
|
+
|
|
65
|
+
jl_operations: dict[str, Operation | Callable[..., Operation]] = {
|
|
66
|
+
"__lt__": _jl_lt,
|
|
67
|
+
"__le__": _jl_le,
|
|
68
|
+
"__eq__": _jl_eq,
|
|
69
|
+
"__ne__": _jl_ne,
|
|
70
|
+
"__gt__": _jl_gt,
|
|
71
|
+
"__ge__": _jl_ge,
|
|
72
|
+
"__add__": _jl_add,
|
|
73
|
+
"__sub__": _jl_sub,
|
|
74
|
+
"__mul__": _jl_mul,
|
|
75
|
+
"__matmul__": _jl_matmul,
|
|
76
|
+
"__truediv__": _jl_truediv,
|
|
77
|
+
"__floordiv__": _jl_floordiv,
|
|
78
|
+
"__mod__": _jl_mod,
|
|
79
|
+
"__divmod__": _jl_divmod,
|
|
80
|
+
"__pow__": _jl_pow,
|
|
81
|
+
"__lshift__": _jl_lshift,
|
|
82
|
+
"__rshift__": _jl_rshift,
|
|
83
|
+
"__and__": _jl_and,
|
|
84
|
+
"__xor__": _jl_xor,
|
|
85
|
+
"__or__": _jl_or,
|
|
86
|
+
"__invert__": _jl_not,
|
|
87
|
+
"contains": _jl_contains,
|
|
88
|
+
"regex": _jl_regex,
|
|
89
|
+
"cat": _jl_cat,
|
|
90
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: jsonlogic-py
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: A Python package that emits JSON Logic
|
|
5
|
+
Author: Peter Pirkelbauer, Seth Bromberger
|
|
6
|
+
Project-URL: Homepage, https://github.com/MetallData/jsonlogic
|
|
7
|
+
Project-URL: Issues, https://github.com/MetallData/jsonlogic/issues
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE.txt
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest; extra == "dev"
|
|
18
|
+
Requires-Dist: coverage; extra == "dev"
|
|
19
|
+
Requires-Dist: json-logic-qubit; extra == "dev"
|
|
20
|
+
Requires-Dist: flake8; extra == "dev"
|
|
21
|
+
Requires-Dist: mypy; extra == "dev"
|
|
22
|
+
|
|
23
|
+
## jsonlogic.py - JSON Logic expression generator
|
|
24
|
+
|
|
25
|
+
This package provides functionality to express JSON Logic using standard Python datastructures.
|
|
26
|
+
|
|
27
|
+
An example:
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
>>> from jsonlogic import Variable
|
|
31
|
+
>>> v1 = Variable('var1')
|
|
32
|
+
>>> v2 = Variable('var2')
|
|
33
|
+
>>> e = (v1 < v2)
|
|
34
|
+
>>> print(e)
|
|
35
|
+
{"<": [{"var": "var1"}, {"var": "var2"}]}
|
|
36
|
+
>>> print (v1 < 3)
|
|
37
|
+
{"<": [{"var": "var1"}, 3]}
|
|
38
|
+
>>> print ( (v1 < 3) & (v1 > v2))
|
|
39
|
+
{"and": [{"<": [{"var": "var1"}, 3]}, {">": [{"var": "var1"}, {"var": "var2"}]}]}
|
|
40
|
+
>>> print ( (v1 < 3) & ~(v1 > v2))) # ~ is "not"
|
|
41
|
+
{"and": [{"<": [{"var": "v1"}, 3]}, {"not": [{">": [{"var": "v1"}, {"var": "v2"}]}]}]}
|
|
42
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
LICENSE.txt
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
requirements-dev.txt
|
|
5
|
+
requirements.txt
|
|
6
|
+
src/jsonlogic/__init__.py
|
|
7
|
+
src/jsonlogic/classes.py
|
|
8
|
+
src/jsonlogic/errors.py
|
|
9
|
+
src/jsonlogic/jsontypes.py
|
|
10
|
+
src/jsonlogic/operations.py
|
|
11
|
+
src/jsonlogic_py.egg-info/PKG-INFO
|
|
12
|
+
src/jsonlogic_py.egg-info/SOURCES.txt
|
|
13
|
+
src/jsonlogic_py.egg-info/dependency_links.txt
|
|
14
|
+
src/jsonlogic_py.egg-info/requires.txt
|
|
15
|
+
src/jsonlogic_py.egg-info/top_level.txt
|
|
16
|
+
test/test_classes.py
|
|
17
|
+
test/test_jsonlogic.py
|
|
18
|
+
test/test_types.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
jsonlogic
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
sys.path.append("src")
|
|
5
|
+
|
|
6
|
+
from jsonlogic import Expression, Variable, Literal, Operand, Operation
|
|
7
|
+
from jsonlogic.classes import Entity
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def op1():
|
|
13
|
+
return Operation("op1", 1)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def op2():
|
|
18
|
+
return Operation("op2", 2)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.fixture
|
|
22
|
+
def v1():
|
|
23
|
+
return Variable("var1", "docstring for var1")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def v2():
|
|
28
|
+
return Variable("var2")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def l1():
|
|
33
|
+
return Literal(5, "docstring for literal")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.fixture
|
|
37
|
+
def l2():
|
|
38
|
+
return Literal("stringliteral")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_expression(op1, op2, v1, v2, l1, l2):
|
|
42
|
+
e = Expression(op1, v1)
|
|
43
|
+
assert e.to_json() == str(e) == '{"op1": [{"var": "var1"}]}'
|
|
44
|
+
|
|
45
|
+
e = Expression(op2, v1, v2)
|
|
46
|
+
assert e.to_json() == str(e) == '{"op2": [{"var": "var1"}, {"var": "var2"}]}'
|
|
47
|
+
|
|
48
|
+
e = Expression(op2, v1, l1)
|
|
49
|
+
assert e.to_json() == str(e) == '{"op2": [{"var": "var1"}, 5]}'
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_operations():
|
|
53
|
+
o = Operation("foo", 1)
|
|
54
|
+
assert repr(o) == "foo"
|
|
55
|
+
assert str(o) == "foo"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_literals(l1, l2):
|
|
59
|
+
assert repr(l1) == str(l1) == "[Number]5"
|
|
60
|
+
assert repr(l2) == str(l2) == "[String]stringliteral"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_operand():
|
|
64
|
+
o = Operand()
|
|
65
|
+
with pytest.raises(NotImplementedError):
|
|
66
|
+
o.prepare()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
sys.path.append("src")
|
|
5
|
+
|
|
6
|
+
from json_logic import jsonLogic
|
|
7
|
+
|
|
8
|
+
import jsonlogic as jl
|
|
9
|
+
from jsonlogic.errors import JsonLogicArgumentError
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class TV(dict):
|
|
15
|
+
|
|
16
|
+
def __init__(self, *args, **kwargs):
|
|
17
|
+
super().__init__(*args, **kwargs)
|
|
18
|
+
|
|
19
|
+
def __getitem__(self, val):
|
|
20
|
+
return self.get(val)[1]
|
|
21
|
+
|
|
22
|
+
def getname(self, val):
|
|
23
|
+
return self.get(val)[0]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def _i():
|
|
28
|
+
return TV({n: (f"var{n}", jl.Variable(f"var{n}")) for n in range(1, 5)})
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def _f():
|
|
33
|
+
fs = [1.1, 2.0, 2.1, 2.2, 3.3, 4.4]
|
|
34
|
+
return TV({n: (f"var{int(n*10)}", jl.Variable(f"var{int(n*10)}")) for n in fs})
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def _s():
|
|
39
|
+
ss = ["a", "b", "c", "d", "e"]
|
|
40
|
+
return TV({n: (f"var{n}", jl.Variable(f"var{n}")) for n in ss})
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def assert_op(e: jl.Expression, d):
|
|
44
|
+
assert jsonLogic(e.prepare(), d)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_lt_gt_ne(_s, _i, _f):
|
|
48
|
+
test_int = [
|
|
49
|
+
_i[1] < _i[2],
|
|
50
|
+
_i[1] <= _i[1],
|
|
51
|
+
_i[1] <= _i[2],
|
|
52
|
+
_i[2] > _i[1],
|
|
53
|
+
_i[2] >= _i[1],
|
|
54
|
+
_i[2] >= _i[2],
|
|
55
|
+
_i[1] != _i[2],
|
|
56
|
+
]
|
|
57
|
+
test_float = [
|
|
58
|
+
_f[1.1] < _f[2.0],
|
|
59
|
+
_f[2.0] <= _f[2.0],
|
|
60
|
+
_f[2.0] <= _f[2.1],
|
|
61
|
+
_f[4.4] >= _f[2.1],
|
|
62
|
+
_f[4.4] != _f[2.2],
|
|
63
|
+
]
|
|
64
|
+
test_str = {
|
|
65
|
+
_s["a"] < _s["b"],
|
|
66
|
+
_s["a"] <= _s["a"],
|
|
67
|
+
_s["b"] > _s["a"],
|
|
68
|
+
_s["b"] >= _s["b"],
|
|
69
|
+
_s["a"] != _s["b"],
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for t in test_int:
|
|
73
|
+
d = {_i.getname(k): k for k in _i}
|
|
74
|
+
assert len(d) > 0
|
|
75
|
+
assert_op(t, d)
|
|
76
|
+
|
|
77
|
+
for t in test_float:
|
|
78
|
+
d = {_f.getname(k): k for k in _f}
|
|
79
|
+
assert_op(t, d)
|
|
80
|
+
|
|
81
|
+
for t in test_str:
|
|
82
|
+
d = {_s.getname(k): k for k in _s}
|
|
83
|
+
assert_op(t, d)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_eq_add_mul_truediv_floordiv(_i, _f):
|
|
87
|
+
test_int = [
|
|
88
|
+
_i[1] + 1 == _i[2],
|
|
89
|
+
_i[2] + 2 == _i[4],
|
|
90
|
+
_i[2] + _i[2] == _i[4],
|
|
91
|
+
_i[2] * 2 == _i[4],
|
|
92
|
+
_i[2] * _i[2] == _i[4],
|
|
93
|
+
]
|
|
94
|
+
test_float = [
|
|
95
|
+
_f[1.1] + 1.1 == _f[2.2],
|
|
96
|
+
_f[2.2] + 2.2 == _f[4.4],
|
|
97
|
+
_f[2.2] + _f[2.2] == _f[4.4],
|
|
98
|
+
_f[2.2] * 2 == _f[4.4],
|
|
99
|
+
_f[2.2] * _f[2.0] == _f[4.4],
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
for t in test_int:
|
|
103
|
+
d = {_i.getname(k): k for k in _i}
|
|
104
|
+
assert_op(t, d)
|
|
105
|
+
|
|
106
|
+
for t in test_float:
|
|
107
|
+
d = {_f.getname(k): k for k in _f}
|
|
108
|
+
assert_op(t, d)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_bad_args():
|
|
112
|
+
with pytest.raises(JsonLogicArgumentError):
|
|
113
|
+
o = jl.Operation("foo", 1)
|
|
114
|
+
v = jl.Variable("var1")
|
|
115
|
+
jl.Expression(o, v, 1)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_operations():
|
|
119
|
+
o = jl.Operation("foo", 1)
|
|
120
|
+
assert repr(o) == "foo"
|
|
121
|
+
assert str(o) == "foo"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# def test_float_eq_add_mul_truediv_floordiv(var1, var2, var4, var5, data_float):
|
|
125
|
+
# for t in tests:
|
|
126
|
+
# assert_op(t, data_float)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
import sys
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
sys.path.append('src')
|
|
6
|
+
from jsonlogic.jsontypes import JsonArray, JsonBool, JsonNumber, JsonStr, JsonObj, deduce_type, PyJsonType, JsonType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_bool():
|
|
10
|
+
b = JsonBool(True)
|
|
11
|
+
assert b.typename == 'Boolean'
|
|
12
|
+
assert b.val is True
|
|
13
|
+
assert isinstance(b.val, bool)
|
|
14
|
+
assert str(b) == "[Boolean]True"
|
|
15
|
+
assert b.prepare() is True
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_int():
|
|
19
|
+
b = JsonNumber(10)
|
|
20
|
+
assert b.typename == 'Number'
|
|
21
|
+
assert b.val == 10
|
|
22
|
+
assert isinstance(b.val, int)
|
|
23
|
+
assert str(b) == "[Number]10"
|
|
24
|
+
assert b.prepare() == 10
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_float():
|
|
28
|
+
b = JsonNumber(10.1)
|
|
29
|
+
assert b.typename == 'Number'
|
|
30
|
+
assert b.val == 10.1
|
|
31
|
+
assert isinstance(b.val, float)
|
|
32
|
+
assert str(b) == "[Number]10.1"
|
|
33
|
+
assert b.prepare() == 10.1
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_dict():
|
|
37
|
+
d = {'a': 1, 'b': 2}
|
|
38
|
+
b = JsonObj(d)
|
|
39
|
+
assert b.typename == 'Object'
|
|
40
|
+
assert b.val == d
|
|
41
|
+
assert isinstance(b.val, dict)
|
|
42
|
+
assert str(b).startswith("[Object]")
|
|
43
|
+
assert b.prepare() == d
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_str():
|
|
47
|
+
b = JsonStr("hello")
|
|
48
|
+
assert b.typename == 'String'
|
|
49
|
+
assert b.val == "hello"
|
|
50
|
+
assert isinstance(b.val, str)
|
|
51
|
+
assert str(b).startswith("[String]")
|
|
52
|
+
assert b.prepare() == "hello"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_list():
|
|
56
|
+
d = [1, 2, 3, 4, 5]
|
|
57
|
+
b = JsonArray(d)
|
|
58
|
+
assert b.typename == 'Array'
|
|
59
|
+
assert b.val == d
|
|
60
|
+
assert isinstance(b.val, list)
|
|
61
|
+
assert str(b).startswith("[Array]")
|
|
62
|
+
assert b.prepare() == d
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_deduce():
|
|
66
|
+
assert isinstance(deduce_type(True), JsonBool)
|
|
67
|
+
assert isinstance(deduce_type(10), JsonNumber)
|
|
68
|
+
assert isinstance(deduce_type(10.1), JsonNumber)
|
|
69
|
+
assert isinstance(deduce_type({'a': 1}), JsonObj)
|
|
70
|
+
assert isinstance(deduce_type(""), JsonStr)
|
|
71
|
+
assert isinstance(deduce_type([]), JsonArray)
|
|
72
|
+
|
|
73
|
+
with pytest.warns(UserWarning, match=r"cannot deduce type.*"):
|
|
74
|
+
assert isinstance(deduce_type(complex(1, 2)), JsonStr)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_jsontype():
|
|
78
|
+
class TestType(JsonType):
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
tt = TestType(10)
|
|
82
|
+
assert tt.typename == "UNDEFINED"
|
|
83
|
+
assert str(tt) == "[UNDEFINED]10"
|
|
84
|
+
with pytest.raises(NotImplementedError):
|
|
85
|
+
tt.prepare()
|