Metadata-Version: 2.4
Name: pydantic_json_patch
Version: 0.3.0
Summary: Pydantic models for implementing JSON Patch.
Author: Jonathan Sharpe
Author-email: Jonathan Sharpe <mail@jonrshar.pe>
License-Expression: ISC
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: FastAPI
Classifier: Framework :: Pydantic
Classifier: Framework :: Pydantic :: 2
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: ISC License (ISCL)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: File Formats :: JSON
Classifier: Topic :: File Formats :: JSON :: JSON Schema
Classifier: Typing :: Typed
Requires-Dist: pydantic>=2.0
Requires-Dist: typing-extensions>=4.15.0
Requires-Python: >=3.10
Project-URL: repository, https://github.com/textbook/pydantic_json_patch
Project-URL: Issues, https://github.com/textbook/pydantic_json_patch/issues
Project-URL: Sponsor, https://ko-fi.com/textbook
Description-Content-Type: text/markdown

# Pydantic JSON Patch

[![Python uv CI][ci-badge]][ci-page]
[![Coverage Status][coverage-badge]][coverage-page]
[![PyPI - Version][pypi-badge]][pypi-page]

[Pydantic] models for implementing [JSON Patch].

## Installation

_Pydantic JSON Patch_ is published to [PyPI], and can be installed with e.g.:

```shell
pip install pydantic-json-patch
```

## Models

A model is provided for each of the six JSON Patch operations:

- `AddOp`
- `CopyOp`
- `MoveOp`
- `RemoveOp`
- `ReplaceOp`
- `TestOp`

As repeating the op is a bit awkward (`CopyOp(op="copy", ...)`), a `create` factory method is available:

```python
>>> from pydantic_json_patch import AddOp
>>> op = AddOp.create(path="/foo/bar", value=123)
>>> op
AddOp(op='add', path='/foo/bar', value=123)
>>> op.model_dump_json()
'{"op":"add","path":"/foo/bar","value":123}'
```

Additionally, there are two compound models:

- `Operation` is the union of all the operators; and
- `JsonPatch` represents a list of that union type.

### Pointer tokens

The `path` property (and `from` property, where present) of an operation is a [JSON Pointer].
This means that any `~` or `/` characters in property names need to be properly encoded.
To aid working with these, the models expose a read-only `path_tokens` property (and, where appropriate, `from_tokens`):

```python
>>> from pydantic_json_patch import CopyOp
>>> op = CopyOp.model_validate_json('{"op":"copy","path":"/foo/bar~1new","from":"/foo/bar~0old"}')
>>> op
CopyOp(op='copy', path='/foo/bar~1new', from_='/foo/bar~0old')
>>> op.path_tokens
('foo', 'bar/new')
>>> op.from_tokens
('foo', 'bar~old')
```

Similarly, the `create` factory methods can accept sequences of tokens, and will encode them appropriately:

```python
>>> from pydantic_json_patch import TestOp
>>> op = TestOp.create(path=("annotations", "scope/value"), value=None)
>>> op
TestOp(op='test', path='/annotations/scope~1value', value=None)
>>> op.model_dump_json()
'{"op":"test","path":"/annotations/scope~1value","value":null}'
```

## FastAPI

You can use this package to validate a JSON Patch endpoint in a FastAPI application, for example:

```python
import typing as tp
from uuid import UUID

from fastapi import Body, FastAPI

from pydantic_json_patch import JsonPatch

app = FastAPI()


@app.patch("/resource/{resource_id}")
def _(resource_id: UUID, operations: tp.Annotated[JsonPatch, Body()]) -> ...:
    ...
```

This will provide a sensible example of the request body:

[![Screenshot of Swagger UI request body example][swagger-example]][swagger-example]

and list the models along with the other schemas:

[![Screenshot of Swagger UI schema list][swagger-schemas]][swagger-schemas]

## Development

This project uses [uv] for managing dependencies.
Having installed uv, you can set the project up for local development with:

```shell
uv sync
uv run pre-commit install
```

The pre-commit hooks will ensure that the code style checks (using [isort] and [ruff]) are applied.

### Testing

The test suite uses [pytest] and can be run with:

```shell
uv run pytest
```

Additionally, there is [ty] type-checking that can be run with:

```shell
uv run ty check
```

### FastAPI

You can preview the FastAPI/Swagger documentation by running:

```shell
uv run fastapi dev tests/app.py
```

and visiting the Documentation link that's logged in the console.
This will auto-restart as you make changes.

  [ci-badge]: https://github.com/textbook/pydantic_json_patch/actions/workflows/push.yml/badge.svg
  [ci-page]: https://github.com/textbook/pydantic_json_patch/actions/workflows/push.yml
  [coverage-badge]: https://coveralls.io/repos/github/textbook/pydantic_json_patch/badge.svg?branch=main
  [coverage-page]: https://coveralls.io/github/textbook/pydantic_json_patch?branch=main
  [fastapi]: https://fastapi.tiangolo.com/
  [isort]: https://pycqa.github.io/isort/
  [json patch]: https://datatracker.ietf.org/doc/html/rfc6902/
  [json pointer]: https://datatracker.ietf.org/doc/html/rfc6901/
  [pydantic]: https://docs.pydantic.dev/latest/
  [pypi]: https://pypi.org/
  [pypi-badge]: https://img.shields.io/pypi/v/pydantic-json-patch?logo=python&logoColor=white&label=PyPI
  [pypi-page]: https://pypi.org/project/pydantic-json-patch/
  [pytest]: https://docs.pytest.org/en/stable/
  [ruff]: https://docs.astral.sh/ruff/
  [swagger-example]: https://github.com/textbook/pydantic_json_patch/blob/main/docs/swagger-example.png?raw=true
  [swagger-schemas]: https://github.com/textbook/pydantic_json_patch/blob/main/docs/swagger-schemas.png?raw=true
  [ty]: https://docs.astral.sh/ty/
  [uv]: https://docs.astral.sh/uv/
