Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Python JSONPath Change Log

## Version 1.3.2 (unreleased)

**Fixes**

- Fixed JSONPath filter context data in embedded JSONPath queries. We were failing to pass on said context data when resolving embedded queries. See [#103](https://github.com/jg-rp/python-jsonpath/issues/103).

## Version 1.3.1

**Fixes**
Expand Down
73 changes: 73 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Contributing to Python JSONPath

Hi. Your contributions and questions are always welcome. Feel free to ask questions, report bugs or request features on the [issue tracker](https://github.com/jg-rp/python-jsonpath/issues) or on [Github Discussions](https://github.com/jg-rp/python-jsonpath/discussions). Pull requests are welcome too.

**Table of contents**

- [Development](#development)
- [Documentation](#documentation)
- [Style Guides](#style-guides)

## Development

The [JSONPath Compliance Test Suite](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite) and [JSONPath Normalized Path Test Suite](https://github.com/jg-rp/jsonpath-compliance-normalized-paths) are included in this repository as Git [submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules). Clone this project and initialize the submodules with something like:

```shell
$ git clone git@github.com:jg-rp/python-jsonpath.git
$ cd python-jsonpath
$ git submodule update --init
```

We use [hatch](https://hatch.pypa.io/latest/) to manage project dependencies and development environments.

Run tests with the _test_ script.

```shell
$ hatch run test
```

Lint with [ruff](https://beta.ruff.rs/docs/).

```shell
$ hatch run lint
```

Typecheck with [Mypy](https://mypy.readthedocs.io/en/stable/).

```shell
$ hatch run typing
```

Check coverage with pytest-cov.

```shell
$ hatch run cov
```

Or generate an HTML coverage report.

```shell
$ hatch run cov-html
```

Then open `htmlcov/index.html` in your browser.

## Documentation

Documentation is currently in the [README](https://github.com/jg-rp/python-jsonpath/blob/main/README.md) and project source code only.

## Style Guides

### Git Commit Messages

There are no hard rules for git commit messages, although you might like to indicate the type of commit by starting the message with `docs:`, `chore:`, `feat:`, `fix:` or `refactor:`, for example.

### Python Style

We use [Ruff](https://docs.astral.sh/ruff/) to lint and format all Python files.

Ruff is configured to:

- follow [Black](https://github.com/psf/black), with its default configuration.
- expect [Google style docstrings](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html).
- enforce Python imports according to [isort](https://pycqa.github.io/isort/) with `force-single-line = true`.
2 changes: 1 addition & 1 deletion jsonpath/__about__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2023-present James Prior <jamesgr.prior@gmail.com>
#
# SPDX-License-Identifier: MIT
__version__ = "1.3.1"
__version__ = "1.3.2"
42 changes: 36 additions & 6 deletions jsonpath/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,12 @@ def evaluate(self, context: FilterContext) -> object:
return context.current
return NodeList()

return NodeList(self.path.finditer(context.current))
return NodeList(
self.path.finditer(
context.current,
filter_context=context.extra_context,
)
)

async def evaluate_async(self, context: FilterContext) -> object:
if isinstance(context.current, str): # TODO: refactor
Expand All @@ -557,7 +562,13 @@ async def evaluate_async(self, context: FilterContext) -> object:
return NodeList()

return NodeList(
[match async for match in await self.path.finditer_async(context.current)]
[
match
async for match in await self.path.finditer_async(
context.current,
filter_context=context.extra_context,
)
]
)


Expand All @@ -576,11 +587,22 @@ def __str__(self) -> str:
return str(self.path)

def evaluate(self, context: FilterContext) -> object:
return NodeList(self.path.finditer(context.root))
return NodeList(
self.path.finditer(
context.root,
filter_context=context.extra_context,
)
)

async def evaluate_async(self, context: FilterContext) -> object:
return NodeList(
[match async for match in await self.path.finditer_async(context.root)]
[
match
async for match in await self.path.finditer_async(
context.root,
filter_context=context.extra_context,
)
]
)


Expand All @@ -600,13 +622,21 @@ def __str__(self) -> str:
return "_" + path_repr[1:]

def evaluate(self, context: FilterContext) -> object:
return NodeList(self.path.finditer(context.extra_context))
return NodeList(
self.path.finditer(
context.extra_context,
filter_context=context.extra_context,
)
)

async def evaluate_async(self, context: FilterContext) -> object:
return NodeList(
[
match
async for match in await self.path.finditer_async(context.extra_context)
async for match in await self.path.finditer_async(
context.extra_context,
filter_context=context.extra_context,
)
]
)

Expand Down
12 changes: 0 additions & 12 deletions tests/test_find.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,18 +127,6 @@ class Case:
}
],
),
Case(
description="issue 72, orders",
path="orders",
data={"orders": [1, 2, 3]},
want=[[1, 2, 3]],
),
Case(
description="issue 72, andy",
path="andy",
data={"andy": [1, 2, 3]},
want=[[1, 2, 3]],
),
Case(
description="quoted reserved word, and",
path="['and']",
Expand Down
69 changes: 69 additions & 0 deletions tests/test_issues.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from jsonpath import findall


def test_issue_72_andy() -> None:
query = "andy"
data = {"andy": [1, 2, 3]}
assert findall(query, data) == [[1, 2, 3]]


def test_issue_72_orders() -> None:
query = "orders"
data = {"orders": [1, 2, 3]}
assert findall(query, data) == [[1, 2, 3]]


def test_issue_103() -> None:
query = "$..book[?(@.borrowers[?(@.name == _.name)])]"
data = {
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95,
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99,
"borrowers": [
{"name": "John", "id": 101},
{"name": "Jane", "id": 102},
],
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99,
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99,
"borrowers": [{"name": "Peter", "id": 103}],
},
],
"bicycle": {"color": "red", "price": 19.95},
}
}

filter_context = {"name": "John"}

want = [
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99,
"borrowers": [{"name": "John", "id": 101}, {"name": "Jane", "id": 102}],
}
]

assert findall(query, data, filter_context=filter_context) == want
Loading