Skip to content

Commit 51f3b48

Browse files
authored
Add documentation into code and examples (#27)
* Add documentation into code and examples This moves the API part of the example usage document into the code where the API is present. Signed-off-by: David Brown <dmlb2000@gmail.com> * Add Docs to the bookstore example This adds better docs to the bookstore example and adds it to the exampleusage page. Signed-off-by: David Brown <dmlb2000@gmail.com>
1 parent 30918bf commit 51f3b48

File tree

7 files changed

+313
-134
lines changed

7 files changed

+313
-134
lines changed

docs/conf.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616
# add these directories to sys.path here. If the directory is relative to the
1717
# documentation root, use os.path.abspath to make it absolute, like shown here.
1818
#
19+
import sys
20+
import os
1921
from recommonmark.parser import CommonMarkParser
2022
from recommonmark.transform import AutoStructify
2123

24+
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'tests'))
2225

2326
# -- Project information -----------------------------------------------------
2427

docs/exampleusage.md

Lines changed: 91 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -3,112 +3,6 @@
33
The JSONPath2 library has several APIs available to perform JSONPath
44
matching.
55

6-
## API
7-
8-
The API has a high level `match()` method and lower level classes to
9-
interact with the parser directly.
10-
11-
### `match` shortcut function
12-
13-
The `jsonpath2.match` function is a shortcut to match a given JSON data
14-
structure against a JSONPath string
15-
16-
```python
17-
>>> import jsonpath2
18-
>>> doc = {'hello': 'Hello, world!'}
19-
>>> [x.current_value for x in jsonpath2.match('$.hello', doc)]
20-
['Hello, world!']
21-
```
22-
23-
### `Path` class
24-
25-
The `jsonpath2.path.Path` class represents a JSONPath.
26-
27-
```python
28-
>>> s = '{"hello":"Hello, world!"}'
29-
'{"hello":"Hello, world!"}'
30-
>>> import json
31-
>>> d = json.loads(s)
32-
{'hello':'Hello, world!'}
33-
>>> from jsonpath2.path import Path
34-
>>> p = Path.parse_str('$["hello"]')
35-
<jsonpath2.path.Path object>
36-
>>> [match_data.current_value for match_data in p.match(d)]
37-
['Hello, world!']
38-
>>> [match_data.node.tojsonpath() for match_data in p.match(d)]
39-
['$["hello"]']
40-
```
41-
42-
This class is constructed with respect to the given instance of the `jsonpath2.nodes.root.RootNode` class (viz., the `ro
43-
ot_node` property).
44-
45-
#### `parse_str(strdata)` class method
46-
47-
Parse the given string and return a new instance of this class.
48-
49-
#### `parse_file(fileName, encoding='ascii')` class method
50-
51-
Parse the contents of the given file and return a new instance of this class.
52-
53-
#### `match(root_value)` instance method
54-
55-
Match the given JSON data structure against this instance.
56-
For each match, yield an instance of the `jsonpath2.node.MatchData` class.
57-
58-
#### `__eq__(other)` instance method
59-
60-
Tests if two instances are equal.
61-
62-
#### `__str__()` instance method
63-
64-
Returns the string representation of this instance.
65-
66-
#### `root_node` property
67-
68-
The root node of the abstract syntax tree for this instance.
69-
70-
### `Node` abstract class
71-
72-
The `jsonpath2.node.Node` class represents the abstract syntax tree for a JSONPath.
73-
74-
#### `__eq__(other)` instance method
75-
76-
Tests if two instances are equal.
77-
78-
#### `__jsonpath__()` instance method
79-
80-
Yields the lexer tokens for the string representation of this instance.
81-
82-
#### `match(root_value, current_value)` instance method
83-
84-
Match the given root and current JSON data structures against this instance.
85-
For each match, yield an instance of the `jsonpath2.node.MatchData` class.
86-
87-
#### `tojsonpath()` instance method
88-
89-
Returns the string representation of this instance.
90-
91-
### `MatchData` class
92-
93-
The `jsonpath2.node.MatchData` class represents the JSON value and context for a JSONPath match.
94-
95-
This class is constructed with respect to a root JSON value, a current JSON value, and an abstract syntax tree node.
96-
97-
#### `__eq__(other)` instance method
98-
99-
Tests if two instances are equal.
100-
101-
#### `root_value` property
102-
103-
The root JSON value.
104-
105-
#### `current_value` property
106-
107-
The current JSON value (i.e., the matching JSON value).
108-
109-
#### `node` property
110-
111-
The abstract syntax tree node.
1126
## Syntax
1137

