# dictdiffer: a small diff helper for Python dictionaries with a thin intro

> dictdiffer is an MIT licensed Python package for diffing and patching dictionaries. The install is one pip line, but the change format and the API surface are left to the hosted documentation, and the newest release tag is v0.9.0 from 2021.

**inveniosoftware/dictdiffer** — Dictdiffer is a module that helps you to diff and patch dictionaries. 

- Repository: https://github.com/inveniosoftware/dictdiffer
- Website: https://dictdiffer.readthedocs.io
- Stars: 849 · Forks: 103
- Language: Python
- License: NOASSERTION
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/inveniosoftware-dictdiffer

## A diff helper for dictionaries that documents its install and little else

dictdiffer exists to answer one narrow question: what changed between two dictionaries, and how do you turn that answer into an updated dictionary. The project description calls it a module that helps you to diff and patch dictionaries, and the pyproject.toml file describes it as a library with that same purpose. It is a Python package published under the Invenio Collaboration, and it is the kind of utility that ends up underneath a configuration editor, a form builder, or a record synchroniser where two snapshots of the same nested structure have to be reconciled.

The scope is narrow enough that you can read the whole public surface in a sitting, and the project is not trying to be a general diff engine for JSON documents, YAML trees, or arbitrary objects. There is no command line interface in the top level listing, no service definition, and no configuration schema. The top level entries are a source directory named dictdiffer, a tests directory, a docs directory, and the usual packaging and repository metadata files. Everything a user touches is the importable module.

That narrowness is the main attraction and also the main risk. You get a small dependency with a permissive licence and a plain pip install, but you inherit a package whose user-facing documentation lives in a separate hosted site rather than in the repository you clone.

## The change format lives in the hosted docs, not in the intro file

The intro file is short enough to read in under a minute, and it covers four things: what the project is, how to install it, where the documentation is, and how to run the tests. It does not list the public functions, it does not show an example of a diff result, and it does not explain the format of a patch operation. The words diff and patch are used, but nowhere in the repository listing is there a documented example of the structure that a diff produces.

This matters more than a thin intro file usually does, because the value of a diff helper is entirely in the shape of its output. If you intend to store a diff, serialise it, ship it between services, or apply it later against a different snapshot, you need to know the exact representation, and the repository is silent on that. The intro file does not document a rollback either, nor any way to invert a patch.

The documentation is published at dictdiffer.readthedocs.io, and the pyproject.toml file points to a Read the Docs URL as well, so the hosted pages are the intended place to look for the API details. Treat the hosted docs as part of the dependency. If your process requires pinned, reviewable API documentation in the repository itself, this package does not meet it, and you would be adopting it against a moving external page.

## Installing from PyPI takes a single pip line

Installation is the least complicated part of this project. The intro file states that the package is on PyPI, and gives one command. The pyproject.toml file names the distribution dictdiffer and declares requires-python as greater than or equal to 3.7, so any supported interpreter can install it.

```bash
pip install dictdiffer
```

After that, the module is importable like any other package. There is no separate configuration step, no environment variable to set, and no server to start, because there is no server. The documentation also mentions an extra that pulls the documentation toolchain:

```bash
pip install dictdiffer[docs]
```

The pyproject.toml file also defines a numpy extra, which installs numpy greater than or equal to 1.20.0. That extra is not described in the intro file, so its purpose has to be inferred from the presence of the dependency rather than read from documentation. If your environment already pins numpy, be aware that asking for this extra can move your numpy version, and check the optional dependency group before you add it.

For a first real use, the practical step is to import the module and apply it to two dictionaries you already understand, then compare the result against your expectations. Keeping the first experiment on data you control is the only way to learn the diff format without reverse engineering it from the source.

## The documented docs build points at a setup.py the repository no longer ships

The intro file tells you that the documentation can be built with Sphinx, and gives two lines for it. This is where the packaging and the instructions drift apart.

```bash
pip install dictdiffer[docs]
```

The build step itself is written against the older packaging style:

```bash
python setup.py build_sphinx
```

There is no setup.py in the top level listing. The build system is declared in pyproject.toml as hatch-vcs and hatchling, with hatchling.build as the build backend, so the command in the intro file does not match the build tool the project actually uses. The docs directory is present, and the sphinx-rtd-theme and sphinx dependencies are declared, so the documentation can be built, but the exact invocation has to be worked out locally rather than copied from the intro file.

The same drift shows up in the test instructions, which are described in setup.py style as well. This is a small sign of a project whose intro file was written for a previous generation of its packaging and not fully updated. It is a minor inconvenience rather than a defect, but it is a useful signal: expect the intro file to be a starting point that needs checking against the current pyproject.toml file.

## run-tests.sh is the whole documented test story

The intro file gives one command for exercising the test suite, and the repository has a matching script at the top level.

```bash
./run-tests.sh
```

The test dependencies are declared as an optional group in pyproject.toml, and they are broader than a bare pytest install: pytest itself is pinned to greater than or equal to 6 and less than 9, alongside pytest-cov, pytest-isort, pytest-pydocstyle, pycodestyle, mock, tox, and sphinx. There is a .coveragerc file, a tox configuration, and a pytest-isort dependency, so the suite appears to check style and import ordering as well as behaviour.

