Biting the Apple

Unlocking macOS with Python

This presentation:
https://rhettbull.github.io/biting-the-apple/

Source:
https://github.com/RhetTbull/biting-the-apple

Biting the Apple: Unlocking macOS with Python

About Me

  • Hobbyist programmer
  • First code in Tandy BASIC on a TRS-80 Model III
  • Reformed Perl hacker
  • Pythonista since 2018
  • github.com/RhetTbull
Biting the Apple: Unlocking macOS with Python

Installing Python on macOS

Just use uv or python.org

Biting the Apple: Unlocking macOS with Python

Automating Mac Apps

Many Mac apps are scriptable using AppleScript

tell application "Notes"
	activate

	set noteTitle to "Hello World"
	set noteBody to "This is my note."

	tell account "iCloud"
		set newNote to make new note at folder "Notes" with properties
		          {name:noteTitle, body:noteBody}
	end tell
end tell
Biting the Apple: Unlocking macOS with Python

Isn't This Talk Supposed To Be About Python?

pip install py-applescript

import applescript

def create_note(title: str, body: str) -> str:
    """Create a new note in Notes.app"""
    script = """
    on createNote(noteTitle, noteBody)
        tell application "Notes"
            activate

            tell account "iCloud"
                set newNote to make new note at folder "Notes" with properties {name:noteTitle, body:noteBody}
                return id of newNote
            end tell
        end tell
    end createNote
    """
    ascript = applescript.AppleScript(script)
    return str(ascript.call("createNote", title, body))

note_id = create_note("Hello World", "This is my note.")
print(f"New note created with id {note_id}")
Biting the Apple: Unlocking macOS with Python

There's a Package For That!

pip install macnotesapp

import macnotesapp

new_note = macnotesapp.NotesApp().make_note("Hello World", "This is my note.")
print(f"New note created with ID {new_note}")

pip install photoscript

import photoscript

for photo in photoscript.PhotosLibrary().selection:
    photo.keywords += ["Travel"]
Biting the Apple: Unlocking macOS with Python

PyXA: Python for Automation

pip install mac-pyxa (Doesn't work yet with Python 3.13)

import datetime
import PyXA

reminders_app = PyXA.Application("Reminders")
reminders_app.new_reminder(
    name="Finish HSV.py talk", due_date=datetime.datetime(2025, 2, 10)
)
Biting the Apple: Unlocking macOS with Python

Beyond Scripting: Accessing the Power of Native APIs

Apple provides useful APIs in Objective-C and Swift that enable features like extracting text from images, speech synthesis, accessing the Mac's camera, etc.

Can we use these from Python?

Biting the Apple: Unlocking macOS with Python

Yes! Python to Objective-C Bridge

PyObjC

Appears to have at least tacit blessing from Apple. Stable, offers support for most macOS APIs. Does not support iOS.

Rubicon

Part of the BeeWare project. Supported by Anaconda. Supports both macOS and iOS.

Biting the Apple: Unlocking macOS with Python

Simple PyObjC Example

Use native copy-on-write for APFS file systems.

Python implementation:

import Foundation

def copyfile(src: str, dest: str):
    """Copy file from src to dest using NSFileManager"""
    filemgr = Foundation.NSFileManager.defaultManager()
    success, error = filemgr.copyItemAtPath_toPath_error_(src, dest, None)
    if not success:
        raise OSError(error)
Biting the Apple: Unlocking macOS with Python

Speech Synthesis Example

import AVFoundation
from Foundation import NSObject
from PyObjCTools.AppHelper import runEventLoop, stopEventLoop

class SpeechSynthesizerDelegate(NSObject):
    def speechSynthesizer_didFinishSpeechUtterance_(self, synthesizer, utterance):
        stopEventLoop()

def speak_string(text: str) -> None:
    synthesizer = AVFoundation.AVSpeechSynthesizer.alloc().init()
    utterance = AVFoundation.AVSpeechUtterance.speechUtteranceWithString_(text)
    voice = AVFoundation.AVSpeechSynthesisVoice.voiceWithLanguage_("en-US")
    utterance.setVoice_(voice)
    delegate = SpeechSynthesizerDelegate.alloc().init()
    synthesizer.setDelegate_(delegate)
    synthesizer.speakUtterance_(utterance)
    runEventLoop()
Biting the Apple: Unlocking macOS with Python

Status Bar Apps

Rumps: Ridiculously Uncomplicated macOS Python Statusbar

import rumps

class AwesomeStatusBarApp(rumps.App):
    @rumps.clicked("Check button")
    def onoff(self, sender):
        sender.state = not sender.state

    @rumps.clicked("Say hello")
    def sayhello(self, _):
        rumps.alert("Hello", "Hello HSV.py!", "Goodbye")

if __name__ == "__main__":
    AwesomeStatusBarApp("Awesome App").run()
Biting the Apple: Unlocking macOS with Python

What about a GUI?

That's a whole other talk

  • Most Python GUI frameworks work fine with macOS
  • Tkinter: Ships with Python standard library
  • Toga: From BeeWare, native widgets, early development
  • AppKitGUI: My own mini-framework, shows how to use AppKit from Python
Biting the Apple: Unlocking macOS with Python

Creating a Standalone App

  • PyInstaller: Good for command line tools
  • Py2App: Standalone Mac apps (like py2exe but for macOS)
  • PyApp: Self-bootstrapping apps with self-update, very fast, written in Rust
  • Briefcase: From BeeWare, similar to Py2App
  • AppleCrate: Create installers for your app
Biting the Apple: Unlocking macOS with Python

Permissions & Entitlements

macOS has a naggy robust security model

  • Certain features require user approval
  • Entitlements: executable permission to use a technology / service
  • Permissions: control run-time access to sensitive user data (e.g. location, files)

Command line apps: the Terminal app is responsible for requesting permission.

GUI apps: the application is responsible for requesting permission.

Biting the Apple: Unlocking macOS with Python

Resources

Automation

Native APIs

  • PyObjC: Python to Objective-C bridge
  • Rubicon: Alternate Python to Objective-C bridge
  • Rumps: macOS statusbar apps

GUIs

Installers / App Packaging

Biting the Apple: Unlocking macOS with Python

Questions?

This talk: https://github.com/RhetTbull/biting-the-apple
GitHub: https://github.com/RhetTbull
LinkedIn: https://www.linkedin.com/in/rhettbull/

Biting the Apple: Unlocking macOS with Python

Supports most Apple Mac apps and some 3rd party apps.

Good for: - Notification-center-based app - Controlling daemons / launching separate programs - Updating simple info from web APIs on a timer Not good for: - Any app that is first and foremost a GUI application