1148
```eval_rst
@@ -163,8 +57,9 @@ The abstract syntax tree node.
16357

16458
> See [#14](https://github.com/pacifica/python-jsonpath2/pull/14) for more information.
16559
166-
The syntax for a function call is the name of the function followed by the arguments in parentheses, i.e., `name(arg1, a
167-
rg2, ..., argN)`, where the arguments are either JSONPaths or JSON values.
60+
The syntax for a function call is the name of the function followed by the
61+
arguments in parentheses, i.e., `name(arg1, arg2, ..., argN)`, where the
62+
arguments are either JSONPaths or JSON values.
16863

16964
```python
17065
>>> s = '{"hello":"Hello, world!"}'
@@ -300,6 +195,94 @@ rg2, ..., argN)`, where the arguments are either JSONPaths or JSON values.
300195
In the above table, the type aliases (`Any`, `List`, `Optional` and `Tuple`) are defined by the
301196
[`typing`](https://docs.python.org/3/library/typing.html) module from the Python Standard Library.
302197

198+
## Examples
199+
200+
Some of the examples are provided by the test suite while some have been contributed via issues.
201+
202+
### Test Suite Examples
203+
204+
```eval_rst
205+
.. automodule:: bookstore_test
206+
:members:
207+
:private-members:
208+
:special-members:
209+
```
210+
211+
### Issue Examples
212+
213+
#### Issue #19
214+
215+
This issue involved finding the full path to the matched attribute.
216+
217+
The result isn't strictly supported by the library but code examples are provided.
218+
219+
```python
220+
import json
221+
import typing
222+
223+
from jsonpath2.node import Node
224+
from jsonpath2.nodes.root import RootNode
225+
from jsonpath2.nodes.subscript import SubscriptNode
226+
from jsonpath2.nodes.terminal import TerminalNode
227+
from jsonpath2.path import Path
228+
from jsonpath2.subscript import Subscript
229+
230+
data = json.loads("""
231+
{
232+
"values": [
233+
{"type": 1, "value": 2},
234+
{"type": 2, "value": 3},
235+
{"type": 1, "value": 10}
236+
]
237+
}
238+
""")
239+
240+
path = Path.parse_str("$.values.*[?(@.type = 1)].value")
241+
242+
def get_subscripts(node: Node) -> typing.List[typing.List[Subscript]]:
243+
return get_subscripts_(node, [])
244+
245+
def get_subscripts_(node: Node, accumulator: typing.List[typing.List[Subscript]]) -> typing.List[typing.List[Subscript]]:
246+
if isinstance(node, RootNode):
247+
return get_subscripts_(node.next_node, accumulator)
248+
elif isinstance(node, SubscriptNode):
249+
accumulator.append(node.subscripts)
250+
return get_subscripts_(node.next_node, accumulator)
251+
elif isinstance(node, TerminalNode):
252+
return accumulator
253+
254+
for match_data in path.match(data):
255+
print(f"Value: {match_data.current_value}")
256+
print(f"JSONPath: {match_data.node.tojsonpath()}")
257+
print(f"Subscripts: {get_subscripts(match_data.node)}")
258+
print("")
259+
```
260+
261+
The snippet above iterates over the match results, prints the value and
262+
JSONPath and then prints the list of subscripts. The list of subscripts
263+
is constructed by traversing the structure of the abstract syntax tree
264+
for the JSONPath.
265+
266+
The results [modulo the memory addresses] are:
267+
268+
```
269+
Value: 2
270+
JSONPath: $["values"][0]["value"]
271+
Subscripts: [[<jsonpath2.subscripts.objectindex.ObjectIndexSubscript object at 0x10f6a3278>], [<jsonpath2.subscripts.arrayindex.ArrayIndexSubscript object at 0x10f6a37b8>], [<jsonpath2.subscripts.objectindex.ObjectIndexSubscript object at 0x10f6a3390>]]
272+
273+
Value: 10
274+
JSONPath: $["values"][2]["value"]
275+
Subscripts: [[<jsonpath2.subscripts.objectindex.ObjectIndexSubscript object at 0x10f6a3278>], [<jsonpath2.subscripts.arrayindex.ArrayIndexSubscript object at 0x10f6a3978>], [<jsonpath2.subscripts.objectindex.ObjectIndexSubscript object at 0x10f6a3390>]]
276+
```
277+
278+
The first subscript is the `"values"` key. The second subscript is the
279+
index of the `{"type":"value"}` object. The third subscript is the
280+
`"value"` key.
281+
282+
Note that the result (the list of subscripts) is a list of lists. This
283+
is because instances of the `SubscriptNode` class are constructed using
284+
zero or more instances of the `Subscript` class.
285+
303286
## Grammar and parser
304287

305288
The [ANTLR v4](https://github.com/antlr/antlr4) grammar for JSONPath is available at `jsonpath2/parser/JSONPath.g4`.

jsonpath2/__init__.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@
88

99

1010
def match(path_str: str, root_value: object) -> Generator[MatchData, None, None]:
11-
"""Match root value of the path."""
11+
"""
12+
Match root value of the path.
13+
14+
The ``jsonpath2.match`` function is a shortcut to match a given JSON data
15+
structure against a JSONPath string.
16+
17+
.. code-block:: python
18+
19+
>>> import jsonpath2
20+
>>> doc = {'hello': 'Hello, world!'}
21+
>>> [x.current_value for x in jsonpath2.match('$.hello', doc)]
22+
['Hello, world!']
23+
"""
1224
path = Path.parse_str(path_str)
1325
return path.match(root_value)

jsonpath2/node.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,20 @@
88

99
# pylint: disable=too-few-public-methods
1010
class MatchData:
11-
"""Match data object for storing node values."""
11+
"""
12+
Match data object for storing node values.
13+
14+
The ``jsonpath2.node.MatchData`` class represents the JSON
15+
value and context for a JSONPath match.
16+
17+
This class is constructed with respect to a root JSON value,
18+
a current JSON value, and an abstract syntax tree node.
19+
20+
Attributes:
21+
- ``root_value`` The root JSON value.
22+
- ``current_value`` The current JSON value (i.e., the matching JSON value).
23+
- ``node`` The abstract syntax tree node.
24+
"""
1225

1326
def __init__(self, node, root_value, current_value):
1427
"""Constructor to save root and current node values."""
@@ -18,22 +31,39 @@ def __init__(self, node, root_value, current_value):
1831
self.current_value = current_value
1932

2033
def __eq__(self, other: object) -> bool:
21-
"""Test if two MatchData objects are the same."""
34+
"""
35+
Test if two MatchData objects are the same.
36+
37+
Tests if two instances are equal.
38+
"""
2239
return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__)
2340
# pylint: enable=too-few-public-methods
2441

2542

2643
class Node(ToJSONPath):
27-
"""Node object for the jsonpath parsetree."""
44+
"""
45+
Node object for the jsonpath parsetree.
46+
47+
The ``jsonpath2.node.Node`` class represents the abstract syntax tree for a JSONPath.
48+
"""
2849

2950
def __eq__(self, other: object) -> bool:
30-
"""Determine if two Nodes are the same."""
51+
"""
52+
Determine if two Nodes are the same.
53+
54+
Tests if two instances are equal.
55+
"""
3156
return isinstance(other, self.__class__) and (self.__dict__ == other.__dict__)
3257

3358
@abstractmethod
3459
def match(
3560
self,
3661
root_value: object,
3762
current_value: object) -> Generator[MatchData, None, None]: # pragma: no cover abstract method.
38-
"""Abstract method to determine a node match."""
63+
"""
64+
Abstract method to determine a node match.
65+
66+
Match the given root and current JSON data structures against this instance.
67+
For each match, yield an instance of the ``jsonpath2.node.MatchData`` class.
68+
"""
3969
raise NotImplementedError()

0 commit comments

Comments
 (0)