The script is not listed in the top level entries by name in the description of the command, but run-tests.sh does exist there. What the intro file does not document is what the script actually runs, whether it needs a tox environment, or which extras have to be installed first. If you plan to contribute a patch, install the tests extra before running the script, because pycodestyle and the pytest plugins are not part of a normal install and their absence usually shows up as a collection error rather than a test failure.

## Versions are derived from dictdiffer/version.py through hatch-vcs

The version is not hard coded in pyproject.toml. The project metadata lists version as dynamic, and the hatch configuration reads it from dictdiffer/version.py, with hatch-vcs in the build requirements. In practice that means the installed version is derived from the repository history rather than written by hand, which is why the package metadata and the release tags on GitHub can tell different stories at the same moment.

The sdist configuration includes only the dictdiffer directory. Tests, docs, and the scripts at the top level are not part of the source distribution, so the artifact you get from a package index is the library alone, and the things a contributor works with are not shipped to end users.

Licensing carries the same kind of mismatch. The project metadata declares MIT in the project table and lists an OSI approved MIT classifier. GitHub cannot classify the licence file and reports it as a custom licence, so the LICENSE file in the repository is the authoritative text if the two ever disagree. Contributors are named in the AUTHORS file, and MAINTAINERS and CONTRIBUTING.rst sit alongside it, with a CHANGES file linked as the changelog. For a package this small, the administrative surface is larger than the code it exposes, and the licence file deserves a direct read.

## The newest release tag is v0.9.0 from 2021 while commits continued into 2026

The release history is short. The recorded versions are v0.9.0 dated 2021-07-22, v0.8.1 dated 2019-12-13, and v0.8.0 dated 2019-03-17. The last push to the repository was on 2026-07-16, so work has continued well after the last tagged release. The project is not archived, and its metadata lists a Development Status of Production/Stable.

The gap between a 2021 tag and a 2026 push is the practical fact to plan around. There is a five year stretch with no release tag, which means the code on the default branch has moved in a way you cannot inspect by comparing tags, and there is no tagged release to pin a production deployment to. The CHANGES file is the place to look for what changed in that window, and the changelog URL in the project metadata points at it directly.

The costs of an upgrade here are low in effort and awkward in process. There is nothing to configure and no migration script to run, but deciding whether to take a specific commit means reading the diff of a small module by hand, because there is no version boundary to reason about.

## Where dictdiffer is the wrong tool

There are situations where this package is simply the wrong shape of dependency. If you need to diff arbitrary JSON or YAML documents, want to read a diff from the command line, or expect a stable serialised format for a diff that you will store and reapply months later, dictdiffer is not that tool, and nothing in the repository suggests an intention to become one.

If your data model involves custom classes, dataclasses with generated equality, or numpy arrays, check carefully before committing. The numpy extra exists in the packaging metadata but is not explained anywhere in the intro file, so the depth of that support is not documented and would have to be confirmed by reading the source. That is a small amount of investigation for a small library, but it is investigation you have to do yourself.

The failure mode to watch is not a crash. It is a diff that produces a structure you did not anticipate, applied later against a snapshot that has drifted, silently giving the wrong result because the format was never specified. The right way to avoid that is to pin the exact version you tested, capture a few real diff outputs from your own data, and treat those captured samples as a regression test in your own suite.

## Conclusion

dictdiffer fits teams that already want a dictionary diff primitive inside a larger Python codebase and are willing to read the hosted documentation for the API. It does not fit anyone who needs a documented, versioned guarantee about the shape of the diff output, because the newest release tag is v0.9.0 from 2021-07-22 while the last push was on 2026-07-16. Before adopting it, check the diff format in the readthedocs pages against your own serialization, and read the LICENSE file directly, because the packaging metadata and the repository disagree about the licence.

## FAQ

### How do I install dictdiffer for a Python project?

The intro file states that the package is on PyPI, so the whole install is pip install dictdiffer. The packaging metadata requires Python 3.7 or newer and defines optional extras for docs, numpy, and tests.

### What format does dictdiffer use to represent the changes between two dictionaries?

The intro file does not document the diff format, and it does not list the public functions either. The API details are published at dictdiffer.readthedocs.io, which is where you have to look for the shape of a diff result.

### Is dictdiffer still maintained and where can I get the newest release?

The repository is not archived and the last push was on 2026-07-16. The recorded releases stop at v0.9.0 from 2021-07-22, with v0.8.1 in 2019 and v0.8.0 in March 2019, and the changelog is the CHANGES file in the repository.

## Sources

- [inveniosoftware/dictdiffer on GitHub](https://github.com/inveniosoftware/dictdiffer)
- [Issues](https://github.com/inveniosoftware/dictdiffer/issues)
- [Project website](https://dictdiffer.readthedocs.io)
- [README](https://github.com/inveniosoftware/dictdiffer/blob/master/README.md)
- [Releases](https://github.com/inveniosoftware/dictdiffer/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/inveniosoftware-dictdiffer
