# dpath: slashed paths and globs for nested dictionaries

> dpath lets you read, search, set and delete inside nested dictionaries with slash separated glob paths, the way you would move through a filesystem. It has no runtime dependencies, ships type hints, and its newest release tag is from 2024.

**dpath-maintainers/dpath-python** — A python library for accessing and searching dictionaries via /slashed/paths ala xpath.

- Repository: https://github.com/dpath-maintainers/dpath-python
- Stars: 628 · Forks: 94
- Language: Python
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/dpath-maintainers-dpath-python

## A dictionary treated like a filesystem

dpath is a Python library for accessing and searching dictionaries via slashed paths, in the spirit of xpath. The README puts it plainly: it lets you glob over a dictionary as if it were a filesystem. You specify globs, using bash eglob syntax by way of some advanced fnmatch.fnmatch magic, to access dictionary elements, and the library provides facility for filtering the results.

The vocabulary is deliberate and worth borrowing. A path is a slash separated string, a glob is a path with wildcards, and a separator is the character that divides path components. That framing is what makes the library feel smaller than it is: once you accept that a dictionary has a filesystem shape, get, search, set, new and delete behave the way their names suggest, and a path like `a/b/[cd]` reads as "the children of b whose key is c or d".

The library is pure Python with no runtime dependencies, and the package metadata marks it as typed by shipping a py.typed marker inside the dpath package, so type checkers can use its annotations.

```python
    import dpath
```

## get, search and values do three different jobs

The distinction between the read functions is where most of the learning is, and the README is explicit about the contract of each.

get takes a glob that is expected to match exactly one leaf and returns that value.

```python
    >>> dpath.get(x, '/a/b/43')
    30
```

If more than one leaf matches the glob, get raises ValueError, and if the glob is not found it raises KeyError. That strictness is a feature: get refuses to guess.

search is the plural form. It returns a dictionary containing all the keys that matched the glob.

```python
    >>> result = dpath.search(x, "a/b/[cd]")
```

search also has a yielded mode. When yielded is true a dictionary is not returned, and instead tuples in the form of path, value are yielded for every element in the document that matched the glob.

```python
    >>> for x in dpath.search(x, "a/b/[cd]", yielded=True): print(x)
    ...
    ('a/b/c', [])
    ('a/b/d', ['red', 'buggy', 'bumpers'])
```

values is the shorthand for the list comprehension over a yielded search, taking the same arguments, with an extra afilter parameter and a dirs flag.

```python
    >>> dpath.values(x, '/a/b/d/*')
    ['red', 'buggy', 'bumpers']
```

## set and new behave differently on purpose

The write side has two functions and the difference is not cosmetic.

set changes elements that already exist. It takes a path glob, sets all existing elements in the document to the given value, and returns the number of elements changed.

```python
    >>> dpath.set(x, 'a/b/[cd]', 'Waffles')
    2
```

new creates what is missing. It sets the element at the terminus of the path to a value, and creates it if it does not exist, as opposed to set, which can only change existing keys. The behaviour is described as being like mkdir -p, in that it makes all the intermediate paths necessary to get to the terminus.

```python
    >>> dpath.new(x, 'a/b/e/f/g', "Roffle")
```

There is a rule attached to new that is easy to miss. The path will not be treated like a glob. If it has globbing characters in it, they become part of the resulting keys. So a path containing a star or a bracket in new is a literal key name, while the same characters in set are a pattern.

Lists behave in the way you would hope and in one way you would not. If you have a list object and set index 10 of it, the list grows with None entries in order to make it big enough.

```python
    >>> dpath.new(x, 'a/b/e/f/h', [])
    >>> dpath.new(x, 'a/b/e/f/h/13', 'Wow this is a big array, it sure is lonely in here by myself')
```

## Deleting takes the same glob syntax

delete accepts the same globbing syntax as the other methods and deletes all elements that match the glob. It takes an afilter parameter too, like values does.

```python
    >>> help(dpath.delete)

    delete(obj, glob, separator='/', afilter=None):
        Given a path glob, delete all elements that match the glob.
```

That symmetry is the reason to pick this library over hand written traversal code. A delete, a search and a read use one syntax, so a rule about which configuration keys are affected can be written once and applied in three places.

The afilter argument deserves a note. It appears in the signatures for values and delete, and the README says the library provides some facility for filtering those results. How a filter is written is not spelled out in the README, so if you need filtering beyond what the glob itself expresses, that is a detail to check in the package documentation rather than assume.

One function is explicitly excluded from the separator convention: merge. Every other function in the library accepts a separator argument, which is the character that should separate path components. The default is a forward slash, but you can set it to whatever you want, which is what you need when your dictionary keys themselves contain slashes.

## Installing and what the package actually contains

The README says the best way to install dpath is via easy_install or pip, and that source distributions are available on PyPI under the name dpath.

```
    easy_install dpath
    pip install dpath
```

