Mercurial > jpath
annotate jpath.py @ 28:0a216b982fd1 default tip
Bump version to 1.6
| author | Radomir Dopieralski <sheep-devel@sheep.art.pl> |
|---|---|
| date | Mon, 22 Jan 2018 15:45:20 +0100 |
| parents | a607cb7f6333 |
| children |
| rev | line source |
|---|---|
| 0 | 1 #!/usr/bin/env python |
| 2 # -*- coding: utf-8 -*- | |
| 3 | |
| 4 """ | |
| 5 Access nested dicts and lists using JSON-like path notation. | |
| 8 | 6 |
| 10 | 7 Note that this code is written for python 3. |
| 8 | |
| 8 | 9 The notation is as follows: |
| 10 | |
| 11 You can just write the names of components in your path like you | |
| 12 would in javascript: | |
| 13 | |
| 14 foo.bar.baz | |
| 15 | |
| 16 To access arrays or names with spaces in them, use the '[]' notation. | |
| 17 You can use negative indices with arrays to count from the end. | |
| 18 | |
| 19 ["foo"]["bar"]["baz"] | |
| 20 array[-1].attr | |
| 21 [3] | |
| 22 | |
| 23 You can use the wildcard character '*' to iterate over all elements: | |
| 24 | |
| 25 foo.*.baz | |
| 26 ["foo"][*]["baz"] | |
| 27 | |
| 9 | 28 This may return more or less than a single element. Use get to get the |
| 29 first one, and get_all to get a list of all possibilities. | |
| 8 | 30 If you want to skip any number of elements in path, use two dots '..': |
| 31 | |
| 32 foo..baz | |
| 33 | |
| 34 You can slice the arrays just like in python: | |
| 35 | |
| 36 array[1:-1:2] | |
| 37 | |
| 38 Finally, you can filter the elements: | |
| 39 | |
| 40 foo(bar.baz=true) | |
| 41 foo.bar(baz>0).baz | |
| 42 foo(bar="yawn").bar | |
| 43 | |
| 44 At the moment only =, >, <, >=, <= and != operators are available, | |
| 45 and you can only use them with strings, integers and boolean values. | |
| 13 | 46 |
| 47 This code was written in STX Next. | |
| 0 | 48 """ |
| 49 | |
| 50 # We use very simple top-down parsing using recursive function calls. | |
| 51 # This may break in python for very very very long jpaths. | |
| 52 | |
| 53 | |
| 54 import re | |
| 55 import operator | |
| 56 | |
| 5 | 57 __all__ = ['get', 'get_all'] |
|
28
0a216b982fd1
Bump version to 1.6
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
27
diff
changeset
|
58 __version__ = '1.6' |
|
18
7dccdab5061e
Fix problems with infinite recursion into unicode strings and magical null objects
Radomir Dopieralski <devel@sheep.art.pl>
parents:
17
diff
changeset
|
59 |
|
7dccdab5061e
Fix problems with infinite recursion into unicode strings and magical null objects
Radomir Dopieralski <devel@sheep.art.pl>
parents:
17
diff
changeset
|
60 |
|
7dccdab5061e
Fix problems with infinite recursion into unicode strings and magical null objects
Radomir Dopieralski <devel@sheep.art.pl>
parents:
17
diff
changeset
|
61 # Make it work both in python 2.x and 3.x |
|
7dccdab5061e
Fix problems with infinite recursion into unicode strings and magical null objects
Radomir Dopieralski <devel@sheep.art.pl>
parents:
17
diff
changeset
|
62 try: |
|
7dccdab5061e
Fix problems with infinite recursion into unicode strings and magical null objects
Radomir Dopieralski <devel@sheep.art.pl>
parents:
17
diff
changeset
|
63 basestring |
|
7dccdab5061e
Fix problems with infinite recursion into unicode strings and magical null objects
Radomir Dopieralski <devel@sheep.art.pl>
parents:
17
diff
changeset
|
64 except NameError: |
|
7dccdab5061e
Fix problems with infinite recursion into unicode strings and magical null objects
Radomir Dopieralski <devel@sheep.art.pl>
parents:
17
diff
changeset
|
65 basestring = str |
| 5 | 66 |
|
15
0b466831544d
compatibility with python 2.7+
Wojciech Lichota <wojciech.lichota@stxnext.pl>
parents:
13
diff
changeset
|
67 |
|
0b466831544d
compatibility with python 2.7+
Wojciech Lichota <wojciech.lichota@stxnext.pl>
parents:
13
diff
changeset
|
68 def _pop_token(tokens): |
|
0b466831544d
compatibility with python 2.7+
Wojciech Lichota <wojciech.lichota@stxnext.pl>
parents:
13
diff
changeset
|
69 try: |
|
20
5c844a14d835
Fix the error with parsing get_all('a[*].b.c', {'a': [{'b': {'c': 0}}, {'b': {'c': 1}}]})
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
19
diff
changeset
|
70 token = tokens.pop() |
|
5c844a14d835
Fix the error with parsing get_all('a[*].b.c', {'a': [{'b': {'c': 0}}, {'b': {'c': 1}}]})
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
19
diff
changeset
|
71 except IndexError: |
|
15
0b466831544d
compatibility with python 2.7+
Wojciech Lichota <wojciech.lichota@stxnext.pl>
parents:
13
diff
changeset
|
72 token = NoneToken() |
|
0b466831544d
compatibility with python 2.7+
Wojciech Lichota <wojciech.lichota@stxnext.pl>
parents:
13
diff
changeset
|
73 return token |
|
0b466831544d
compatibility with python 2.7+
Wojciech Lichota <wojciech.lichota@stxnext.pl>
parents:
13
diff
changeset
|
74 |
|
0b466831544d
compatibility with python 2.7+
Wojciech Lichota <wojciech.lichota@stxnext.pl>
parents:
13
diff
changeset
|
75 |
| 3 | 76 class Tokenizer: |
| 8 | 77 # list of all token classes marked with @Tokenizer.token |
| 0 | 78 tokens = {} |
| 79 | |
| 3 | 80 def __init__(self): |
| 81 self.tokens_re = re.compile(r'|'.join( | |
| 82 r'(?P<{0}>{1})'.format(token.name, token.regexp) | |
| 83 for token in sorted(self.tokens.values(), | |
| 84 key=operator.attrgetter("priority")) | |
| 85 )) | |
| 86 | |
| 0 | 87 @classmethod |
| 3 | 88 def token(cls, token_class): |
| 0 | 89 """Marks a token class to be used in tokenizer.""" |
| 90 | |
| 91 cls.tokens[token_class.name] = token_class | |
| 1 | 92 return cls |
| 0 | 93 |
| 3 | 94 def tokenize(self, jpath): |
| 0 | 95 """ |
| 96 Breaks a string into tokens. | |
| 97 | |
| 3 | 98 >>> [(t.name, t.value) for t in TOKENIZER.tokenize("ziew")] |
| 0 | 99 [('fragment', 'ziew')] |
| 3 | 100 >>> [(t.name, t.value) for t in TOKENIZER.tokenize("ziew.yawn")] |
| 0 | 101 [('fragment', 'ziew'), ('fragment', 'yawn')] |
| 3 | 102 >>> [(t.name, t.value) for t in TOKENIZER.tokenize('ziew["yawn"]')] |
| 0 | 103 [('fragment', 'ziew'), ('element', 'yawn')] |
| 3 | 104 >>> [(t.name, t.value) for t in TOKENIZER.tokenize('ziew[0]')] |
| 0 | 105 [('fragment', 'ziew'), ('index', 0)] |
| 3 | 106 >>> [(t.name, t.value) for t in TOKENIZER.tokenize("ziew.yawn+")] |
| 0 | 107 [('fragment', 'ziew'), ('fragment', 'yawn'), ('error', '+')] |
| 108 """ | |
| 109 | |
| 3 | 110 for match in self.tokens_re.finditer(jpath): |
| 0 | 111 name = match.lastgroup |
| 112 if match.group(name): | |
| 113 params = dict(p for p in match.groupdict().items() | |
| 114 if p[1] is not None) | |
| 3 | 115 yield self.tokens[name](**params) |
| 116 | |
| 117 | |
| 118 class Token: | |
| 119 """Base class for tokens.""" | |
| 0 | 120 |
| 2 | 121 @staticmethod |
| 122 def apply(tokens, data): | |
|
15
0b466831544d
compatibility with python 2.7+
Wojciech Lichota <wojciech.lichota@stxnext.pl>
parents:
13
diff
changeset
|
123 token = _pop_token(tokens) |
| 2 | 124 for value in token.parse(tokens, data): |
| 125 yield value | |
| 126 | |
| 0 | 127 def parse(self, tokens, data): |
| 128 """ | |
| 129 Default parsing method for most tokens: parse the subtree | |
| 130 under token's value in data. | |
| 131 """ | |
| 132 | |
|
27
a607cb7f6333
Fix #1: KeyError instead of returning []
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
26
diff
changeset
|
133 try: |
|
a607cb7f6333
Fix #1: KeyError instead of returning []
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
26
diff
changeset
|
134 value = data[self.value] |
|
a607cb7f6333
Fix #1: KeyError instead of returning []
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
26
diff
changeset
|
135 except (IndexError, KeyError): |
|
a607cb7f6333
Fix #1: KeyError instead of returning []
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
26
diff
changeset
|
136 return [] |
|
a607cb7f6333
Fix #1: KeyError instead of returning []
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
26
diff
changeset
|
137 return Token.apply(tokens, value) |
| 0 | 138 |
| 139 | |
| 3 | 140 @Tokenizer.token |
| 0 | 141 class FragmentToken(Token): |
| 142 """foo.bar.baz""" | |
| 143 | |
| 1 | 144 regexp = r'(^|(?<=\.\.)|\.)(?P<fragment_name>[a-zA-Z0-9_]+)' |
| 0 | 145 name = 'fragment' |
| 1 | 146 priority = 50 |
| 0 | 147 |
| 148 def __init__(self, fragment, fragment_name): | |
| 149 self.value = fragment_name | |
| 150 | |
| 151 | |
| 3 | 152 @Tokenizer.token |
| 0 | 153 class ElementToken(Token): |
| 154 """["foo"]["bar"]["baz"], with quote escaping""" | |
| 155 | |
| 156 regexp = r'\["(?P<element_name>([^"]|\\")*)"\]' | |
| 157 name = 'element' | |
| 1 | 158 priority = 50 |
| 0 | 159 |
| 160 def __init__(self, element, element_name): | |
| 161 """ | |
| 162 >>> ElementToken(r'["ziew"]', r'ziew').value | |
| 163 'ziew' | |
| 164 >>> ElementToken('[r'["\"quotes\""]', r'\"quotes\"').value | |
| 165 '"quotes"' | |
| 166 """ | |
| 167 | |
| 168 # Un-escape escaped double quotes. | |
| 169 self.value = element_name.replace(r'\"', '"') | |
| 170 | |
| 171 | |
| 3 | 172 @Tokenizer.token |
| 0 | 173 class IndexToken(Token): |
| 174 """[0][1][-1]""" | |
| 175 | |
| 176 regexp = r'\[(?P<index_value>-?[0-9]+)\]' | |
| 177 name = 'index' | |
| 1 | 178 priority = 50 |
| 0 | 179 |
| 180 def __init__(self, index, index_value): | |
| 181 self.value = int(index_value) | |
| 182 | |
| 183 | |
| 3 | 184 @Tokenizer.token |
| 2 | 185 class SliceToken(Token): |
| 186 """[0:-1:2], may return multiple results""" | |
| 187 | |
| 188 regexp = (r'\[(?P<slice_start>-?[0-9]+)?:(?P<slice_end>-?[0-9]+)?' | |
| 189 r'(:(?P<slice_step>-?[0-9]+))?\]') | |
| 190 name = '_slice' | |
| 191 priority = 50 | |
| 192 | |
| 193 def __init__(self, _slice, slice_start=None, slice_end=None, | |
| 194 slice_step=None): | |
| 195 self.start = int(slice_start) if slice_start is not None else None | |
| 196 self.end = int(slice_end) if slice_end is not None else None | |
| 197 self.step = int(slice_step) if slice_step is not None else None | |
| 198 | |
| 199 def parse(self, tokens, data): | |
|
25
28dc252c2300
Go back to the original slice semantics
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
24
diff
changeset
|
200 return Token.apply(tokens, data[self.start:self.end:self.step]) |
|
22
4b25553183cc
Change the semantics of the slice operator to be more intuitive
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
21
diff
changeset
|
201 |
| 2 | 202 |
| 6 | 203 @Tokenizer.token |
| 204 class FilterToken(Token): | |
| 205 """(foo=3)""" | |
| 206 | |
| 7 | 207 regexp = (r'[(](?P<filter_path>[^(<=>)!]+)' |
| 8 | 208 r'\s*(?P<filter_operator>[<=>]|>=|<=|!=|==)\s*' |
| 6 | 209 r'(?P<filter_value>"([^"]|\\")*"|-?[0-9]|true|false)[)]') |
| 210 name = '_filter' | |
| 211 priority = 50 | |
| 7 | 212 op = { |
| 213 '=': operator.eq, | |
| 214 '>': operator.gt, | |
| 215 '<': operator.lt, | |
| 216 '!=': operator.ne, | |
| 217 '>=': operator.ge, | |
| 218 '<=': operator.le, | |
| 8 | 219 '==': operator.eq |
| 7 | 220 } |
| 6 | 221 |
| 7 | 222 def __init__(self, _filter, filter_path, filter_operator, filter_value): |
| 6 | 223 """ |
| 7 | 224 >>> FilterToken(r'(foo="b\"ar")', r'foo', r'=', r'"b\"ar"').value |
| 6 | 225 'b"ar' |
| 7 | 226 >>> FilterToken(r'(bar=-1)', r'bar', r'=', r'-1').value |
| 6 | 227 -1 |
| 228 """ | |
| 229 self.path = filter_path | |
| 230 if filter_value.startswith('"'): | |
| 231 self.value = filter_value[1:-1].replace(r'\"', '"') | |
| 232 elif filter_value in ('true', 'false'): | |
| 233 self.value = filter_value == 'true' | |
| 234 else: | |
| 235 self.value = int(filter_value) | |
| 7 | 236 self.operator = filter_operator |
| 6 | 237 |
| 238 def parse(self, tokens, data): | |
|
15
0b466831544d
compatibility with python 2.7+
Wojciech Lichota <wojciech.lichota@stxnext.pl>
parents:
13
diff
changeset
|
239 token = _pop_token(tokens) |
| 6 | 240 try: |
| 241 # If it's a dict, iterate over values, not keys. | |
| 242 data = data.values() | |
| 243 except AttributeError: | |
| 244 pass | |
| 245 for item in data: | |
| 10 | 246 try: |
| 247 item_value = get(self.path, item) | |
| 248 except (TypeError, IndexError, KeyError): | |
| 249 # Skip the branches that don't have the value we look for. | |
| 250 continue | |
| 251 if self.op[self.operator](item_value, self.value): | |
| 6 | 252 for value in token.parse(tokens, item): |
| 253 yield value | |
| 2 | 254 |
|
28
0a216b982fd1
Bump version to 1.6
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
27
diff
changeset
|
255 |
| 3 | 256 @Tokenizer.token |
| 0 | 257 class WildcardToken(Token): |
| 258 """*[*].*, may return multiple results""" | |
| 259 | |
| 1 | 260 regexp = r'(^|(?<=\.\.)|\.)\*|\[\*\]' |
| 0 | 261 name = 'wildcard' |
| 2 | 262 priority = 50 |
| 0 | 263 |
| 264 def __init__(self, wildcard): | |
| 265 self.value = None | |
| 266 | |
| 267 def parse(self, tokens, data): | |
|
15
0b466831544d
compatibility with python 2.7+
Wojciech Lichota <wojciech.lichota@stxnext.pl>
parents:
13
diff
changeset
|
268 token = _pop_token(tokens) |
| 1 | 269 try: |
| 3 | 270 # If it's a dict, iterate over values, not keys. |
| 0 | 271 data = data.values() |
| 272 except AttributeError: | |
| 273 pass | |
| 1 | 274 for item in data: |
| 275 try: | |
|
20
5c844a14d835
Fix the error with parsing get_all('a[*].b.c', {'a': [{'b': {'c': 0}}, {'b': {'c': 1}}]})
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
19
diff
changeset
|
276 for value in token.parse(list(tokens), item): |
| 1 | 277 yield value |
| 278 except (TypeError, IndexError, KeyError): | |
| 279 # Ignore errors inside branches, because some | |
| 280 # of the branches may have different structure | |
| 281 # than our jpath wants. | |
| 282 pass | |
| 283 | |
| 284 | |
| 3 | 285 @Tokenizer.token |
| 1 | 286 class EllipsisToken(Token): |
| 287 """.., may return multiple results""" | |
| 288 | |
| 289 regexp = r'\.\.' | |
| 290 name = 'ellipsis' | |
| 291 priority = 60 | |
| 292 | |
| 293 def __init__(self, ellipsis): | |
| 294 self.value = None | |
| 295 | |
| 296 def parse(self, tokens, data): | |
| 297 def dive(data): | |
| 298 yield data | |
| 299 stack = [data] | |
| 300 while stack: | |
| 301 data = stack.pop() | |
| 0 | 302 try: |
| 3 | 303 # If it's a dict, iterate over values, not keys. |
| 1 | 304 data = data.values() |
| 305 except AttributeError: | |
| 0 | 306 pass |
|
18
7dccdab5061e
Fix problems with infinite recursion into unicode strings and magical null objects
Radomir Dopieralski <devel@sheep.art.pl>
parents:
17
diff
changeset
|
307 orig_data = data |
| 3 | 308 try: |
| 309 # Skip non-iterable datatypes. | |
| 310 data = iter(data) | |
| 311 except TypeError: | |
| 312 continue | |
| 1 | 313 for item in data: |
|
18
7dccdab5061e
Fix problems with infinite recursion into unicode strings and magical null objects
Radomir Dopieralski <devel@sheep.art.pl>
parents:
17
diff
changeset
|
314 if isinstance(item, basestring): |
| 1 | 315 # Strings are nasty, because they can be iterated |
| 316 # recursively idefinitely. | |
|
18
7dccdab5061e
Fix problems with infinite recursion into unicode strings and magical null objects
Radomir Dopieralski <devel@sheep.art.pl>
parents:
17
diff
changeset
|
317 continue |
|
7dccdab5061e
Fix problems with infinite recursion into unicode strings and magical null objects
Radomir Dopieralski <devel@sheep.art.pl>
parents:
17
diff
changeset
|
318 if item is orig_data: |
|
7dccdab5061e
Fix problems with infinite recursion into unicode strings and magical null objects
Radomir Dopieralski <devel@sheep.art.pl>
parents:
17
diff
changeset
|
319 # We are iterating over something that behaves like |
|
7dccdab5061e
Fix problems with infinite recursion into unicode strings and magical null objects
Radomir Dopieralski <devel@sheep.art.pl>
parents:
17
diff
changeset
|
320 # the strings, refuse to descend. |
|
7dccdab5061e
Fix problems with infinite recursion into unicode strings and magical null objects
Radomir Dopieralski <devel@sheep.art.pl>
parents:
17
diff
changeset
|
321 break |
|
7dccdab5061e
Fix problems with infinite recursion into unicode strings and magical null objects
Radomir Dopieralski <devel@sheep.art.pl>
parents:
17
diff
changeset
|
322 stack.append(item) |
|
7dccdab5061e
Fix problems with infinite recursion into unicode strings and magical null objects
Radomir Dopieralski <devel@sheep.art.pl>
parents:
17
diff
changeset
|
323 yield item |
|
15
0b466831544d
compatibility with python 2.7+
Wojciech Lichota <wojciech.lichota@stxnext.pl>
parents:
13
diff
changeset
|
324 token = _pop_token(tokens) |
| 1 | 325 for item in dive(data): |
| 326 try: | |
|
20
5c844a14d835
Fix the error with parsing get_all('a[*].b.c', {'a': [{'b': {'c': 0}}, {'b': {'c': 1}}]})
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
19
diff
changeset
|
327 for value in token.parse(list(tokens), item): |
| 1 | 328 yield value |
| 329 except (TypeError, IndexError, KeyError): | |
| 330 # Ignore errors inside branches, because some | |
| 331 # of the branches may have different structure | |
| 332 # than our jpath wants. | |
| 333 pass | |
| 0 | 334 |
| 335 | |
| 3 | 336 @Tokenizer.token |
| 0 | 337 class ErrorToken(Token): |
| 338 """Catchall for everything that didn't match other tokens.""" | |
| 339 | |
| 340 regexp = r'.*' | |
| 341 name = 'error' | |
| 342 priority = 100 | |
| 343 | |
| 344 def __init__(self, error): | |
| 345 self.value = error | |
| 346 | |
| 347 def parse(self, tokens, data): | |
| 348 raise SyntaxError("Unexpexted '{0}' in jpath.".format(self.value)) | |
| 349 yield None | |
| 350 | |
| 351 | |
| 1 | 352 class NoneToken(Token): |
| 353 """Special token for marking lack of tokens.""" | |
| 354 | |
| 355 def parse(self, tokens, data): | |
| 356 yield data | |
| 357 | |
| 358 | |
| 3 | 359 # We only ever need one tokenizer |
| 360 TOKENIZER = Tokenizer() | |
| 361 | |
| 362 | |
| 0 | 363 def get_all(jpath, data): |
| 364 """ | |
| 5 | 365 Get list of all elements matched by given jpath in the data datastructure. |
| 0 | 366 |
| 367 >>> get_all('', 1) | |
| 368 [1] | |
| 369 >>> get_all('[1]', [1, 2]) | |
| 370 [2] | |
| 371 >>> get_all('[*]', [1, 2]) | |
| 372 [1, 2] | |
| 373 >>> get_all('*', [1, 2]) | |
| 374 [1, 2] | |
| 375 >>> get_all('*[0]', [1, [2, 4], 3]) | |
| 376 [2] | |
| 377 >>> get_all('*[0]', [1, [2, 4], 3, []]) | |
| 378 [2] | |
| 379 >>> get_all('*.ziew', [1, {'ziew':2}, [3], []]) | |
| 380 [2] | |
| 1 | 381 >>> get_all('..ziew', [1, 'ziew', {'ziew': 3, 'yawn': [{'ziew': 4}]}]) |
| 382 [3, 4] | |
| 383 >>> get_all('a.b', {'a': {'b': 3, 'c': {'b': 4}}}) | |
| 384 [3] | |
| 385 >>> get_all('a..b', {'a': {'b': 3, 'c': {'b': 4}}}) | |
| 386 [3, 4] | |
| 2 | 387 >>> get_all('[1:]', [1, 2, 3]) |
|
25
28dc252c2300
Go back to the original slice semantics
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
24
diff
changeset
|
388 [[2, 3]] |
| 2 | 389 >>> get_all('[:-1]', [1, 2, 3]) |
|
25
28dc252c2300
Go back to the original slice semantics
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
24
diff
changeset
|
390 [[1, 2]] |
| 6 | 391 >>> get_all('[::2]', [1, 2, 3, 4]) |
|
25
28dc252c2300
Go back to the original slice semantics
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
24
diff
changeset
|
392 [[1, 3]] |
|
24
64328843ba3b
one more slice test
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
23
diff
changeset
|
393 >>> get_all('[::2][0]', [[1, 2], [3, 4], [5, 6]]) |
|
25
28dc252c2300
Go back to the original slice semantics
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
24
diff
changeset
|
394 [[1, 2]] |
| 6 | 395 >>> get_all('(x=1).y', [{'x':1, 'y':2}, {'x':0, 'y':3}, {'x':1, 'y':4}]) |
| 396 [2, 4] | |
| 7 | 397 >>> get_all('(x>0).y', [{'x':1, 'y':2}, {'x':0, 'y':3}, {'x':1, 'y':4}]) |
| 398 [2, 4] | |
| 6 | 399 >>> get_all('..', {'a':1, 'x':[2], 'c':[{'b': '3'}]}) |
| 400 [{'a': 1, 'x': [2], 'c': [{'b': '3'}]}, 1, [2], [{'b': '3'}], {'b': '3'}, 2] | |
|
19
b21203132ef6
Add a test for the issue reported by James Hendricks
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
18
diff
changeset
|
401 >>> get_all('a[*].b.c', {'a': [{'b': {'c': 0}}, {'b': {'c': 1}}]}) |
|
b21203132ef6
Add a test for the issue reported by James Hendricks
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
18
diff
changeset
|
402 [0, 1] |
|
27
a607cb7f6333
Fix #1: KeyError instead of returning []
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
26
diff
changeset
|
403 >>> get_all('a', {}) |
|
a607cb7f6333
Fix #1: KeyError instead of returning []
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
26
diff
changeset
|
404 [] |
| 0 | 405 """ |
| 406 | |
|
20
5c844a14d835
Fix the error with parsing get_all('a[*].b.c', {'a': [{'b': {'c': 0}}, {'b': {'c': 1}}]})
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
19
diff
changeset
|
407 tokens = list(TOKENIZER.tokenize(jpath)) |
|
5c844a14d835
Fix the error with parsing get_all('a[*].b.c', {'a': [{'b': {'c': 0}}, {'b': {'c': 1}}]})
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
19
diff
changeset
|
408 # Popping from the end of the list is faster. |
|
5c844a14d835
Fix the error with parsing get_all('a[*].b.c', {'a': [{'b': {'c': 0}}, {'b': {'c': 1}}]})
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
19
diff
changeset
|
409 tokens.reverse() |
|
5c844a14d835
Fix the error with parsing get_all('a[*].b.c', {'a': [{'b': {'c': 0}}, {'b': {'c': 1}}]})
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
19
diff
changeset
|
410 return list(Token.apply(tokens, data)) |
|
5c844a14d835
Fix the error with parsing get_all('a[*].b.c', {'a': [{'b': {'c': 0}}, {'b': {'c': 1}}]})
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
19
diff
changeset
|
411 |
| 0 | 412 |
| 413 def get(jpath, data): | |
| 414 """ | |
| 5 | 415 Get the first element matched by given jpath in the data datastructure. |
| 0 | 416 |
| 417 >>> get('', 3) | |
| 418 3 | |
| 419 >>> get('ziew', {"ziew": 3}) | |
| 420 3 | |
| 421 >>> get('ziew.yawn', {"ziew": {"yawn": 3}}) | |
| 422 3 | |
| 423 >>> get('ziew["yawn"]', {"ziew": {"yawn": 3}}) | |
| 424 3 | |
| 425 >>> get('ziew[2]', {"ziew": [1, 2, 3]}) | |
| 426 3 | |
| 427 >>> get('[2]', [1, 2, 3]) | |
| 428 3 | |
| 429 >>> get('[*]', [1, 2, 3]) | |
| 430 1 | |
| 431 """ | |
| 432 | |
| 433 return get_all(jpath, data)[0] | |
| 434 | |
|
28
0a216b982fd1
Bump version to 1.6
Radomir Dopieralski <sheep-devel@sheep.art.pl>
parents:
27
diff
changeset
|
435 |
| 0 | 436 if __name__ == "__main__": |
| 437 # Run tests when started. | |
| 438 import doctest | |
| 439 doctest.testmod() |
