Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

python-jsonpath

Package Overview
Dependencies
Maintainers
1
Versions
28
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

python-jsonpath - pypi Package Compare versions

Comparing version
1.1.1
to
1.2.0
+4
-1
.gitignore

@@ -88,2 +88,5 @@ # Byte-compiled / optimized / DLL files

comparison_regression_suite.yaml
cts.json
cts.json
# System
.DS_Store
+1
-1
# SPDX-FileCopyrightText: 2023-present James Prior <jamesgr.prior@gmail.com>
#
# SPDX-License-Identifier: MIT
__version__ = "1.1.1"
__version__ = "1.2.0"

@@ -6,2 +6,4 @@ # SPDX-FileCopyrightText: 2023-present James Prior <jamesgr.prior@gmail.com>

from .env import JSONPathEnvironment
from .exceptions import JSONPatchError
from .exceptions import JSONPatchTestFailure
from .exceptions import JSONPathError

@@ -12,2 +14,3 @@ from .exceptions import JSONPathIndexError

from .exceptions import JSONPathTypeError
from .exceptions import JSONPointerEncodeError
from .exceptions import JSONPointerError

@@ -22,2 +25,3 @@ from .exceptions import JSONPointerIndexError

from .filter import UNDEFINED
from .fluent_api import Projection
from .fluent_api import Query

@@ -42,2 +46,4 @@ from .lex import Lexer

"JSONPatch",
"JSONPatchError",
"JSONPatchTestFailure",
"JSONPath",

@@ -52,2 +58,3 @@ "JSONPathEnvironment",

"JSONPointer",
"JSONPointerEncodeError",
"JSONPointerError",

@@ -61,2 +68,3 @@ "JSONPointerIndexError",

"Parser",
"Projection",
"query",

@@ -63,0 +71,0 @@ "Query",

"""Core JSONPath configuration object."""
from __future__ import annotations

@@ -320,3 +321,3 @@

) -> 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_.

@@ -356,4 +357,19 @@ `Query` objects are iterable.

