Skip to main content

relib

Small Python utilities for dictionaries, iterables, files, async tasks and local text search. Python 3.10+. The package has no required third-party dependencies.

Install

uv add relib

Most utilities are exported from relib. Import the search classes from relib.search.

from relib import distinct, group, read_json, roll_tasks
from relib.search import SearchIndex

Utilities

Module What it provides
dict_utils Pick, omit, group, remap, flatten and rebuild dictionaries
iter_utils Chunk, partition, deduplicate, interleave and seek through iterables
io_utils Read/write JSON and remove empty directories
processing_utils Clamp values, filter strings and convert arrays to data frames
runtime_tools Async task limits, thread wrappers, timings and progress
type_utils Small helpers for optional values and tuples
search Ranked dictionary search backed by a local index file

Dictionaries and iterables

from relib import distinct, group, partition, pick

assert distinct(["Orion", "Moon", "Orion"]) == ["Orion", "Moon"]
assert group([("clear", "Monday"), ("cloudy", "Tuesday"), ("clear", "Friday")]) == {
    "clear": ["Monday", "Friday"],
    "cloudy": ["Tuesday"],
}
assert pick({"object": "Orion", "exposure": 30, "note": "clear"}, ["object", "exposure"]) == {
    "object": "Orion", "exposure": 30,
}
assert partition((seconds >= 30, seconds) for seconds in [10, 30, 60]) == ([30, 60], [10])

JSON files

from pathlib import Path
from relib import read_json, write_json

path = Path("settings.json")
settings = read_json(path, default={"exposure": 30})
write_json(path, settings, indent=2)

read_json uses the default only when the file is absent. write_json writes to an existing parent directory. clear_directory removes .DS_Store files and empty directories; it does not recursively delete ordinary files.

Async tasks

import asyncio
from pathlib import Path
from relib import as_async, roll_tasks

@as_async(workers=4)
def read_text(path: Path) -> str:
    return path.read_text()

async def main():
    texts = await roll_tasks(
        (read_text(path) for path in Path("notes").glob("*.txt")),
        workers=4,
    )
    print(len(texts))

asyncio.run(main())

as_async runs a synchronous function in a thread pool. roll_tasks limits how many awaitables run concurrently and returns results in input order. async_limit(workers=4) decorates an existing async function with a concurrency limit. measure_duration("name") is a context manager that prints elapsed time.

Progress bars require tqdm; df_from_array requires numpy and pandas. These packages are imported only by the features that use them.

Search dictionaries from disk

SearchIndex builds from JSON-compatible dictionaries. Parsing CSV, XML or other sources belongs to the caller. Records can arrive through a regular one-pass generator; the index does not retain the complete dictionaries in memory.

The examples below use an astronomy observation log.

This is a complete runnable example:

import asyncio
from relib.search import SearchIndex

observations = [
    {
        "id": "000021",
        "title": "Orion nebula red 80 mm",
        "description": "Hydrogen emission long exposure",
        "observer": "Anaïs",
        "categories": ["Deep sky", "Deep sky > Nebulae"],
        "aperture": "80mm",
        "image": "images/orion-red.fits",
    },
    {
        "id": "000022",
        "title": "Orion nebula blue 80mm",
        "description": "Oxygen emission long exposure",
        "observer": "Anaïs",
        "categories": ["Deep sky", "Deep sky > Nebulae"],
        "aperture": "80mm",
        "image": "images/orion-blue.fits",
    },
    {
        "id": "000031",
        "title": "Lunar mosaic",
        "description": "High resolution crater survey",
        "observer": "Sam",
        "categories": ["Solar system", "Solar system > Moon"],
        "aperture": "200mm",
        "image": "images/moon.fits",
    },
]

async def main():
    index = await SearchIndex.build(
        "observations.idx",
        iter(observations),
        fields={"id": 8, "title": 4, "observer": 2, "categories": 2, "description": 1, "aperture": 1},
        filter_fields=["observer", "categories"],
    )
    with index:
        for hit in await index.search("anais red 80mm"):
            print(hit.record["id"], hit.record["title"], hit.matched_terms)
            # 000021 Orion nebula red 80 mm ('anais', 'red', '80', 'mm')

asyncio.run(main())

fields selects searchable fields and their relative weights. Passing a list of names instead gives every field weight 1. Searchable values can be scalars or lists. Other fields, such as image, remain in the stored record without being indexed. Keep identifiers as strings to preserve leading zeros.

