python-jsonlogic 0.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.
- python-jsonlogic-0.0.1/LICENSE +21 -0
- python-jsonlogic-0.0.1/PKG-INFO +66 -0
- python-jsonlogic-0.0.1/README.rst +21 -0
- python-jsonlogic-0.0.1/pyproject.toml +74 -0
- python-jsonlogic-0.0.1/setup.cfg +4 -0
- python-jsonlogic-0.0.1/src/jsonlogic/__init__.py +0 -0
- python-jsonlogic-0.0.1/src/jsonlogic/_compat.py +8 -0
- python-jsonlogic-0.0.1/src/jsonlogic/core.py +73 -0
- python-jsonlogic-0.0.1/src/jsonlogic/json_schema/__init__.py +77 -0
- python-jsonlogic-0.0.1/src/jsonlogic/json_schema/resolvers.py +51 -0
- python-jsonlogic-0.0.1/src/jsonlogic/json_schema/types.py +130 -0
- python-jsonlogic-0.0.1/src/jsonlogic/operators/__init__.py +24 -0
- python-jsonlogic-0.0.1/src/jsonlogic/operators/base.py +0 -0
- python-jsonlogic-0.0.1/src/jsonlogic/operators/operators.py +159 -0
- python-jsonlogic-0.0.1/src/jsonlogic/operators/typechecking.py +16 -0
- python-jsonlogic-0.0.1/src/jsonlogic/py.typed +0 -0
- python-jsonlogic-0.0.1/src/jsonlogic/registry.py +94 -0
- python-jsonlogic-0.0.1/src/jsonlogic/typing.py +17 -0
- python-jsonlogic-0.0.1/src/jsonlogic/utils.py +14 -0
- python-jsonlogic-0.0.1/src/python_jsonlogic.egg-info/PKG-INFO +66 -0
- python-jsonlogic-0.0.1/src/python_jsonlogic.egg-info/SOURCES.txt +22 -0
- python-jsonlogic-0.0.1/src/python_jsonlogic.egg-info/dependency_links.txt +1 -0
- python-jsonlogic-0.0.1/src/python_jsonlogic.egg-info/requires.txt +3 -0
- python-jsonlogic-0.0.1/src/python_jsonlogic.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Victorien
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: python-jsonlogic
|
|
3
|
+
Version: 0.0.1
|
|
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.6.0; python_version < "3.12"
|
|
45
|
+
|
|
46
|
+
=========
|
|
47
|
+
jsonlogic
|
|
48
|
+
=========
|
|
49
|
+
|
|
50
|
+
|Pythons| |PyPI|
|
|
51
|
+
|
|
52
|
+
.. |Pythons| image:: https://img.shields.io/pypi/pyversions/jsonlogic.svg
|
|
53
|
+
:alt: Supported Python versions
|
|
54
|
+
:target: https://pypi.org/project/jsonlogic/
|
|
55
|
+
|
|
56
|
+
.. |PyPI| image:: https://img.shields.io/pypi/v/jsonlogic.svg
|
|
57
|
+
:alt: PyPI - Version
|
|
58
|
+
:target: https://pypi.org/project/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://pypi.org/project/jsonlogic/
|
|
63
|
+
|
|
64
|
+
``jsonlogic`` is an extensible and sane implementation of `JsonLogic`_.
|
|
65
|
+
|
|
66
|
+
.. _`JsonLogic`: https://jsonlogic.com/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
=========
|
|
2
|
+
jsonlogic
|
|
3
|
+
=========
|
|
4
|
+
|
|
5
|
+
|Pythons| |PyPI|
|
|
6
|
+
|
|
7
|
+
.. |Pythons| image:: https://img.shields.io/pypi/pyversions/jsonlogic.svg
|
|
8
|
+
:alt: Supported Python versions
|
|
9
|
+
:target: https://pypi.org/project/jsonlogic/
|
|
10
|
+
|
|
11
|
+
.. |PyPI| image:: https://img.shields.io/pypi/v/jsonlogic.svg
|
|
12
|
+
:alt: PyPI - Version
|
|
13
|
+
:target: https://pypi.org/project/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://pypi.org/project/jsonlogic/
|
|
18
|
+
|
|
19
|
+
``jsonlogic`` is an extensible and sane implementation of `JsonLogic`_.
|
|
20
|
+
|
|
21
|
+
.. _`JsonLogic`: https://jsonlogic.com/
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "python-jsonlogic"
|
|
7
|
+
description = "An extensible and sane implementation of JsonLogic"
|
|
8
|
+
version = "0.0.1"
|
|
9
|
+
readme = "README.rst"
|
|
10
|
+
authors = [
|
|
11
|
+
{name = "Victorien", email = "contact@vctrn.dev"}
|
|
12
|
+
]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
18
|
+
"Programming Language :: Python :: 3.8",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
"License :: OSI Approved :: MIT License",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
|
+
]
|
|
28
|
+
license = {file = "LICENSE"}
|
|
29
|
+
requires-python = ">=3.8"
|
|
30
|
+
dependencies = [
|
|
31
|
+
"typing-extensions>=4.6.0; python_version < '3.12'",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[tool.setuptools]
|
|
35
|
+
package-dir = {"" = "src"}
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.packages.find]
|
|
38
|
+
where = ["src"]
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.package-data]
|
|
41
|
+
"jsonlogic" = ["py.typed"]
|
|
42
|
+
|
|
43
|
+
[tool.ruff]
|
|
44
|
+
line-length = 120
|
|
45
|
+
src = ["src"]
|
|
46
|
+
target-version = "py38"
|
|
47
|
+
|
|
48
|
+
[tool.ruff.lint]
|
|
49
|
+
preview = true
|
|
50
|
+
explicit-preview-rules = true
|
|
51
|
+
select = [
|
|
52
|
+
"E", # pycodestyle (E)
|
|
53
|
+
"W", # pycodestyle (W)
|
|
54
|
+
"F", # Pyflakes
|
|
55
|
+
"UP", # pyupgrade
|
|
56
|
+
"I", # isort
|
|
57
|
+
"PL", # Pylint
|
|
58
|
+
"RUF", # Ruff
|
|
59
|
+
"RUF022", # Ruff-preview
|
|
60
|
+
"YTT", # flake8-2020
|
|
61
|
+
"B", # flake8-bugbear
|
|
62
|
+
"C4", # flake8-comprehensions
|
|
63
|
+
"T10", # flake8-debugger
|
|
64
|
+
"PIE", # flake8-pie
|
|
65
|
+
"T20", # flake8-print
|
|
66
|
+
"RSE", # flake8-raise
|
|
67
|
+
"PTH", # flake8-use-pathlib
|
|
68
|
+
]
|
|
69
|
+
ignore = [
|
|
70
|
+
"PLR2004"
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
[tool.ruff.lint.isort]
|
|
74
|
+
known-first-party = ["jsonlogic"]
|
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from ._compat import Self, TypeAlias
|
|
8
|
+
from .json_schema.types import AnyType, JSONSchemaType
|
|
9
|
+
from .typing import JSON, JSONLogicPrimitive, OperatorArgument
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
# This is a hack to make pyright think `TypeAlias` comes from `typing`
|
|
13
|
+
from typing import TypeAlias
|
|
14
|
+
|
|
15
|
+
from .registry import OperatorRegistry
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class Operator(ABC):
|
|
20
|
+
"""The base class for all operators."""
|
|
21
|
+
|
|
22
|
+
operator: str = field(repr=False)
|
|
23
|
+
"""The string representation of the operator."""
|
|
24
|
+
|
|
25
|
+
# metadata: Any | None = None
|
|
26
|
+
# """Extra metadata for this operator.
|
|
27
|
+
|
|
28
|
+
# For any exception encountered, this will be included.
|
|
29
|
+
# """
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def from_expression(cls, operator: str, arguments: list[OperatorArgument]) -> Self:
|
|
34
|
+
"""Return an instance of the operator from the list of provided arguments."""
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def apply(self, data: JSON) -> Any:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
def typecheck(self, data_schema: dict[str, Any]) -> JSONSchemaType:
|
|
41
|
+
"""Typecheck the operator (and all children) given the data schema."""
|
|
42
|
+
|
|
43
|
+
return AnyType()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
NormalizedExpression: TypeAlias = "dict[str, list[JSONLogicExpression]]"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class JSONLogicExpression:
|
|
51
|
+
expression: JSONLogicPrimitive | NormalizedExpression
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def from_json(cls, json: JSON) -> Self: # TODO disallow list?
|
|
55
|
+
if not isinstance(json, dict):
|
|
56
|
+
return cls(expression=json)
|
|
57
|
+
|
|
58
|
+
operator, op_args = next(iter(json.items()))
|
|
59
|
+
if not isinstance(op_args, list):
|
|
60
|
+
op_args = [op_args]
|
|
61
|
+
|
|
62
|
+
sub_expressions = [cls.from_json(op_arg) for op_arg in op_args]
|
|
63
|
+
|
|
64
|
+
return cls({operator: sub_expressions})
|
|
65
|
+
|
|
66
|
+
def as_operator_tree(self, operator_registry: OperatorRegistry) -> JSONLogicPrimitive | Operator:
|
|
67
|
+
if not isinstance(self.expression, dict):
|
|
68
|
+
return self.expression
|
|
69
|
+
|
|
70
|
+
op_id, op_args = next(iter(self.expression.items()))
|
|
71
|
+
OperatorCls = operator_registry.get(op_id)
|
|
72
|
+
|
|
73
|
+
return OperatorCls.from_expression(op_id, [op_arg.as_operator_tree(operator_registry) for op_arg in op_args])
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from datetime import date, datetime
|
|
2
|
+
from types import NoneType
|
|
3
|
+
from typing import Any, Callable, cast
|
|
4
|
+
|
|
5
|
+
from jsonlogic.typing import JSONLogicPrimitive
|
|
6
|
+
|
|
7
|
+
from .types import (
|
|
8
|
+
AnyType,
|
|
9
|
+
BooleanType,
|
|
10
|
+
DatetimeType,
|
|
11
|
+
DateType,
|
|
12
|
+
IntegerType,
|
|
13
|
+
JSONSchemaType,
|
|
14
|
+
NullType,
|
|
15
|
+
NumberType,
|
|
16
|
+
StringType,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
_VALUE_TYPE_MAP: dict[type[Any], JSONSchemaType] = {
|
|
20
|
+
bool: BooleanType(),
|
|
21
|
+
float: NumberType(),
|
|
22
|
+
int: IntegerType(),
|
|
23
|
+
NoneType: NullType(),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
_VALUE_FORMAT_MAP: dict[Callable[[str], Any], JSONSchemaType] = {
|
|
27
|
+
datetime.fromisoformat: DatetimeType(),
|
|
28
|
+
date.fromisoformat: DateType(),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def from_value(value: JSONLogicPrimitive) -> JSONSchemaType:
|
|
33
|
+
if type(value) in _VALUE_TYPE_MAP:
|
|
34
|
+
return _VALUE_TYPE_MAP[type(value)]
|
|
35
|
+
|
|
36
|
+
if isinstance(value, str):
|
|
37
|
+
for func, js_type in _VALUE_FORMAT_MAP.items():
|
|
38
|
+
try:
|
|
39
|
+
func(value)
|
|
40
|
+
except Exception:
|
|
41
|
+
pass
|
|
42
|
+
else:
|
|
43
|
+
return js_type
|
|
44
|
+
|
|
45
|
+
return StringType()
|
|
46
|
+
|
|
47
|
+
return AnyType()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
_TYPE_MAP: dict[str, JSONSchemaType] = {
|
|
51
|
+
"boolean": BooleanType(),
|
|
52
|
+
"number": NumberType(),
|
|
53
|
+
"integer": IntegerType(),
|
|
54
|
+
"null": NullType(),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_FORMAT_MAP: dict[str, JSONSchemaType] = {
|
|
58
|
+
"date-time": DatetimeType(),
|
|
59
|
+
"date": DateType(),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def from_json_schema(json_schema: dict[str, Any]) -> JSONSchemaType:
|
|
64
|
+
# TODO support for unions
|
|
65
|
+
js_type = cast(str | None, json_schema.get("type"))
|
|
66
|
+
if js_type in _TYPE_MAP:
|
|
67
|
+
return _TYPE_MAP[js_type]
|
|
68
|
+
|
|
69
|
+
if js_type == "string":
|
|
70
|
+
format = cast(str | None, json_schema.get("format"))
|
|
71
|
+
|
|
72
|
+
if format in _FORMAT_MAP:
|
|
73
|
+
return _FORMAT_MAP[format]
|
|
74
|
+
|
|
75
|
+
return StringType()
|
|
76
|
+
|
|
77
|
+
return AnyType()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, Generic, TypeVar
|
|
3
|
+
from urllib.parse import unquote
|
|
4
|
+
|
|
5
|
+
PointerT = TypeVar("PointerT")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Unresolvable(Exception, Generic[PointerT]):
|
|
9
|
+
"""A pointer was unresolvable."""
|
|
10
|
+
|
|
11
|
+
# TODO `details` argument, providing more info?
|
|
12
|
+
def __init__(self, pointer: PointerT, /) -> None:
|
|
13
|
+
self.pointer = pointer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class JSONSchemaResolver(ABC, Generic[PointerT]):
|
|
17
|
+
"""Resolves the (sub) JSON Schema at the given pointer.
|
|
18
|
+
|
|
19
|
+
The implementation of the "pointer" is managed by subclasses.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, json_schema: dict[str, Any]) -> None:
|
|
23
|
+
self.json_schema = json_schema
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def resolve(self, pointer: PointerT) -> dict[str, Any]:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class JSONSchemaPointerResolver(JSONSchemaResolver[str]):
|
|
31
|
+
"""Resolves the (sub) JSON Schema at the given JSON Pointer, according to RFC 6901."""
|
|
32
|
+
|
|
33
|
+
def resolve(self, pointer: str) -> dict[str, Any]:
|
|
34
|
+
# Implementation inspired from the `referencing` library
|
|
35
|
+
schema = self.json_schema
|
|
36
|
+
for segment in unquote(pointer[1:]).split("/"):
|
|
37
|
+
schema_type = schema.get("type")
|
|
38
|
+
parsed_segment = segment.replace("~1", "/").replace("~0", "~")
|
|
39
|
+
if schema_type == "object":
|
|
40
|
+
try:
|
|
41
|
+
schema = schema["properties"][parsed_segment]
|
|
42
|
+
continue
|
|
43
|
+
except KeyError as e:
|
|
44
|
+
raise Unresolvable(pointer) from e
|
|
45
|
+
if schema_type == "array":
|
|
46
|
+
try:
|
|
47
|
+
int(parsed_segment)
|
|
48
|
+
except ValueError as e:
|
|
49
|
+
raise Unresolvable(pointer) from e
|
|
50
|
+
schema = schema["items"]
|
|
51
|
+
return schema
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Literal, TypeAlias, TypeVar, overload
|
|
6
|
+
|
|
7
|
+
from jsonlogic._compat import Self
|
|
8
|
+
|
|
9
|
+
JSONSchemaPrimitiveType: TypeAlias = (
|
|
10
|
+
"AnyType | BooleanType | NumberType | StringType | DatetimeType | DateType | NullType"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
JSONSchemaPrimitiveTypeT = TypeVar(
|
|
14
|
+
"JSONSchemaPrimitiveTypeT",
|
|
15
|
+
"AnyType",
|
|
16
|
+
"BooleanType",
|
|
17
|
+
"NumberType",
|
|
18
|
+
"IntegerType",
|
|
19
|
+
"StringType",
|
|
20
|
+
"DatetimeType",
|
|
21
|
+
"DateType",
|
|
22
|
+
"NullType",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class JSONSchemaType(ABC):
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def comparable_with(self, other: JSONSchemaType) -> bool:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
@overload
|
|
32
|
+
def __or__(self, value: Self, /) -> Self: ... # type: ignore
|
|
33
|
+
|
|
34
|
+
@overload
|
|
35
|
+
def __or__(self, value: JSONSchemaType, /) -> UnionType: ...
|
|
36
|
+
|
|
37
|
+
def __or__(self, value, /): # type: ignore
|
|
38
|
+
return UnionType(self, value)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class UnionType(JSONSchemaType):
|
|
42
|
+
types: set[JSONSchemaPrimitiveType]
|
|
43
|
+
|
|
44
|
+
@overload
|
|
45
|
+
def __new__(cls, *types: JSONSchemaPrimitiveTypeT) -> JSONSchemaPrimitiveTypeT: ...
|
|
46
|
+
|
|
47
|
+
@overload
|
|
48
|
+
def __new__(cls, *types: JSONSchemaType) -> Self: ...
|
|
49
|
+
|
|
50
|
+
def __new__(cls, *types):
|
|
51
|
+
if all(isinstance(t, type(types[0])) for t in types):
|
|
52
|
+
return types[0]
|
|
53
|
+
self = super().__new__(cls)
|
|
54
|
+
self.types = set()
|
|
55
|
+
for typ in types:
|
|
56
|
+
if isinstance(typ, UnionType):
|
|
57
|
+
self.types.update(typ.types)
|
|
58
|
+
else:
|
|
59
|
+
self.types.add(typ)
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
def __repr__(self) -> str:
|
|
63
|
+
return f"{self.__class__.__qualname__}({', '.join(str(t) for t in self.types)})"
|
|
64
|
+
|
|
65
|
+
def comparable_with(self, other: JSONSchemaType) -> bool:
|
|
66
|
+
for typ in self.types:
|
|
67
|
+
if isinstance(other, UnionType):
|
|
68
|
+
comparable = all(other_typ.comparable_with(typ) for other_typ in other.types)
|
|
69
|
+
else:
|
|
70
|
+
comparable = other.comparable_with(typ)
|
|
71
|
+
if not comparable:
|
|
72
|
+
return False
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(frozen=True)
|
|
77
|
+
class AnyType(JSONSchemaType):
|
|
78
|
+
def comparable_with(self, other: JSONSchemaType) -> Literal[False]:
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True)
|
|
83
|
+
class BooleanType(JSONSchemaType):
|
|
84
|
+
def comparable_with(self, other: JSONSchemaType) -> Literal[False]:
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass(frozen=True)
|
|
89
|
+
class NumberType(JSONSchemaType):
|
|
90
|
+
def comparable_with(self, other: JSONSchemaType) -> bool:
|
|
91
|
+
if isinstance(other, UnionType):
|
|
92
|
+
return other.comparable_with(self)
|
|
93
|
+
return isinstance(other, (NumberType, IntegerType))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass(frozen=True)
|
|
97
|
+
class IntegerType(JSONSchemaType):
|
|
98
|
+
def comparable_with(self, other: JSONSchemaType) -> bool:
|
|
99
|
+
if isinstance(other, UnionType):
|
|
100
|
+
return other.comparable_with(self)
|
|
101
|
+
return isinstance(other, (NumberType, IntegerType))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass(frozen=True)
|
|
105
|
+
class StringType(JSONSchemaType):
|
|
106
|
+
def comparable_with(self, other: JSONSchemaType) -> Literal[False]:
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass(frozen=True)
|
|
111
|
+
class DatetimeType(JSONSchemaType):
|
|
112
|
+
def comparable_with(self, other: JSONSchemaType) -> bool:
|
|
113
|
+
if isinstance(other, UnionType):
|
|
114
|
+
return other.comparable_with(self)
|
|
115
|
+
# Probably doesn't make sense with date?
|
|
116
|
+
return isinstance(other, DatetimeType)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass(frozen=True)
|
|
120
|
+
class DateType(JSONSchemaType):
|
|
121
|
+
def comparable_with(self, other: JSONSchemaType) -> bool:
|
|
122
|
+
if isinstance(other, UnionType):
|
|
123
|
+
return other.comparable_with(self)
|
|
124
|
+
return isinstance(other, DateType)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass(frozen=True)
|
|
128
|
+
class NullType(JSONSchemaType):
|
|
129
|
+
def comparable_with(self, other: JSONSchemaType) -> Literal[False]:
|
|
130
|
+
return False
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from jsonlogic.registry import OperatorRegistry
|
|
2
|
+
|
|
3
|
+
from .operators import (
|
|
4
|
+
EqualOperator,
|
|
5
|
+
GreaterThan,
|
|
6
|
+
GreaterThanOrEqual,
|
|
7
|
+
IfOperator,
|
|
8
|
+
LessThan,
|
|
9
|
+
LessThanOrEqual,
|
|
10
|
+
NotEqualOperator,
|
|
11
|
+
Var,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
operator_registry = OperatorRegistry()
|
|
15
|
+
"""The operator registry."""
|
|
16
|
+
|
|
17
|
+
operator_registry.register("var", Var)
|
|
18
|
+
operator_registry.register("==", EqualOperator)
|
|
19
|
+
operator_registry.register("!=", NotEqualOperator)
|
|
20
|
+
operator_registry.register("if", IfOperator)
|
|
21
|
+
operator_registry.register(">", GreaterThan)
|
|
22
|
+
operator_registry.register(">=", GreaterThanOrEqual)
|
|
23
|
+
operator_registry.register("<", LessThan)
|
|
24
|
+
operator_registry.register("<=", LessThanOrEqual)
|
|
File without changes
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import operator
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Callable, ClassVar
|
|
7
|
+
|
|
8
|
+
from jsonlogic._compat import Self
|
|
9
|
+
from jsonlogic.core import Operator
|
|
10
|
+
from jsonlogic.json_schema import from_json_schema, from_value
|
|
11
|
+
from jsonlogic.json_schema.resolvers import JSONSchemaPointerResolver, JSONSchemaResolver, Unresolvable
|
|
12
|
+
from jsonlogic.json_schema.types import AnyType, BooleanType, JSONSchemaType
|
|
13
|
+
from jsonlogic.typing import OperatorArgument
|
|
14
|
+
from jsonlogic.utils import UNSET, Unset
|
|
15
|
+
|
|
16
|
+
from .typechecking import TypecheckError, get_type
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from _typeshed import SupportsAllComparisons
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Var(Operator):
|
|
24
|
+
json_schema_resolver: ClassVar[type[JSONSchemaResolver]] = JSONSchemaPointerResolver
|
|
25
|
+
|
|
26
|
+
variable_path: str | Operator
|
|
27
|
+
|
|
28
|
+
default_value: OperatorArgument | Unset = UNSET
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_expression(cls, operator: str, arguments: list[OperatorArgument]) -> Self:
|
|
32
|
+
assert len(arguments) in (1, 2)
|
|
33
|
+
assert isinstance(arguments[0], (str, Operator))
|
|
34
|
+
if len(arguments) == 1:
|
|
35
|
+
return cls(operator=operator, variable_path=arguments[0])
|
|
36
|
+
return cls(operator=operator, variable_path=arguments[0], default_value=arguments[1])
|
|
37
|
+
|
|
38
|
+
def typecheck(self, data_schema: dict[str, Any]) -> JSONSchemaType:
|
|
39
|
+
if isinstance(self.variable_path, Operator):
|
|
40
|
+
self.variable_path.typecheck(data_schema)
|
|
41
|
+
return AnyType()
|
|
42
|
+
|
|
43
|
+
resolver = self.json_schema_resolver(data_schema)
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
js_type = resolver.resolve(self.variable_path)
|
|
47
|
+
return from_json_schema(js_type)
|
|
48
|
+
except Unresolvable as e:
|
|
49
|
+
if isinstance(self.default_value, Unset):
|
|
50
|
+
raise TypecheckError("Unresolvable reference", self) from e
|
|
51
|
+
if isinstance(self.default_value, Operator):
|
|
52
|
+
self.default_value.typecheck(data_schema)
|
|
53
|
+
return AnyType()
|
|
54
|
+
return from_value(self.default_value)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class EqualityOperator(Operator):
|
|
59
|
+
equality_func: ClassVar[Callable[[object, object], bool]]
|
|
60
|
+
|
|
61
|
+
left: OperatorArgument
|
|
62
|
+
right: OperatorArgument
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def from_expression(cls, operator: str, arguments: list[OperatorArgument]) -> Self:
|
|
66
|
+
assert len(arguments) == 2
|
|
67
|
+
return cls(operator=operator, left=arguments[0], right=arguments[1])
|
|
68
|
+
|
|
69
|
+
def typecheck(self, data_schema: dict[str, Any]) -> JSONSchemaType:
|
|
70
|
+
if isinstance(self.left, Operator):
|
|
71
|
+
self.left.typecheck(data_schema)
|
|
72
|
+
if isinstance(self.right, Operator):
|
|
73
|
+
self.right.typecheck(data_schema)
|
|
74
|
+
return BooleanType()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class EqualOperator(EqualityOperator):
|
|
79
|
+
equality_func = operator.eq
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class NotEqualOperator(EqualityOperator):
|
|
84
|
+
equality_func = operator.ne
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class IfOperator(Operator):
|
|
89
|
+
if_elses: list[tuple[OperatorArgument, OperatorArgument]]
|
|
90
|
+
leading_else: OperatorArgument
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def from_expression(cls, operator: str, arguments: list[OperatorArgument]) -> Self:
|
|
94
|
+
assert len(arguments) % 2 == 1
|
|
95
|
+
return cls(operator=operator, if_elses=list(zip(arguments[::2], arguments[1::2])), leading_else=arguments[-1])
|
|
96
|
+
|
|
97
|
+
def typecheck(self, data_schema: dict[str, Any]) -> JSONSchemaType:
|
|
98
|
+
for cond, _ in self.if_elses:
|
|
99
|
+
cond_type = get_type(cond, data_schema)
|
|
100
|
+
if not isinstance(cond_type, BooleanType):
|
|
101
|
+
raise TypecheckError("should be boolean", self)
|
|
102
|
+
|
|
103
|
+
return functools.reduce(operator.or_, (get_type(rv, data_schema) for _, rv in self.if_elses)) | get_type(
|
|
104
|
+
self.leading_else, data_schema
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass
|
|
109
|
+
class ComparableOperator(Operator):
|
|
110
|
+
operator_func: ClassVar[Callable[[SupportsAllComparisons, SupportsAllComparisons], bool]]
|
|
111
|
+
|
|
112
|
+
left: OperatorArgument
|
|
113
|
+
right: OperatorArgument
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def from_expression(cls, operator: str, arguments: list[OperatorArgument]) -> Self:
|
|
117
|
+
assert len(arguments) == 2
|
|
118
|
+
# TODO validate valid primitives (e.g. array probably not valid)
|
|
119
|
+
# Maybe not necessary, `typecheck` will handle it
|
|
120
|
+
return cls(operator=operator, left=arguments[0], right=arguments[1])
|
|
121
|
+
|
|
122
|
+
def typecheck(self, data_schema: dict[str, Any]) -> JSONSchemaType:
|
|
123
|
+
left_type = get_type(self.left, data_schema)
|
|
124
|
+
right_type = get_type(self.right, data_schema)
|
|
125
|
+
|
|
126
|
+
if not left_type.comparable_with(right_type):
|
|
127
|
+
raise TypecheckError("Cannot compare types", self)
|
|
128
|
+
|
|
129
|
+
return BooleanType()
|
|
130
|
+
|
|
131
|
+
def apply(self, data) -> bool:
|
|
132
|
+
left = self.left
|
|
133
|
+
if isinstance(left, Operator):
|
|
134
|
+
left = left.apply(data)
|
|
135
|
+
right = self.right
|
|
136
|
+
if isinstance(right, Operator):
|
|
137
|
+
right = right.apply(data)
|
|
138
|
+
|
|
139
|
+
return self.operator_func(left, right)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@dataclass
|
|
143
|
+
class GreaterThan(ComparableOperator):
|
|
144
|
+
operator_func = operator.gt
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@dataclass
|
|
148
|
+
class GreaterThanOrEqual(ComparableOperator):
|
|
149
|
+
operator_func = operator.ge
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass
|
|
153
|
+
class LessThan(ComparableOperator):
|
|
154
|
+
operator_func = operator.lt
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass
|
|
158
|
+
class LessThanOrEqual(ComparableOperator):
|
|
159
|
+
operator_func = operator.le
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from jsonlogic.core import Operator
|
|
4
|
+
from jsonlogic.json_schema import from_value
|
|
5
|
+
from jsonlogic.json_schema.types import JSONSchemaType
|
|
6
|
+
from jsonlogic.typing import OperatorArgument
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_type(obj: OperatorArgument, data_schema: dict[str, Any]) -> JSONSchemaType:
|
|
10
|
+
return obj.typecheck(data_schema) if isinstance(obj, Operator) else from_value(obj)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TypecheckError(Exception):
|
|
14
|
+
def __init__(self, message: str, operator: Operator, /) -> None:
|
|
15
|
+
self.operator = operator
|
|
16
|
+
self.message = message
|
|
File without changes
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from typing import Callable, overload
|
|
2
|
+
|
|
3
|
+
from ._compat import Self, TypeAlias
|
|
4
|
+
from .core import Operator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AlreadyRegistered(Exception):
|
|
8
|
+
"""The provided ID is already registered."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, operator_id: str, /) -> None:
|
|
11
|
+
self.operator_id = operator_id
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class UnkownOperator(Exception):
|
|
15
|
+
"""The provided ID does not exist in the registry."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, operator_id: str, /) -> None:
|
|
18
|
+
self.operator_id = operator_id
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
OperatorType: TypeAlias = type[Operator]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class OperatorRegistry:
|
|
25
|
+
def __init__(self) -> None:
|
|
26
|
+
self._registry: dict[str, OperatorType] = {}
|
|
27
|
+
|
|
28
|
+
@overload
|
|
29
|
+
def register(self, operator_id: str, *, force: bool = ...) -> Callable[[OperatorType], OperatorType]: ...
|
|
30
|
+
|
|
31
|
+
@overload
|
|
32
|
+
def register(self, operator_id: str, operator_type: OperatorType, *, force: bool = ...) -> OperatorType: ...
|
|
33
|
+
|
|
34
|
+
def register(
|
|
35
|
+
self, operator_id: str, operator_type: OperatorType | None = None, *, force: bool = False
|
|
36
|
+
) -> Callable[[OperatorType], OperatorType] | OperatorType:
|
|
37
|
+
"""Register an operator type under the provided ID.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
operator_id: The ID to be used to register the operator.
|
|
41
|
+
operator_type: Type class of the operator. If not provided,
|
|
42
|
+
will return a callable to be applied on an operator class.
|
|
43
|
+
force: Whether to override any existing operator under the provided ID.
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
AlreadyRegistered: If ``force`` wasn't set and the ID already exist.
|
|
47
|
+
|
|
48
|
+
Note:
|
|
49
|
+
The method is usable as a decorator:
|
|
50
|
+
|
|
51
|
+
.. code-block:: python
|
|
52
|
+
:emphasize-lines: 5,6
|
|
53
|
+
|
|
54
|
+
reg = OperatorRegistry()
|
|
55
|
+
reg.register("==", EqualOperator)
|
|
56
|
+
|
|
57
|
+
# Or:
|
|
58
|
+
@reg.register("==")
|
|
59
|
+
class EqualOperator(Operator): ...
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
if operator_type is None:
|
|
63
|
+
|
|
64
|
+
def dec(operator_type: OperatorType, /) -> OperatorType:
|
|
65
|
+
return self.register(operator_id, operator_type)
|
|
66
|
+
|
|
67
|
+
return dec
|
|
68
|
+
|
|
69
|
+
if operator_id in self._registry and not force:
|
|
70
|
+
raise AlreadyRegistered(operator_id)
|
|
71
|
+
|
|
72
|
+
self._registry[operator_id] = operator_type
|
|
73
|
+
return operator_type
|
|
74
|
+
|
|
75
|
+
def get(self, operator_id: str, /) -> OperatorType:
|
|
76
|
+
"""Get the operator class corresponding to the ID.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
operator_id: The registered ID of the operator.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
UnkownOperator: If the provided ID does not exist.
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
return self._registry[operator_id]
|
|
86
|
+
except KeyError:
|
|
87
|
+
raise UnkownOperator(operator_id) # noqa: B904
|
|
88
|
+
|
|
89
|
+
def copy(self) -> Self:
|
|
90
|
+
"""Create a new instance of the registry."""
|
|
91
|
+
|
|
92
|
+
new = self.__class__()
|
|
93
|
+
new._registry = self._registry.copy()
|
|
94
|
+
return new
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from ._compat import TypeAlias
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
# This is a hack to make pyright think `TypeAlias` comes from `typing`
|
|
7
|
+
from .typing import TypeAlias
|
|
8
|
+
|
|
9
|
+
JSONPrimitive: TypeAlias = str | int | float | bool | None
|
|
10
|
+
JSONObject: TypeAlias = "dict[str, JSON]"
|
|
11
|
+
JSONArray: TypeAlias = "list[JSON]"
|
|
12
|
+
JSON: TypeAlias = "JSONPrimitive | JSONArray | JSONObject"
|
|
13
|
+
|
|
14
|
+
JSONLogicPrimitive: TypeAlias = JSONPrimitive | list[JSONPrimitive]
|
|
15
|
+
|
|
16
|
+
OperatorArgument: TypeAlias = "JSONLogicPrimitive | Operator"
|
|
17
|
+
"""A valid operator argument, either a JSON Logic primitive or an operator."""
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Unset:
|
|
5
|
+
"""A class representing an unset value."""
|
|
6
|
+
|
|
7
|
+
def __bool__(self) -> Literal[False]:
|
|
8
|
+
return False
|
|
9
|
+
|
|
10
|
+
def __repr__(self) -> str:
|
|
11
|
+
return f"{self.__class__.__qualname__}()"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
UNSET: Unset = Unset()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: python-jsonlogic
|
|
3
|
+
Version: 0.0.1
|
|
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.6.0; python_version < "3.12"
|
|
45
|
+
|
|
46
|
+
=========
|
|
47
|
+
jsonlogic
|
|
48
|
+
=========
|
|
49
|
+
|
|
50
|
+
|Pythons| |PyPI|
|
|
51
|
+
|
|
52
|
+
.. |Pythons| image:: https://img.shields.io/pypi/pyversions/jsonlogic.svg
|
|
53
|
+
:alt: Supported Python versions
|
|
54
|
+
:target: https://pypi.org/project/jsonlogic/
|
|
55
|
+
|
|
56
|
+
.. |PyPI| image:: https://img.shields.io/pypi/v/jsonlogic.svg
|
|
57
|
+
:alt: PyPI - Version
|
|
58
|
+
:target: https://pypi.org/project/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://pypi.org/project/jsonlogic/
|
|
63
|
+
|
|
64
|
+
``jsonlogic`` is an extensible and sane implementation of `JsonLogic`_.
|
|
65
|
+
|
|
66
|
+
.. _`JsonLogic`: https://jsonlogic.com/
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.rst
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/jsonlogic/__init__.py
|
|
5
|
+
src/jsonlogic/_compat.py
|
|
6
|
+
src/jsonlogic/core.py
|
|
7
|
+
src/jsonlogic/py.typed
|
|
8
|
+
src/jsonlogic/registry.py
|
|
9
|
+
src/jsonlogic/typing.py
|
|
10
|
+
src/jsonlogic/utils.py
|
|
11
|
+
src/jsonlogic/json_schema/__init__.py
|
|
12
|
+
src/jsonlogic/json_schema/resolvers.py
|
|
13
|
+
src/jsonlogic/json_schema/types.py
|
|
14
|
+
src/jsonlogic/operators/__init__.py
|
|
15
|
+
src/jsonlogic/operators/base.py
|
|
16
|
+
src/jsonlogic/operators/operators.py
|
|
17
|
+
src/jsonlogic/operators/typechecking.py
|
|
18
|
+
src/python_jsonlogic.egg-info/PKG-INFO
|
|
19
|
+
src/python_jsonlogic.egg-info/SOURCES.txt
|
|
20
|
+
src/python_jsonlogic.egg-info/dependency_links.txt
|
|
21
|
+
src/python_jsonlogic.egg-info/requires.txt
|
|
22
|
+
src/python_jsonlogic.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
jsonlogic
|