jqon 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.
- jqon-0.1.0/LICENSE +21 -0
- jqon-0.1.0/MANIFEST.in +2 -0
- jqon-0.1.0/PKG-INFO +51 -0
- jqon-0.1.0/README.md +28 -0
- jqon-0.1.0/jqon/__init__.py +32 -0
- jqon-0.1.0/jqon/binary.py +111 -0
- jqon-0.1.0/jqon/enumerable.py +416 -0
- jqon-0.1.0/jqon/errors.py +22 -0
- jqon-0.1.0/jqon/funcs.py +238 -0
- jqon-0.1.0/jqon/getters.py +65 -0
- jqon-0.1.0/jqon/logical.py +79 -0
- jqon-0.1.0/jqon/path.py +108 -0
- jqon-0.1.0/jqon/query.py +66 -0
- jqon-0.1.0/jqon/register.py +19 -0
- jqon-0.1.0/jqon/unary.py +74 -0
- jqon-0.1.0/jqon/value.py +32 -0
- jqon-0.1.0/jqon/variable.py +68 -0
- jqon-0.1.0/jqon.egg-info/PKG-INFO +51 -0
- jqon-0.1.0/jqon.egg-info/SOURCES.txt +33 -0
- jqon-0.1.0/jqon.egg-info/dependency_links.txt +1 -0
- jqon-0.1.0/jqon.egg-info/not-zip-safe +1 -0
- jqon-0.1.0/jqon.egg-info/top_level.txt +2 -0
- jqon-0.1.0/setup.cfg +4 -0
- jqon-0.1.0/setup.py +33 -0
- jqon-0.1.0/unittests/__init__.py +12 -0
- jqon-0.1.0/unittests/data.py +32 -0
- jqon-0.1.0/unittests/test_binary.py +322 -0
- jqon-0.1.0/unittests/test_enumerable.py +343 -0
- jqon-0.1.0/unittests/test_funcs.py +206 -0
- jqon-0.1.0/unittests/test_getters.py +43 -0
- jqon-0.1.0/unittests/test_logical.py +72 -0
- jqon-0.1.0/unittests/test_path.py +157 -0
- jqon-0.1.0/unittests/test_unary.py +209 -0
- jqon-0.1.0/unittests/test_value.py +53 -0
- jqon-0.1.0/unittests/test_variable.py +114 -0
jqon-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.
|
jqon-0.1.0/MANIFEST.in
ADDED
jqon-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jqon
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: JSON expression language for querying Python objects.
|
|
5
|
+
Home-page: https://github.com/xxao/jqon
|
|
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
|
+
# JqON
|
|
25
|
+
|
|
26
|
+
The *jqon* package was developed to provide a simple way to define queries in JSON format, which can later be run
|
|
27
|
+
in Python. It can be utilized in testing automations, where expected output of a system can be submitted a
|
|
28
|
+
predefined set of tests.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
This package is fully implemented in Python. No additional compiler is necessary. After downloading the source
|
|
34
|
+
code just run the following command from the *jexon* folder:
|
|
35
|
+
|
|
36
|
+
`$ python setup.py install`
|
|
37
|
+
|
|
38
|
+
or simply by using pip
|
|
39
|
+
|
|
40
|
+
`$ pip install jqson`
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
## Requirements
|
|
44
|
+
|
|
45
|
+
- [Python 3.11+](https://www.python.org)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## Disclaimer
|
|
49
|
+
|
|
50
|
+
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
|
51
|
+
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
jqon-0.1.0/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# JqON
|
|
2
|
+
|
|
3
|
+
The *jqon* package was developed to provide a simple way to define queries in JSON format, which can later be run
|
|
4
|
+
in Python. It can be utilized in testing automations, where expected output of a system can be submitted a
|
|
5
|
+
predefined set of tests.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
This package is fully implemented in Python. No additional compiler is necessary. After downloading the source
|
|
11
|
+
code just run the following command from the *jexon* folder:
|
|
12
|
+
|
|
13
|
+
`$ python setup.py install`
|
|
14
|
+
|
|
15
|
+
or simply by using pip
|
|
16
|
+
|
|
17
|
+
`$ pip install jqson`
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- [Python 3.11+](https://www.python.org)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## Disclaimer
|
|
26
|
+
|
|
27
|
+
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
|
28
|
+
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Created byMartin.cz
|
|
2
|
+
# Copyright (c) Martin Strohalm. All rights reserved.
|
|
3
|
+
|
|
4
|
+
version = (0, 1, 0)
|
|
5
|
+
|
|
6
|
+
from .errors import *
|
|
7
|
+
from .register import register
|
|
8
|
+
from .query import Query
|
|
9
|
+
from .value import Value
|
|
10
|
+
from .variable import Variable
|
|
11
|
+
from .path import Path
|
|
12
|
+
from .getters import Attr, Item
|
|
13
|
+
from .unary import Unary
|
|
14
|
+
from .binary import Binary
|
|
15
|
+
from .logical import AND, OR, NOT
|
|
16
|
+
from .funcs import Bool, Len, Any, All, Min, Max, Sum, Avg
|
|
17
|
+
from .enumerable import Take, Skip, Slice
|
|
18
|
+
from .enumerable import Select, Many, Where, Distinct
|
|
19
|
+
from .enumerable import Single, First, Last, Count
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# init shortcuts
|
|
23
|
+
def from_text(text):
|
|
24
|
+
"""Initialize query from text."""
|
|
25
|
+
|
|
26
|
+
return Query.from_text(text)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def from_json(data):
|
|
30
|
+
"""Initialize query from JSON."""
|
|
31
|
+
|
|
32
|
+
return Query.from_json(data)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Created byMartin.cz
|
|
2
|
+
# Copyright (c) Martin Strohalm. All rights reserved.
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from .errors import *
|
|
6
|
+
from .register import register
|
|
7
|
+
from .query import Query
|
|
8
|
+
from .path import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
@register("=", "==", "!=", ">", "<", ">=", "<=")
|
|
13
|
+
@register("eq", "ne", "gt", "lt", "ge", "gte", "le", "lte")
|
|
14
|
+
@register("in", "not_in", "contains")
|
|
15
|
+
class Binary(Query):
|
|
16
|
+
"""Evaluates binary condition to boolean."""
|
|
17
|
+
|
|
18
|
+
operand: str
|
|
19
|
+
left: Path | None = None
|
|
20
|
+
right: Path | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def apply(self, data, *args, **kwargs):
|
|
24
|
+
"""Applies query to data."""
|
|
25
|
+
|
|
26
|
+
# get left value
|
|
27
|
+
left = data
|
|
28
|
+
if isinstance(self.left, Query):
|
|
29
|
+
left = self.left(data, *args, **kwargs)
|
|
30
|
+
|
|
31
|
+
# get right value
|
|
32
|
+
right = data
|
|
33
|
+
if isinstance(self.right, Query):
|
|
34
|
+
right = self.right(data, *args, **kwargs)
|
|
35
|
+
|
|
36
|
+
# equals
|
|
37
|
+
if self.operand in ("=", "==", "eq"):
|
|
38
|
+
return left == right
|
|
39
|
+
|
|
40
|
+
# not equals
|
|
41
|
+
if self.operand in ("!=", "ne"):
|
|
42
|
+
return left != right
|
|
43
|
+
|
|
44
|
+
# greater than
|
|
45
|
+
if self.operand in (">", "gt"):
|
|
46
|
+
return left > right
|
|
47
|
+
|
|
48
|
+
# less than
|
|
49
|
+
if self.operand in ("<", "lt"):
|
|
50
|
+
return left < right
|
|
51
|
+
|
|
52
|
+
# greater than or equal
|
|
53
|
+
if self.operand in (">=", "ge", "gte"):
|
|
54
|
+
return left >= right
|
|
55
|
+
|
|
56
|
+
# less than or equal
|
|
57
|
+
if self.operand in ("<=", "le", "lte"):
|
|
58
|
+
return left <= right
|
|
59
|
+
|
|
60
|
+
# in
|
|
61
|
+
if self.operand == "in":
|
|
62
|
+
return left in right
|
|
63
|
+
|
|
64
|
+
# not in
|
|
65
|
+
if self.operand == "not_in":
|
|
66
|
+
return left not in right
|
|
67
|
+
|
|
68
|
+
# contains
|
|
69
|
+
if self.operand == "contains":
|
|
70
|
+
return right in left
|
|
71
|
+
|
|
72
|
+
# unknown operand
|
|
73
|
+
raise QSyntaxError(f"Unrecognized unary condition operand '{self.operand}'.")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def from_json(cls, data):
|
|
78
|
+
"""Initialize instance from JSON."""
|
|
79
|
+
|
|
80
|
+
# get values
|
|
81
|
+
operand = next(iter(data))
|
|
82
|
+
values = next(iter(data.values()))
|
|
83
|
+
|
|
84
|
+
# convert single value
|
|
85
|
+
if not isinstance(values, list):
|
|
86
|
+
values = [values]
|
|
87
|
+
|
|
88
|
+
# check values
|
|
89
|
+
if not isinstance(values, list) or len(values) not in (1, 2):
|
|
90
|
+
raise QSyntaxError("Binary condition requires one or two values.")
|
|
91
|
+
|
|
92
|
+
# get values
|
|
93
|
+
if len(values) == 1:
|
|
94
|
+
left = None
|
|
95
|
+
right = values[0]
|
|
96
|
+
else:
|
|
97
|
+
left = values[0]
|
|
98
|
+
right = values[1]
|
|
99
|
+
|
|
100
|
+
# convert paths
|
|
101
|
+
if left is not None:
|
|
102
|
+
left = Path.from_json(left)
|
|
103
|
+
if right is not None:
|
|
104
|
+
right = Path.from_json(right)
|
|
105
|
+
|
|
106
|
+
# init instance
|
|
107
|
+
return cls(
|
|
108
|
+
operand = operand,
|
|
109
|
+
left = left,
|
|
110
|
+
right = right
|
|
111
|
+
)
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# Created byMartin.cz
|
|
2
|
+
# Copyright (c) Martin Strohalm. All rights reserved.
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from .errors import *
|
|
6
|
+
from .register import register
|
|
7
|
+
from .query import Query
|
|
8
|
+
from .path import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
@register("take")
|
|
13
|
+
class Take(Query):
|
|
14
|
+
"""Takes number of items from sequence."""
|
|
15
|
+
|
|
16
|
+
count: int | Path | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def apply(self, data, *args, **kwargs):
|
|
20
|
+
"""Applies query to data."""
|
|
21
|
+
|
|
22
|
+
# get count
|
|
23
|
+
count = self.count
|
|
24
|
+
if isinstance(self.count, Query):
|
|
25
|
+
count = self.count(data, *args, **kwargs)
|
|
26
|
+
|
|
27
|
+
# get slice
|
|
28
|
+
return data[:count]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def from_json(cls, data):
|
|
33
|
+
"""Initialize instance from JSON."""
|
|
34
|
+
|
|
35
|
+
# get value
|
|
36
|
+
count = next(iter(data.values()))
|
|
37
|
+
|
|
38
|
+
# convert to path
|
|
39
|
+
if not isinstance(count, (int, type(None))):
|
|
40
|
+
count = Path.from_json(count)
|
|
41
|
+
|
|
42
|
+
# init instance
|
|
43
|
+
return cls(
|
|
44
|
+
count = count
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
@register("skip")
|
|
50
|
+
class Skip(Query):
|
|
51
|
+
"""Skips number of items from sequence."""
|
|
52
|
+
|
|
53
|
+
count: int | Path | None = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def apply(self, data, *args, **kwargs):
|
|
57
|
+
"""Applies query to data."""
|
|
58
|
+
|
|
59
|
+
# get count
|
|
60
|
+
count = self.count
|
|
61
|
+
if isinstance(self.count, Query):
|
|
62
|
+
count = self.count(data, *args, **kwargs)
|
|
63
|
+
|
|
64
|
+
# get slice
|
|
65
|
+
return data[count:]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def from_json(cls, data):
|
|
70
|
+
"""Initialize instance from JSON."""
|
|
71
|
+
|
|
72
|
+
# get value
|
|
73
|
+
count = next(iter(data.values()))
|
|
74
|
+
|
|
75
|
+
# convert to path
|
|
76
|
+
if not isinstance(count, (int, type(None))):
|
|
77
|
+
count = Path.from_json(count)
|
|
78
|
+
|
|
79
|
+
# init instance
|
|
80
|
+
return cls(
|
|
81
|
+
count = count
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
@register("slice")
|
|
87
|
+
class Slice(Query):
|
|
88
|
+
"""Takes slice of items from sequence."""
|
|
89
|
+
|
|
90
|
+
start: int | Path | None = None
|
|
91
|
+
end: int | Path | None = None
|
|
92
|
+
step: int | Path | None = None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def apply(self, data, *args, **kwargs):
|
|
96
|
+
"""Applies query to data."""
|
|
97
|
+
|
|
98
|
+
# get start
|
|
99
|
+
start = self.start
|
|
100
|
+
if isinstance(self.start, Query):
|
|
101
|
+
start = self.start(data, *args, **kwargs)
|
|
102
|
+
|
|
103
|
+
# get end
|
|
104
|
+
end = self.end
|
|
105
|
+
if isinstance(self.end, Query):
|
|
106
|
+
end = self.end(data, *args, **kwargs)
|
|
107
|
+
|
|
108
|
+
# get step
|
|
109
|
+
step = self.step
|
|
110
|
+
if isinstance(self.step, Query):
|
|
111
|
+
step = self.step(data, *args, **kwargs)
|
|
112
|
+
|
|
113
|
+
# get slice
|
|
114
|
+
return data[start:end:step]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def from_json(cls, data):
|
|
119
|
+
"""Initialize instance from JSON."""
|
|
120
|
+
|
|
121
|
+
# get values
|
|
122
|
+
values = next(iter(data.values()))
|
|
123
|
+
|
|
124
|
+
# check empty
|
|
125
|
+
if values is None:
|
|
126
|
+
return cls()
|
|
127
|
+
|
|
128
|
+
# check values
|
|
129
|
+
if not isinstance(values, list) or len(values) > 3:
|
|
130
|
+
raise QSyntaxError("Slice definition must be a list of one, two or three values.")
|
|
131
|
+
|
|
132
|
+
# get values
|
|
133
|
+
start = values[0]
|
|
134
|
+
end = values[1] if len(values) > 1 else None
|
|
135
|
+
step = values[2] if len(values) > 2 else None
|
|
136
|
+
|
|
137
|
+
# convert to paths
|
|
138
|
+
if not isinstance(start, (int, type(None))):
|
|
139
|
+
start = Path.from_json(start)
|
|
140
|
+
if not isinstance(end, (int, type(None))):
|
|
141
|
+
end = Path.from_json(end)
|
|
142
|
+
if not isinstance(step, (int, type(None))):
|
|
143
|
+
step = Path.from_json(step)
|
|
144
|
+
|
|
145
|
+
# init instance
|
|
146
|
+
return cls(
|
|
147
|
+
start = start,
|
|
148
|
+
end = end,
|
|
149
|
+
step = step
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@dataclass
|
|
154
|
+
@register("select")
|
|
155
|
+
class Select(Query):
|
|
156
|
+
"""Selects value from every item in sequence."""
|
|
157
|
+
|
|
158
|
+
path: Path
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def apply(self, data, *args, **kwargs):
|
|
162
|
+
"""Applies query to data."""
|
|
163
|
+
|
|
164
|
+
# select values
|
|
165
|
+
return [self.path(item, *args, **kwargs) for item in data]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def from_json(cls, data):
|
|
170
|
+
"""Initialize instance from JSON."""
|
|
171
|
+
|
|
172
|
+
# get values
|
|
173
|
+
value = next(iter(data.values()))
|
|
174
|
+
|
|
175
|
+
# convert to path
|
|
176
|
+
if value is not None:
|
|
177
|
+
value = Path.from_json(value)
|
|
178
|
+
|
|
179
|
+
# init instance
|
|
180
|
+
return cls(
|
|
181
|
+
path = value
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@dataclass
|
|
186
|
+
@register("many")
|
|
187
|
+
class Many(Query):
|
|
188
|
+
"""Flattens value from every item in sequence."""
|
|
189
|
+
|
|
190
|
+
path: Path | None = None
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def apply(self, data, *args, **kwargs):
|
|
194
|
+
"""Applies query to data."""
|
|
195
|
+
|
|
196
|
+
# get values
|
|
197
|
+
if isinstance(self.path, Query):
|
|
198
|
+
data = (self.path(item, *args, **kwargs) for item in data)
|
|
199
|
+
|
|
200
|
+
# flatten lists
|
|
201
|
+
return [item for chain in data for item in chain]
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@classmethod
|
|
205
|
+
def from_json(cls, data):
|
|
206
|
+
"""Initialize instance from JSON."""
|
|
207
|
+
|
|
208
|
+
# get values
|
|
209
|
+
value = next(iter(data.values()))
|
|
210
|
+
|
|
211
|
+
# convert to path
|
|
212
|
+
if value is not None:
|
|
213
|
+
value = Path.from_json(value)
|
|
214
|
+
|
|
215
|
+
# init instance
|
|
216
|
+
return cls(
|
|
217
|
+
path = value
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@dataclass
|
|
222
|
+
@register("where")
|
|
223
|
+
class Where(Query):
|
|
224
|
+
"""Gets items from sequence where condition met."""
|
|
225
|
+
|
|
226
|
+
path: Path
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def apply(self, data, *args, **kwargs):
|
|
230
|
+
"""Applies query to data."""
|
|
231
|
+
|
|
232
|
+
# filter items
|
|
233
|
+
return [item for item in data if self.path(item, *args, **kwargs)]
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@classmethod
|
|
237
|
+
def from_json(cls, data):
|
|
238
|
+
"""Initialize instance from JSON."""
|
|
239
|
+
|
|
240
|
+
# get value
|
|
241
|
+
value = next(iter(data.values()))
|
|
242
|
+
|
|
243
|
+
# init instance
|
|
244
|
+
return cls(
|
|
245
|
+
path = Path.from_json(value)
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@dataclass
|
|
250
|
+
@register("distinct")
|
|
251
|
+
class Distinct(Query):
|
|
252
|
+
"""Gets distinct items from sequence."""
|
|
253
|
+
|
|
254
|
+
path: Path | None = None
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def apply(self, data, *args, **kwargs):
|
|
258
|
+
"""Applies query to data."""
|
|
259
|
+
|
|
260
|
+
# check selector
|
|
261
|
+
if not isinstance(self.path, Query):
|
|
262
|
+
return list(set(data))
|
|
263
|
+
|
|
264
|
+
# find distinct
|
|
265
|
+
seen = set()
|
|
266
|
+
items = []
|
|
267
|
+
for item in data:
|
|
268
|
+
key = self.path(item, *args, **kwargs)
|
|
269
|
+
if key not in seen:
|
|
270
|
+
seen.add(key)
|
|
271
|
+
items.append(item)
|
|
272
|
+
|
|
273
|
+
return items
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@classmethod
|
|
277
|
+
def from_json(cls, data):
|
|
278
|
+
"""Initialize instance from JSON."""
|
|
279
|
+
|
|
280
|
+
# get value
|
|
281
|
+
value = next(iter(data.values()))
|
|
282
|
+
|
|
283
|
+
# convert to path
|
|
284
|
+
if value is not None:
|
|
285
|
+
value = Path.from_json(value)
|
|
286
|
+
|
|
287
|
+
# init instance
|
|
288
|
+
return cls(
|
|
289
|
+
path = value
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@dataclass
|
|
294
|
+
@register("single")
|
|
295
|
+
class Single(Query):
|
|
296
|
+
"""Gets the single item from sequence where condition met."""
|
|
297
|
+
|
|
298
|
+
path: Path | None = None
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def apply(self, data, *args, **kwargs):
|
|
302
|
+
"""Applies query to data."""
|
|
303
|
+
|
|
304
|
+
# get data
|
|
305
|
+
if isinstance(self.path, Query):
|
|
306
|
+
data = (item for item in data if self.path(item, *args, **kwargs))
|
|
307
|
+
|
|
308
|
+
# use single
|
|
309
|
+
i = -1
|
|
310
|
+
item = None
|
|
311
|
+
for i, item in enumerate(data):
|
|
312
|
+
if i > 0:
|
|
313
|
+
raise QIterError("More then one item found.")
|
|
314
|
+
|
|
315
|
+
# found
|
|
316
|
+
if i == 0:
|
|
317
|
+
return item
|
|
318
|
+
|
|
319
|
+
# no item found
|
|
320
|
+
raise QIterError("No item found.")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@classmethod
|
|
324
|
+
def from_json(cls, data):
|
|
325
|
+
"""Initialize instance from JSON."""
|
|
326
|
+
|
|
327
|
+
# get value
|
|
328
|
+
value = next(iter(data.values()))
|
|
329
|
+
|
|
330
|
+
# convert to path
|
|
331
|
+
if value is not None:
|
|
332
|
+
value = Path.from_json(value)
|
|
333
|
+
|
|
334
|
+
# init instance
|
|
335
|
+
return cls(
|
|
336
|
+
path = value
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@dataclass
|
|
341
|
+
@register("first")
|
|
342
|
+
class First(Single):
|
|
343
|
+
"""Gets the first item from sequence where condition met."""
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def apply(self, data, *args, **kwargs):
|
|
347
|
+
"""Applies query to data."""
|
|
348
|
+
|
|
349
|
+
# get data
|
|
350
|
+
if isinstance(self.path, Query):
|
|
351
|
+
data = (item for item in data if self.path(item, *args, **kwargs))
|
|
352
|
+
|
|
353
|
+
# use first
|
|
354
|
+
for item in data:
|
|
355
|
+
return item
|
|
356
|
+
|
|
357
|
+
# no item found
|
|
358
|
+
raise QIterError("No item found.")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
@dataclass
|
|
362
|
+
@register("last")
|
|
363
|
+
class Last(Single):
|
|
364
|
+
"""Gets the last item from sequence where condition met."""
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def apply(self, data, *args, **kwargs):
|
|
368
|
+
"""Applies query to data."""
|
|
369
|
+
|
|
370
|
+
# get data
|
|
371
|
+
data = reversed(data)
|
|
372
|
+
if isinstance(self.path, Query):
|
|
373
|
+
data = (item for item in data if self.path(item, *args, **kwargs))
|
|
374
|
+
|
|
375
|
+
# use first
|
|
376
|
+
for item in data:
|
|
377
|
+
return item
|
|
378
|
+
|
|
379
|
+
# no item found
|
|
380
|
+
raise QIterError("No item found.")
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
@dataclass
|
|
384
|
+
@register("count")
|
|
385
|
+
class Count(Query):
|
|
386
|
+
"""Gets count of items in sequence where condition met."""
|
|
387
|
+
|
|
388
|
+
path: Path | None = None
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def apply(self, data, *args, **kwargs):
|
|
392
|
+
"""Applies query to data."""
|
|
393
|
+
|
|
394
|
+
# check selector
|
|
395
|
+
if not isinstance(self.path, Query):
|
|
396
|
+
return len(data)
|
|
397
|
+
|
|
398
|
+
# count true
|
|
399
|
+
return sum(1 for item in data if self.path(item, *args, **kwargs))
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
@classmethod
|
|
403
|
+
def from_json(cls, data):
|
|
404
|
+
"""Initialize instance from JSON."""
|
|
405
|
+
|
|
406
|
+
# get value
|
|
407
|
+
value = next(iter(data.values()))
|
|
408
|
+
|
|
409
|
+
# convert to path
|
|
410
|
+
if value is not None:
|
|
411
|
+
value = Path.from_json(value)
|
|
412
|
+
|
|
413
|
+
# init instance
|
|
414
|
+
return cls(
|
|
415
|
+
path = value
|
|
416
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Created byMartin.cz
|
|
2
|
+
# Copyright (c) Martin Strohalm. All rights reserved.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class QError(Exception):
|
|
6
|
+
"""Represents a jqson exception base."""
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class QSyntaxError(QError):
|
|
11
|
+
"""Represents an incorrect syntax exception."""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class QUndefError(QError):
|
|
16
|
+
"""Represents a missing definition exception."""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class QIterError(QError):
|
|
21
|
+
"""Represents an iteration exception."""
|
|
22
|
+
pass
|