A lazy selector for nested Python data structures.
Project description
dictselect
A lightweight Python library for extracting data from nested dicts and lists using composable, reusable selectors.
Installation
pip install dictselect
Requires Python ≥ 3.9.
Quick start
from dictselect import Selector
data = {
"image_id": "xa001",
"annotations": [
{"id": 1, "x_min": 10, "x_max": 20, "label": "cat"},
{"id": 2, "x_min": 30, "x_max": 50, "label": "dog"},
],
}
Selector["image_id"](data) # → "xa001"
Selector["annotations"][0]["label"](data) # → "cat"
Selector["annotations"][:]["label"](data) # → ["cat", "dog"]
Selector["annotations"][:]["x_min", "x_max"](data) # → [[10, 20], [30, 50]]
Build a selector once, apply it to many objects:
sel = Selector["annotations"][:]["x_min", "x_max"]
sel(record_a)
sel(record_b)
Operations
| Syntax | What it does |
|---|---|
Selector["key"] |
Dict key or list index lookup |
Selector[0], Selector[-1] |
List index (positive or negative) |
Selector[1:3] |
Slice — returns a sub-list or sub-string |
Selector[:] or Selector[...] |
Fan-out — map remaining steps over every element |
Selector["a", "b"] |
Pluck multiple keys at once, returns a list |
Selector.attr |
Attribute access (getattr) |
Selector.method(args) |
Attribute access followed by a call |
sel_a + sel_b |
Compose two selectors |
sel(data, include_keys=True) |
Wrap result in a dict keyed by the last access key |
sel(data, include_null=True) |
Return None instead of raising on missing keys |
Selector[{"alias": "key"}] |
Aliased key — access via "key", label as "alias" |
Selector["fn"].invoke(*args) |
Call a callable value stored in data |
Usage
Key and index lookup
Selector["name"]({"name": "Alice"}) # → "Alice"
Selector[0]([10, 20, 30]) # → 10
Selector[-1]([10, 20, 30]) # → 30
Selector["a"]["b"]({"a": {"b": 7}}) # → 7
Slicing
Selector[1:3]([0, 1, 2, 3, 4]) # → [1, 2]
Selector[:2]([10, 20, 30]) # → [10, 20]
Selector[-2:]([10, 20, 30]) # → [20, 30]
Selector[::2]([0, 1, 2, 3, 4]) # → [0, 2, 4]
Fan-out [:]
[:] (or [...]) maps all subsequent steps over every element of the current sequence. Steps after [:] run on each element individually.
data = [{"v": 1}, {"v": 2}, {"v": 3}]
Selector[:]["v"](data) # → [1, 2, 3]
Selector[:][0]([[10, 20], [30, 40]]) # → [10, 30]
Plucking multiple keys
Pass a tuple or list of keys to retrieve several fields at once. All keys must be the same type (all str or all int). Returns a list of values in the order given.
Selector["x", "y"]({"x": 1, "y": 2, "z": 3}) # → [1, 2]
Selector[0, 2]([10, 20, 30, 40]) # → [10, 30]
Combined with fan-out:
Selector["annotations"][:]["x_min", "x_max"](data)
# → [[10, 20], [30, 50]]
Attribute access and method calls
Chain attribute access with .attr, then call it like a regular Python method:
Selector.upper()("hello") # → "HELLO"
Selector.replace("l", "r")("hello") # → "herro"
Selector[:].upper()(["hi", "there"]) # → ["HI", "THERE"]
Selector["title"].upper()({"title": "hi"}) # → "HI"
Composing selectors
Join two selectors with +. Neither operand is mutated:
head = Selector["data"][:]
tail = Selector["value"]
sel = head + tail
sel({"data": [{"value": 1}, {"value": 2}]}) # → [1, 2]
Calling vs. evaluating
Normally, calling a selector evaluates it against the data:
sel = Selector["key"]
sel({"key": 42}) # → 42
sel.apply({"key": 42}) # same thing
Attribute-access exception: if the last recorded step is an attribute name, calling the selector records a method-call step instead of evaluating. Use .apply() to force evaluation:
Selector["title"].upper() # records the .upper() call — returns a new Selector
Selector["title"].upper()(data) # evaluates → "HELLO"
Selector.upper.apply("hello") # force-evaluates → the bound method object (not "HELLO")
Calling a function stored in data: accessing a callable value via ["key"] and then calling with () would evaluate the selector (returning the function itself), not invoke it. Use .invoke() to record a function call as a step:
data = {"fn": lambda x: x * 2}
Selector["fn"](data) # → the lambda object (selector evaluated, function not called)
Selector["fn"].invoke(21)(data) # → 42 (function is called with 21 at evaluation time)
Returning keys alongside values
Pass include_keys=True to wrap the result in a dict keyed by the last access key.
Selector["a"]["b"]({"a": {"b": 12}}, include_keys=True)
# → {"b": 12}
Selector["x"].apply({"x": 7}, include_keys=True)
# → {"x": 7}
With fan-out, wrapping happens per element:
Selector[:]["a"]([{"a": 1}, {"a": 2}], include_keys=True)
# → [{"a": 1}, {"a": 2}]
Selector[:]["a", "b"]([{"a": 1, "b": 2, "c": 3}, {"a": 4, "c": 6, "b": 5}], include_keys=True)
# → [{"a": 1, "b": 2}, {"a": 4, "b": 5}]
Terminal steps that are not key lookups (slices, method calls) pass through unchanged.
Aliasing output keys
Use a single-entry dict {"alias": "key"} to rename a field in the output. The dict key is the output name, the dict value is the access key. Plain keys keep their original name.
Selector[{"alias_a": "a"}]({"a": 7}, include_keys=True)
# → {"alias_a": 7}
Selector[{"alias_a": "a"}, "b", {"alias_c": "c"}](
{"a": 1, "b": 2, "c": 3}, include_keys=True
)
# → {"alias_a": 1, "b": 2, "alias_c": 3}
Without include_keys, aliases are ignored and raw values are returned as usual.
Sub-selector aliases
The access value in an alias dict can itself be a Selector. It is applied to the current data and stored under the given alias. A bare Selector without an alias is not allowed.
name_sel = Selector["name"]["first_name", "last_name"]
Selector["employees"][:][{"name": name_sel}, "adress"](data, include_keys=True)
# → [
# {"name": ["Alice", "Smith"], "adress": "1 Main St"},
# {"name": ["Bob", "Jones"], "adress": "2 Oak Ave"},
# ]
Handling missing values
Pass include_null=True to return None instead of raising KeyError or IndexError when a key or index is absent. Once a step fails, the rest of the chain is skipped.
Selector["a"]["missing"]({"a": {}}, include_null=True)
# → None
Selector[:]["x"]([{"x": 1}, {"y": 2}, {"x": 3}], include_null=True)
# → [1, None, 3]
Selector["a", "b"]({"a": 1}, include_null=True)
# → [1, None] (each missing key becomes None individually)
Both flags can be combined:
Selector[:]["x"]([{"x": 1}, {"y": 2}], include_null=True, include_keys=True)
# → [{"x": 1}, {"x": None}]
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dictselect-0.2.0.tar.gz.
File metadata
- Download URL: dictselect-0.2.0.tar.gz
- Upload date:
- Size: 14.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8a530b4378eca7060fa30740c055b3fba85ba06eda4b35768d2128d2f6dcea69
|
|
| MD5 |
a36c698d065919ed3b90cbabf8f84e72
|
|
| BLAKE2b-256 |
e06c88178ed2b740072772ad6484e6743728a5636dde84bc69d107c358f722c3
|
Provenance
The following attestation bundles were made for dictselect-0.2.0.tar.gz:
Publisher:
publish.yml on alphacena/dictselect
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dictselect-0.2.0.tar.gz -
Subject digest:
8a530b4378eca7060fa30740c055b3fba85ba06eda4b35768d2128d2f6dcea69 - Sigstore transparency entry: 1965596350
- Sigstore integration time:
-
Permalink:
alphacena/dictselect@d566a89e7f88c4b4aead26fb366be64650482fdf -
Branch / Tag:
refs/tags/Release-0.2.0 - Owner: https://github.com/alphacena
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d566a89e7f88c4b4aead26fb366be64650482fdf -
Trigger Event:
release
-
Statement type:
File details
Details for the file dictselect-0.2.0-py3-none-any.whl.
File metadata
- Download URL: dictselect-0.2.0-py3-none-any.whl
- Upload date:
- Size: 13.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
275e0019cea68514323fb47fc0e254f1469265338729ee5fa4171caebb3bb47b
|
|
| MD5 |
164a6070fd2c4c4a076f351301602e7c
|
|
| BLAKE2b-256 |
87e632cac5ed6a8839d010cfd93573127f81ff0222c48797fc854b06d7ed6985
|
Provenance
The following attestation bundles were made for dictselect-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on alphacena/dictselect
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dictselect-0.2.0-py3-none-any.whl -
Subject digest:
275e0019cea68514323fb47fc0e254f1469265338729ee5fa4171caebb3bb47b - Sigstore transparency entry: 1965596435
- Sigstore integration time:
-
Permalink:
alphacena/dictselect@d566a89e7f88c4b4aead26fb366be64650482fdf -
Branch / Tag:
refs/tags/Release-0.2.0 - Owner: https://github.com/alphacena
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d566a89e7f88c4b4aead26fb366be64650482fdf -
Trigger Event:
release
-
Statement type: