path-seeker
Find, count, and update keys anywhere in a nested dict or list of dicts — with
full path disambiguation when the same key occurs at multiple depths.
Installation
pip install path-seeker
Usage
from path_seeker import PathSeeker
data = {
"user": {
"id": 1,
"address": {"id": 42, "city": "Berlin"},
},
"orders": [
{"id": 100, "status": "shipped"},
{"id": 101, "status": "pending"},
],
}
seeker = PathSeeker(data)
# All values found under a key, at any depth
seeker.find_values("id")
# [1, 42, 100, 101]
# All (path, value) matches — use this to disambiguate duplicates
seeker.find_paths("id")
# [Match(path=['user', 'id'], value=1),
# Match(path=['user', 'address', 'id'], value=42),
# Match(path=['orders', 0, 'id'], value=100),
# Match(path=['orders', 1, 'id'], value=101)]
# The first/last value found under a key, in traversal order
seeker.find_first_value("id")
# 1
seeker.find_last_value("id")
# 101
# The first/last (path, value) match found under a key, in traversal order
seeker.find_first_path("id")
# Match(path=['user', 'id'], value=1)
seeker.find_last_path("id")
# Match(path=['orders', 1, 'id'], value=101)
# Every distinct key name in the structure
seeker.get_keys()
# ['address', 'city', 'id', 'orders', 'status', 'user']
# How many times a key occurs
seeker.count_key("id")
# 4
# Whether a key occurs anywhere
seeker.has_key("id")
# True
# Update one exact occurrence, using a path from find_paths
seeker.set_value(["orders", 1, "status"], "cancelled")
# Update every occurrence of a key at once
seeker.update_values("id", 0)
# 4 (number of occurrences updated)
# Update just the first/last occurrence, in traversal order
seeker.update_first("id", -1)
# True
seeker.update_last("id", -2)
# True
# Delete every occurrence of a key
seeker.delete_key("status")
# 2 (number of occurrences deleted)
# Delete just the first/last occurrence, in traversal order
seeker.delete_first("id")
# True
seeker.delete_last("id")
# True
API
| Method | Returns | Description |
|---|---|---|
find_values(key) |
list[Any] |
Every value stored under key, at any depth. |
find_paths(key) |
list[Match] |
Every (path, value) pair for key, at any depth. |
find_first_value(key) |
Any |
The first value found under key, in traversal order. Raises UnknownKeyProvided if key doesn't occur anywhere. |
find_last_value(key) |
Any |
The last value found under key, in traversal order. Raises UnknownKeyProvided if key doesn't occur anywhere. |
find_first_path(key) |
Match |
The first (path, value) match found under key, in traversal order. Raises UnknownKeyProvided if key doesn't occur anywhere. |
find_last_path(key) |
Match |
The last (path, value) match found under key, in traversal order. Raises UnknownKeyProvided if key doesn't occur anywhere. |
get_keys() |
list[str] |
Every distinct key name in the structure, sorted. |
count_key(key) |
int |
How many times key occurs. |
has_key(key) |
bool |
Whether key occurs anywhere in the structure. |
set_value(path, new_value) |
None |
Update the value at an exact path (from find_paths). |
update_values(key, new_value) |
int |
Update every occurrence of key; returns count updated. |
update_first(key, new_value) |
bool |
Update the first occurrence of key, in traversal order; False if absent. |
update_last(key, new_value) |
bool |
Update the last occurrence of key, in traversal order; False if absent. |
delete_key(key) |
int |
Delete every occurrence of key, at any depth; returns count deleted. |
delete_first(key) |
bool |
Delete the first occurrence of key, in traversal order; False if absent. |
delete_last(key) |
bool |
Delete the last occurrence of key, in traversal order; False if absent. |
.data |
dict | list |
The current (possibly mutated) underlying structure. |
Search is by key name, not by value — searching for a value and getting its path back is not supported yet.
find_first_*/find_last_* raise UnknownKeyProvided when key doesn't occur
anywhere; update_first/update_last/delete_first/delete_last/delete_key
return False/0 instead, since "the key wasn't there" is an expected outcome for
a write, not an error.
Limitations
PathSeekerholds a reference to the structure you pass in — it does not make a defensive copy, so updates viaset_value/update_valuesmutate your original object too.- Nested
dicts andlists are traversed; other iterables (tuples, sets, generators) are treated as opaque values, since list-index-style paths don't make sense for them. - No cycle detection. This is fine for JSON/YAML-shaped data, which can't contain cycles, but a structure with a manually-introduced reference cycle will recurse forever.
count_key,has_key, andget_keysdo a full traversal on every call — there's no indexing or caching, so repeated calls on a large structure cost proportionally more each time.
Development
make install # uv sync --all-extras
make check # lint + typecheck + test (what CI runs)
Individual targets: make test, make lint, make format, make typecheck.
See CONTRIBUTING.md for the full workflow.
License
MIT — see LICENSE.
Release files for path-seeker 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| path_seeker-0.4.0.tar.gz | 74.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| path_seeker-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 80.6 kB
Release files / path_seeker-0.4.0.tar.gz
| Download URL | path_seeker-0.4.0.tar.gz |
|---|---|
| Size | 74.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
566e8d6d3682d88c3ff9cc036c74d8ef83bdd15895c9495777d345229c26d4ec
|
|
BLAKE2b-256 checksum How to use checksums |
0c190dffa7c1b739eae8bafabdb9a3785b5c26dc7aa3041eb33d6c6baeb191e1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 5, 2026.
Transparency logRelease files / path_seeker-0.4.0-py3-none-any.whl
| Download URL | path_seeker-0.4.0-py3-none-any.whl |
|---|---|
| Size | 6.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
07c75c890553fb324d1e1669dd5467588194d47b4f0a279c495b47800879bcfe
|
|
BLAKE2b-256 checksum How to use checksums |
b693a7c97ff49e7c99fdcfdef8dc73fb4d336b3f225a5d3f45599fe2b76894dd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 5, 2026.
Transparency log