Skip to main content

Python runtime interface for Sparkle and WinSparkle app updates

Project description

1. SparkleHelper

English | 简体中文

A Python runtime interface for native app updates with Sparkle on macOS and WinSparkle on Windows.

SparkleHelper lets you integrate check-for-updates, background download, and native update UI into packaged Python desktop apps — Sparkle for macOS .app bundles and WinSparkle for Windows executables — without writing Objective-C / Swift or Win32 update plumbing.

2. Why

Sparkle and WinSparkle are mature native update frameworks, but they expose platform-specific Objective-C and C APIs. SparkleHelper gives Python desktop apps one runtime facade while preserving the native updater on each platform. It solves three things:

  1. Runtime loading — dynamically load the bundled Sparkle.framework on macOS or WinSparkle.dll on Windows.
  2. Pythonic API — expose a typed Updater facade over SPUStandardUpdaterController / SPUUpdater and the WinSparkle C API.
  3. Offline packaging — ship platform-native update runtimes inside wheels and let supported bundlers collect them without network access.

3. Quick start

3.1. Install

# uv project
uv add sparklehelper

# or plain pip
pip install sparklehelper

3.2. Use in an app

On macOS, Sparkle must run inside a packaged .app bundle because it depends on the bundle structure and Info.plist. On Windows, WinSparkle is configured from Updater(...) before its native runtime is started.

macOS minimal usage:

from sparklehelper import Updater

updater = Updater()              # reads SUFeedURL from Info.plist
updater.check_for_updates()      # pops the native Sparkle update window

Windows minimal usage:

from sparklehelper import Updater

updater = Updater(
    feed_url="https://example.com/appcast.xml",
    public_key="...",
    company="Example",
    app_name="ExampleApp",
    version="0.1.0",
)
updater.check_for_updates()      # pops the native WinSparkle update window

On macOS, bind it to a GUI menu item and drive its enabled state via KVO:

updater = Updater()

with updater.observe_can_check_for_updates(menu_item.setEnabled):
    # the menu item's state refreshes automatically while inside the block
    ...

Other macOS KVO properties can be subscribed to through the unified entry point:

subscription = updater.observe(
    "automatically_downloads_updates",
    settings_view.set_auto_download_enabled,
)

macOS custom feed source and channel filtering (implement any subset of UpdaterDelegate). On Windows, pass feed_url directly to Updater(...):

class MyDelegate:
    def feed_url_string_for_updater(self):
        return "https://example.com/appcast.xml"
    def allowed_channels_for_updater(self):
        return ("beta",) if is_beta_user() else ()

updater = Updater(delegate=MyDelegate())

4. Platform configuration

4.1. macOS

Sparkle's core macOS configuration lives in the .app Info.plist:

Key Required Description
SUFeedURL Yes Public URL of appcast.xml
SUPublicEDKey Yes EdDSA public key used to verify update signatures
SUEnableAutomaticChecks No Enable automatic checks (on by default)
SUScheduledCheckInterval No Check interval in seconds (default 86400)

The Nuitka wrapper supports Sparkle's full host-app plist key set exposed by Sparkle 2.x SUConstants.h; pass any of them with --sparkle-key KEY=VALUE or the generated kebab-case option such as --su-automatically-update true.

4.2. Windows

WinSparkle must receive its init-time settings from Python before win_sparkle_init():

Updater(...) argument Required Description
feed_url Yes Public URL of appcast.xml
public_key Recommended EdDSA public key used to verify update signatures
company / app_name / version Recommended App identity shown by WinSparkle and used for its settings
build No Build version used for update comparison

5. Packaging

SparkleHelper ships first-class support for the two mainstream Python bundlers. Wheels include the native runtime for their target platform: Sparkle.framework for macOS and WinSparkle.dll for Windows, so packaging never downloads native assets.

5.1. PyInstaller

SparkleHelper registers a built-in PyInstaller hook (via the pyinstaller40 entry point) that collects the platform-native updater at build time. It uses the wheel copy by default; SPARKLEHELPER_FRAMEWORK_PATH on macOS and SPARKLEHELPER_WINSPARKLE_PATH on Windows can override the source for development or compatibility testing.

