Skip to content

Commit 3aab7af

Browse files
committed
docs: add docstrings to jsonpath module and classes
1 parent cea50cc commit 3aab7af

File tree

1 file changed

+132
-5
lines changed

1 file changed

+132
-5
lines changed

jsonpath/jsonpath.py

Lines changed: 132 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
"""JSONPath implementation for Python.
2+
3+
This module provides a lightweight JSONPath implementation with support for:
4+
- Standard JSONPath operators ($, @, ., .., *, [])
5+
- Filter expressions with comparison, membership, and regex operators
6+
- Sorter expressions for ordering results
7+
- Field extractor expressions
8+
- Value updates via JSONPath
9+
10+
Example:
11+
>>> from jsonpath import JSONPath, search
12+
>>> data = {"store": {"book": [{"price": 10}, {"price": 20}]}}
13+
>>> JSONPath("$..price").parse(data)
14+
[10, 20]
15+
>>> search("$.store.book[0].price", data)
16+
[10]
17+
"""
18+
119
import logging
220
import os
321
import re
@@ -29,14 +47,39 @@ def create_logger(name: str = None, level: Union[int, str] = logging.INFO):
2947

3048

3149
class ExprSyntaxError(Exception):
32-
pass
50+
"""Raised when a JSONPath expression has invalid syntax.
51+
52+
Examples of invalid syntax:
53+
- Using sorter on non-collection types
54+
- Using field-extractor on non-dict types
55+
"""
3356

3457

3558
class JSONPathTypeError(Exception):
36-
pass
59+
"""Raised when type-related errors occur during JSONPath operations.
60+
61+
Examples:
62+
- Comparing incompatible types during sorting (e.g., str vs int)
63+
- Sorting with missing keys that result in None comparisons
64+
"""
3765

3866

