Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve docs and doc comments. #56

Merged
merged 2 commits into from
Mar 11, 2024
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
9 changes: 6 additions & 3 deletions docs/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@ This is a list of things that you might find in other JSONPath implementation th
- We do not support arithmetic in filter expression.
- We don't allow dotted array indices. An array index must be surrounded by square brackets.
- Python JSONPath is strictly read only. There are no update "selectors", but we do provide methods for converting `JSONPathMatch` instances to `JSONPointer`s, and a `JSONPatch` builder API for modifying JSON-like data structures using said pointers.
- We don't attempt to handle JSON documents without a top-level array or object (or equivalent Python objects).

And this is a list of areas where we deviate from [RFC 9535](https://datatracker.ietf.org/doc/html/rfc9535).

Expand All @@ -209,14 +208,18 @@ And this is a list of areas where we deviate from [RFC 9535](https://datatracker
- We don't require the recursive descent segment to have a selector. `$..` is equivalent to `$..*`.
- We support explicit comparisons to `undefined` as well as implicit existence tests.
- Float literals without a fractional digit are OK. `1.` is equivalent to `1.0`.
- We treat literals (such as `true` and `false`) as valid "basic" expressions. So `$[?true || false]` does not raise a syntax error, which is and invalid query according to RFC 9535.
- We treat literals (such as `true` and `false`) as valid "basic" expressions. For example, `$[?true || false]`, without an existence test or comparison either side of logical _or_, does not raise a syntax error.
- By default, `and` is equivalent to `&&` and `or` is equivalent to `||`.
- `none` and `nil` are aliases for `null`.
- `null` (and its aliases), `true` and `false` can start with an upper or lower case letter.

And this is a list of features that are uncommon or unique to Python JSONPath.

- We support membership operators `in` and `contains`, plus list/array literals.
- `|` is a union operator, where matches from two or more JSONPaths are combined. This is not part of the Python API, but built-in to the JSONPath syntax.
- `&` is an intersection operator, where we exclude matches that don't exist in both left and right paths. This is not part of the Python API, but built-in to the JSONPath syntax.
- `#` is the current key/property or index identifier when filtering a mapping or sequence.
- `_` is a filter context selector. With usage similar to `$` and `@`, `_` exposes arbitrary data from the `filter_context` argument to `findall()` and `finditer()`.
- `_` is a filter context identifier. With usage similar to `$` and `@`, `_` exposes arbitrary data from the `filter_context` argument to `findall()` and `finditer()`.
- `~` is a "keys" or "properties" selector.
- `^` is a "fake root" identifier. It is equivalent to `$`, but wraps the target JSON document in a single-element array, so the root value can be conditionally selected with a filter selector.
- `=~` is the the regex match operator, matching a value to a JavaScript-style regex literal.
46 changes: 35 additions & 11 deletions jsonpath/selectors.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""JSONPath selector objects, as returned from `Parser.parse`."""
"""JSONPath segments and selectors, as returned from `Parser.parse`."""
from __future__ import annotations

from abc import ABC
Expand Down Expand Up @@ -28,7 +28,7 @@


class JSONPathSelector(ABC):
"""Base class for all JSONPath selectors."""
"""Base class for all JSONPath segments and selectors."""

__slots__ = ("env", "token")

Expand All @@ -38,7 +38,17 @@ def __init__(self, *, env: JSONPathEnvironment, token: Token) -> None:

@abstractmethod
def resolve(self, matches: Iterable[JSONPathMatch]) -> Iterable[JSONPathMatch]:
"""Expand matches from previous JSONPath selectors in to new matches."""
"""Apply the segment/selector to each node in _matches_.

Arguments:
matches: Nodes matched by preceding segments/selectors. This is like
a lazy _NodeList_, as described in RFC 9535, but each match carries
more than the node's value and location.

Returns:
The `JSONPathMatch` instances created by applying this selector to each
preceding node.
"""

@abstractmethod
def resolve_async(
Expand All @@ -48,7 +58,7 @@ def resolve_async(


class PropertySelector(JSONPathSelector):
"""A JSONPath property."""
"""A shorthand or bracketed property selector."""

__slots__ = ("name", "shorthand")

Expand Down Expand Up @@ -115,7 +125,12 @@ async def resolve_async(


class IndexSelector(JSONPathSelector):
"""Dotted and bracketed sequence access by index."""
"""Select an element from an array by index.

Considering we don't require mapping (JSON object) keys/properties to
be quoted, and that we support mappings with numeric keys, we also check
to see if the "index" is a mapping key, which is non-standard.
"""

__slots__ = ("index", "_as_key")

Expand Down Expand Up @@ -213,7 +228,10 @@ async def resolve_async(


class KeysSelector(JSONPathSelector):
"""Select an mapping's keys/properties."""
"""Select mapping/object keys/properties.

NOTE: This is a non-standard selector.
"""

__slots__ = ("shorthand",)

Expand Down Expand Up @@ -354,7 +372,7 @@ async def resolve_async(


class WildSelector(JSONPathSelector):
"""Wildcard expansion selector."""
"""Select all items from a sequence/array or values from a mapping/object."""

__slots__ = ("shorthand",)

Expand Down Expand Up @@ -433,7 +451,10 @@ async def resolve_async(


class RecursiveDescentSelector(JSONPathSelector):
"""A JSONPath selector that visits all objects recursively."""
"""A JSONPath selector that visits all nodes recursively.

NOTE: Strictly this is a "segment", not a "selector".
"""

def __str__(self) -> str:
return ".."
Expand Down Expand Up @@ -504,7 +525,10 @@ async def _alist(it: List[T]) -> AsyncIterable[T]:


class ListSelector(JSONPathSelector):
"""A JSONPath selector representing a list of properties, slices or indices."""
"""A bracketed list of selectors, the results of which are concatenated together.

NOTE: Strictly this is a "segment", not a "selector".
"""

__slots__ = ("items",)

Expand Down Expand Up @@ -555,7 +579,7 @@ async def resolve_async(


class Filter(JSONPathSelector):
"""A filter selector."""
"""Filter sequence/array items or mapping/object values with a filter expression."""

__slots__ = ("expression", "cacheable_nodes")

Expand Down Expand Up @@ -713,7 +737,7 @@ async def resolve_async( # noqa: PLR0912


class FilterContext:
"""A filter expression context."""
"""Contextual information and data for evaluating a filter expression."""

__slots__ = (
"current_key",
Expand Down
Loading