Note the naming. The import name and the PyPI name are both dpath, while the GitHub repository is dpath-python, because the project moved to a maintainers organisation.

The packaging is deliberately plain. The setup script declares no runtime dependencies, ships the dpath package with its py.typed marker, and reads the long description straight from README.rst.

```python
        license="MIT",
        install_requires=[],
        scripts=[],
        packages=["dpath"],
        data_files=[],
        package_data={"dpath": ["py.typed"]},
```

The declared Python requirement is 3.7 or newer, and the classifiers list 3.7 and 3.8 among the supported versions. The comment above that line explains the choice: type hinting support has to be limited to Python versions of 3.6 or above, and 3.6 was dropped because it reached end of life. The repository also carries a tox.ini, a flake8.ini, a MANIFEST.in, a tests/ directory and a LICENSE.txt, which is the shape you want from a library with a small surface and a long life.

One file in the root is unusual for a library: maintainers_log.md, alongside MAINTAINERS.md. For a project whose releases are sporadic, the history of who did what is a genuine part of the documentation.

## Where dpath is the wrong tool

The strictness of get is the first limit. A path glob that matches more than one leaf raises ValueError, which means a caller who does not know the shape of the document cannot use get safely. In practice that pushes you to search, and search returns a merged view of the document rather than a flat list of results, so you often end up writing the same traversal by hand anyway.

The second limit is that this is a library for dictionaries, not for arbitrary objects. The paths address mapping keys and list indices, and the glob semantics are the ones from fnmatch, not the ones from XPath even though the README uses the word in its description. If your data is a deeply nested structure with mixed types and you need type aware traversal or predicates, the notation stops helping.

The third is version risk. The release list shows v2.1.5 on 2023-03-25, v2.1.6 on 2023-05-21 and v2.2.0 on 2024-06-12, while the last push to the repository was on 2026-10-14. The tree is being worked on but the tags are not moving, so if you depend on a behaviour that changed after 2024 you are depending on the working tree or on a fork, and the README does not document a migration path between versions.

## Compared with writing the traversal yourself

The alternative to dpath is the obvious one: write a recursive function that walks the dictionary and collects what you need. That is twenty lines of code, it has no dependencies, and it does exactly what you wrote it to do.

The difference in approach is in the notation and in the guarantees. Hand written traversal expresses structure through control flow, so a rule like "every key named id at any depth" is a function with a flag argument and a stack, and every variation needs a new branch. dpath expresses the same rule as a path string, `*/id`, and the same string works for reading, writing, deleting and filtering. The library also fixes the semantics of the edge cases: get refuses to pick between multiple matches, search returns a document shaped like the original, and new creates the intermediate structure rather than failing.

The other difference is maintenance. A recursive walker is code you own and test, and it will drift as your data shapes change. dpath is a small library with a stable surface, no runtime dependencies, type hints and a permissive licence, so the tradeoff is one dependency in exchange for the path notation and the behaviour you would otherwise have to specify yourself.

The licence is MIT, recorded in the setup metadata and shipped as LICENSE.txt, which makes it an easy dependency to justify in a codebase that has to pass a licence check.

## Conclusion

dpath suits Python code that has to reach into deeply nested dictionaries by pattern rather than by hard coded key chain, which is common in configuration handling, device payloads and API responses. It does not suit code where a path may match several leaves and you need one deterministic value, since get raises ValueError in that case, and it does not suit a project that needs a maintained release trail, because the newest tag is v2.2.0 from 2024 while the last push was on 2026-10-14. Verify first that the installed dpath version exposes the functions you plan to call, and read the README note that new() treats its path literally rather than as a glob.

## FAQ

### How do I install dpath?

The README says the best way to install dpath is via easy_install or pip, so easy_install dpath or pip install dpath. Source distributions are published on PyPI under the name dpath.

### How do I read a value out of a nested dictionary with dpath?

Use dpath.get with a path, for example dpath.get(x, '/a/b/43'). If more than one leaf matches the glob, get raises ValueError, and if the glob is not found it raises KeyError.

### What is the difference between dpath.set and dpath.new?

set changes existing elements matching a glob and returns the number of elements changed, while new creates the element if it does not exist, making the intermediate paths, and its path is treated literally rather than as a glob.

### Which Python versions does dpath support?

The packaging metadata declares python_requires=">=3.7", with classifiers listing Python 3.7 and 3.8. The package ships type hints through the py.typed marker.

## Sources

- [dpath-maintainers/dpath-python on GitHub](https://github.com/dpath-maintainers/dpath-python)
- [Issues](https://github.com/dpath-maintainers/dpath-python/issues)
- [License: MIT](https://github.com/dpath-maintainers/dpath-python/blob/master/LICENSE)
- [README](https://github.com/dpath-maintainers/dpath-python/blob/master/README.md)
- [Releases](https://github.com/dpath-maintainers/dpath-python/releases)

---

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