Python runtime interface for Sparkle and WinSparkle app updates
Project description
1. SparkleHelper
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:
- Runtime loading — dynamically load the bundled
Sparkle.frameworkon macOS orWinSparkle.dllon Windows. - Pythonic API — expose a typed
Updaterfacade overSPUStandardUpdaterController/SPUUpdaterand the WinSparkle C API. - Offline packaging — ship platform-native update runtimes inside wheels and let supported bundlers collect them without network access.
3. Quick start
3.1. Install
# Install this repo in editable mode, including dev dependencies
uv pip install -e ".[dev]"
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-inSPUStandardUserDriver+ 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
startUpdateris asynchronous and depends on the NSApp run loop to complete;canCheckForUpdatesonly becomesTrueonce 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). Seeexamples/demo_app/README.md. - macOS delay
start():Updater()does not start automatic checks by default. In GUI apps, callstart()after entering the mainloop (the demo does this viawx.CallAfter), or passstart=Trueonly when the NSApp run loop is already ready. - Windows cleanup: WinSparkle starts background work in
win_sparkle_init(). Callcleanup()before application exit, or useUpdater(...)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/andwinsparkle/are Git submodules pinned to upstream commits for source browsing, matching the opencv-python packaging-repo style where GitHub shows entries such asSparkle @ <commit>. - Fresh clone — use
git clone --recursive, or rungit submodule update --init --recursiveafter cloning. - Building a wheel —
uv build --wheelresolves 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 underbuild/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, andsrc/sparklehelper/licenses/*.txtare git-ignored and regenerated byscripts/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.txtWinSparkle.dll:sparklehelper/licenses/WinSparkle-LICENSE.txt
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sparklehelper-0.1.0.tar.gz.
File metadata
- Download URL: sparklehelper-0.1.0.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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
032950b633508e4b41a7e94c2dbe014c4c7653f5c8f80952b3f294f0e92be1aa
|
|
| MD5 |
f4841b050a8d81708c17897a9ed89f0a
|
|
| BLAKE2b-256 |
4c20081b25d752c6e2418b79f066bcefffbe4e2929301d682d5303004974727a
|
File details
Details for the file sparklehelper-0.1.0-py3-none-win_amd64.whl.
File metadata
- Download URL: sparklehelper-0.1.0-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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fa2fddd8fa7dd752bf468807eedb7e24779650023282a800f441b21fc8fc6d8f
|
|
| MD5 |
ac1287e12fd9acda1fada6ee4dcc9cfa
|
|
| BLAKE2b-256 |
4e9a1f3414aea92d5907a9394a3b3fbe9e8bc9aed37accabd15720b24df056b2
|
File details
Details for the file sparklehelper-0.1.0-py3-none-macosx_11_0_universal2.whl.
File metadata
- Download URL: sparklehelper-0.1.0-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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
387c3d905e28cf8c0d645a49fc69eecb904df82c8411b347a20c3dc7483727e6
|
|
| MD5 |
18c4a676f5bab7f7bcb9ea5d7bb570be
|
|
| BLAKE2b-256 |
cb1f282cc4eb27e2373a6595b6698080187b00f00aaddffdc34a9ce3e21aa25a
|