PyObjC Framework FinderSync
pyobjc-framework-findersync provides Python wrappers for the macOS FinderSync.framework, allowing Python applications to extend Finder functionality. It is part of the larger PyObjC project, which provides a bridge between Python and Objective-C, enabling Python code to interact with macOS Cocoa frameworks. The PyObjC project, including its framework wrappers, typically releases new versions in alignment with macOS SDK updates and Python version support changes.
Warnings
- breaking PyObjC versions frequently drop support for older Python versions. PyObjC 12.0 dropped Python 3.9 support, and PyObjC 11.0 dropped Python 3.8 support. Future versions will likely continue this trend.
- breaking PyObjC 11.1 aligned its internal behavior for 'init' methods with clang's Automatic Reference Counting (ARC) documentation. This means 'init' methods are now correctly modeled as stealing a reference to self and returning a new reference, potentially affecting custom memory management logic or bridging in older code.
- gotcha In PyObjC 10.3, calling __init__ on PyObjC-proxied classes was restricted when the class relies on PyObjC's provided __new__. While partially re-enabled in 10.3.1 for user-implemented __new__, code relying on PyObjC's __new__ still cannot use __init__. Instead, use Objective-C style initialization (e.g., MyClass.alloc().init()) or override __new__ and handle initialization there.
- gotcha Prior to PyObjC 10.1, `os.fspath()` did not work correctly with Cocoa `NSURL` objects (or `CFURLRef`) that referred to local filesystem paths, raising a `TypeError` for other URL types. While fixed in 10.1, older PyObjC versions required manual path conversion for `NSURL` objects.
- gotcha PyObjC 11.0 introduced experimental support for free-threading (PEP 703) in Python 3.13. However, PyObjC does not fully support the experimental free threading in Python 3.13, which required significant changes in its core. Exercise caution and thoroughly test any code running with free-threading enabled.
Install
-
pip install pyobjc-framework-findersync
Imports
- FIFinderSyncController
from FinderSync import FIFinderSyncController
- FIFinderSync
from FinderSync import FIFinderSync
Quickstart
import objc
from FinderSync import FIFinderSync, FIFinderSyncController
from Foundation import NSURL
import os
class MyFinderSyncDelegate(FIFinderSync):
"""
A minimal Finder Sync delegate class.
In a real Finder Sync Extension, this class would implement methods
like `requestBadgeIdentifierForURL_` or `toolbarItemImageForURL_`.
"""
def init(self):
self = objc.super().init()
print("MyFinderSyncDelegate initialized.")
return self
# Example: You would implement FinderSync methods here.
# def requestBadgeIdentifierForURL_(self, url):
# print(f"Requesting badge for: {url}")
# return "MyBadgeIdentifier" # Define this in Info.plist
def setup_finder_sync():
"""
Illustrates how to configure the Finder Sync Controller.
Note: This script itself cannot become a Finder Sync Extension.
A Finder Sync Extension must be embedded within a host application
bundle and configured via its Info.plist to be loaded by Finder.
This code demonstrates the PyObjC API usage within such an extension.
"""
controller = FIFinderSyncController.sharedExtensionController()
if controller:
# Create an instance of our delegate
delegate_instance = MyFinderSyncDelegate.alloc().init()
controller.setDelegate_(delegate_instance)
# Specify directories to monitor
# For demonstration, let's try to monitor the user's Downloads folder
downloads_path = os.path.expanduser("~/Downloads")
downloads_url = NSURL.fileURLWithPath_(downloads_path)
controller.setDirectoryURLs_([downloads_url])
print(f"FinderSyncController delegate set: {controller.delegate()}")
print(f"Watching directories: {controller.directoryURLs()}")
print("\nNote: For this to take effect, this code must be run from within")
print("a properly configured macOS Finder Sync Extension bundle.")
print("Simply running this Python script will not register the extension.")
else:
print("FIFinderSyncController.sharedExtensionController() returned None.")
print("This usually means the code is not running within a Finder Sync Extension process.")
print("A Finder Sync Extension requires specific macOS bundle setup to function.")
if __name__ == "__main__":
setup_finder_sync()