jqson 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.
- jqson-0.1.0/LICENSE +21 -0
- jqson-0.1.0/MANIFEST.in +2 -0
- jqson-0.1.0/PKG-INFO +143 -0
- jqson-0.1.0/README.md +120 -0
- jqson-0.1.0/jqson/__init__.py +15 -0
- jqson-0.1.0/jqson/conditions.py +176 -0
- jqson-0.1.0/jqson/errors.py +22 -0
- jqson-0.1.0/jqson/getters.py +114 -0
- jqson-0.1.0/jqson/path.py +94 -0
- jqson-0.1.0/jqson/query.py +66 -0
- jqson-0.1.0/jqson/register.py +19 -0
- jqson-0.1.0/jqson/select.py +67 -0
- jqson-0.1.0/jqson/take.py +79 -0
- jqson-0.1.0/jqson/undefined.py +64 -0
- jqson-0.1.0/jqson/variable.py +60 -0
- jqson-0.1.0/jqson/where.py +211 -0
- jqson-0.1.0/jqson.egg-info/PKG-INFO +143 -0
- jqson-0.1.0/jqson.egg-info/SOURCES.txt +30 -0
- jqson-0.1.0/jqson.egg-info/dependency_links.txt +1 -0
- jqson-0.1.0/jqson.egg-info/not-zip-safe +1 -0
- jqson-0.1.0/jqson.egg-info/top_level.txt +2 -0
- jqson-0.1.0/setup.cfg +4 -0
- jqson-0.1.0/setup.py +32 -0
- jqson-0.1.0/unittests/__init__.py +12 -0
- jqson-0.1.0/unittests/data.py +32 -0
- jqson-0.1.0/unittests/test_conditions.py +216 -0
- jqson-0.1.0/unittests/test_getters.py +85 -0
- jqson-0.1.0/unittests/test_path.py +157 -0
- jqson-0.1.0/unittests/test_select.py +116 -0
- jqson-0.1.0/unittests/test_take.py +88 -0
- jqson-0.1.0/unittests/test_variable.py +114 -0
- jqson-0.1.0/unittests/test_where.py +328 -0
jqson-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Martin Strohalm
|
|
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.
|
jqson-0.1.0/MANIFEST.in
ADDED
jqson-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jqson
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Define and run test queries from JSON.
|
|
5
|
+
Home-page: https://github.com/xxao/jqson
|
|
6
|
+
Author: Martin Strohalm
|
|
7
|
+
Author-email:
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Topic :: Utilities
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: classifier
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: description-content-type
|
|
19
|
+
Dynamic: home-page
|
|
20
|
+
Dynamic: license
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
Dynamic: summary
|
|
23
|
+
|
|
24
|
+
# JqSON
|
|
25
|
+
|
|
26
|
+
The *JqSON* library was developed to provide a simple way to define queries in JSON format, which can later be run
|
|
27
|
+
in Python.
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from dataclasses import dataclass
|
|
31
|
+
from jqson import Query
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class Person(object):
|
|
35
|
+
name: str = ""
|
|
36
|
+
age: int = 0
|
|
37
|
+
children: list[Person] = ()
|
|
38
|
+
|
|
39
|
+
P1 = Person(name="P1", age=10)
|
|
40
|
+
P2 = Person(name="P2", age=7)
|
|
41
|
+
P3 = Person(name="P3", age=50, children=[P1, P2])
|
|
42
|
+
P4 = Person(name="P4", age=77, children=[P3])
|
|
43
|
+
|
|
44
|
+
text = """
|
|
45
|
+
[
|
|
46
|
+
{"query": "where", "key": [
|
|
47
|
+
{"query": "not_empty", "left": "children"}
|
|
48
|
+
]},
|
|
49
|
+
{"query": "all", "key": [
|
|
50
|
+
{"query": "attr", "name":"age"},
|
|
51
|
+
{"query": "and", "conditions": [
|
|
52
|
+
{"query": "not_null"},
|
|
53
|
+
{"query": ">", "value": 20},
|
|
54
|
+
{"query": "<", "value": 100}
|
|
55
|
+
]}
|
|
56
|
+
]}
|
|
57
|
+
]
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
data = [P1, P2, P3, P4]
|
|
61
|
+
query = Query.from_text(text)
|
|
62
|
+
print(query(data))
|
|
63
|
+
|
|
64
|
+
# equivalent of
|
|
65
|
+
print(all((p.age is not None and 20 < p.age < 100) for p in data if p.children))
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
|
|
70
|
+
The *JqSON* library is fully implemented in Python. No additional compiler is necessary. After downloading the source
|
|
71
|
+
code just run the following command from the *jqson* folder:
|
|
72
|
+
|
|
73
|
+
```$ python setup.py install```
|
|
74
|
+
|
|
75
|
+
or simply by using pip
|
|
76
|
+
|
|
77
|
+
```$ pip install jqson```
|
|
78
|
+
|
|
79
|
+
## Requirements
|
|
80
|
+
|
|
81
|
+
- [Python 3.14+](https://www.python.org)
|
|
82
|
+
|
|
83
|
+
## Selector Queries
|
|
84
|
+
|
|
85
|
+
- **Path**: *{"query": "path", "path": "PATH"}*
|
|
86
|
+
- **Attr**: *{"query": "attr", "name": "NAME"}*
|
|
87
|
+
- **Item**: *{"query": "item", "key": "KEY"}*
|
|
88
|
+
- **Bool**: *{"query": "bool"}*
|
|
89
|
+
- **Len**: *{"query": "len"}*
|
|
90
|
+
- **Variable**: *{"query": "var", "name": "NAME", "value": "VALUE", "path": "PATH"}*
|
|
91
|
+
|
|
92
|
+
## Collections Queries
|
|
93
|
+
|
|
94
|
+
- **Select**: *{"query": "select", "path": "PATH"}*
|
|
95
|
+
- **Many**: *{"query": "many", "path": "PATH"}*
|
|
96
|
+
- **Sum**: *{"query": "sum", "path": "PATH"}*
|
|
97
|
+
- **Mean**: *{"query": "mean", "path": "PATH"}*
|
|
98
|
+
|
|
99
|
+
- **Where**: *{"query": "where", "key": "KEY"}*
|
|
100
|
+
- **First**: *{"query": "first", "key": "KEY"}*
|
|
101
|
+
- **Last**: *{"query": "last", "key": "KEY"}*
|
|
102
|
+
- **Single**: *{"query": "single", "key": "KEY"}*
|
|
103
|
+
- **Distinct**: *{"query": "distinct", "key": "KEY"}*
|
|
104
|
+
- **Any**: *{"query": "any", "key": "KEY"}*
|
|
105
|
+
- **All**: *{"query": "all", "key": "KEY"}*
|
|
106
|
+
- **Noone**: *{"query": "none", "key": "KEY"}*
|
|
107
|
+
- **Count**: *{"query": "count", "key": "KEY"}*
|
|
108
|
+
- **Min**: *{"query": "min", "key": "KEY"}*
|
|
109
|
+
- **Max**: *{"query": "max", "key": "KEY"}*
|
|
110
|
+
|
|
111
|
+
- **Sort**: *{"query": "sort", "key": "KEY", "reverse": "REVERSE"}*
|
|
112
|
+
|
|
113
|
+
## Slicing Queries
|
|
114
|
+
|
|
115
|
+
- **Take**: *{"query": "take", "count": "COUNT"}*
|
|
116
|
+
- **Skip**: *{"query": "skip", "count": "COUNT"}*
|
|
117
|
+
- **Slice**: *{"query": "slice", "start": "START", "end": "END", "step": "STEP"}*
|
|
118
|
+
|
|
119
|
+
## Conditions Queries
|
|
120
|
+
|
|
121
|
+
- **AND**: *{"query": "and", "conditions": "CONDITIONS"}*
|
|
122
|
+
- **OR**: *{"query": "or", "conditions": "CONDITIONS"}*
|
|
123
|
+
|
|
124
|
+
- **==**: *{"query": "==", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
125
|
+
- **!=**: *{"query": "!=", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
126
|
+
- **>**: *{"query": ">", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
127
|
+
- **<**: *{"query": "<", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
128
|
+
- **>=**: *{"query": ">=", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
129
|
+
- **<=**: *{"query": "<=", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
130
|
+
|
|
131
|
+
- **in**: *{"query": "in", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
132
|
+
- **contains**: *{"query": "contains", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
133
|
+
- **has**: *{"query": "has", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
134
|
+
|
|
135
|
+
- **true**: *{"query": "true"}*
|
|
136
|
+
- **false**: *{"query": "false"}*
|
|
137
|
+
- **null**: *{"query": "null"}*
|
|
138
|
+
- **empty**: *{"query": "empty"}*
|
|
139
|
+
|
|
140
|
+
## Disclaimer
|
|
141
|
+
|
|
142
|
+
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
|
143
|
+
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
jqson-0.1.0/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# JqSON
|
|
2
|
+
|
|
3
|
+
The *JqSON* library was developed to provide a simple way to define queries in JSON format, which can later be run
|
|
4
|
+
in Python.
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from jqson import Query
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Person(object):
|
|
12
|
+
name: str = ""
|
|
13
|
+
age: int = 0
|
|
14
|
+
children: list[Person] = ()
|
|
15
|
+
|
|
16
|
+
P1 = Person(name="P1", age=10)
|
|
17
|
+
P2 = Person(name="P2", age=7)
|
|
18
|
+
P3 = Person(name="P3", age=50, children=[P1, P2])
|
|
19
|
+
P4 = Person(name="P4", age=77, children=[P3])
|
|
20
|
+
|
|
21
|
+
text = """
|
|
22
|
+
[
|
|
23
|
+
{"query": "where", "key": [
|
|
24
|
+
{"query": "not_empty", "left": "children"}
|
|
25
|
+
]},
|
|
26
|
+
{"query": "all", "key": [
|
|
27
|
+
{"query": "attr", "name":"age"},
|
|
28
|
+
{"query": "and", "conditions": [
|
|
29
|
+
{"query": "not_null"},
|
|
30
|
+
{"query": ">", "value": 20},
|
|
31
|
+
{"query": "<", "value": 100}
|
|
32
|
+
]}
|
|
33
|
+
]}
|
|
34
|
+
]
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
data = [P1, P2, P3, P4]
|
|
38
|
+
query = Query.from_text(text)
|
|
39
|
+
print(query(data))
|
|
40
|
+
|
|
41
|
+
# equivalent of
|
|
42
|
+
print(all((p.age is not None and 20 < p.age < 100) for p in data if p.children))
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
The *JqSON* library is fully implemented in Python. No additional compiler is necessary. After downloading the source
|
|
48
|
+
code just run the following command from the *jqson* folder:
|
|
49
|
+
|
|
50
|
+
```$ python setup.py install```
|
|
51
|
+
|
|
52
|
+
or simply by using pip
|
|
53
|
+
|
|
54
|
+
```$ pip install jqson```
|
|
55
|
+
|
|
56
|
+
## Requirements
|
|
57
|
+
|
|
58
|
+
- [Python 3.14+](https://www.python.org)
|
|
59
|
+
|
|
60
|
+
## Selector Queries
|
|
61
|
+
|
|
62
|
+
- **Path**: *{"query": "path", "path": "PATH"}*
|
|
63
|
+
- **Attr**: *{"query": "attr", "name": "NAME"}*
|
|
64
|
+
- **Item**: *{"query": "item", "key": "KEY"}*
|
|
65
|
+
- **Bool**: *{"query": "bool"}*
|
|
66
|
+
- **Len**: *{"query": "len"}*
|
|
67
|
+
- **Variable**: *{"query": "var", "name": "NAME", "value": "VALUE", "path": "PATH"}*
|
|
68
|
+
|
|
69
|
+
## Collections Queries
|
|
70
|
+
|
|
71
|
+
- **Select**: *{"query": "select", "path": "PATH"}*
|
|
72
|
+
- **Many**: *{"query": "many", "path": "PATH"}*
|
|
73
|
+
- **Sum**: *{"query": "sum", "path": "PATH"}*
|
|
74
|
+
- **Mean**: *{"query": "mean", "path": "PATH"}*
|
|
75
|
+
|
|
76
|
+
- **Where**: *{"query": "where", "key": "KEY"}*
|
|
77
|
+
- **First**: *{"query": "first", "key": "KEY"}*
|
|
78
|
+
- **Last**: *{"query": "last", "key": "KEY"}*
|
|
79
|
+
- **Single**: *{"query": "single", "key": "KEY"}*
|
|
80
|
+
- **Distinct**: *{"query": "distinct", "key": "KEY"}*
|
|
81
|
+
- **Any**: *{"query": "any", "key": "KEY"}*
|
|
82
|
+
- **All**: *{"query": "all", "key": "KEY"}*
|
|
83
|
+
- **Noone**: *{"query": "none", "key": "KEY"}*
|
|
84
|
+
- **Count**: *{"query": "count", "key": "KEY"}*
|
|
85
|
+
- **Min**: *{"query": "min", "key": "KEY"}*
|
|
86
|
+
- **Max**: *{"query": "max", "key": "KEY"}*
|
|
87
|
+
|
|
88
|
+
- **Sort**: *{"query": "sort", "key": "KEY", "reverse": "REVERSE"}*
|
|
89
|
+
|
|
90
|
+
## Slicing Queries
|
|
91
|
+
|
|
92
|
+
- **Take**: *{"query": "take", "count": "COUNT"}*
|
|
93
|
+
- **Skip**: *{"query": "skip", "count": "COUNT"}*
|
|
94
|
+
- **Slice**: *{"query": "slice", "start": "START", "end": "END", "step": "STEP"}*
|
|
95
|
+
|
|
96
|
+
## Conditions Queries
|
|
97
|
+
|
|
98
|
+
- **AND**: *{"query": "and", "conditions": "CONDITIONS"}*
|
|
99
|
+
- **OR**: *{"query": "or", "conditions": "CONDITIONS"}*
|
|
100
|
+
|
|
101
|
+
- **==**: *{"query": "==", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
102
|
+
- **!=**: *{"query": "!=", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
103
|
+
- **>**: *{"query": ">", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
104
|
+
- **<**: *{"query": "<", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
105
|
+
- **>=**: *{"query": ">=", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
106
|
+
- **<=**: *{"query": "<=", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
107
|
+
|
|
108
|
+
- **in**: *{"query": "in", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
109
|
+
- **contains**: *{"query": "contains", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
110
|
+
- **has**: *{"query": "has", "value": "VALUE", "left": "LEFT", "right": "RIGHT"}*
|
|
111
|
+
|
|
112
|
+
- **true**: *{"query": "true"}*
|
|
113
|
+
- **false**: *{"query": "false"}*
|
|
114
|
+
- **null**: *{"query": "null"}*
|
|
115
|
+
- **empty**: *{"query": "empty"}*
|
|
116
|
+
|
|
117
|
+
## Disclaimer
|
|
118
|
+
|
|
119
|
+
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
|
120
|
+
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Created byMartin.cz
|
|
2
|
+
# Copyright (c) Martin Strohalm. All rights reserved.
|
|
3
|
+
|
|
4
|
+
version = (0, 1, 0)
|
|
5
|
+
|
|
6
|
+
from .errors import QueryError, QuerySyntaxError, IterationError
|
|
7
|
+
from .undefined import UNDEF
|
|
8
|
+
from .query import Query
|
|
9
|
+
from .variable import Variable
|
|
10
|
+
from .path import Path
|
|
11
|
+
from .getters import Attr, Item, Bool, Len
|
|
12
|
+
from .conditions import Condition, AND, OR
|
|
13
|
+
from .take import Take, Skip, Slice
|
|
14
|
+
from .select import Select, Many, Sum, Mean
|
|
15
|
+
from .where import Where, First, Last, Single, Distinct, Any, All, Noone, Count, Min, Max, Sort
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Created byMartin.cz
|
|
2
|
+
# Copyright (c) Martin Strohalm. All rights reserved.
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any
|
|
6
|
+
from .errors import QuerySyntaxError
|
|
7
|
+
from .undefined import UNDEF
|
|
8
|
+
from .register import register
|
|
9
|
+
from .query import Query
|
|
10
|
+
from .path import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
@register("=", "==", "!=", ">", "<", ">=", "<=")
|
|
15
|
+
@register("in", "contains", "has")
|
|
16
|
+
@register("is_true", "true", True, "is_false", "false", False)
|
|
17
|
+
@register("is_null", "null", None, "not_null")
|
|
18
|
+
@register("is_empty", "empty", "not_empty")
|
|
19
|
+
class Condition(Query):
|
|
20
|
+
"""Evaluates condition."""
|
|
21
|
+
|
|
22
|
+
operand: str
|
|
23
|
+
left: Path | None = None
|
|
24
|
+
right: Path | None = None
|
|
25
|
+
value: Any = UNDEF
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def apply(self, data, *args, **kwargs):
|
|
29
|
+
"""Applies query to data."""
|
|
30
|
+
|
|
31
|
+
# get left value
|
|
32
|
+
left = data
|
|
33
|
+
if isinstance(self.left, Query):
|
|
34
|
+
left = self.left(data, *args, **kwargs)
|
|
35
|
+
|
|
36
|
+
# get right value
|
|
37
|
+
right = self.value
|
|
38
|
+
if isinstance(self.right, Query):
|
|
39
|
+
right = self.right(data, *args, **kwargs)
|
|
40
|
+
|
|
41
|
+
# equals
|
|
42
|
+
if self.operand in ("=", "=="):
|
|
43
|
+
return left == right
|
|
44
|
+
|
|
45
|
+
# not equals
|
|
46
|
+
if self.operand == "!=":
|
|
47
|
+
return left != right
|
|
48
|
+
|
|
49
|
+
# greater than
|
|
50
|
+
if self.operand == ">":
|
|
51
|
+
return left > right
|
|
52
|
+
|
|
53
|
+
# less than
|
|
54
|
+
if self.operand == "<":
|
|
55
|
+
return left < right
|
|
56
|
+
|
|
57
|
+
# greater than or equal
|
|
58
|
+
if self.operand == ">=":
|
|
59
|
+
return left >= right
|
|
60
|
+
|
|
61
|
+
# less than or equal
|
|
62
|
+
if self.operand == "<=":
|
|
63
|
+
return left <= right
|
|
64
|
+
|
|
65
|
+
# in
|
|
66
|
+
if self.operand == "in":
|
|
67
|
+
return left in right
|
|
68
|
+
|
|
69
|
+
# contains
|
|
70
|
+
if self.operand == "contains":
|
|
71
|
+
return right in left
|
|
72
|
+
|
|
73
|
+
# has
|
|
74
|
+
if self.operand == "has":
|
|
75
|
+
return hasattr(left, right)
|
|
76
|
+
|
|
77
|
+
# is true
|
|
78
|
+
if self.operand in ("is_true", "true", True):
|
|
79
|
+
return bool(left)
|
|
80
|
+
|
|
81
|
+
# is false
|
|
82
|
+
if self.operand in ("is_false", "false", False):
|
|
83
|
+
return not bool(left)
|
|
84
|
+
|
|
85
|
+
# is null
|
|
86
|
+
if self.operand in ("is_null", "null", None):
|
|
87
|
+
return left is None
|
|
88
|
+
|
|
89
|
+
# is not null
|
|
90
|
+
if self.operand == "not_null":
|
|
91
|
+
return left is not None
|
|
92
|
+
|
|
93
|
+
# is empty
|
|
94
|
+
if self.operand in ("is_empty", "empty"):
|
|
95
|
+
return left is None or left == "" or left == [] or left == () or left == {}
|
|
96
|
+
|
|
97
|
+
# is not empty
|
|
98
|
+
if self.operand == "not_empty":
|
|
99
|
+
return not (left is None or left == "" or left == [] or left == () or left == {})
|
|
100
|
+
|
|
101
|
+
# unknown condition type
|
|
102
|
+
raise QuerySyntaxError(f"Unrecognized condition operand '{self.operand}'.")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def from_json(cls, data):
|
|
107
|
+
"""Initialize instance from JSON dict."""
|
|
108
|
+
|
|
109
|
+
left = data.get("left", None)
|
|
110
|
+
if left is not None:
|
|
111
|
+
left = Path.from_json(left)
|
|
112
|
+
|
|
113
|
+
right = data.get("right", None)
|
|
114
|
+
if right is not None:
|
|
115
|
+
right = Path.from_json(right)
|
|
116
|
+
|
|
117
|
+
return cls(
|
|
118
|
+
operand = data.get("query"),
|
|
119
|
+
left = left,
|
|
120
|
+
right = right,
|
|
121
|
+
value = data.get("value", UNDEF)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass
|
|
126
|
+
@register("AND", "and")
|
|
127
|
+
class AND(Query):
|
|
128
|
+
"""Evaluates sequence of conditions with AND."""
|
|
129
|
+
|
|
130
|
+
conditions: list[Query] = ()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def apply(self, data, *args, **kwargs):
|
|
134
|
+
"""Applies query to data."""
|
|
135
|
+
|
|
136
|
+
passed = True
|
|
137
|
+
for query in self.conditions:
|
|
138
|
+
passed = passed and query(data, *args, **kwargs)
|
|
139
|
+
|
|
140
|
+
return passed
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def from_json(cls, data):
|
|
145
|
+
"""Initialize instance from JSON dict."""
|
|
146
|
+
|
|
147
|
+
return cls(
|
|
148
|
+
conditions = [Query.from_json(item) for item in data.get("conditions", [])]
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass
|
|
153
|
+
@register("OR", "or")
|
|
154
|
+
class OR(Query):
|
|
155
|
+
"""Evaluates sequence of conditions with OR."""
|
|
156
|
+
|
|
157
|
+
conditions: list[Query] = ()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def apply(self, data, *args, **kwargs):
|
|
161
|
+
"""Applies query to data."""
|
|
162
|
+
|
|
163
|
+
for query in self.conditions:
|
|
164
|
+
if query(data, *args, **kwargs):
|
|
165
|
+
return True
|
|
166
|
+
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def from_json(cls, data):
|
|
172
|
+
"""Initialize instance from JSON dict."""
|
|
173
|
+
|
|
174
|
+
return cls(
|
|
175
|
+
conditions = [Query.from_json(item) for item in data.get("conditions", [])]
|
|
176
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Created byMartin.cz
|
|
2
|
+
# Copyright (c) Martin Strohalm. All rights reserved.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class QueryError(Exception):
|
|
6
|
+
"""Represents a query exception base."""
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class QuerySyntaxError(QueryError):
|
|
11
|
+
"""Represents a missing definition exception."""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class UndefinedError(QueryError):
|
|
16
|
+
"""Represents a missing definition exception."""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class IterationError(QueryError):
|
|
21
|
+
"""Represents a selector exception."""
|
|
22
|
+
pass
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Created byMartin.cz
|
|
2
|
+
# Copyright (c) Martin Strohalm. All rights reserved.
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from .errors import UndefinedError
|
|
6
|
+
from .register import register
|
|
7
|
+
from .undefined import UNDEF
|
|
8
|
+
from .query import Query
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
@register("property", "prop", "attr")
|
|
13
|
+
class Attr(Query):
|
|
14
|
+
"""Gets property by name."""
|
|
15
|
+
|
|
16
|
+
name: str = UNDEF
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def apply(self, data, *args, **kwargs):
|
|
20
|
+
"""Applies query to data."""
|
|
21
|
+
|
|
22
|
+
# get variables
|
|
23
|
+
variables = kwargs.get("variables", {})
|
|
24
|
+
|
|
25
|
+
# no name defined
|
|
26
|
+
if self.name is UNDEF:
|
|
27
|
+
raise UndefinedError("Attribute 'name' is not defined.")
|
|
28
|
+
|
|
29
|
+
# get value
|
|
30
|
+
for part in self.name.split("."):
|
|
31
|
+
|
|
32
|
+
# get from variables
|
|
33
|
+
if self.name in variables:
|
|
34
|
+
data = variables[self.name]
|
|
35
|
+
|
|
36
|
+
# get from data
|
|
37
|
+
else:
|
|
38
|
+
data = getattr(data, part)
|
|
39
|
+
|
|
40
|
+
return data
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def from_json(cls, data):
|
|
45
|
+
"""Initialize instance from JSON dict."""
|
|
46
|
+
|
|
47
|
+
return cls(
|
|
48
|
+
name = data.get("name")
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
@register("item")
|
|
54
|
+
class Item(Query):
|
|
55
|
+
"""Gets item by key."""
|
|
56
|
+
|
|
57
|
+
key: str | int = UNDEF
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def apply(self, data, *args, **kwargs):
|
|
61
|
+
"""Applies query to data."""
|
|
62
|
+
|
|
63
|
+
# no key defined
|
|
64
|
+
if self.key is UNDEF:
|
|
65
|
+
raise UndefinedError("Attribute 'key' is not defined.")
|
|
66
|
+
|
|
67
|
+
return data[self.key]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def from_json(cls, data):
|
|
72
|
+
"""Initialize instance from JSON dict."""
|
|
73
|
+
|
|
74
|
+
return cls(
|
|
75
|
+
key = data.get("key")
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
@register("bool")
|
|
81
|
+
class Bool(Query):
|
|
82
|
+
"""Gets boolean value."""
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def apply(self, data, *args, **kwargs):
|
|
86
|
+
"""Applies query to data."""
|
|
87
|
+
|
|
88
|
+
return bool(data)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def from_json(cls, data):
|
|
93
|
+
"""Initialize instance from JSON dict."""
|
|
94
|
+
|
|
95
|
+
return cls()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
@register("len")
|
|
100
|
+
class Len(Query):
|
|
101
|
+
"""Gets sequence length."""
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def apply(self, data, *args, **kwargs):
|
|
105
|
+
"""Applies query to data."""
|
|
106
|
+
|
|
107
|
+
return len(data)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def from_json(cls, data):
|
|
112
|
+
"""Initialize instance from JSON dict."""
|
|
113
|
+
|
|
114
|
+
return cls()
|