App Tracking Transparency Framework for PyObjC
pyobjc-framework-AppTrackingTransparency provides Python wrappers for Apple's App Tracking Transparency framework on macOS. This framework enables applications to request user authorization for tracking across apps and websites, a critical privacy feature introduced in iOS 14 and macOS 11. It is part of the larger PyObjC project, which offers Python bindings for many macOS (Cocoa) frameworks. The current version is 12.1, with releases typically aligning with macOS SDK updates and Python compatibility.
Warnings
- breaking PyObjC 12.0 dropped support for Python 3.9, and PyObjC 11.0 dropped support for Python 3.8. Ensure your project uses Python 3.10 or newer.
- gotcha To display the App Tracking Transparency authorization prompt, you *must* add the `NSUserTrackingUsageDescription` key to your application's `Info.plist` file (typically done in Xcode). Without this, the system will not present the dialog.
- gotcha The `requestTrackingAuthorization` method is a one-time request. The system remembers the user's choice and will not prompt again unless the user uninstalls and then reinstalls the app, or if the status is `notDetermined`. Calls only prompt when the application state is active.
- breaking PyObjC 11.1 introduced significant changes to how the core bridge handles Automatic Reference Counting (ARC) for initializer (`init` family) methods, aligning with `clang`'s documentation. This can affect memory management for custom Objective-C classes implemented in Python.
Install
-
pip install pyobjc-framework-apptrackingtransparency
Imports
- ATTrackingManager
from AppTrackingTransparency import ATTrackingManager
- ATTrackingManagerAuthorizationStatus
from AppTrackingTransparency import ATTrackingManagerAuthorizationStatus
Quickstart
import objc
from Foundation import NSObject, NSLog, NSRunLoop, NSDefaultRunLoopMode, objc_object
from AppTrackingTransparency import ATTrackingManager, ATTrackingManagerAuthorizationStatus
class TrackingDelegate(NSObject):
def init(self):
self = objc.super(TrackingDelegate, self).init()
if self is None: return None
return self
def requestTrackingPermission(self):
current_status = ATTrackingManager.trackingAuthorizationStatus()
NSLog(f"Initial tracking status: {current_status}")
if current_status == ATTrackingManagerAuthorizationStatus.notDetermined:
NSLog("Requesting tracking authorization...")
# Objective-C method with completion handler translates to PyObjC with underscore suffix
ATTrackingManager.requestTrackingAuthorization_(self.trackingAuthorizationCompletionHandler_)
else:
NSLog("Authorization already determined or restricted.")
self.checkTrackingStatus()
def trackingAuthorizationCompletionHandler_(self, status: ATTrackingManagerAuthorizationStatus) -> None:
# This handler might not be called on the main thread; dispatch UI updates if any.
NSLog(f"Tracking authorization completion handler called with status: {status}")
self.checkTrackingStatus()
def checkTrackingStatus(self):
status = ATTrackingManager.trackingAuthorizationStatus()
if status == ATTrackingManagerAuthorizationStatus.authorized:
NSLog("Tracking authorized!")
elif status == ATTrackingManagerAuthorizationStatus.denied:
NSLog("Tracking denied.")
elif status == ATTrackingManagerAuthorizationStatus.notDetermined:
NSLog("Tracking not determined.")
elif status == ATTrackingManagerAuthorizationStatus.restricted:
NSLog("Tracking restricted by parental controls or MDM.")
else:
NSLog("Unknown tracking status.")
def main():
delegate = TrackingDelegate.alloc().init()
delegate.requestTrackingPermission()
# For the completion handler to be called, an NSRunLoop must be active.
# In a full AppKit application, this is handled automatically.
# For a console script, run a short loop.
NSLog("Running runloop for 5 seconds to allow async callback...")
NSRunLoop.currentRunLoop().runUntilDate_(objc.NSTimeIntervalToBeDateTime(objc.current_time() + 5.0))
NSLog("Runloop finished.")
if __name__ == '__main__':
main()