pyinstaller my_app.spec

On macOS, inject the Sparkle keys via info_plist={} in your .spec; see examples/demo_app/build.spec for a reference. On Windows, pass WinSparkle settings to Updater(...).

For macOS onefile .app specs shaped like BUNDLE(exe, ...), use the wrapper command instead:

uv run sparklehelper pyinstaller my_app.spec

The wrapper creates a temporary patched spec that moves Sparkle.framework from Analysis into BUNDLE, so it lands in Contents/Frameworks instead of the onefile _MEI... extraction directory. Onedir specs shaped like BUNDLE(coll, ...) do not need this wrapper and should keep using plain pyinstaller. If a onefile .app spec is built with plain pyinstaller, the hook stops the build and points to this wrapper command.

5.2. Nuitka

SparkleHelper ships a Nuitka package config and user plugin as package data. Nuitka does not auto-discover either third-party resource, so the sparklehelper nuitka wrapper injects both before forwarding all remaining arguments to Nuitka.

Requirements:

  • Nuitka 4.1.2 or newer.
  • macOS 11 or newer for Sparkle wheels, or Windows for WinSparkle wheels.
uv run sparklehelper nuitka \
  --version 0.1.0 \
  --build-version 1 \
  --feed-url https://example.com/appcast.xml \
  --public-ed-key YOUR_EDDSA_PUBLIC_KEY_BASE64 \
  --mode=app \
  my_app.py

The config collects the wheel's bundled copy and places it at Contents/Frameworks/Sparkle.framework on macOS and at WinSparkle.dll in the Windows Nuitka dist directory; the runtime locates this directory through Nuitka's __compiled__.containing_dir.

On macOS, the plugin restores the framework's top-level Autoupdate symlink before Nuitka signs the app. The wrapper's --version option writes CFBundleShortVersionString and defaults to 0.1.0 when neither --version nor Nuitka's native --macos-app-version is passed. --build-version optionally writes the Sparkle-comparable CFBundleVersion; when it is omitted, the wrapper derives an Apple-compatible build version from --version, for example 0.1.0 becomes build version 1. The wrapper also accepts Sparkle Info.plist keys via aliases such as --feed-url, --public-ed-key, --automatic-checks, and --scheduled-check-interval, or via repeated --sparkle-key KEY=VALUE entries for the full Sparkle key set. If Nuitka's native --macos-app-version is forwarded without wrapper version options, the plugin reads Nuitka's generated CFBundleShortVersionString from Info.plist and only fills in a missing CFBundleVersion. If the application also uses pypylon, Nuitka intentionally keeps its loader-relative framework at Contents/Frameworks/pypylon/pylon.framework.

To build the demo:

cd examples/demo_app
uv run sparklehelper nuitka \
  --version 0.1.0 \
  --build-version 1 \
  --feed-url https://example.com/appcast.xml \
  --public-ed-key YOUR_EDDSA_PUBLIC_KEY_BASE64 \
  --mode=app \
  demo.py

The wheel does not include Sparkle release authoring tools such as generate_keys or sign_update; obtain those separately when producing signed updates.

6. API overview

Updater                       main entry (wraps the native platform backend)
├─ check_for_updates()        pops the native update window
├─ check_for_updates_in_background()
├─ start() / reset_update_cycle() / reset_update_cycle_after_short_delay()
├─ can_check_for_updates      macOS-only KVO property
├─ feed_url / host_bundle_path / last_update_check_date / system_profile
├─ automatically_checks_for_updates / update_check_interval
├─ automatically_downloads_updates / allows_automatic_updates
├─ user_agent_string / http_headers / sends_system_profile
└─ observe(property_name, cb) -> Subscription  macOS-only KVO subscription

