Skip to content

Pointer Backends

This module contains the pointer backend protocol and related backend-level utilities used by JSONPointer.

PointerBackend

Bases: Protocol

NOTE: also require that parent pointers are constructable from parts[:-1] OR require that in certain methods! Protocol for custom JSON Pointer backends.

This library is pointer-backend agnostic. By default it uses jsonpointer.JsonPointer, but advanced users may plug in a custom backend (different parsing or escaping rules, richer pointer objects, alternative traversal semantics, and so on).

A backend only needs to provide a small pointer-shaped surface area:

  • Constructible from a pointer string.
  • Exposes unescaped path tokens via parts.
  • Can be reconstructed from tokens via from_parts.
  • Can resolve a pointer against a document via resolve.
  • Has a round-trippable string form via __str__.
Notes
  • The backend defines its own pointer syntax; there is no universal "root" string.
  • Round-trip invariants should hold for the backend's canonical string form: PointerBackend(x) equals PointerBackend(str(PointerBackend(x))) and PointerBackend(x) equals PointerBackend.from_parts(PointerBackend(x).parts).
  • The library may cache backend instances; implementations should be immutable or otherwise safe to reuse across calls.
  • Backends may raise whatever exceptions are natural for them. Higher-level APIs normalize backend failures into library patch errors for a consistent user experience.
Source code in jsonpatchx/backend.py
@runtime_checkable
class PointerBackend(Protocol):
    """
    NOTE: also require that parent pointers are constructable from parts[:-1] OR require that in certain methods!
    Protocol for custom JSON Pointer backends.

    This library is pointer-backend agnostic. By default it uses ``jsonpointer.JsonPointer``,
    but advanced users may plug in a custom backend (different parsing or escaping rules, richer
    pointer objects, alternative traversal semantics, and so on).

    A backend only needs to provide a small pointer-shaped surface area:

    - Constructible from a pointer string.
    - Exposes unescaped path tokens via ``parts``.
    - Can be reconstructed from tokens via ``from_parts``.
    - Can resolve a pointer against a document via ``resolve``.
    - Has a round-trippable string form via ``__str__``.

    Notes:
        - The backend defines its own pointer syntax; there is no universal "root" string.
        - Round-trip invariants should hold for the backend's canonical string form:
          ``PointerBackend(x)`` equals ``PointerBackend(str(PointerBackend(x)))`` and
          ``PointerBackend(x)`` equals ``PointerBackend.from_parts(PointerBackend(x).parts)``.
        - The library may cache backend instances; implementations should be immutable or otherwise
          safe to reuse across calls.
        - Backends may raise whatever exceptions are natural for them. Higher-level APIs normalize
          backend failures into library patch errors for a consistent user experience.
    """

    @abstractmethod
    def __init__(self, pointer: str) -> None:
        """Parse and construct a backend-specific pointer."""

    @classmethod
    @abstractmethod
    def from_parts(cls, parts: Iterable[str]) -> Self:
        """
        Construct a pointer from unescaped tokens.

        Implementations may accept tokens beyond strings (e.g. ints) and stringify them,
        but must preserve the invariant that ``from_parts(ptr.parts)`` round-trips.
        """

    @abstractmethod
    def resolve(self, doc: JSONValue) -> JSONValue:
        """
        Resolve the pointer against a document using backend-defined traversal semantics.

        Implementations typically follow dict/list traversal rules, but the library
        does not require a particular exception type on failure.
        """

    @override
    @abstractmethod
    def __str__(self) -> str:
        """
        Return the backend's canonical string form (escaped tokens, if applicable).

        Must round-trip such that ``PointerBackend(str(ptr))`` yields an equivalent pointer.
        """

    @property
    @abstractmethod
    def parts(self) -> Sequence[str]:
        """Unescaped backend-specific tokens."""

parts abstractmethod property

Unescaped backend-specific tokens.

from_parts(parts) abstractmethod classmethod

Construct a pointer from unescaped tokens.

Implementations may accept tokens beyond strings (e.g. ints) and stringify them, but must preserve the invariant that from_parts(ptr.parts) round-trips.

Source code in jsonpatchx/backend.py
@classmethod
@abstractmethod
def from_parts(cls, parts: Iterable[str]) -> Self:
    """
    Construct a pointer from unescaped tokens.

    Implementations may accept tokens beyond strings (e.g. ints) and stringify them,
    but must preserve the invariant that ``from_parts(ptr.parts)`` round-trips.
    """

resolve(doc) abstractmethod

Resolve the pointer against a document using backend-defined traversal semantics.

Implementations typically follow dict/list traversal rules, but the library does not require a particular exception type on failure.

Source code in jsonpatchx/backend.py
@abstractmethod
def resolve(self, doc: JSONValue) -> JSONValue:
    """
    Resolve the pointer against a document using backend-defined traversal semantics.

    Implementations typically follow dict/list traversal rules, but the library
    does not require a particular exception type on failure.
    """

TargetState

Bases: Enum

Internal: classification of JSONPointer target resolution states.

Only use when subclassing JSONPointer for custom stateful behaviors.

Source code in jsonpatchx/backend.py
class TargetState(Enum):
    """
    Internal: classification of JSONPointer target resolution states.

    Only use when subclassing JSONPointer for custom stateful behaviors.
    """

    ROOT = auto()
    PARENT_NOT_FOUND = auto()
    PARENT_NOT_CONTAINER = auto()
    OBJECT_KEY_MISSING = auto()
    ARRAY_KEY_INVALID = auto()
    ARRAY_INDEX_OUT_OF_RANGE = auto()
    ARRAY_INDEX_AT_END = auto()
    ARRAY_INDEX_APPEND = auto()
    VALUE_PRESENT = auto()
    VALUE_PRESENT_AT_NEGATIVE_ARRAY_INDEX = auto()

classify_state(ptr, doc)

Internal: Classify the state of a JSONPointer resolution against a document.

Only use when subclassing JSONPointer for custom stateful behaviors.

Source code in jsonpatchx/backend.py
def classify_state(ptr: PointerBackend, doc: JSONValue) -> TargetState:
    """
    Internal: Classify the state of a JSONPointer resolution against a document.

    Only use when subclassing JSONPointer for custom stateful behaviors.
    """
    if _is_root_ptr(ptr, doc):
        return TargetState.ROOT

    try:
        parent_ptr = _parent_ptr_of(ptr)
        container = parent_ptr.resolve(doc)
        token = ptr.parts[-1]
    except Exception:
        return TargetState.PARENT_NOT_FOUND  # resolution failed to complete
    if not _is_container(container):
        return TargetState.PARENT_NOT_CONTAINER

    if _is_object(container):
        key = token
        if key not in container:
            return TargetState.OBJECT_KEY_MISSING
        return TargetState.VALUE_PRESENT

    elif _is_array(container):
        if token == "-":
            return TargetState.ARRAY_INDEX_APPEND
        if _INTEGER_ARRAY_INDEX_PATTERN.fullmatch(token):
            index = int(token)
            if index > len(container) or index < -len(container):
                return TargetState.ARRAY_INDEX_OUT_OF_RANGE
            if index == len(container):
                return TargetState.ARRAY_INDEX_AT_END
            if index < 0:
                return TargetState.VALUE_PRESENT_AT_NEGATIVE_ARRAY_INDEX
            return TargetState.VALUE_PRESENT
        return TargetState.ARRAY_KEY_INVALID

    else:  # pragma: no cover
        assert_never(container)