Skip to main content

A reusable Tkinter search widget with fuzzy multi-word matching

Project description

tksearchengine

PyPI version Python versions License Tests

tksearchengine provides a reusable Tkinter search field with fuzzy, multi-word suggestions. It combines an entry, a search button, and a listbox in a single configurable widget.

Features

  • Placeholder text while the empty entry is not focused.
  • Live suggestions while the entry is focused.
  • Mouse selection and keyboard navigation with Up and Down.
  • Tab accepts the highlighted suggestion, or the first suggestion by default.
  • Return and the search button invoke the same callback.
  • Case-insensitive, accent-insensitive, multi-word fuzzy matching.
  • - and ' are treated as word separators.
  • Missing spaces and spaces accidentally replaced by n or b are handled.
  • Exact words, exact prefixes, approximate prefixes, and approximate complete words are ranked in that order.
  • No third-party runtime dependencies.

Requirements

  • Python 3.10 or newer.
  • Tkinter, which is included with many Python installations but may require a separate operating-system package on Linux. For example, Debian and Ubuntu provide it in the python3-tk package.

Installation

From PyPI:

python -m pip install tksearchengine

From the project directory:

python -m pip install .

For editable development:

python -m pip install -e .

Basic usage

import tkinter as tk

from tksearchengine import SearchEngine


def run_search(text: str, selected_item: str | None) -> None:
    print("Search text:", text)
    print("Selected suggestion:", selected_item)


root = tk.Tk()

search = SearchEngine(
    root,
    items=["Paris", "Marseille", "Lyon", "Orléans"],
    command=run_search,
    placeholder="Search for a city",
    max_visible_results=6,
)
search.pack(fill="x", padx=20, pady=20)

root.mainloop()

Configuration

The constructor accepts:

  • items: searchable strings.
  • command: callback receiving the current text and the selected suggestion. The second argument is None when the user submits free text without accepting a suggestion.
  • placeholder: text displayed when the unfocused entry is empty.
  • button_text: label or symbol displayed on the search button.
  • search_function: optional replacement for the complete ranking function.
  • max_distance_function: optional function returning the accepted edit distance for a given reference-word length.
  • max_visible_results: maximum visible listbox rows.
  • placeholder_color and text_color: entry text colors.
  • entry_options, button_options, and listbox_options: mappings forwarded to the corresponding Tkinter widgets.
  • Any remaining keyword arguments are forwarded to the containing tk.Frame.

Here is the __init__ shape:

    def __init__(
        self,
        master: tk.Misc | None = None,
        *,
        items: Sequence[str] = (),
        command: SearchCallback | None = None,
        placeholder: str = "Search…",
        button_text: str = "🔍",
        search_function: SearchFunction | None = None,
        max_distance_function: MaxDistanceFunction = _default_max_distance,
        max_visible_results: int = 8,
        placeholder_color: str = "grey",
        text_color: str = "black",
        entry_options: Mapping[str, Any] | None = None,
        button_options: Mapping[str, Any] | None = None,
        listbox_options: Mapping[str, Any] | None = None,
        **frame_options: Any,
    ) -> None:

Example with widget customization:

search = SearchEngine(
    root,
    items=["Paris", "Marseille", "Lyon", "Orléans"],
    entry_options={
        "width": 32,
        "font": ("Segoe UI", 11),
        "relief": "solid",
    },
    button_options={
        "text": "Search",
        "font": ("Segoe UI", 10, "bold"),
        "padx": 12,
    },
    listbox_options={
        "font": ("Segoe UI", 10),
        "height": 6,
        "activestyle": "dotbox",
    },
)

These mappings are passed directly to tk.Entry, tk.Button, and tk.Listbox. SearchEngine still owns the entry text variable, the button command, the listbox selection behavior, and the visible result count.

A custom search function has this shape:

from collections.abc import Sequence


def custom_search(items: Sequence[str], query: str) -> Sequence[str]:
    normalized_query = query.lower()
    return [item for item in items if normalized_query in item.lower()]

Pass it with search_function=custom_search. When a custom search function is provided, it is responsible for filtering and ordering all results.

Public methods

  • get() returns the current search text without the placeholder.
  • set(text) replaces the current search text.
  • set_items(items) replaces the searchable collection.
  • invoke() runs the configured callback.
  • selected_item returns the highlighted or accepted suggestion, if any.

Matching behavior

The default matcher normalizes text to lowercase, replaces common accented letters with their unaccented forms, and treats - and ' as spaces. Results are ranked by:

  1. number of unmatched query words;
  2. match type, from exact word to approximate complete word;
  3. normalized Levenshtein distance;
  4. query-word order;
  5. original item order.

The default maximum edit distance is (word_length + 2) // 5. Override max_distance_function(word_length) when an application needs stricter or more permissive matching.

Running the example

The example contains 1,000 French commune names:

python examples/communes.py

Filtering the 1,000 sample names should feel immediate on a typical desktop Python installation.

Running the tests

After an editable installation:

python -m unittest discover -s tests -v

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tksearchengine-1.0.2.tar.gz (12.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tksearchengine-1.0.2-py3-none-any.whl (9.1 kB view details)

Uploaded Python 3

File details

Details for the file tksearchengine-1.0.2.tar.gz.

File metadata

  • Download URL: tksearchengine-1.0.2.tar.gz
  • Upload date:
  • Size: 12.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tksearchengine-1.0.2.tar.gz
Algorithm Hash digest
SHA256 db868b1487dbb8cbe0f2670b8f7888ba7c91abe786b65266024e99f55606f2d5
MD5 a0fa790fd7c4b0d969c5d247d476e8ba
BLAKE2b-256 84e37e6b27c61ffac20acc355f3b07aedcf55741de6e602a316023525db4c5db

See more details on using hashes here.

Provenance

The following attestation bundles were made for tksearchengine-1.0.2.tar.gz:

Publisher: release.yml on WhatIsMyRealName/tksearchengine

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tksearchengine-1.0.2-py3-none-any.whl.

File metadata

  • Download URL: tksearchengine-1.0.2-py3-none-any.whl
  • Upload date:
  • Size: 9.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tksearchengine-1.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 c7b703c2fa8606b762d5ce6963fdd7112133310bb933e71992f112904ceb4873
MD5 a1aac42a9a193a8511dbbedc7bc2873d
BLAKE2b-256 5807036dd47bf8711ccd36026189169d78fcf567fb65cdc4d6191465511e13d2

See more details on using hashes here.

Provenance

The following attestation bundles were made for tksearchengine-1.0.2-py3-none-any.whl:

Publisher: release.yml on WhatIsMyRealName/tksearchengine

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page