```
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))
return Query(self.finditer(path, data, filter_context=filter_context), self)

@@ -553,5 +569,5 @@ async def findall_async(

return self._lt(left, right) or self._eq(left, right)
if operator == "in" and isinstance(right, Sequence):
if operator == "in" and isinstance(right, (Mapping, Sequence)):
return left in right
if operator == "contains" and isinstance(left, Sequence):
if operator == "contains" and isinstance(left, (Mapping, Sequence)):
return right in left

@@ -558,0 +574,0 @@ if operator == "=~" and isinstance(right, re.Pattern) and isinstance(left, str):

@@ -671,2 +671,5 @@ """Filter expression nodes."""

def __str__(self) -> str:
return "#"
def __eq__(self, other: object) -> bool:

@@ -673,0 +676,0 @@ return isinstance(other, CurrentKey)

@@ -1,2 +0,3 @@

"""A fluent API for managing JSONPathMatch iterators."""
"""A fluent API for working with `JSONPathMatch` iterators."""
from __future__ import annotations

@@ -6,9 +7,20 @@

import itertools
from enum import Enum
from enum import auto
from typing import TYPE_CHECKING
from typing import Any
from typing import Dict
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Mapping
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Union
if TYPE_CHECKING:
from jsonpath import CompoundJSONPath
from jsonpath import JSONPath
from jsonpath import JSONPathEnvironment
from jsonpath import JSONPathMatch

@@ -18,2 +30,17 @@ from jsonpath import JSONPointer

class Projection(Enum):
"""Projection style used by `Query.select()`."""
RELATIVE = auto()
"""The default projection. Selections include parent arrays and objects relative
to the JSONPathMatch."""
ROOT = auto()
"""Selections include parent arrays and objects relative to the root JSON value."""
FLAT = auto()
"""All selections are appended to a new array/list, without arrays and objects
on the path to the selected value."""
class Query:

@@ -32,4 +59,5 @@ """A fluent API for managing `JSONPathMatch` iterators.

def __init__(self, it: Iterable[JSONPathMatch]) -> None:
def __init__(self, it: Iterable[JSONPathMatch], env: JSONPathEnvironment) -> None:
self._it = iter(it)
self._env = env

@@ -124,3 +152,3 @@ def __iter__(self) -> Iterator[JSONPathMatch]:

def items(self) -> Iterable[Tuple[str, object]]:
"""Return an iterable of (object, path) tuples, one for each match."""
"""Return an iterable of (path, object) tuples, one for each match."""
return ((m.path, m.obj) for m in self._it)

@@ -158,3 +186,3 @@

"""
return tuple(Query(it) for it in itertools.tee(self._it, n))
return tuple(Query(it, self._env) for it in itertools.tee(self._it, n))

@@ -166,2 +194,99 @@ def take(self, n: int) -> Query:

"""
return Query(list(itertools.islice(self._it, n)))
return Query(list(itertools.islice(self._it, n)), self._env)
def select(
self,
*expressions: Union[str, JSONPath, CompoundJSONPath],
projection: Projection = Projection.RELATIVE,
) -> Iterable[object]:
"""Query projection using relative JSONPaths.
Arguments:
expressions: One or more JSONPath query expressions to select relative
to each match in this query iterator.
projection: The style of projection used when selecting values. Can be
one of `Projection.RELATIVE`, `Projection.ROOT` or `Projection.FLAT`.
Defaults to `Projection.RELATIVE`.
Returns:
An iterable of objects built from selecting _expressions_ relative to
each match from the current query.
**New in version 1.2.0**
"""
return filter(
bool,
(self._select(m, expressions, projection) for m in self._it),
)
def _select(
self,
match: JSONPathMatch,
expressions: Tuple[Union[str, JSONPath, CompoundJSONPath], ...],
projection: Projection,
) -> object:
if not isinstance(match.obj, (Mapping, Sequence)) or isinstance(match.obj, str):
return None
if projection == Projection.RELATIVE:
obj: Dict[Union[int, str], Any] = {}
for expr in expressions:
path = self._env.compile(expr) if isinstance(expr, str) else expr
for rel_match in path.finditer(match.obj): # type: ignore
_patch_obj(rel_match.parts, obj, rel_match.obj)
return _fix_sparse_arrays(obj)
if projection == Projection.FLAT:
arr: List[object] = []
for expr in expressions:
path = self._env.compile(expr) if isinstance(expr, str) else expr
for rel_match in path.finditer(match.obj): # type: ignore
arr.append(rel_match.obj)
return arr
# Project from the root document
obj = {}
for expr in expressions:
path = self._env.compile(expr) if isinstance(expr, str) else expr
for rel_match in path.finditer(match.obj): # type: ignore
_patch_obj(match.parts + rel_match.parts, obj, rel_match.obj)
return _fix_sparse_arrays(obj)
def _patch_obj(
parts: Tuple[Union[int, str], ...],
obj: Mapping[Union[str, int], Any],
value: object,
) -> None:
_obj = obj
# For lack of a better idea, we're patching arrays to dictionaries with
# integer keys. This is to handle sparse array selections without having
# to keep track of indexes and how they map from the root JSON value to
# the selected JSON value.
#
# We'll fix these "sparse arrays" after the patch has been applied.
for part in parts[:-1]:
if part not in _obj:
_obj[part] = {} # type: ignore
_obj = _obj[part]
_obj[parts[-1]] = value # type: ignore
def _fix_sparse_arrays(obj: Any) -> object:
"""Fix sparse arrays (dictionaries with integer keys)."""
if isinstance(obj, str) or not obj:
return obj
if isinstance(obj, Sequence):
return [_fix_sparse_arrays(e) for e in obj]
if isinstance(obj, Mapping):
if isinstance(next(iter(obj)), int):
return [_fix_sparse_arrays(v) for v in obj.values()]
return {k: _fix_sparse_arrays(v) for k, v in obj.items()}
return obj
"""JSONPath tokenization."""
from __future__ import annotations

@@ -141,4 +142,4 @@

(TOKEN_DOT_PROPERTY, self.dot_property_pattern),
(TOKEN_FLOAT, r"-?\d+\.\d*(?:e[+-]?\d+)?"),
(TOKEN_INT, r"-?\d+(?P<G_EXP>e[+\-]?\d+)?\b"),
(TOKEN_FLOAT, r"-?\d+\.\d*(?:[eE][+-]?\d+)?"),
(TOKEN_INT, r"-?\d+(?P<G_EXP>[eE][+\-]?\d+)?\b"),
(TOKEN_DDOT, r"\.\."),

@@ -145,0 +146,0 @@ (TOKEN_AND, self.logical_and_pattern),

@@ -33,2 +33,4 @@ """The default JSONPath parser."""

from .filter import ListLiteral
from .filter import Literal
from .filter import Nil
from .filter import Path

@@ -195,5 +197,22 @@ from .filter import PrefixExpression

"!=",
"=~",
]
)
# Infix operators that accept filter expression literals.
INFIX_LITERAL_OPERATORS = frozenset(
[
"==",
">=",
">",
"<=",
"<",
"!=",
"<>",
"=~",
"in",
"contains",
]
)
PREFIX_OPERATORS = frozenset(

@@ -460,2 +479,8 @@ [

if stream.peek.kind == TOKEN_RBRACKET:
raise JSONPathSyntaxError(
"unexpected trailing comma",
token=stream.peek,
)
stream.next_token()

@@ -483,2 +508,9 @@

if isinstance(expr, (Literal, Nil)):
raise JSONPathSyntaxError(
"filter expression literals outside of "
"function expressions must be compared",
token=tok,
)
return Filter(env=self.env, token=tok, expression=BooleanExpression(expr))

@@ -527,2 +559,16 @@

if operator not in self.INFIX_LITERAL_OPERATORS:
if isinstance(left, (Literal, Nil)):
raise JSONPathSyntaxError(
"filter expression literals outside of "
"function expressions must be compared",
token=tok,
)
if isinstance(right, (Literal, Nil)):
raise JSONPathSyntaxError(
"filter expression literals outside of "
"function expressions must be compared",
token=tok,
)
return InfixExpression(left, operator, right)

@@ -540,2 +586,9 @@

)
if stream.current.kind not in self.BINARY_OPERATORS:
raise JSONPathSyntaxError(
f"expected an expression, found '{stream.current.value}'",
token=stream.current,
)
expr = self.parse_infix_expression(stream, expr)

@@ -548,3 +601,2 @@

root = stream.next_token()
assert root.kind in {TOKEN_ROOT, TOKEN_FAKE_ROOT} # XXX:
return RootPath(

@@ -551,0 +603,0 @@ JSONPath(

"""JSON Patch, as per RFC 6902."""
from __future__ import annotations

@@ -88,2 +89,74 @@

class OpAddNe(OpAdd):
"""A non-standard _add if not exists_ operation.
This is like _OpAdd_, but only adds object/dict keys/values if they key does
not already exist.
**New in version 1.2.0**
"""
__slots__ = ("path", "value")
name = "addne"
def apply(
self, data: Union[MutableSequence[object], MutableMapping[str, object]]
) -> Union[MutableSequence[object], MutableMapping[str, object]]:
"""Apply this patch operation to _data_."""
parent, obj = self.path.resolve_parent(data)
if parent is None:
# Replace the root object.
# The following op, if any, will raise a JSONPatchError if needed.
return self.value # type: ignore
target = self.path.parts[-1]
if isinstance(parent, MutableSequence):
if obj is UNDEFINED:
parent.append(self.value)
else:
parent.insert(int(target), self.value)
elif isinstance(parent, MutableMapping) and target not in parent:
parent[target] = self.value
return data
class OpAddAp(OpAdd):
"""A non-standard add operation that appends to arrays/lists .
This is like _OpAdd_, but assumes an index of "-" if the path can not
be resolved.
**New in version 1.2.0**
"""
__slots__ = ("path", "value")
name = "addap"
def apply(
self, data: Union[MutableSequence[object], MutableMapping[str, object]]
) -> Union[MutableSequence[object], MutableMapping[str, object]]:
"""Apply this patch operation to _data_."""
parent, obj = self.path.resolve_parent(data)
if parent is None:
# Replace the root object.
# The following op, if any, will raise a JSONPatchError if needed.
return self.value # type: ignore
target = self.path.parts[-1]
if isinstance(parent, MutableSequence):
if obj is UNDEFINED:
parent.append(self.value)
else:
parent.insert(int(target), self.value)
elif isinstance(parent, MutableMapping):
parent[target] = self.value
else:
raise JSONPatchError(
f"unexpected operation on {parent.__class__.__name__!r}"
)
return data
class OpRemove(Op):

@@ -344,2 +417,12 @@ """The JSON Patch _remove_ operation."""

)
elif op == "addne":
self.addne(
path=self._op_pointer(operation, "path", "addne", i),
value=self._op_value(operation, "value", "addne", i),
)
elif op == "addap":
self.addne(
path=self._op_pointer(operation, "path", "addap", i),
value=self._op_value(operation, "value", "addap", i),
)
elif op == "remove":

@@ -429,2 +512,34 @@ self.remove(path=self._op_pointer(operation, "path", "add", i))

def addne(self: Self, path: Union[str, JSONPointer], value: object) -> Self:
"""Append an _addne_ operation to this patch.
Arguments:
path: A string representation of a JSON Pointer, or one that has
already been parsed.
value: The object to add.
Returns:
This `JSONPatch` instance, so we can build a JSON Patch by chaining
calls to JSON Patch operation methods.
"""
pointer = self._ensure_pointer(path)
self.ops.append(OpAddNe(path=pointer, value=value))
return self
def addap(self: Self, path: Union[str, JSONPointer], value: object) -> Self:
"""Append an _addap_ operation to this patch.
Arguments:
path: A string representation of a JSON Pointer, or one that has
already been parsed.
value: The object to add.
Returns:
This `JSONPatch` instance, so we can build a JSON Patch by chaining
calls to JSON Patch operation methods.
"""
pointer = self._ensure_pointer(path)
self.ops.append(OpAddAp(path=pointer, value=value))
return self
def remove(self: Self, path: Union[str, JSONPointer]) -> Self:

@@ -557,2 +672,3 @@ """Append a _remove_ operation to this patch.

raise JSONPatchError(f"{err} ({op.name}:{i})") from err
return _data

@@ -559,0 +675,0 @@

@@ -18,2 +18,3 @@ # noqa: D100

from jsonpath._data import load_data
from jsonpath.fluent_api import Query
from jsonpath.match import FilterContextVars

@@ -214,2 +215,26 @@ from jsonpath.match import JSONPathMatch

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:

@@ -412,2 +437,26 @@ """Return `True` if this path has no selectors."""

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 union(self, path: JSONPath) -> CompoundJSONPath:

@@ -414,0 +463,0 @@ """Union of this path and another path."""

"""JSON Pointer. See https://datatracker.ietf.org/doc/html/rfc6901."""
from __future__ import annotations

@@ -329,2 +330,5 @@

def __hash__(self) -> int:
return hash(self.parts)
def __repr__(self) -> str:

@@ -331,0 +335,0 @@ return f"JSONPointer({self._s!r})"

Metadata-Version: 2.1
Name: python-jsonpath
Version: 1.1.1
Version: 1.2.0
Summary: JSONPath, JSON Pointer and JSON Patch for Python.

@@ -59,2 +59,3 @@ Project-URL: Documentation, https://jg-rp.github.io/python-jsonpath/

- [Links](#links)
- [Related projects](#related-projects)
- [Examples](#examples)

@@ -92,2 +93,10 @@ - [License](#license)

## Related projects
- [Python JSONPath RFC 9535](https://github.com/jg-rp/python-jsonpath-rfc9535) - An implementation of JSONPath that follows RFC 9535 much more strictly. If you require maximum interoperability with JSONPath implemented in other languages - at the expense of extra features - choose python-jsonpath-rfc9535 over python-jsonpath.
python-jsonpath-rfc9535 matches RFC 9535's JSONPath model internally and is careful to match the spec's terminology. It also includes utilities for verifying and testing the [JSONPath Compliance Test Suite](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite). Most notably the nondeterministic behavior of some JSONPath selectors.
- [JSON P3](https://github.com/jg-rp/json-p3) - RFC 9535 implemented in TypeScript. JSON P3 does not include all the non-standard features of Python JSONPath, but does define some optional [extra syntax](https://jg-rp.github.io/json-p3/guides/jsonpath-extra).
## Examples

@@ -115,3 +124,3 @@

Since version 0.8.0, we include an [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) compliant implementation of JSON Pointer. See JSON Pointer [quick start](https://jg-rp.github.io/python-jsonpath/quickstart/#pointerresolvepointer-data), [guide](https://jg-rp.github.io/python-jsonpath/pointers/) and [API reference](https://jg-rp.github.io/python-jsonpath/api/#jsonpath.JSONPointer)
We include an [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) compliant implementation of JSON Pointer. See JSON Pointer [quick start](https://jg-rp.github.io/python-jsonpath/quickstart/#pointerresolvepointer-data), [guide](https://jg-rp.github.io/python-jsonpath/pointers/) and [API reference](https://jg-rp.github.io/python-jsonpath/api/#jsonpath.JSONPointer)

@@ -139,3 +148,3 @@ ```python

Since version 0.8.0, we also include an [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902) compliant implementation of JSON Patch. See JSON Patch [quick start](https://jg-rp.github.io/python-jsonpath/quickstart/#patchapplypatch-data) and [API reference](https://jg-rp.github.io/python-jsonpath/api/#jsonpath.JSONPatch)
We also include an [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902) compliant implementation of JSON Patch. See JSON Patch [quick start](https://jg-rp.github.io/python-jsonpath/quickstart/#patchapplypatch-data) and [API reference](https://jg-rp.github.io/python-jsonpath/api/#jsonpath.JSONPatch)

@@ -142,0 +151,0 @@ ```python

@@ -34,2 +34,3 @@ <h1 align="center">Python JSONPath</h1>

- [Links](#links)
- [Related projects](#related-projects)
- [Examples](#examples)

@@ -67,2 +68,10 @@ - [License](#license)

## Related projects
- [Python JSONPath RFC 9535](https://github.com/jg-rp/python-jsonpath-rfc9535) - An implementation of JSONPath that follows RFC 9535 much more strictly. If you require maximum interoperability with JSONPath implemented in other languages - at the expense of extra features - choose python-jsonpath-rfc9535 over python-jsonpath.
python-jsonpath-rfc9535 matches RFC 9535's JSONPath model internally and is careful to match the spec's terminology. It also includes utilities for verifying and testing the [JSONPath Compliance Test Suite](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite). Most notably the nondeterministic behavior of some JSONPath selectors.
- [JSON P3](https://github.com/jg-rp/json-p3) - RFC 9535 implemented in TypeScript. JSON P3 does not include all the non-standard features of Python JSONPath, but does define some optional [extra syntax](https://jg-rp.github.io/json-p3/guides/jsonpath-extra).
## Examples

@@ -90,3 +99,3 @@

Since version 0.8.0, we include an [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) compliant implementation of JSON Pointer. See JSON Pointer [quick start](https://jg-rp.github.io/python-jsonpath/quickstart/#pointerresolvepointer-data), [guide](https://jg-rp.github.io/python-jsonpath/pointers/) and [API reference](https://jg-rp.github.io/python-jsonpath/api/#jsonpath.JSONPointer)
We include an [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) compliant implementation of JSON Pointer. See JSON Pointer [quick start](https://jg-rp.github.io/python-jsonpath/quickstart/#pointerresolvepointer-data), [guide](https://jg-rp.github.io/python-jsonpath/pointers/) and [API reference](https://jg-rp.github.io/python-jsonpath/api/#jsonpath.JSONPointer)

@@ -114,3 +123,3 @@ ```python

Since version 0.8.0, we also include an [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902) compliant implementation of JSON Patch. See JSON Patch [quick start](https://jg-rp.github.io/python-jsonpath/quickstart/#patchapplypatch-data) and [API reference](https://jg-rp.github.io/python-jsonpath/api/#jsonpath.JSONPatch)
We also include an [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902) compliant implementation of JSON Patch. See JSON Patch [quick start](https://jg-rp.github.io/python-jsonpath/quickstart/#patchapplypatch-data) and [API reference](https://jg-rp.github.io/python-jsonpath/api/#jsonpath.JSONPatch)

@@ -117,0 +126,0 @@ ```python