3967
class JSONPath:
68+
"""JSONPath expression parser and evaluator.
69+
70+
A JSONPath expression is used to navigate and extract data from JSON objects.
71+
This implementation supports extended syntax including filters, sorters, and
72+
field extractors.
73+
74+
Attributes:
75+
RESULT_TYPE: Supported result types ('VALUE' or 'PATH').
76+
77+
Example:
78+
>>> jp = JSONPath("$.store.book[?(@.price < 10)].title")
79+
>>> jp.parse({"store": {"book": [{"title": "A", "price": 5}]}})
80+
['A']
81+
"""
82+
4083
RESULT_TYPE = {
4184
"VALUE": "A list of specific values.",
4285
"PATH": "All path of specific values.",
@@ -71,6 +114,11 @@ class JSONPath:
71114
REP_DOTDOT_BRACKET = re.compile(r"\.(\.#B)")
72115

73116
def __init__(self, expr: str):
117+
"""Initialize JSONPath with an expression.
118+
119+
Args:
120+
expr: JSONPath expression string (e.g., "$.store.book[*].price")
121+
"""
74122
# Initialize instance variables
75123
self.subx = defaultdict(list)
76124
self.segments = []
@@ -86,6 +134,22 @@ def __init__(self, expr: str):
86134
logger.debug(f"segments : {self.segments}")
87135

88136
def parse(self, obj, result_type="VALUE", eval_func=eval):
137+
"""Parse JSON object using the JSONPath expression.
138+
139+
Args:
140+
obj: JSON object (dict or list) to parse
141+
result_type: Type of result to return
142+
- 'VALUE': Return matched values (default)
143+
- 'PATH': Return JSONPath strings of matched locations
144+
eval_func: Custom eval function for filter expressions (default: builtin eval)
145+
146+
Returns:
147+
List of matched values or paths depending on result_type
148+
149+
Raises:
150+
TypeError: If obj is not a dict or list
151+
ValueError: If result_type is invalid
152+
"""
89153
if not isinstance(obj, (list, dict)):
90154
raise TypeError("obj must be a list or a dict.")
91155

@@ -101,9 +165,15 @@ def parse(self, obj, result_type="VALUE", eval_func=eval):
101165
return self.result
102166

103167
def search(self, obj, result_type="VALUE"):
168+
"""Alias for parse(). Search JSON object using the JSONPath expression."""
104169
return self.parse(obj, result_type)
105170

106171
def _parse_expr(self, expr):
172+
"""Parse and normalize JSONPath expression into segments.
173+
174+
Handles special patterns (quotes, brackets, parentheses) by temporarily
175+
replacing them with placeholders, then splits by dots and restores.
176+
"""
107177
if logger.isEnabledFor(logging.DEBUG):
108178
logger.debug(f"before expr : {expr}")
109179
# pick up special patterns
@@ -237,6 +307,15 @@ def _extract_key_from_group(group: dict):
237307

238308
@staticmethod
239309
def _traverse(f, obj, i: int, path: str, *args):
310+
"""Traverse object children and apply function to each.
311+
312+
Args:
313+
f: Function to apply to each child element
314+
obj: Object to traverse (list or dict)
315+
i: Current segment index
316+
path: Current JSONPath string
317+
*args: Additional arguments to pass to function f
318+
"""
240319
if isinstance(obj, list):
241320
for idx, v in enumerate(obj):
242321
f(v, i, JSONPath._build_path(path, idx), *args)
@@ -246,6 +325,16 @@ def _traverse(f, obj, i: int, path: str, *args):
246325

247326
@staticmethod
248327
def _getattr(obj: Any, path: str, *, convert_number_str=False):
328+
"""Get attribute value from object by dot-notation path.
329+
330+
Args:
331+
obj: Source object (dict)
332+
path: Dot-separated path string (e.g., "author.name")
333+
convert_number_str: If True, convert numeric strings to int/float
334+
335+
Returns:
336+
The value at the path, or _MISSING sentinel if not found
337+
"""
249338
# Fast path for single key (most common case)
250339
if "." not in path:
251340
if isinstance(obj, dict) and path in obj:
@@ -295,6 +384,14 @@ def key_func(t, k):
295384
raise JSONPathTypeError(f"not possible to compare str and int when sorting: {e}") from e
296385

297386
def _filter(self, obj, i: int, path: str, step: str):
387+
"""Evaluate filter expression and continue trace if condition is true.
388+
389+
Args:
390+
obj: Current object to evaluate against filter
391+
i: Next segment index to trace
392+
path: Current JSONPath string
393+
step: Python expression string to evaluate
394+
"""
298395
r = False
299396
try:
300397
r = self.eval_func(step, None, {"__obj": obj, "RegexPattern": RegexPattern})
@@ -304,11 +401,15 @@ def _filter(self, obj, i: int, path: str, step: str):
304401
self._trace(obj, i, path)
305402

306403
def _trace(self, obj, i: int, path):
307-
"""Perform operation on object.
404+
"""Recursively traverse object following JSONPath segments.
405+
406+
This is the core evaluation method that processes each segment of the
407+
parsed JSONPath expression and navigates through the object accordingly.
308408
309409
Args:
310-
obj ([type]): current operating object
311-
i (int): current operation specified by index in self.segments
410+
obj: Current object being traversed
411+
i: Index of current segment in self.segments
412+
path: JSONPath string representing current location
312413
"""
313414

314415
# store
@@ -446,17 +547,43 @@ def update(self, obj: Union[list, dict], value_or_func: Union[Any, Callable[[Any
446547

447548

448549
class RegexPattern:
550+
"""Regex pattern wrapper for use with the =~ operator in filter expressions.
551+
552+
This class enables regex matching syntax like: @.name =~ /pattern/
553+
The @ operator is overloaded to perform the regex search.
554+
555+
Example:
556+
>>> pattern = RegexPattern(r"^test")
557+
>>> "testing" @ pattern
558+
True
559+
"""
560+
449561
def __init__(self, pattern):
562+
"""Initialize with a regex pattern string."""
450563
self.pattern = pattern
451564
self._compiled = re.compile(pattern) # Pre-compile for better performance
452565

453566
def __rmatmul__(self, other):
567+
"""Right matmul operator (@) - checks if other matches the pattern."""
454568
if isinstance(other, str):
455569
return bool(self._compiled.search(other))
456570
return False
457571

458572

459573
def compile(expr):
574+
"""Compile a JSONPath expression for reuse.
575+
576+
Args:
577+
expr: JSONPath expression string
578+
579+
Returns:
580+
JSONPath object that can be used to parse multiple JSON objects
581+
582+
Example:
583+
>>> jp = compile("$.store.book[*].price")
584+
>>> jp.parse(data1)
585+
>>> jp.parse(data2)
586+
"""
460587
return JSONPath(expr)
461588

462589

0 commit comments

Comments
 (0)