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 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.
@@ -0,0 +1,2 @@
1
+ include README.md
2
+ include LICENSE
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()