Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pyinstaller = ["Pyinstaller~=6.13"]
pytest = [
"pytest==8.3.5",
"pytest-cov==6.1.1",
"pytest-mock==3.15.1",
"pytest-qt==4.4.0",
"syrupy==4.9.1",
]
Expand Down
27 changes: 20 additions & 7 deletions src/tagstudio/core/ts_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,18 +193,31 @@ def _build_instagram_url(cls, entry: Entry):

@staticmethod
@lru_cache(maxsize=1)
def get_most_recent_release_version() -> str:
"""Get the version of the most recent Github release."""
resp = requests.get("https://api.github.com/repos/TagStudioDev/TagStudio/releases/latest")
assert resp.status_code == 200, "Could not fetch information on latest release."
def get_most_recent_release_version() -> str | None:
"""Get the version of the most recent GitHub release."""
try:
resp = requests.get(
"https://api.github.com/repos/TagStudioDev/TagStudio/releases/latest"
)
except Exception as e:
logger.error("Error getting most recent GitHub release.", error=e)
return None

if resp.status_code != 200:
logger.error("Error getting most recent GitHub release.", status_code=resp.status_code)
return None

data = resp.json()
tag: str = data["tag_name"]
assert tag.startswith("v")
if not tag.startswith("v"):
logger.error("Unexpected tag format.", tag=tag)
return None

version = tag[1:]
# the assert does not allow for prerelease/build,
# the assertion does not allow for prerelease/build,
# because the latest release should never have them
assert re.match(r"^\d+\.\d+\.\d+$", version) is not None, "Invalid version format."
if re.match(r"^\d+\.\d+\.\d+$", version) is None:
logger.error("Invalid version format.", version=version)
return None

return version
3 changes: 2 additions & 1 deletion src/tagstudio/qt/controllers/out_of_date_message_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from tagstudio.core.constants import VERSION
from tagstudio.core.ts_core import TagStudioCore
from tagstudio.core.utils.types import unwrap
from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color
from tagstudio.qt.translations import Translations

Expand All @@ -30,7 +31,7 @@ def __init__(self):

red = get_ui_color(ColorType.PRIMARY, UiColor.RED)
green = get_ui_color(ColorType.PRIMARY, UiColor.GREEN)
latest_release_version = TagStudioCore.get_most_recent_release_version()
latest_release_version = unwrap(TagStudioCore.get_most_recent_release_version())
status = Translations.format(
"version_modal.status",
installed_version=f"<span style='color:{red}'>{VERSION}</span>",
Expand Down
3 changes: 2 additions & 1 deletion src/tagstudio/qt/mixed/about_modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from tagstudio.core.constants import VERSION, VERSION_BRANCH
from tagstudio.core.enums import Theme
from tagstudio.core.ts_core import TagStudioCore
from tagstudio.core.utils.types import unwrap
from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color
from tagstudio.qt.previews.vendored import ffmpeg
from tagstudio.qt.resource_manager import ResourceManager
Expand Down Expand Up @@ -106,7 +107,7 @@ def __init__(self, config_path):

# Version
version_title = QLabel("Version")
most_recent_release = TagStudioCore.get_most_recent_release_version()
most_recent_release = unwrap(TagStudioCore.get_most_recent_release_version(), "UNKNOWN")
version_content_style = self.form_content_style
if most_recent_release == VERSION:
version_content = QLabel(f"{VERSION}")
Expand Down
3 changes: 2 additions & 1 deletion src/tagstudio/qt/ts_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,8 @@ def create_about_modal():
if not which(FFMPEG_CMD) or not which(FFPROBE_CMD):
FfmpegMissingMessageBox().show()

if is_version_outdated(VERSION, TagStudioCore.get_most_recent_release_version()):
latest_version = TagStudioCore.get_most_recent_release_version()
if latest_version and is_version_outdated(VERSION, latest_version):
OutOfDateMessageBox().exec()

self.app.exec()
Expand Down
14 changes: 14 additions & 0 deletions tests/qt/test_about_modal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from pytestqt.qtbot import QtBot

from tagstudio.qt.mixed.about_modal import AboutModal


def test_github_api_unavailable(qtbot: QtBot, mocker) -> None:
mocker.patch(
"requests.get",
side_effect=ConnectionError(
"Failed to resolve 'api.github.com' ([Errno -3] Temporary failure in name resolution)"
),
)
modal = AboutModal("/tmp")
qtbot.addWidget(modal)