UpdaterDelegate              macOS-only optional callback Protocol
├─ updater_may_perform_update_check()
├─ feed_url_string_for_updater()
├─ allowed_channels_for_updater()
├─ feed_parameters_for_updater() / allowed_system_profile_keys_for_updater()
├─ updater_did_find_valid_update()
├─ updater_did_not_find_update()
├─ updater_should_proceed_with_update() / updater_user_did_make_choice()
├─ updater_will_download_update() / updater_did_download_update()
├─ updater_failed_to_download_update() / user_did_cancel_download()
├─ updater_will_extract_update() / updater_did_extract_update()
├─ updater_will_install_update() / updater_should_relaunch_application()
├─ updater_will_schedule_update_check() / updater_will_not_schedule_update_check()
└─ updater_did_abort() / updater_did_finish_cycle()

UpdateInfo / SystemProfileEntry / UpdateCheckResult / UserUpdateState   dataclass
UpdateCheckKind / UserUpdateChoice / UserUpdateStage                    enums

ensure_runnable()            aggregate check: platform/native runtime/config
errors                       SparkleError exception hierarchy

7. Platform constraints

  • macOS main thread: Sparkle/Cocoa APIs must be called on the main thread; SparkleHelper asserts this at every entry point.
  • macOS bundle: Sparkle must be packaged as a .app; ensure_runnable() checks the bundle and plist.
  • Sparkle ships its own UI: check_for_updates() pops the native window with zero config (built-in SPUStandardUserDriver + the in-framework nib).
  • macOS persistence: dynamic settings still land in Sparkle's own NSUserDefaults, following the official advice to "not add another layer on top of user preferences".
  • macOS GUI run loop: Sparkle's startUpdater is asynchronous and depends on the NSApp run loop to complete; canCheckForUpdates only becomes True once the run loop is spinning. So the host GUI must drive the NSApp run loop — wxPython, PyQt/PySide, PyObjC/AppKit native all work; tkinter does not (it uses a separate Tcl event loop that does not interoperate with NSApp, leaving menu items permanently greyed out). See examples/demo_app/README.md.
  • macOS delay start(): Updater() does not start automatic checks by default. In GUI apps, call start() after entering the mainloop (the demo does this via wx.CallAfter), or pass start=True only when the NSApp run loop is already ready.
  • Windows cleanup: WinSparkle starts background work in win_sparkle_init(). Call cleanup() before application exit, or use Updater(...) as a context manager.

8. Development

uv venv && uv pip install -e ".[dev]"
uv run pytest                # unit tests (mock the ObjC layer; run on any platform)

8.1. Building wheels (maintainers)

