Metadata-Version: 2.1
Name: nested_diff
Version: 1.4.0
Summary: Recursive diff and patch for nested structures.
Keywords: diff,nested-diff,recursive-diff,nested-data,data-structures
Author-email: Michael Samoglyadov <mixas.sr@gmail.com>
Requires-Python: >=3.7
Description-Content-Type: text/markdown
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: File Formats :: JSON
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Utilities
Requires-Dist: pyyaml ; extra == "cli"
Requires-Dist: tomli >= 1.1.0  ; extra == "cli" and ( python_version < "3.11")
Requires-Dist: tomli-w >= 1.0.0 ; extra == "cli"
Requires-Dist: darglint ; extra == "lint"
Requires-Dist: flake8 < 5 ; extra == "lint"
Requires-Dist: flake8-builtins ; extra == "lint"
Requires-Dist: flake8-bugbear ; extra == "lint"
Requires-Dist: flake8-commas ; extra == "lint"
Requires-Dist: flake8-comprehensions ; extra == "lint"
Requires-Dist: flake8-docstrings ; extra == "lint"
Requires-Dist: flake8-eradicate ; extra == "lint"
Requires-Dist: flake8-pytest-style ; extra == "lint"
Requires-Dist: flake8_quotes ; extra == "lint"
Requires-Dist: flake8-return ; extra == "lint"
Requires-Dist: flake8-simplify ; extra == "lint"
Requires-Dist: flake8-unused-arguments ; extra == "lint"
Requires-Dist: pep8-naming ; extra == "lint"
Requires-Dist: pytest-flake8 ; extra == "lint"
Requires-Dist: pytest ; extra == "test"
Requires-Dist: pytest-ruff ; extra == "test"
Requires-Dist: ruff==0.1.15 ; extra == "test"
Project-URL: Homepage, https://github.com/mr-mixas/Nested-Diff.py
Project-URL: Repository, https://github.com/mr-mixas/Nested-Diff.py.git
Provides-Extra: cli
Provides-Extra: lint
Provides-Extra: test

# Nested-Diff.py

Recursive diff and patch for nested structures.

[![Tests Status](https://github.com/mr-mixas/Nested-Diff.py/actions/workflows/tests.yml/badge.svg)](https://github.com/mr-mixas/Nested-Diff.py/actions?query=branch%3Amaster)
[![Coverage Status](https://coveralls.io/repos/github/mr-mixas/Nested-Diff.py/badge.svg)](https://coveralls.io/github/mr-mixas/Nested-Diff.py)
[![Supported Python versions](https://img.shields.io/pypi/pyversions/nested_diff.svg)](https://pypi.org/project/nested_diff/)
[![License](https://img.shields.io/pypi/l/nested_diff.svg)](https://pypi.org/project/nested_diff/)

## Main features

* Machine-readable diff structure.
* Human-friendly diff visualization, collapsible html diffs.
* All operation tags are optional and may be disabled.
* Extensibility.

**[See Live Demo!](https://nesteddiff.pythonanywhere.com/)**

## Install

`pip install nested_diff`

For extra formats support (YAML, TOML) in cli tools, use

`pip install nested_diff[cli]`

## Command line tools

```sh
$ cat a.json b.json
[0, [1],    3]
[0, [1, 2], 3]
```

```sh
$ nested_diff a.json b.json
  [1]
+   [1]
+     2
```

```sh
nested_diff a.json b.json --ofmt json > patch.json
nested_patch a.json patch.json
```

## Library usage

```py
>>> from nested_diff import diff, patch
>>>
>>> a = {'one': 1, 'two': 2, 'three': 3}
>>> b = {'one': 1, 'two': 42}
>>>
>>> diff(a, b)
{'D': {'three': {'R': 3}, 'two': {'N': 42, 'O': 2}, 'one': {'U': 1}}}
>>>
>>> diff(a, b, O=False, U=False)
{'D': {'three': {'R': 3}, 'two': {'N': 42}}}
>>>
>>>
>>> c = [0,1,2,3]
>>> d = [  1,2,4,5]
>>>
>>> c = patch(c, diff(c, d))
>>> assert c == d
>>>
```

### Formatting diffs

```py
>>> from nested_diff import diff, handlers
>>> from nested_diff.formatters import TextFormatter
>>>
>>> a = {'one': 1, 'two': 'some\ntext\ninside'}
>>> b = {'one': 0, 'two': 'some\ntext'}
>>>
>>> d = diff(a, b, U=False, extra_handlers=[handlers.TextHandler(context=3)])
>>> print(TextFormatter().format(d))
  {'one'}
-   1
+   0
  {'two'}
#   <str>
    @@ -1,3 +1,2 @@
    some
    text
-   inside
<BLANKLINE>
>>>
```

For more examples see [Live Demo](https://nesteddiff.pythonanywhere.com/),
[HOWTO](https://github.com/mr-mixas/Nested-Diff.py/blob/master/HOWTO.md) and
[tests](https://github.com/mr-mixas/Nested-Diff.py/tree/master/tests).

## Diff structure

Diff is a dict and may contain status keys:

* `A` stands for 'added', it's value - added item.
* `D` means 'different' and contains subdiff.
* `N` is a new value for changed item.
* `O` is a changed item's old value.
* `R` key used for removed item.
* `U` represent unchanged item.

and auxiliary keys:

* `C` comment; optional, value - arbitrary string.
* `E` extension ID (optional).
* `I` index for sequence item, used only when prior item was omitted.

Diff metadata alternates with actual data; simple types specified as is, dicts,
lists and tuples contain subdiffs for their items with native for such types
addressing: indexes for lists and tuples, keys for dictionaries. Any status
key, except `D` may be omitted during diff computation. `E` key is used with
`D` when entity unable to contain diff by itself (set, frozenset for example);
`D` contain a list of subdiffs in this case.

### Annotated example

```text
a:  {"one": [5,7]}
b:  {"one": [5], "two": 2}
opts: U=False  # omit unchanged items

diff:
{"D": {"one": {"D": [{"I": 1, "R": 7}]}, "two": {"A": 2}}}
| |   |  |    | |   || |   |   |   |       |    | |   |
| |   |  |    | |   || |   |   |   |       |    | |   +- with value 2
| |   |  |    | |   || |   |   |   |       |    | +- key 'two' was added
| |   |  |    | |   || |   |   |   |       |    +- subdiff for it
| |   |  |    | |   || |   |   |   |       +- another key from top-level
| |   |  |    | |   || |   |   |   +- what it was (item's value: 7)
| |   |  |    | |   || |   |   +- what happened to item (removed)
| |   |  |    | |   || |   +- list item's actual index
| |   |  |    | |   || +- prior item was omitted
| |   |  |    | |   |+- subdiff for list item
| |   |  |    | |   +- it's value - list
| |   |  |    | +- it is deeply changed
| |   |  |    +- subdiff for key 'one'
| |   |  +- it has key 'one'
| |   +- top-level thing is a dict
| +- changes somewhere deeply inside
+- diff is always a dict
```

## License

Licensed under the terms of the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).

## See Also

[HOWTO](https://github.com/mr-mixas/Nested-Diff.py/blob/master/HOWTO.md)

[deepdiff](https://pypi.org/project/deepdiff/),
[jsondiff](https://pypi.org/project/jsondiff/),
[jsonpatch](https://pypi.org/project/jsonpatch/),
[json-delta](https://pypi.org/project/json-delta/)

