Wrappers for the Cocoa frameworks on macOS
—
High-level utility modules that simplify common PyObjC development tasks. These tools provide Python-friendly wrappers for application lifecycle management, data conversion between Python and Objective-C, and enhanced functionality for existing Cocoa classes.
Essential functions for managing PyObjC application lifecycle, event loops, and cross-thread communication in macOS applications.
def runEventLoop():
"""
Runs the main application event loop with enhanced exception handling.
Provides a safer alternative to NSApplicationMain() that catches and logs
Python exceptions that would otherwise crash the application.
Raises:
SystemExit: When application termination is requested
"""
def runConsoleEventLoop():
"""
Runs a console-friendly event loop that can be stopped programmatically.
Unlike runEventLoop(), this doesn't start a full GUI application but
allows processing of events and timers in command-line applications.
"""
def stopEventLoop():
"""
Stops the current event loop or terminates the application.
Can be called from any thread to cleanly shut down the application
or stop a console event loop started with runConsoleEventLoop().
"""
def endSheetMethod(method):
"""
Decorator that properly signs a method for use as a sheet callback.
Args:
method: Method to be used as sheet end callback
Returns:
Decorated method with correct Objective-C signature
Example:
@endSheetMethod
def sheetDidEnd_returnCode_contextInfo_(self, sheet, returnCode, contextInfo):
# Handle sheet completion
pass
"""
def callAfter(func, *args, **kwargs):
"""
Schedules a function to be called on the main thread asynchronously.
Args:
func: Function to call
*args: Positional arguments for function
**kwargs: Keyword arguments for function
Returns:
None (executes asynchronously)
"""
def callLater(delay, func, *args, **kwargs):
"""
Schedules a function to be called on the main thread after a delay.
Args:
delay (float): Delay in seconds before calling function
func: Function to call
*args: Positional arguments for function
**kwargs: Keyword arguments for function
Returns:
None (executes asynchronously)
"""Functions for converting data between Python and Objective-C types, particularly useful for property lists and decimal numbers.
def pythonCollectionFromPropertyList(collection):
"""
Converts Objective-C property list collections to Python equivalents.
Recursively converts NSArray, NSDictionary, NSString, NSNumber, NSDate,
and NSData objects to Python list, dict, str, int/float, datetime, and bytes.
Args:
collection: Objective-C collection object (NSArray, NSDictionary, etc.)
Returns:
Python equivalent collection (list, dict, etc.)
"""
def propertyListFromPythonCollection(collection):
"""
Converts Python collections to Objective-C property list objects.
Recursively converts Python list, dict, str, int, float, bool, datetime,
and bytes objects to NSArray, NSDictionary, NSString, NSNumber, NSDate, and NSData.
Args:
collection: Python collection (list, dict, etc.)
Returns:
Objective-C property list object
"""
def serializePropertyList(plist, format):
"""
Serializes a property list object to NSData.
Args:
plist: Property list object (NSDictionary, NSArray, etc.)
format: Serialization format (NSPropertyListXMLFormat_v1_0,
NSPropertyListBinaryFormat_v1_0, etc.)
Returns:
NSData: Serialized property list data
Raises:
ValueError: If serialization fails
"""
def deserializePropertyList(data):
"""
Deserializes property list data to objects.
Args:
data: NSData containing serialized property list
Returns:
tuple: (property_list_object, format_used)
Raises:
ValueError: If deserialization fails
"""
def toPythonDecimal(decimal):
"""
Converts NSDecimalNumber to Python decimal.Decimal.
Args:
decimal: NSDecimalNumber object
Returns:
decimal.Decimal: High-precision decimal number
"""
def fromPythonDecimal(decimal):
"""
Converts Python decimal.Decimal to NSDecimalNumber.
Args:
decimal: decimal.Decimal object
Returns:
NSDecimalNumber: Objective-C decimal number
"""PYTHON_TYPES: tuple # Tuple of Python types supported for conversion
FORMATS: dict # Dictionary mapping format names to NSPropertyList format constantsModules that automatically enhance existing Cocoa classes with Python-friendly methods when imported.
# NSGraphicsContext enhancements (automatically applied when imported)
@classmethod
def savedGraphicsState(cls):
"""
Context manager for preserving and restoring graphics state.
Returns:
Context manager that saves current graphics state on entry
and restores it on exit
Usage:
with NSGraphicsContext.savedGraphicsState():
# Modify graphics state
NSColor.redColor().set()
# State automatically restored on exit
"""
# NSAnimationContext enhancements (automatically applied when imported)
def __enter__(self):
"""Context manager entry for animation grouping."""
def __exit__(self, exc_type, exc_val, exc_tb):
"""Context manager exit for animation grouping."""# NSAffineTransform enhancements (automatically applied when imported)
def rotateByDegrees_atPoint_(self, degrees, point):
"""
Rotates the coordinate space by degrees around a specific point.
Args:
degrees (float): Rotation angle in degrees
point: NSPoint around which to rotate
"""
def rotateByRadians_atPoint_(self, radians, point):
"""
Rotates the coordinate space by radians around a specific point.
Args:
radians (float): Rotation angle in radians
point: NSPoint around which to rotate
"""from PyObjCTools import AppHelper
import AppKit
class MyAppDelegate(AppKit.NSObject):
def applicationDidFinishLaunching_(self, notification):
print("Application started")
# Schedule something to run later
AppHelper.callLater(2.0, self.delayedAction, "Hello", world=True)
def delayedAction(self, message, world=False):
print(f"Delayed message: {message}")
if world:
print("World!")
# Set up application
app = AppKit.NSApplication.sharedApplication()
delegate = MyAppDelegate.alloc().init()
app.setDelegate_(delegate)
# Run event loop with exception handling
try:
AppHelper.runEventLoop()
except KeyboardInterrupt:
AppHelper.stopEventLoop()from PyObjCTools import AppHelper
import threading
import time
def background_work():
"""Simulate background work that needs to update UI."""
for i in range(10):
time.sleep(1)
# Update UI on main thread
AppHelper.callAfter(update_progress, i + 1)
# Final update
AppHelper.callAfter(work_completed)
def update_progress(value):
"""Called on main thread to update UI."""
print(f"Progress: {value}/10")
# Update progress bar, label, etc.
def work_completed():
"""Called on main thread when work is done."""
print("Background work completed!")
# Start background work
threading.Thread(target=background_work, daemon=True).start()from PyObjCTools import AppHelper
import AppKit
class DocumentController(AppKit.NSObject):
@AppHelper.endSheetMethod
def saveSheetDidEnd_returnCode_contextInfo_(self, sheet, returnCode, contextInfo):
"""Handle save sheet completion."""
if returnCode == AppKit.NSModalResponseOK:
# User clicked Save
self.performSave()
else:
# User clicked Cancel
self.cancelSave()
def showSaveSheet(self):
"""Show save sheet with proper callback."""
save_panel = AppKit.NSSavePanel.savePanel()
save_panel.beginSheetModalForWindow_completionHandler_(
self.window,
self.saveSheetDidEnd_returnCode_contextInfo_
)from PyObjCTools import Conversion
import Foundation
import decimal
# Convert Python data to Objective-C property list
python_data = {
"name": "Test Application",
"version": 1.0,
"features": ["feature1", "feature2", "feature3"],
"enabled": True,
"config": {
"debug": False,
"timeout": 30
}
}
# Convert to Objective-C objects
objc_data = Conversion.propertyListFromPythonCollection(python_data)
# Serialize to XML format
xml_data = Conversion.serializePropertyList(objc_data, Conversion.FORMATS['xml'])
# Save to file
xml_data.writeToFile_atomically_("/tmp/config.plist", True)
# Read back and convert to Python
file_data = Foundation.NSData.dataWithContentsOfFile_("/tmp/config.plist")
deserialized, format_used = Conversion.deserializePropertyList(file_data)
python_data_back = Conversion.pythonCollectionFromPropertyList(deserialized)
print("Original:", python_data)
print("Restored:", python_data_back)from PyObjCTools import Conversion
import decimal
import Foundation
# High-precision decimal arithmetic
python_decimal = decimal.Decimal("123.456789012345678901234567890")
objc_decimal = Conversion.fromPythonDecimal(python_decimal)
# Use in calculations
multiplier = Foundation.NSDecimalNumber.decimalNumberWithString_("2.0")
result = objc_decimal.decimalNumberByMultiplyingBy_(multiplier)
# Convert back to Python
python_result = Conversion.toPythonDecimal(result)
print(f"Result: {python_result}") # Maintains full precisionfrom PyObjCTools import AppCategories # Auto-applies enhancements
import AppKit
def drawCustomShape(self):
"""Custom drawing method using enhanced graphics context."""
# Use context manager for automatic state management
with AppKit.NSGraphicsContext.savedGraphicsState():
# Set custom drawing state
AppKit.NSColor.blueColor().set()
path = AppKit.NSBezierPath.bezierPathWithOvalInRect_(self.bounds())
path.setLineWidth_(3.0)
path.stroke()
# Change state for different drawing
AppKit.NSColor.redColor().set()
inner_rect = self.bounds().insetBy(10.0, 10.0)
inner_path = AppKit.NSBezierPath.bezierPathWithRect_(inner_rect)
inner_path.fill()
# Graphics state automatically restored herefrom PyObjCTools import AppCategories # Auto-applies enhancements
import AppKit
def animateViewChanges(self):
"""Animate view changes using enhanced animation context."""
# Use context manager for animation grouping
with AppKit.NSAnimationContext.currentContext():
# Configure animation
AppKit.NSAnimationContext.currentContext().setDuration_(0.3)
AppKit.NSAnimationContext.currentContext().setTimingFunction_(
AppKit.CAMediaTimingFunction.functionWithName_(AppKit.kCAMediaTimingFunctionEaseInEaseOut)
)
# Animated changes
self.view.animator().setFrame_(new_frame)
self.view.animator().setAlphaValue_(0.5)
# Animation automatically committed on exitfrom PyObjCTools import FndCategories # Auto-applies enhancements
import Foundation
# Create and manipulate affine transform
transform = Foundation.NSAffineTransform.transform()
# Rotate around specific point using enhanced methods
center_point = Foundation.NSMakePoint(100, 100)
transform.rotateByDegrees_atPoint_(45.0, center_point)
# Or use radians
import math
transform.rotateByRadians_atPoint_(math.pi / 4, center_point)
# Apply transform to graphics context
transform.concat()# Individual tool imports
from PyObjCTools import AppHelper
from PyObjCTools import Conversion
# Category modules (auto-apply enhancements)
from PyObjCTools import AppCategories # Enhances AppKit classes
from PyObjCTools import FndCategories # Enhances Foundation classes
# Specific function imports
from PyObjCTools.AppHelper import runEventLoop, callAfter, callLater
from PyObjCTools.Conversion import pythonCollectionFromPropertyList, toPythonDecimalThe PyObjCTools modules provide robust error handling:
All utilities are designed to work seamlessly with standard PyObjC exception handling and logging patterns.
Install with Tessl CLI
npx tessl i tessl/pypi-pyobjc-framework-cocoa