This is a packaging repo: the main package source tree no longer ships the large native runtime binaries (Sparkle.framework, WinSparkle.dll). They are fetched at wheel build time.

  • Normal install — published wheels already embed the native assets, so end users need no network access or compiler.
  • Upstream source refs — Sparkle/ and winsparkle/ are Git submodules pinned to upstream commits for source browsing, matching the opencv-python packaging-repo style where GitHub shows entries such as Sparkle @ <commit>.
  • Fresh clone — use git clone --recursive, or run git submodule update --init --recursive after cloning.
  • Building a wheel — uv build --wheel resolves the latest upstream Sparkle / WinSparkle release assets, verifies their SHA256, syncs the matching upstream license files, and unpacks them into the wheel. The archive is cached under build/native-cache/ and reused on subsequent builds.
  • Offline build — either keep the previously generated native files in place, or set SPARKLEHELPER_SKIP_NATIVE_SYNC=1, which forbids network access and only validates the local native/license assets (the build fails with a clear error if they are missing).
  • Tracked vs. generated — upstream source references are tracked as gitlinks; package resources only commit src/sparklehelper/winsparkle/winsparkle.h. Sparkle.framework/, winsparkle/*/WinSparkle.dll, and src/sparklehelper/licenses/*.txt are git-ignored and regenerated by scripts/sync_native_deps.py.

9. License

SparkleHelper is MIT licensed. Bundled native dependencies are redistributed under their upstream licenses:

  • Sparkle.framework: sparklehelper/licenses/Sparkle-LICENSE.txt
  • WinSparkle.dll: sparklehelper/licenses/WinSparkle-LICENSE.txt

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sparklehelper-0.1.1.tar.gz (91.2 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

sparklehelper-0.1.1-py3-none-win_arm64.whl (3.5 MB view details)

Uploaded Python 3Windows ARM64

sparklehelper-0.1.1-py3-none-win_amd64.whl (3.5 MB view details)

Uploaded Python 3Windows x86-64

sparklehelper-0.1.1-py3-none-win32.whl (3.5 MB view details)

Uploaded Python 3Windows x86

sparklehelper-0.1.1-py3-none-macosx_11_0_universal2.whl (1.7 MB view details)

Uploaded Python 3macOS 11.0+ universal2 (ARM64, x86-64)

File details

Details for the file sparklehelper-0.1.1.tar.gz.

File metadata

  • Download URL: sparklehelper-0.1.1.tar.gz
  • Upload date:
  • Size: 91.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.27 {"installer":{"name":"uv","version":"0.11.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for sparklehelper-0.1.1.tar.gz
Algorithm Hash digest
SHA256 15bc621e1c95be77b5ddd87f239d9cb583cd05dd53a008f5950d18c9580c68e8
MD5 c58c5ec32fbc22f7bf233246c7fdfc9c
BLAKE2b-256 67ab5e811eb0351a1231e95139250434294718469c7edde623d075401ff77b22

See more details on using hashes here.

File details

Details for the file sparklehelper-0.1.1-py3-none-win_arm64.whl.

File metadata

  • Download URL: sparklehelper-0.1.1-py3-none-win_arm64.whl
  • Upload date:
  • Size: 3.5 MB
  • Tags: Python 3, Windows ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.27 {"installer":{"name":"uv","version":"0.11.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for sparklehelper-0.1.1-py3-none-win_arm64.whl
Algorithm Hash digest
SHA256 9ed00781d6a1612298167bf7edcb8ac0245b19cc676a92128c11ca0ffcea8620
MD5 de6d6a1489073691be1823c0fdacf6af
BLAKE2b-256 4f280a754c542d1f51edf4db885bfbdbb4b5cd33e3f0077558aca8686af43043

See more details on using hashes here.

File details

Details for the file sparklehelper-0.1.1-py3-none-win_amd64.whl.

File metadata

  • Download URL: sparklehelper-0.1.1-py3-none-win_amd64.whl
  • Upload date:
  • Size: 3.5 MB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.27 {"installer":{"name":"uv","version":"0.11.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for sparklehelper-0.1.1-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 e5dc4a71a3ef17578b8eb9bcfefe5d9864875e27cf07e9f55fe248d90d94e7d2
MD5 766c8a893d448e4a82aff0e41b417fee
BLAKE2b-256 f178032fa9f11ef430148fe1cfbd8a3e40042904ec18df31620ed5b3d7320704

See more details on using hashes here.

File details

Details for the file sparklehelper-0.1.1-py3-none-win32.whl.

File metadata

  • Download URL: sparklehelper-0.1.1-py3-none-win32.whl
  • Upload date:
  • Size: 3.5 MB
  • Tags: Python 3, Windows x86
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.27 {"installer":{"name":"uv","version":"0.11.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for sparklehelper-0.1.1-py3-none-win32.whl
Algorithm Hash digest
SHA256 fb2d1e16adadb530275aa023c2640a6592443767c3844fca52f307fc63698223
MD5 ad951b79aaf471ddddda2b8447a5c86f
BLAKE2b-256 804ee73f5610958335b30324393b67cbc44c74f67f62a03ea548214e8a539bb4

See more details on using hashes here.

File details

Details for the file sparklehelper-0.1.1-py3-none-macosx_11_0_universal2.whl.

File metadata

  • Download URL: sparklehelper-0.1.1-py3-none-macosx_11_0_universal2.whl
  • Upload date:
  • Size: 1.7 MB
  • Tags: Python 3, macOS 11.0+ universal2 (ARM64, x86-64)
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.27 {"installer":{"name":"uv","version":"0.11.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for sparklehelper-0.1.1-py3-none-macosx_11_0_universal2.whl
Algorithm Hash digest
SHA256 a3d467caf83d04c0c9f938d5fbe8bea6f2f9fb70d5be8e909d981dc320665baa
MD5 2f62607141b13ec055337ac23ce39c02
BLAKE2b-256 28c34f40f4c6d8e35c68a1b2b6de7f0146fe62d9333257906b5c79488f79d2eb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page