From f766b5041ca66fc667eadf5c6bc843917d12714e Mon Sep 17 00:00:00 2001 From: James Prior Date: Wed, 10 Jul 2024 07:34:28 +0100 Subject: [PATCH] Add `JSONPath.query()` for getting a query iterator from a compiled path --- CHANGELOG.md | 3 ++- docs/query.md | 2 +- jsonpath/env.py | 17 ++++++++++++++++- jsonpath/path.py | 25 +++++++++++++++++++++++++ tests/test_fluent_api.py | 9 +++++++++ 5 files changed, 53 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c851ec..a1e594c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/query.md b/docs/query.md index 740a2c6..c008a70 100644 --- a/docs/query.md +++ b/docs/query.md @@ -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. diff --git a/jsonpath/env.py b/jsonpath/env.py index ef49d4b..8980414 100644 --- a/jsonpath/env.py +++ b/jsonpath/env.py @@ -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. @@ -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) diff --git a/jsonpath/path.py b/jsonpath/path.py index d521be0..03cf04c 100644 --- a/jsonpath/path.py +++ b/jsonpath/path.py @@ -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 @@ -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) diff --git a/tests/test_fluent_api.py b/tests/test_fluent_api.py index d48e955..4a1bc78 100644 --- a/tests/test_fluent_api.py +++ b/tests/test_fluent_api.py @@ -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 @@ -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]