Each result is a SearchHit with the original record dictionary, a numeric score and normalized matched_terms. Only records on the returned page are decoded, during the search call. hit.record is already a dictionary.

Query options

Reuse an open index across queries. Within an async function:

with SearchIndex("observations.idx") as index:
    by_id = await index.search("000021")
    words = await index.search("hydrogen exposure")
    completion = await index.search("Orion neb", prefix=True)
    category = await index.search(filters={"categories": "Deep sky"})
    filtered = await index.search("nebula", filters={"observer": "anais"})
    next_page = await index.search("nebula", offset=1, limit=1)
    alternatives = await index.search("Orion ultraviolet", match="any")
  • All words must match by default, across any indexed fields. Unknown words are not silently removed. match="any" permits partial matches and ranks the number of matched words before relevance.
  • Case and accents normalize: anais matches Anaïs. Letters and numbers split, so 80mm matches 80 mm. 000021 and 21 remain different tokens.
  • prefix=True completes only the final query word. Punctuation is text separation, not query syntax.
  • Filters match complete values, ignoring case and accents. A list of values means OR within one field; separate fields combine with AND. Declare filter fields when building. Category trees have no special meaning: include parent paths in each record if you want to filter by them.
  • Empty queries browse filtered records in source order. Without filters, an empty query returns no results. len(index) gives the record count.
  • Ranking combines field weights, word frequencies, field lengths and how rare each word is in the collection. Equal scores use source order as the tie-break.
  • There is no stemming, automatic translation, typo correction or semantic matching. Add aliases to your input dictionaries when needed.

Storage and lifetime

An index is one file containing records, term/filter postings, record offsets and metadata. Opening it memory-maps the file and reads the term/filter catalog; it does not decode every record. Query caches retain up to 128 text lookups and 128 filter values.

Builds create a new snapshot and replace the target file after success. Failed record streams leave the previous file intact. Existing open readers keep their snapshot; new readers see the replacement. Updates require rebuilding the index.

Building retains the vocabulary and compact postings in memory, so its memory usage still grows with the collection. Broad partial-match queries can inspect many more candidates than selective queries. Concurrent reads are supported; close the index only after its queries finish.

Metadata

Release files for relib 1.3.18

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for relib 1.3.18
File Size Uploaded
relib-1.3.18.tar.gz 32.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for relib 1.3.18
File Interpreter ABI Platform
relib-1.3.18-py3-none-any.whl Python 3 none any Details

Total release size: 52.2 kB

Release files / relib-1.3.18.tar.gz

Download URL relib-1.3.18.tar.gz
Size 32.4 kB
Tags Source
SHA-256 checksum
How to use checksums
819a6aaee0c37ab9507d3eda066b3af93ac4eba50e99a1deab0b7231d6533c98
BLAKE2b-256 checksum
How to use checksums
ce4693ffdce201cc5929dd6a826e3211624f45c06992d4a4dd310d5022cb8132
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.3

Release files / relib-1.3.18-py3-none-any.whl

Download URL relib-1.3.18-py3-none-any.whl
Size 19.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7fae65380ab1c162b4a41ef7203ff7e036c3a8c56da76fcd4c72c9dcf9d34f2d
BLAKE2b-256 checksum
How to use checksums
2bce0316330494df2df9ce2044efe834b112c684258cc1820396e9ea66106d27
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.3

Release history Release notifications | RSS feed

This release

1.3.18 This release

2 release files

1.3.17

2 release files

1.3.15

2 release files

1.3.14

2 release files

1.3.13

2 release files

1.3.12

2 release files

1.3.11

2 release files

1.3.10

2 release files

1.3.9

2 release files

1.3.8

2 release files

1.3.7

2 release files

1.3.6

2 release files

1.3.5

2 release files

1.3.4

2 release files

1.3.3

2 release files

1.3.2

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.11

2 release files

1.2.10

2 release files

1.2.9

2 release files

1.2.8

2 release files

1.2.7

2 release files

1.2.6

2 release files

1.2.5

2 release files

1.2.4

2 release files

1.2.3

2 release files

1.2.2

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.8

1 release file

1.0.7

1 release file

1.0.6

1 release file

1.0.5

1 release file

1.0.4

1 release file

1.0.3

1 release file

1.0.2

1 release file

1.0.1

1 release file

1.0.0

1 release file

0.2.4

1 release file

0.2.3

1 release file

0.2.2

1 release file

0.2.1

1 release file

0.2.0

1 release file

0.1.1

1 release file

0.1

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page