PyObjC Framework SearchKit
PyObjC is a bridge between Python and Objective-C, enabling Python scripts to interact with and extend existing Objective-C class libraries, most notably Apple's Cocoa frameworks on macOS. The `pyobjc-framework-searchkit` package provides Python wrappers specifically for the macOS SearchKit framework, allowing for programmatic creation, management, and searching of full-text indexes. The PyObjC library (which includes this framework wrapper) is actively maintained, with version 12.1 released on 2025-11-14, and generally aligns its releases with new macOS SDK versions and Python language support changes.
Warnings
- breaking PyObjC v12.0 dropped support for Python 3.9. Prior to that, v11.0 dropped support for Python 3.8. Users should ensure their Python version meets the `requires_python` specification (currently `>=3.10`).
- deprecated The separate framework wrappers for `DictionaryServices`, `LaunchServices`, and `SearchKit` are deprecated. While `pyobjc-framework-searchkit` still exists, it is now effectively an alias for the `CoreServices` bindings. Users are encouraged to transition to using the `CoreServices` bindings directly, as they may expose more symbols and represent the current recommended approach.
- gotcha Behavior of initializer methods changed in PyObjC v11.1 to align with `clang`'s Automatic Reference Counting (ARC) documentation. Methods in the 'init' family now correctly model that they steal a reference to `self` and return a new reference. This might affect custom Objective-C subclassing or complex object lifecycle management.
- gotcha Changes in PyObjC v10.3 regarding the calling of `__init__` when a user implements `__new__` for Objective-C subclasses caused issues for several projects, leading to a partial reintroduction of the old behavior in v10.3.1. Code relying on custom `__new__` implementations in Objective-C subclasses might still encounter unexpected behavior if not carefully managed.
- gotcha When working with `NSURL` objects that represent file system paths, `os.fspath(someURL)` can be used to convert them to Python filesystem paths. However, this will raise a `TypeError` if the `NSURL` does not refer to a local filesystem path.
Install
-
pip install pyobjc-framework-searchkit -
pip install pyobjc
Imports
- SearchKit
from SearchKit import SKIndex, SKSearch
- Foundation (or Cocoa)
from Foundation import NSURL
Quickstart
import os
import tempfile
import shutil
from Foundation import NSURL
from SearchKit import SKIndex, SKDocument, SKSearch, SKSearchGroup, kSKSearchOptionFindSimilar, kSKSearchOptionNone
# Create a temporary directory for the index
temp_dir = tempfile.mkdtemp()
index_path = os.path.join(temp_dir, "MySearchIndex")
print(f"Creating SearchKit index at: {index_path}")
# 1. Create an SKIndex
# Convert Python path to NSURL
index_url = NSURL.fileURLWithPath_(index_path)
# Create a new index (kSKIndexTypeInverted or kSKIndexTypeVector)
# For simplicity, using kSKIndexTypeInverted here
index = SKIndex.alloc().initForURL_create_dictionary_(
index_url,
True,
None # No special options dictionary
)
if not index:
print("Failed to create SearchKit index.")
shutil.rmtree(temp_dir)
exit()
# 2. Add documents to the index
doc_id_counter = 0
def add_document(content, filename):
global doc_id_counter
doc_id_counter += 1
doc_name = f"doc_{doc_id_counter}"
# Create an SKDocument from content string
document = SKDocument.alloc().initWithURL_mimeType_textEncoding_(
NSURL.fileURLWithPath_(filename), # URL identifies the document, can be dummy
None, # MIME type, can be None
None # Text encoding, can be None
)
if document:
index.addDocumentWithText_url_properties_(document, content, None, None)
print(f"Added '{filename}' to index.")
else:
print(f"Failed to create SKDocument for {filename}")
add_document("The quick brown fox jumps over the lazy dog.", "file1.txt")
add_document("Lazy dogs often sleep deeply.", "file2.txt")
add_document("A quick jump is good exercise.", "file3.txt")
index.flush()
index.close()
# 3. Perform a search
search_index = SKIndex.alloc().initForURL_create_dictionary_(index_url, False, None)
if not search_index:
print("Failed to open SearchKit index for searching.")
shutil.rmtree(temp_dir)
exit()
search_query = "quick dog"
print(f"\nSearching for: '{search_query}'")
search = SKSearch.alloc().initWithIndex_(
search_index
)
if search:
search_options = kSKSearchOptionNone
search_group = SKSearchGroup.alloc().init()
# You can specify the maximum number of results
results = search.findMatchesForQuery_maxCount_(
search_query,
10 # Max 10 results
)
if results:
for i, doc_ref in enumerate(results.objectAtIndex_(0)):
score = results.objectAtIndex_(1)[i]
# Get original document URL (which we used as a dummy path)
doc_url = search_index.documentPropertiesForDocumentRef_(doc_ref).objectForKey_("kSKDocumentURL").path()
print(f" Match: {doc_url} (Score: {score:.2f})")
else:
print(" No matches found.")
else:
print("Failed to create SKSearch object.")
search_index.close()
# 4. Clean up
shutil.rmtree(temp_dir)
print(f"\nCleaned up temporary index directory: {temp_dir}")