Skip to content

Commit 207f202

Browse files
authored
Merge pull request #98 from jg-rp/v2
Python JSONPath Version 2
2 parents 6b571e1 + 07b1cad commit 207f202

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+5145
-2352
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: test-no-regex
2+
on: [push, pull_request]
3+
4+
jobs:
5+
lint:
6+
runs-on: ubuntu-latest
7+
steps:
8+
- uses: actions/checkout@v3
9+
with:
10+
submodules: true
11+
- name: Set up Python 3.11
12+
uses: actions/setup-python@v4
13+
with:
14+
python-version: "3.11"
15+
- name: Install dependencies
16+
run: |
17+
python -m pip install --upgrade pip
18+
python -m pip install --upgrade hatch
19+
- run: hatch -e no-regex run test

CHANGELOG.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,36 @@
11
# Python JSONPath Change Log
22

3+
## Version 2.0.0 (unreleased)
4+
5+
**JSONPath syntax changes**
6+
7+
These breaking changes apply to Python JSONPath in its default configuration. We've also introduced a _strict mode_ where we follow the RFC 9535 specification exactly. See [optional dependencies](https://jg-rp.github.io/python-jsonpath/#optional-dependencies) and the [syntax guide](https://jg-rp.github.io/python-jsonpath/syntax/) for more information.
8+
9+
- Using bracket notation, unquoted property names are no longer interpreted as quoted property names. These paths used to be equivalent, `$[foo]`, `$['foo']` and `$["foo"]`. Now, names without quotes start a _singular query selector_. With an implicit _root identifier_, `$.a[b]` is equivalent to `$.a[$.b]`. See [Singular query selector](https://jg-rp.github.io/python-jsonpath/syntax/#singular-query-selector) in the syntax guide.
10+
- In filter selector expressions, float literals now follow the specification. Previously `.1` and `1.` where allowed, now it must be `0.1` and `1.0`, with at least one digit either side of the decimal point.
11+
- Slice selector indexes and step now follow the specification. Previously leading zeros and negative zero were allowed, now they raise a `JSONPathSyntaxError`.
12+
- Whitespace is no longer allowed between a dot (`.` or `..`) and a name when using shorthand notation for the name selector. Whitespace before the dot oor double dot is OK.
13+
14+
**JSONPath features**
15+
16+
- Added the [Keys filter selector](https://jg-rp.github.io/python-jsonpath/syntax/#keys-filter-selector).
17+
- Added the [Singular query selector](https://jg-rp.github.io/python-jsonpath/syntax/#singular-query-selector).
18+
- We now use the [regex] package, if available, instead of `re` for match and search function extensions. See [optional dependencies](https://jg-rp.github.io/python-jsonpath/#optional-dependencies).
19+
- Added the `strict` argument to all [convenience functions](https://jg-rp.github.io/python-jsonpath/convenience/), the CLI and the `JSONPathEnvironment` constructor. When `strict=True`, all extensions to RFC 9535 and any lax parsing rules will be disabled.
20+
- Added class variable `JSONPathEnvironment.max_recursion_depth` to control the maximum recursion depth of descendant segments.
21+
- Added pretty exception messages.
22+
23+
**Python API changes**
24+
25+
- Renamed class variable `JSONPathEnvironment.fake_root_token` to `JSONPathEnvironment.pseudo_root_token`.
26+
27+
**Low level API changes**
28+
29+
These breaking changes will only affect you if you're customizing the JSONPath lexer or parser.
30+
31+
- The tokens produced by the JSONPath lexer have changed. Previously we broadly skipped some punctuation and whitespace. Now the parser can make better choices about when to accept whitespace and do a better job of enforcing dots.
32+
- We've change the internal representation of compiled JSONPath queries. We now model segments and selectors explicitly and use terminology that matches RFC 9535.
33+
334
## Version 1.3.2
435

536
**Fixes**

docs/advanced.md

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Filter Variables
44

5-
Arbitrary variables can be made available to [filter expressions](syntax.md#filters-expression) using the _filter_context_ argument to [`findall()`](quickstart.md#findallpath-data) and [`finditer()`](quickstart.md#finditerpath-data). _filter_context_ should be a [mapping](https://docs.python.org/3/library/typing.html#typing.Mapping) of strings to JSON-like objects, like lists, dictionaries, strings and integers.
5+
Arbitrary variables can be made available to [filter selectors](syntax.md#filter-selector) using the `filter_context` argument to [`findall()`](quickstart.md#findallpath-data) and [`finditer()`](quickstart.md#finditerpath-data). `filter_context` should be a [mapping](https://docs.python.org/3/library/typing.html#typing.Mapping) of strings to JSON-like objects, like lists, dictionaries, strings and integers.
66

77
Filter context variables are selected using a filter query starting with the _filter context identifier_, which defaults to `_` and has usage similar to `$` and `@`.
88

@@ -257,23 +257,3 @@ env = MyJSONPathEnvironment()
257257
query = env.compile("$.users[999]")
258258
# jsonpath.exceptions.JSONPathIndexError: index out of range, line 1, column 8
259259
```
260-
261-
### Subclassing Lexer
262-
263-
TODO:
264-
265-
### Subclassing Parser
266-
267-
TODO:
268-
269-
### Get Item
270-
271-
TODO:
272-
273-
### Truthiness and Existence
274-
275-
TODO:
276-
277-
### Filter Infix Expressions
278-
279-
TODO:

docs/async.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,3 @@ data = {
5959
best_a_team_players = jsonpath.findall_async("$.teams['A Team'][?rank >= 8]", data)
6060

6161
```
62-
63-
## Custom Async Item Getting
64-
65-
TODO:

docs/cli.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ optional arguments:
6262
File to write resulting objects to, as a JSON array. Defaults to the standard
6363
output stream.
6464
--no-type-checks Disables filter expression well-typedness checks.
65+
--strict Compile and evaluate JSONPath expressions with strict compliance with RFC 9535.
6566
```
6667

6768
## Global Options
@@ -191,6 +192,12 @@ _New in version 0.10.0_
191192

192193
Disables JSONPath filter expression well-typedness checks. The well-typedness of a filter expression is defined by RFC 9535.
193194

195+
#### `--strict`
196+
197+
_New in version 2.0.0_
198+
199+
Compile and evaluate JSONPath expressions with strict compliance with RFC 9535.
200+
194201
### `pointer`
195202

196203
Resolve a JSON Pointer against a JSON document. One of `-p`/`--pointer` or `-r`/`--pointer-file` must be given. `-p` being a JSON Pointer given on the command line as a string, `-r` being the path to a file containing a JSON Pointer.

docs/convenience.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Convenience Functions
2+
3+
These package-level functions use the default [JSONPathEnvironment](api.md#jsonpath.JSONPathEnvironment), `jsonpath.DEFAULT_ENV` when `strict=False`, or the preconfigured strict environment, `jsonpath.STRICT_ENV` when `strict=True`.
4+
5+
::: jsonpath.compile
6+
7+
handler: python
8+
9+
::: jsonpath.findall
10+
11+
handler: python
12+
13+
::: jsonpath.finditer
14+
15+
handler: python
16+
17+
::: jsonpath.findall_async
18+
19+
handler: python
20+
21+
::: jsonpath.finditer_async
22+
23+
handler: python
24+
25+
::: jsonpath.match
26+
27+
handler: python
28+
29+
::: jsonpath.query
30+
31+
handler: python

docs/functions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Filter Functions
22

3-
A filter function is a named function that can be called as part of a [filter selector](syntax.md#filters-expression) expression. Here we describe built-in filters. You can [define your own function extensions](advanced.md#function-extensions) too.
3+
A filter function is a named function that can be called as part of a [filter selector](syntax.md#filter-selector). Here we describe built in filters. You can [define your own function extensions](advanced.md#function-extensions) too.
44

55
## `count()`
66

docs/index.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
JSONPath is a mini language for selecting values from data formatted in JavaScript Object Notation, or equivalent Python objects, like dictionaries and lists.
44

5-
Python JSONPath is a non-evaluating, read-only implementation of JSONPath, suitable for situations where JSONPath query authors are untrusted. We follow most of [RFC 9535](https://datatracker.ietf.org/doc/html/rfc9535). See [Notable differences](syntax.md#notable-differences) for a list of areas where we deviate from the standard.
5+
Python JSONPath is a non-evaluating, read-only implementation of JSONPath, suitable for situations where JSONPath query authors are untrusted. We follow [RFC 9535](https://datatracker.ietf.org/doc/html/rfc9535) and test against the [JSONPath Compliance Test Suite](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite).
66

77
We also include implementations of [JSON Pointer](pointers.md) ([RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901)) and [JSON Patch](api.md#jsonpath.JSONPatch) ([RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902)), plus methods for converting a [JSONPathMatch](api.md#jsonpath.JSONPathMatch) to a `JSONPointer`.
88

@@ -32,6 +32,14 @@ Or from [conda-forge](https://anaconda.org/conda-forge/python-jsonpath):
3232
conda install -c conda-forge python-jsonpath
3333
```
3434

35+
### Optional dependencies
36+
37+
By default, and without any additional dependencies, the syntax supported by Python JSONPath is **very close** to RFC 9535. For strict compatibility with the specification, install [regex](https://pypi.org/project/regex/) and [iregexp-check](https://pypi.org/project/iregexp-check/) packages too.
38+
39+
With these two packages installed, the [`match()`](functions.md#match) and [`search()`](functions.md#search) filter functions will use [regex](https://pypi.org/project/regex/) instead of `re` from the standard library, and will validate regular expression patterns against [RFC 9485](https://datatracker.ietf.org/doc/html/rfc9485).
40+
41+
See the [syntax guide](syntax.md) for more information about strict compatibility with RFC 9535 and extensions to the specification.
42+
3543
## Example
3644

3745
```python

docs/pointers.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ JSON Pointers are a fundamental component of JSON Patch ([RFC 6902](https://data
1010

1111
We have extended RFC 6901 to support:
1212

13-
- Interoperability with the JSONPath [keys selector](syntax.md#keys-or) (`~`)
13+
- Interoperability with the JSONPath [keys selector](syntax.md#keys-selector) (`~`)
1414
- A special non-standard syntax for targeting **keys or indices themselves**, used in conjunction with [Relative JSON Pointer](#torel)
1515

1616
**Keys Selector Compatibility**

docs/quickstart.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@ This page gets you started using JSONPath, JSON Pointer and JSON Patch wih Pytho
44

55
## `findall(path, data)`
66

7-
Find all values matching a JSONPath expression using [`jsonpath.findall()`](api.md#jsonpath.JSONPathEnvironment.findall).
7+
Find all values matching a JSONPath query using [`jsonpath.findall()`](convenience.md#jsonpath.findall).
88

99
This function takes two arguments:
1010

11-
- `path`: a JSONPath expression as a string (e.g., `"$.users[*].name"`)
11+
- `path`: a JSONPath query as a string (e.g. `"$.users[*].name"`)
1212
- `data`: the JSON document to query
1313

14-
It always returns a **list** of matched values, even if the path resolves to a single result or nothing at all.
14+
It **always** returns a list of matched values, even if the path resolves to a single result or nothing at all.
1515

1616
The `data` argument can be:
1717

18-
- A Python [`Mapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping) (e.g., `dict`) or [`Sequence`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Sequence) (e.g., `list`)
18+
- A Python [`Mapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping) (e.g. `dict`) or [`Sequence`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Sequence) (e.g. `list`)
1919
- A JSON-formatted string
2020
- A file-like object containing JSON
2121

@@ -65,7 +65,7 @@ with open("users.json") as fd:
6565

6666
## `finditer(path, data)`
6767

68-
Use [`jsonpath.finditer()`](api.md#jsonpath.JSONPathEnvironment.finditer) to iterate over instances of [`jsonpath.JSONPathMatch`](api.md#jsonpath.JSONPathMatch) for every object in _data_ that matches _path_. It accepts the same arguments as [`findall()`](#findallpath-data), a path string and data from which to select matches.
68+
Use [`jsonpath.finditer()`](convenience.md#jsonpath.finditer) to iterate over instances of [`jsonpath.JSONPathMatch`](api.md#jsonpath.JSONPathMatch) for every object in _data_ that matches _path_. It accepts the same arguments as [`findall()`](#findallpath-data), a query string and data from which to select matches.
6969

7070
```python
7171
import jsonpath
@@ -109,7 +109,7 @@ The selected object is available from a [`JSONPathMatch`](api.md#jsonpath.JSONPa
109109

110110
## `compile(path)`
111111

112-
When you have a JSONPath that needs to be matched against different data repeatedly, you can _compile_ the path ahead of time using [`jsonpath.compile()`](api.md#jsonpath.JSONPathEnvironment.compile). It takes a path as a string and returns a [`JSONPath`](api.md#jsonpath.JSONPath) instance. `JSONPath` has `findall()` and `finditer()` methods that behave similarly to package-level `findall()` and `finditer()`, just without the `path` argument.
112+
When you have a JSONPath query that needs to be matched against different data repeatedly, you can compile the path ahead of time using [`jsonpath.compile()`](convenience.md#jsonpath.compile). It takes a query as a string and returns an instance of [`JSONPath`](api.md#jsonpath.JSONPath). `JSONPath` has `findall()` and `finditer()` methods that behave similarly to package-level `findall()` and `finditer()`, just without the `path` argument.
113113

114114
```python
115115
import jsonpath

0 commit comments

Comments
 (0)