{"id":6168,"library":"pyobjc-framework-searchkit","title":"PyObjC Framework SearchKit","description":"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.","status":"active","version":"12.1","language":"en","source_language":"en","source_url":"https://github.com/ronaldoussoren/pyobjc","tags":["macos","framework","search","objective-c","cocoa","indexing","full-text-search"],"install":[{"cmd":"pip install pyobjc-framework-searchkit","lang":"bash","label":"Install specific framework"},{"cmd":"pip install pyobjc","lang":"bash","label":"Install all PyObjC frameworks (including SearchKit)"}],"dependencies":[{"reason":"This is the core bridge package required for all PyObjC framework wrappers.","package":"pyobjc-core","optional":false},{"reason":"Meta-package that installs all available PyObjC framework wrappers, including SearchKit. Not strictly required if only `pyobjc-framework-searchkit` is installed.","package":"pyobjc","optional":true}],"imports":[{"note":"Individual classes like SKIndex and SKSearch are accessed directly from the SearchKit module.","symbol":"SearchKit","correct":"from SearchKit import SKIndex, SKSearch"},{"note":"NSURL from Foundation (or Cocoa) is often used for file paths with SearchKit.","symbol":"Foundation (or Cocoa)","correct":"from Foundation import NSURL"}],"quickstart":{"code":"import os\nimport tempfile\nimport shutil\nfrom Foundation import NSURL\nfrom SearchKit import SKIndex, SKDocument, SKSearch, SKSearchGroup, kSKSearchOptionFindSimilar, kSKSearchOptionNone\n\n# Create a temporary directory for the index\ntemp_dir = tempfile.mkdtemp()\nindex_path = os.path.join(temp_dir, \"MySearchIndex\")\n\nprint(f\"Creating SearchKit index at: {index_path}\")\n\n# 1. Create an SKIndex\n# Convert Python path to NSURL\nindex_url = NSURL.fileURLWithPath_(index_path)\n\n# Create a new index (kSKIndexTypeInverted or kSKIndexTypeVector)\n# For simplicity, using kSKIndexTypeInverted here\nindex = SKIndex.alloc().initForURL_create_dictionary_(\n    index_url,\n    True,\n    None # No special options dictionary\n)\n\nif not index:\n    print(\"Failed to create SearchKit index.\")\n    shutil.rmtree(temp_dir)\n    exit()\n\n# 2. Add documents to the index\ndoc_id_counter = 0\ndef add_document(content, filename):\n    global doc_id_counter\n    doc_id_counter += 1\n    doc_name = f\"doc_{doc_id_counter}\"\n    # Create an SKDocument from content string\n    document = SKDocument.alloc().initWithURL_mimeType_textEncoding_(\n        NSURL.fileURLWithPath_(filename), # URL identifies the document, can be dummy\n        None, # MIME type, can be None\n        None  # Text encoding, can be None\n    )\n    if document:\n        index.addDocumentWithText_url_properties_(document, content, None, None)\n        print(f\"Added '{filename}' to index.\")\n    else:\n        print(f\"Failed to create SKDocument for {filename}\")\n\nadd_document(\"The quick brown fox jumps over the lazy dog.\", \"file1.txt\")\nadd_document(\"Lazy dogs often sleep deeply.\", \"file2.txt\")\nadd_document(\"A quick jump is good exercise.\", \"file3.txt\")\n\nindex.flush()\nindex.close()\n\n# 3. Perform a search\nsearch_index = SKIndex.alloc().initForURL_create_dictionary_(index_url, False, None)\nif not search_index:\n    print(\"Failed to open SearchKit index for searching.\")\n    shutil.rmtree(temp_dir)\n    exit()\n\nsearch_query = \"quick dog\"\nprint(f\"\\nSearching for: '{search_query}'\")\n\nsearch = SKSearch.alloc().initWithIndex_(\n    search_index\n)\n\nif search:\n    search_options = kSKSearchOptionNone\n    search_group = SKSearchGroup.alloc().init()\n    # You can specify the maximum number of results\n    results = search.findMatchesForQuery_maxCount_(\n        search_query,\n        10 # Max 10 results\n    )\n\n    if results:\n        for i, doc_ref in enumerate(results.objectAtIndex_(0)):\n            score = results.objectAtIndex_(1)[i]\n            # Get original document URL (which we used as a dummy path)\n            doc_url = search_index.documentPropertiesForDocumentRef_(doc_ref).objectForKey_(\"kSKDocumentURL\").path()\n            print(f\"  Match: {doc_url} (Score: {score:.2f})\")\n    else:\n        print(\"  No matches found.\")\nelse:\n    print(\"Failed to create SKSearch object.\")\n\nsearch_index.close()\n\n# 4. Clean up\nshutil.rmtree(temp_dir)\nprint(f\"\\nCleaned up temporary index directory: {temp_dir}\")\n","lang":"python","description":"This quickstart demonstrates how to create a basic SearchKit index in a temporary directory, add text documents, and then perform a simple query to retrieve matching documents and their relevance scores. It highlights the use of `NSURL` for path handling, the `alloc().init...` pattern for object instantiation, and basic `SKIndex` and `SKSearch` operations. Ensure you have the `Foundation` framework available (it's part of `pyobjc-framework-cocoa` or the `pyobjc` meta-package)."},"warnings":[{"fix":"Upgrade Python to a supported version (e.g., Python 3.10 or newer).","message":"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`).","severity":"breaking","affected_versions":"11.0+"},{"fix":"Consider importing from `CoreServices` instead of `SearchKit` for future compatibility, e.g., `from CoreServices import SKIndex`.","message":"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.","severity":"deprecated","affected_versions":"10.0+"},{"fix":"Review custom Objective-C subclassing logic, especially `init` methods, for correct reference handling according to ARC guidelines. PyObjC's documentation on 'Two-phase instantiation' is relevant here.","message":"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.","severity":"gotcha","affected_versions":"11.1+"},{"fix":"If subclassing Objective-C classes in Python with custom `__new__` methods, be aware of `__init__` call restrictions. Refer to PyObjC documentation on 'Two-phase instantiation' for best practices.","message":"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.","severity":"gotcha","affected_versions":"10.3, 10.3.1+"},{"fix":"Always check if an `NSURL` represents a local file path before using `os.fspath()`. For non-local URLs, use other `NSURL` methods to extract components.","message":"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.","severity":"gotcha","affected_versions":"10.1+"}],"env_vars":null,"last_verified":"2026-04-14T00:00:00.000Z","next_check":"2026-07-13T00:00:00.000Z"}