How the Automatic Update Mechanism Works in biliTickerBuy's app_update.py
The automatic update mechanism queries the GitHub Releases API, filters releases by update channel and semantic version, and returns a structured ReleaseInfo object that the UI layer consumes to display available updates to users.
The open-source ticketing application biliTickerBuy implements a self-contained update pipeline in the app_update.py module. This component automatically polls the project's GitHub releases to determine whether a newer stable or pre-release version is available, handling version normalization, channel filtering, and asset parsing without requiring external update servers.
The Core Update Pipeline in app_update.py
The automatic update mechanism is designed as a stateless pipeline that transforms raw GitHub API responses into structured, UI-ready data. All core logic resides in app_update.py, which operates independently of the presentation layer.
Entry Point: The fetch_update Function
In app_update.py, the fetch_update() function serves as the primary entry point. It accepts two parameters: the current application version string and an update channel identifier (typically 稳定版 for stable or 测试版 for pre-release). The function creates a requests.Session instance and performs a GET request to the GitHub API endpoint https://api.github.com/repos/mikumifa/biliTickerBuy/releases.
The returned JSON payload—a list of release objects—is immediately passed to the internal select_update() function for candidate evaluation. This design keeps network logic separate from selection logic.
Version Normalization with normalize_version
Before semantic comparison, release tag names (e.g., "v1.2.3") are sanitized via normalize_version(). This helper strips any leading "v" or "V" prefix and instantiates a packaging.version.Version object. If the conversion fails due to malformed tag names, the function raises an UpdateError, ensuring that only valid semantic versions propagate through the pipeline.
Channel-Based Selection Logic
The select_update() function validates the requested channel against the constants UPDATE_CHANNEL_STABLE and UPDATE_CHANNEL_PRERELEASE, which are defined in util/Constant.py. The function iterates over the release list, applying three filters:
- Draft exclusion: Skips any release where
draftis true. - Channel matching: For stable channels, ignores releases where
prereleaseis true. - Version comparison: Collects releases where the normalized version is strictly greater than the current version.
The newest candidate is selected using Python's built-in max() function with a lambda key: max(candidates, key=lambda item: item[0]). This ensures the highest semantic version is returned regardless of release order.
The ReleaseInfo Dataclass
Once a valid update candidate is identified, it is encapsulated in a ReleaseInfo dataclass. This structure exposes critical metadata fields:
version: The normalizedVersionobject.tag_name: The original GitHub tag string.html_url: Direct link to the release page.body: Release notes in HTML or Markdown.prerelease: Boolean flag indicating pre-release status.published_at: ISO 8601 timestamp of publication.assets: Tuple of dictionaries containing asset name, browser download URL, and size in bytes.
The ReleaseInfo.to_dict() method facilitates JSON serialization, allowing the Gradio UI to consume the data without additional parsing logic.
Error Handling and Edge Cases
The update mechanism implements defensive error handling to prevent network or API anomalies from crashing the application. All requests.RequestException instances (timeouts, connection errors, HTTP 4xx/5xx) are caught and wrapped in UpdateError exceptions. Similarly, invalid channel strings or malformed version tags trigger UpdateError with descriptive messages.
This exception-based flow allows the calling code to distinguish between "no updates available" (indicated by a None return value) and "update check failed" (indicated by an exception).
UI Integration in tab/update.py
The Gradio-based interface in tab/update.py consumes this pipeline through the _check_updates() function. It retrieves the current installed version via get_app_version() from app_version.py, then invokes fetch_update() with the user-selected channel retrieved from the configuration database.
The UI handles three distinct states:
- Update available: Formats a status block using
_format_update(), displaying the new version number, release notes, and a hyperlink to the GitHub release page. - Up to date: Displays a confirmation banner when
fetch_update()returnsNone. - Error state: Catches
UpdateErrorin lines 100-110 of the source to render a red error banner, informing the user of connectivity issues.
The update mechanism itself remains agnostic to the runtime mode (bundled executable, source code, or pip installation), leaving environment-specific messaging to the UI layer.
Practical Implementation Examples
Example 1: Manual Update Check
from app_update import fetch_update, UPDATE_CHANNEL_STABLE, UpdateError
current_version = "1.2.3"
try:
release = fetch_update(current_version, UPDATE_CHANNEL_STABLE)
if release:
print(f"New version available: {release.version}")
print(f"Release notes: {release.body}")
for asset in release.assets:
print(f"- {asset['name']} ({asset['size']} bytes): {asset['browser_download_url']}")
else:
print("You are already on the latest stable version.")
except UpdateError as exc:
print(f"Update lookup failed: {exc}")
Example 2: CLI Integration with Channel Selection
import argparse
from app_update import fetch_update, UPDATE_CHANNEL_PRERELEASE, UpdateError
from app_version import get_app_version
parser = argparse.ArgumentParser()
parser.add_argument("--pre", action="store_true", help="Check pre-release channel")
args = parser.parse_args()
channel = UPDATE_CHANNEL_PRERELEASE if args.pre else UPDATE_CHANNEL_STABLE
try:
release = fetch_update(get_app_version(), channel)
if release:
print(f"Update found: {release.version} – {release.html_url}")
else:
print("No updates available.")
except UpdateError as e:
print(f"Could not check for updates: {e}")
Summary
- The
fetch_update()function inapp_update.pyorchestrates the entire update check by querying the GitHub Releases API and returning either aReleaseInfoobject orNone. - Version normalization ensures semantic versioning compliance by stripping "v" prefixes and utilizing
packaging.version.Versionfor reliable comparison. - Channel filtering supports dual release tracks (stable and pre-release) via the
UPDATE_CHANNEL_STABLEandUPDATE_CHANNEL_PRERELEASEconstants defined inutil/Constant.py. - The
ReleaseInfodataclass provides a structured, JSON-serializable representation of release metadata, including downloadable asset descriptors. - Errors are centralized through the
UpdateErrorexception class, allowing the UI intab/update.pyto gracefully handle network failures and API changes. - The system is fully stateless, requiring only the current version string and channel identifier to operate without persistent local state.
Frequently Asked Questions
How does fetch_update determine if a release is newer?
The select_update() function converts both the current version and candidate tag names into packaging.version.Version objects. It collects all releases with versions strictly greater than the current version, then selects the maximum value using Python's max() built-in with a lambda key. This ensures the latest semantic version wins, even if GitHub returns releases out of chronological order.
What happens if the GitHub API is unreachable?
Network failures, DNS errors, or HTTP non-success codes during the requests.Session.get() call raise a requests.RequestException, which is immediately caught and re-raised as an UpdateError. The calling code in tab/update.py catches this specific exception and renders an error banner, preventing the application from crashing while informing the user of the connectivity issue.
Can I check for pre-release updates?
Yes. Pass the UPDATE_CHANNEL_PRERELEASE constant (typically the string 测试版) to fetch_update(). When this channel is selected, select_update() includes GitHub releases where prerelease: true in the candidate pool. Passing UPDATE_CHANNEL_STABLE explicitly filters out these pre-releases.
Where is the update channel configuration stored?
The channel preference is persisted in the application's configuration database and defined in util/Constant.py alongside other constants like PACKAGE_NAME. The UI retrieves this value before invoking fetch_update(), making the core update mechanism agnostic to storage implementation details.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →