Skip to content

Commit

Permalink
Add JSONPath.query() for getting a query iterator from a compiled path
Browse files Browse the repository at this point in the history
  • Loading branch information
jg-rp committed Jul 10, 2024
1 parent 8cada95 commit f766b50
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 3 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
**Features**

- Allow JSONPath filter expression membership operators (`contains` and `in`) to operate on object/mapping data as well as arrays/sequences. See [#55](https://github.com/jg-rp/python-jsonpath/issues/55).
- Added a `select` method to the JSONPath [query iterator interface](https://jg-rp.github.io/python-jsonpath/query/), generating a projection of each JSONPath match by selecting a subset of its values.
- Added a `select()` method to the JSONPath [query iterator interface](https://jg-rp.github.io/python-jsonpath/query/), generating a projection of each JSONPath match by selecting a subset of its values.
- Added the `query()` method to the `JSONPath` class. Get a query iterator from an already compiled path.
- Added the `addne` and `addap` operations to [JSONPatch](https://jg-rp.github.io/python-jsonpath/api/#jsonpath.JSONPatch). `addne` (add if not exists) is like the standard `add` operation, but only adds object keys/values if the key does not exist. `addap` (add or append) is like the standard `add` operation, but assumes an index of `-` if the target index can not be resolved.

## Version 1.1.1
Expand Down
2 changes: 1 addition & 1 deletion docs/query.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

In addition to [`findall()`](api.md#jsonpath.JSONPathEnvironment.findall) and [`finditer()`](api.md#jsonpath.JSONPathEnvironment.finditer), covered in the [quick start guide](./quickstart.md), Python JSONPath offers a fluent _query iterator_ interface.

[`Query`](api.md#jsonpath.Query) objects provide chainable methods for manipulating a [`JSONPathMatch`](api.md#jsonpath.JSONPathMatch) iterator, like you'd get from `finditer()`. Obtain a `Query` object using the package-level `query()` function or [`JSONPathEnvironment.query()`](api.md#jsonpath.JSONPathEnvironment.query).
[`Query`](api.md#jsonpath.Query) objects provide chainable methods for manipulating a [`JSONPathMatch`](api.md#jsonpath.JSONPathMatch) iterator, like you'd get from `finditer()`. Obtain a `Query` object using the package-level `query()` function, [`JSONPathEnvironment.query()`](api.md#jsonpath.JSONPathEnvironment.query) or using the [`query()`](api.md#jsonpath.JSONPath.query) method of a compiled JSONPath.

This example uses the query API to skip the first five matches, limit the total number of matches to ten, then get the value associated with each match.

Expand Down
17 changes: 16 additions & 1 deletion jsonpath/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ def query(
data: Union[str, IOBase, Sequence[Any], Mapping[str, Any]],
filter_context: Optional[FilterContextVars] = None,
) -> Query:
"""Return a `Query` object over matches found by applying _path_ to _data_.
"""Return a `Query` iterator over matches found by applying _path_ to _data_.
`Query` objects are iterable.
Expand Down Expand Up @@ -353,6 +353,21 @@ def query(
for obj in jsonpath.query("$.foo..bar", data).limit(5).values():
...
```
Arguments:
path: The JSONPath as a string.
data: A JSON document or Python object implementing the `Sequence`
or `Mapping` interfaces.
filter_context: Arbitrary data made available to filters using
the _filter context_ selector.
Returns:
A query iterator.
Raises:
JSONPathSyntaxError: If the path is invalid.
JSONPathTypeError: If a filter expression attempts to use types in
an incompatible way.
"""
return Query(self.finditer(path, data, filter_context=filter_context), self)

Expand Down
25 changes: 25 additions & 0 deletions jsonpath/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from typing import Union

from jsonpath._data import load_data
from jsonpath.fluent_api import Query
from jsonpath.match import FilterContextVars
from jsonpath.match import JSONPathMatch
from jsonpath.selectors import IndexSelector
Expand Down Expand Up @@ -210,6 +211,30 @@ def match(
except StopIteration:
return None

def query(
self,
data: Union[str, IOBase, Sequence[Any], Mapping[str, Any]],
*,
filter_context: Optional[FilterContextVars] = None,
) -> Query:
"""Return a `Query` iterator over matches found by applying this path to _data_.
Arguments:
data: A JSON document or Python object implementing the `Sequence`
or `Mapping` interfaces.
filter_context: Arbitrary data made available to filters using
the _filter context_ selector.
Returns:
A query iterator.
Raises:
JSONPathSyntaxError: If the path is invalid.
JSONPathTypeError: If a filter expression attempts to use types in
an incompatible way.
"""
return Query(self.finditer(data, filter_context=filter_context), self.env)

def empty(self) -> bool:
"""Return `True` if this path has no selectors."""
return not bool(self.selectors)
Expand Down
9 changes: 9 additions & 0 deletions tests/test_fluent_api.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Test cases for the fluent API."""

import pytest

from jsonpath import JSONPathMatch
from jsonpath import JSONPointer
from jsonpath import compile
from jsonpath import query


Expand Down Expand Up @@ -267,3 +269,10 @@ def test_query_take_more() -> None:
assert len(head) == 4 # noqa: PLR2004
assert head == [0, 1, 2, 3]
assert list(it.values()) == []


def test_query_from_compiled_path() -> None:
"""Test that we can get a query iterator from a compiled path."""
path = compile("$.some.*")
it = path.query({"some": [0, 1, 2, 3]}).values()
assert list(it) == [0, 1, 2, 3]

0 comments on commit f766b50

Please sign in to comment.