synodic-client 0.0.1.dev45__tar.gz → 0.0.1.dev47__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/PKG-INFO +2 -2
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/pyproject.toml +2 -4
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/qt.py +1 -2
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/screen/settings.py +4 -2
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/screen/tray.py +4 -1
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/client.py +12 -2
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/resolution.py +0 -32
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/updater.py +34 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_settings.py +8 -2
- synodic_client-0.0.1.dev47/tests/unit/test_client_version.py +98 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/test_resolution.py +1 -59
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/test_updater.py +41 -0
- synodic_client-0.0.1.dev45/synodic_client/_version.py +0 -1
- synodic_client-0.0.1.dev45/tests/unit/test_client_version.py +0 -44
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/LICENSE.md +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/README.md +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/__init__.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/__main__.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/__init__.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/bootstrap.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/data.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/icon.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/init.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/instance.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/screen/__init__.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/screen/action_card.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/screen/card.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/screen/install.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/screen/log_panel.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/screen/screen.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/screen/sidebar.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/screen/spinner.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/screen/update_banner.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/theme.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/update_controller.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/uri.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/workers.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/cli.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/config.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/logging.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/protocol.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/py.typed +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/startup.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/__init__.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/conftest.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/__init__.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/__init__.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/conftest.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_action_card.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_gather_packages.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_install_preview.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_log_panel.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_logging.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_preview_model.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_sidebar.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_tray_window_show.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_update_banner.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_update_controller.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_update_feedback.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/test_cli.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/test_client_updater.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/test_config.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/test_examples.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/test_init.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/test_install.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/test_uri.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/test_workers.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/windows/__init__.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/windows/conftest.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/windows/test_protocol.py +0 -0
- {synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/windows/test_startup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: synodic_client
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.dev47
|
|
4
4
|
Author-Email: Synodic Software <contact@synodic.software>
|
|
5
5
|
License: LGPL-3.0-or-later
|
|
6
6
|
Project-URL: homepage, https://github.com/synodic/synodic-client
|
|
@@ -8,7 +8,7 @@ Project-URL: repository, https://github.com/synodic/synodic-client
|
|
|
8
8
|
Requires-Python: <3.15,>=3.14
|
|
9
9
|
Requires-Dist: pyside6>=6.10.2
|
|
10
10
|
Requires-Dist: packaging>=26.0
|
|
11
|
-
Requires-Dist: porringer>=0.2.1.
|
|
11
|
+
Requires-Dist: porringer>=0.2.1.dev73
|
|
12
12
|
Requires-Dist: qasync>=0.28.0
|
|
13
13
|
Requires-Dist: velopack>=0.0.1444.dev49733
|
|
14
14
|
Requires-Dist: typer>=0.24.1
|
|
@@ -10,12 +10,12 @@ requires-python = ">=3.14, <3.15"
|
|
|
10
10
|
dependencies = [
|
|
11
11
|
"pyside6>=6.10.2",
|
|
12
12
|
"packaging>=26.0",
|
|
13
|
-
"porringer>=0.2.1.
|
|
13
|
+
"porringer>=0.2.1.dev73",
|
|
14
14
|
"qasync>=0.28.0",
|
|
15
15
|
"velopack>=0.0.1444.dev49733",
|
|
16
16
|
"typer>=0.24.1",
|
|
17
17
|
]
|
|
18
|
-
version = "0.0.1.
|
|
18
|
+
version = "0.0.1.dev47"
|
|
19
19
|
|
|
20
20
|
[project.license]
|
|
21
21
|
text = "LGPL-3.0-or-later"
|
|
@@ -102,8 +102,6 @@ replace-imports-with-any = [
|
|
|
102
102
|
|
|
103
103
|
[tool.pdm.version]
|
|
104
104
|
source = "scm"
|
|
105
|
-
write_to = "synodic_client/_version.py"
|
|
106
|
-
write_template = "__version__ = '{}'\n"
|
|
107
105
|
|
|
108
106
|
[tool.pdm.resolution]
|
|
109
107
|
allow-prereleases = true
|
|
@@ -28,7 +28,6 @@ from synodic_client.resolution import (
|
|
|
28
28
|
ResolvedConfig,
|
|
29
29
|
resolve_config,
|
|
30
30
|
resolve_update_config,
|
|
31
|
-
resolve_version,
|
|
32
31
|
)
|
|
33
32
|
from synodic_client.updater import initialize_velopack
|
|
34
33
|
|
|
@@ -52,7 +51,7 @@ def _init_services(logger: logging.Logger) -> tuple[Client, API, ResolvedConfig]
|
|
|
52
51
|
|
|
53
52
|
logger.info(
|
|
54
53
|
'Synodic Client v%s started (channel: %s, source: %s, cached_projects: %d)',
|
|
55
|
-
|
|
54
|
+
client.version,
|
|
56
55
|
update_config.channel.name,
|
|
57
56
|
update_config.repo_url,
|
|
58
57
|
len(cached_dirs),
|
|
@@ -27,7 +27,6 @@ from PySide6.QtWidgets import (
|
|
|
27
27
|
QWidget,
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
-
from synodic_client._version import __version__
|
|
31
30
|
from synodic_client.application.icon import app_icon
|
|
32
31
|
from synodic_client.application.screen.card import CardFrame
|
|
33
32
|
from synodic_client.application.theme import SETTINGS_WINDOW_MIN_SIZE, UPDATE_STATUS_CHECKING_STYLE
|
|
@@ -58,16 +57,19 @@ class SettingsWindow(QMainWindow):
|
|
|
58
57
|
def __init__(
|
|
59
58
|
self,
|
|
60
59
|
config: ResolvedConfig,
|
|
60
|
+
version: str = '',
|
|
61
61
|
parent: QWidget | None = None,
|
|
62
62
|
) -> None:
|
|
63
63
|
"""Initialise the settings window.
|
|
64
64
|
|
|
65
65
|
Args:
|
|
66
66
|
config: The current resolved configuration snapshot.
|
|
67
|
+
version: The application version string to display.
|
|
67
68
|
parent: Optional parent widget.
|
|
68
69
|
"""
|
|
69
70
|
super().__init__(parent)
|
|
70
71
|
self._config = config
|
|
72
|
+
self._version = version
|
|
71
73
|
self.setWindowTitle('Synodic Settings')
|
|
72
74
|
self.setMinimumSize(*SETTINGS_WINDOW_MIN_SIZE)
|
|
73
75
|
self.setWindowIcon(app_icon())
|
|
@@ -93,7 +95,7 @@ class SettingsWindow(QMainWindow):
|
|
|
93
95
|
layout.addWidget(self._build_advanced_section())
|
|
94
96
|
layout.addStretch()
|
|
95
97
|
|
|
96
|
-
version_label = QLabel(f'Version {
|
|
98
|
+
version_label = QLabel(f'Version {self._version}')
|
|
97
99
|
version_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
98
100
|
version_label.setStyleSheet('color: rgba(255, 255, 255, 0.4); font-size: 11px;')
|
|
99
101
|
layout.addWidget(version_label)
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/screen/tray.py
RENAMED
|
@@ -69,7 +69,10 @@ class TrayScreen:
|
|
|
69
69
|
self._build_menu(app, window)
|
|
70
70
|
|
|
71
71
|
# Settings window (created once, shown/hidden on demand)
|
|
72
|
-
self._settings_window = SettingsWindow(
|
|
72
|
+
self._settings_window = SettingsWindow(
|
|
73
|
+
self._resolve_config(),
|
|
74
|
+
version=str(self._client.version),
|
|
75
|
+
)
|
|
73
76
|
self._settings_window.settings_changed.connect(self._on_settings_changed)
|
|
74
77
|
|
|
75
78
|
# MainWindow gear button → open settings
|
|
@@ -25,11 +25,21 @@ class Client:
|
|
|
25
25
|
|
|
26
26
|
@property
|
|
27
27
|
def version(self) -> Version:
|
|
28
|
-
"""
|
|
28
|
+
"""Return the best-known application version.
|
|
29
|
+
|
|
30
|
+
When a Velopack-installed updater is available the authoritative
|
|
31
|
+
version comes from the native binary manifest. Otherwise, the
|
|
32
|
+
Python package metadata version (``importlib.metadata``) is used.
|
|
29
33
|
|
|
30
34
|
Returns:
|
|
31
|
-
The version
|
|
35
|
+
The resolved version.
|
|
32
36
|
"""
|
|
37
|
+
if self._updater is not None:
|
|
38
|
+
try:
|
|
39
|
+
if self._updater.is_installed:
|
|
40
|
+
return self._updater.current_version
|
|
41
|
+
except Exception:
|
|
42
|
+
logger.debug('Failed to query Velopack version, falling back', exc_info=True)
|
|
33
43
|
try:
|
|
34
44
|
return Version(importlib.metadata.version(self.distribution))
|
|
35
45
|
except importlib.metadata.PackageNotFoundError:
|
|
@@ -18,8 +18,6 @@ import logging
|
|
|
18
18
|
import sys
|
|
19
19
|
from dataclasses import dataclass
|
|
20
20
|
|
|
21
|
-
from packaging.version import Version
|
|
22
|
-
|
|
23
21
|
from synodic_client.config import (
|
|
24
22
|
UserConfig,
|
|
25
23
|
load_build_config,
|
|
@@ -210,36 +208,6 @@ def resolve_update_config(config: ResolvedConfig) -> UpdateConfig:
|
|
|
210
208
|
)
|
|
211
209
|
|
|
212
210
|
|
|
213
|
-
def resolve_version(client: object) -> Version:
|
|
214
|
-
"""Return the best-known application version.
|
|
215
|
-
|
|
216
|
-
When a Velopack-installed ``Updater`` is available the authoritative
|
|
217
|
-
version comes from the native binary manifest. Otherwise, the
|
|
218
|
-
Python package metadata version (``importlib.metadata``) is used.
|
|
219
|
-
|
|
220
|
-
Accepts ``Client`` (or any object with ``.updater`` and ``.version``
|
|
221
|
-
attributes) so that :mod:`resolution` does not need a hard import
|
|
222
|
-
of :class:`~synodic_client.client.Client` — avoiding a tighter
|
|
223
|
-
coupling than necessary.
|
|
224
|
-
|
|
225
|
-
Args:
|
|
226
|
-
client: The application service facade (typically a
|
|
227
|
-
:class:`~synodic_client.client.Client` instance).
|
|
228
|
-
|
|
229
|
-
Returns:
|
|
230
|
-
The resolved :class:`~packaging.version.Version`.
|
|
231
|
-
"""
|
|
232
|
-
updater = getattr(client, 'updater', None)
|
|
233
|
-
if updater is not None:
|
|
234
|
-
try:
|
|
235
|
-
if updater.is_installed:
|
|
236
|
-
return updater.current_version
|
|
237
|
-
except Exception:
|
|
238
|
-
logger.debug('Failed to query Velopack version, falling back', exc_info=True)
|
|
239
|
-
|
|
240
|
-
return getattr(client, 'version', Version('0.0.0'))
|
|
241
|
-
|
|
242
|
-
|
|
243
211
|
def resolve_enabled_plugins(
|
|
244
212
|
config: ResolvedConfig,
|
|
245
213
|
all_plugin_names: list[str],
|
|
@@ -32,6 +32,40 @@ GITHUB_REPO_URL = 'https://github.com/synodic/synodic-client'
|
|
|
32
32
|
_DEV_RELEASE_TAG = 'dev'
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
def pep440_to_semver(version_string: str) -> str:
|
|
36
|
+
"""Convert a PEP 440 version string to a SemVer string for Velopack.
|
|
37
|
+
|
|
38
|
+
Velopack requires strict SemVer (``MAJOR.MINOR.PATCH[-pre.N]``) while
|
|
39
|
+
Python tooling produces PEP 440 (e.g. ``0.1.dev47+g799543c``). This
|
|
40
|
+
function bridges the two:
|
|
41
|
+
|
|
42
|
+
* Normalises the base to three components (``0.1`` → ``0.1.0``).
|
|
43
|
+
* Converts ``.devN`` to ``-dev.N``.
|
|
44
|
+
* Strips local segments (``+g…``).
|
|
45
|
+
* Stable versions pass through unchanged (``1.0.0`` → ``1.0.0``).
|
|
46
|
+
|
|
47
|
+
Examples::
|
|
48
|
+
|
|
49
|
+
>>> pep440_to_semver('0.1.dev47+g799543c')
|
|
50
|
+
'0.1.0-dev.47'
|
|
51
|
+
>>> pep440_to_semver('0.1.1.dev3')
|
|
52
|
+
'0.1.1-dev.3'
|
|
53
|
+
>>> pep440_to_semver('1.0.0')
|
|
54
|
+
'1.0.0'
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
version_string: A PEP 440 version string.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
A SemVer-compatible version string.
|
|
61
|
+
"""
|
|
62
|
+
v = Version(version_string)
|
|
63
|
+
base = f'{v.major}.{v.minor}.{v.micro}'
|
|
64
|
+
if v.dev is not None:
|
|
65
|
+
return f'{base}-dev.{v.dev}'
|
|
66
|
+
return base
|
|
67
|
+
|
|
68
|
+
|
|
35
69
|
def github_release_asset_url(repo_url: str, channel: UpdateChannel) -> str:
|
|
36
70
|
"""Convert a GitHub repository URL into a release-asset download URL.
|
|
37
71
|
|
|
@@ -32,10 +32,10 @@ def _make_config(**overrides: Any) -> ResolvedConfig:
|
|
|
32
32
|
return ResolvedConfig(**defaults)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
def _make_window(config: ResolvedConfig | None = None) -> SettingsWindow:
|
|
35
|
+
def _make_window(config: ResolvedConfig | None = None, version: str = '0.0.0.test') -> SettingsWindow:
|
|
36
36
|
"""Create a ``SettingsWindow`` without showing it."""
|
|
37
37
|
cfg = config or _make_config()
|
|
38
|
-
window = SettingsWindow(cfg)
|
|
38
|
+
window = SettingsWindow(cfg, version=version)
|
|
39
39
|
return window
|
|
40
40
|
|
|
41
41
|
|
|
@@ -60,6 +60,12 @@ class TestSettingsWindowConstruction:
|
|
|
60
60
|
assert window.minimumWidth() == SETTINGS_WINDOW_MIN_SIZE[0]
|
|
61
61
|
assert window.minimumHeight() == SETTINGS_WINDOW_MIN_SIZE[1]
|
|
62
62
|
|
|
63
|
+
@staticmethod
|
|
64
|
+
def test_version_label_displays_passed_version() -> None:
|
|
65
|
+
"""Version label shows the version string passed to the constructor."""
|
|
66
|
+
window = _make_window(version='1.2.3.dev42')
|
|
67
|
+
assert window._version == '1.2.3.dev42'
|
|
68
|
+
|
|
63
69
|
|
|
64
70
|
# ---------------------------------------------------------------------------
|
|
65
71
|
# sync_from_config
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Tests for Client.version property behavior."""
|
|
2
|
+
|
|
3
|
+
import importlib.metadata
|
|
4
|
+
from unittest.mock import MagicMock, PropertyMock, patch
|
|
5
|
+
|
|
6
|
+
from packaging.version import Version
|
|
7
|
+
|
|
8
|
+
from synodic_client.client import Client
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestClientVersion:
|
|
12
|
+
"""Tests for Client.version property."""
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def test_version_from_metadata() -> None:
|
|
16
|
+
"""Verify version is retrieved from importlib.metadata when available."""
|
|
17
|
+
client = Client()
|
|
18
|
+
|
|
19
|
+
with patch.object(importlib.metadata, 'version', return_value='1.2.3'):
|
|
20
|
+
version = client.version
|
|
21
|
+
|
|
22
|
+
assert version == Version('1.2.3')
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def test_version_is_version_object() -> None:
|
|
26
|
+
"""Verify version property returns a Version object."""
|
|
27
|
+
client = Client()
|
|
28
|
+
version = client.version
|
|
29
|
+
|
|
30
|
+
assert isinstance(version, Version)
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def test_version_dev_format() -> None:
|
|
34
|
+
"""Verify dev versions are parsed correctly."""
|
|
35
|
+
client = Client()
|
|
36
|
+
dev_version = '1.0.0.dev5+gabcdef1'
|
|
37
|
+
expected = Version(dev_version)
|
|
38
|
+
|
|
39
|
+
with patch.object(importlib.metadata, 'version', return_value=dev_version):
|
|
40
|
+
version = client.version
|
|
41
|
+
|
|
42
|
+
assert version == expected
|
|
43
|
+
assert version.dev == expected.dev
|
|
44
|
+
assert version.local == expected.local
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TestClientVersionResolution:
|
|
48
|
+
"""Verify Client.version prefers Velopack when available."""
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def test_returns_velopack_version_when_installed() -> None:
|
|
52
|
+
"""Velopack version is preferred when an installed updater exists."""
|
|
53
|
+
mock_updater = MagicMock()
|
|
54
|
+
mock_updater.is_installed = True
|
|
55
|
+
mock_updater.current_version = Version('5.6.7')
|
|
56
|
+
|
|
57
|
+
client = Client()
|
|
58
|
+
client._updater = mock_updater
|
|
59
|
+
|
|
60
|
+
assert client.version == Version('5.6.7')
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def test_falls_back_when_not_installed() -> None:
|
|
64
|
+
"""Metadata version is used when the updater is not Velopack-installed."""
|
|
65
|
+
mock_updater = MagicMock()
|
|
66
|
+
mock_updater.is_installed = False
|
|
67
|
+
|
|
68
|
+
client = Client()
|
|
69
|
+
client._updater = mock_updater
|
|
70
|
+
|
|
71
|
+
with patch.object(importlib.metadata, 'version', return_value='1.0.0.dev1'):
|
|
72
|
+
version = client.version
|
|
73
|
+
|
|
74
|
+
assert version == Version('1.0.0.dev1')
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def test_falls_back_when_no_updater() -> None:
|
|
78
|
+
"""Metadata version is used when the updater has not been initialized."""
|
|
79
|
+
client = Client()
|
|
80
|
+
|
|
81
|
+
with patch.object(importlib.metadata, 'version', return_value='2.3.4'):
|
|
82
|
+
version = client.version
|
|
83
|
+
|
|
84
|
+
assert version == Version('2.3.4')
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def test_falls_back_on_exception() -> None:
|
|
88
|
+
"""Graceful fallback when querying the updater raises an exception."""
|
|
89
|
+
mock_updater = MagicMock()
|
|
90
|
+
type(mock_updater).is_installed = PropertyMock(side_effect=RuntimeError('boom'))
|
|
91
|
+
|
|
92
|
+
client = Client()
|
|
93
|
+
client._updater = mock_updater
|
|
94
|
+
|
|
95
|
+
with patch.object(importlib.metadata, 'version', return_value='3.0.0'):
|
|
96
|
+
version = client.version
|
|
97
|
+
|
|
98
|
+
assert version == Version('3.0.0')
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Any
|
|
5
|
-
from unittest.mock import
|
|
6
|
-
|
|
7
|
-
from packaging.version import Version
|
|
5
|
+
from unittest.mock import patch
|
|
8
6
|
|
|
9
7
|
from synodic_client.config import BuildConfig, UserConfig
|
|
10
8
|
from synodic_client.resolution import (
|
|
@@ -13,7 +11,6 @@ from synodic_client.resolution import (
|
|
|
13
11
|
resolve_config,
|
|
14
12
|
resolve_enabled_plugins,
|
|
15
13
|
resolve_update_config,
|
|
16
|
-
resolve_version,
|
|
17
14
|
seed_user_config_from_build,
|
|
18
15
|
update_user_config,
|
|
19
16
|
)
|
|
@@ -425,58 +422,3 @@ class TestResolveUpdateConfig:
|
|
|
425
422
|
result = resolve_update_config(config)
|
|
426
423
|
assert result.auto_update_interval_minutes == 0
|
|
427
424
|
assert result.tool_update_interval_minutes == 0
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
# ---------------------------------------------------------------------------
|
|
431
|
-
# resolve_version
|
|
432
|
-
# ---------------------------------------------------------------------------
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
class TestResolveVersion:
|
|
436
|
-
"""Tests for resolve_version."""
|
|
437
|
-
|
|
438
|
-
@staticmethod
|
|
439
|
-
def test_returns_velopack_version_when_installed() -> None:
|
|
440
|
-
"""Verify the Velopack version is preferred when a manager is present."""
|
|
441
|
-
mock_updater = MagicMock()
|
|
442
|
-
mock_updater.is_installed = True
|
|
443
|
-
mock_updater.current_version = Version('5.6.7')
|
|
444
|
-
|
|
445
|
-
mock_client = MagicMock()
|
|
446
|
-
mock_client.updater = mock_updater
|
|
447
|
-
mock_client.version = Version('1.0.0.dev1')
|
|
448
|
-
|
|
449
|
-
assert resolve_version(mock_client) == Version('5.6.7')
|
|
450
|
-
|
|
451
|
-
@staticmethod
|
|
452
|
-
def test_falls_back_when_not_installed() -> None:
|
|
453
|
-
"""Verify importlib.metadata version is used when not Velopack-installed."""
|
|
454
|
-
mock_updater = MagicMock()
|
|
455
|
-
mock_updater.is_installed = False
|
|
456
|
-
|
|
457
|
-
mock_client = MagicMock()
|
|
458
|
-
mock_client.updater = mock_updater
|
|
459
|
-
mock_client.version = Version('1.0.0.dev1')
|
|
460
|
-
|
|
461
|
-
assert resolve_version(mock_client) == Version('1.0.0.dev1')
|
|
462
|
-
|
|
463
|
-
@staticmethod
|
|
464
|
-
def test_falls_back_when_no_updater() -> None:
|
|
465
|
-
"""Verify importlib.metadata version is used when updater is None."""
|
|
466
|
-
mock_client = MagicMock()
|
|
467
|
-
mock_client.updater = None
|
|
468
|
-
mock_client.version = Version('2.3.4')
|
|
469
|
-
|
|
470
|
-
assert resolve_version(mock_client) == Version('2.3.4')
|
|
471
|
-
|
|
472
|
-
@staticmethod
|
|
473
|
-
def test_falls_back_on_exception() -> None:
|
|
474
|
-
"""Verify graceful fallback when querying the updater raises."""
|
|
475
|
-
mock_updater = MagicMock()
|
|
476
|
-
type(mock_updater).is_installed = PropertyMock(side_effect=RuntimeError('boom'))
|
|
477
|
-
|
|
478
|
-
mock_client = MagicMock()
|
|
479
|
-
mock_client.updater = mock_updater
|
|
480
|
-
mock_client.version = Version('3.0.0')
|
|
481
|
-
|
|
482
|
-
assert resolve_version(mock_client) == Version('3.0.0')
|
|
@@ -16,6 +16,7 @@ from synodic_client.updater import (
|
|
|
16
16
|
UpdateState,
|
|
17
17
|
github_release_asset_url,
|
|
18
18
|
initialize_velopack,
|
|
19
|
+
pep440_to_semver,
|
|
19
20
|
platform_suffix,
|
|
20
21
|
)
|
|
21
22
|
|
|
@@ -528,3 +529,43 @@ class TestGetVelopackManager:
|
|
|
528
529
|
|
|
529
530
|
assert updater._current_version == Version('9.8.7')
|
|
530
531
|
assert updater.current_version == Version('9.8.7')
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
class TestPep440ToSemver:
|
|
535
|
+
"""Tests for pep440_to_semver conversion."""
|
|
536
|
+
|
|
537
|
+
@staticmethod
|
|
538
|
+
def test_dev_version_two_part_base() -> None:
|
|
539
|
+
"""PDM SCM 2-part base normalises to 3-part SemVer."""
|
|
540
|
+
assert pep440_to_semver('0.1.dev47+g799543c') == '0.1.0-dev.47'
|
|
541
|
+
|
|
542
|
+
@staticmethod
|
|
543
|
+
def test_dev_version_three_part_base() -> None:
|
|
544
|
+
"""Three-part dev version converts correctly."""
|
|
545
|
+
assert pep440_to_semver('0.1.1.dev3') == '0.1.1-dev.3'
|
|
546
|
+
|
|
547
|
+
@staticmethod
|
|
548
|
+
def test_stable_version() -> None:
|
|
549
|
+
"""Stable version passes through unchanged."""
|
|
550
|
+
assert pep440_to_semver('1.0.0') == '1.0.0'
|
|
551
|
+
|
|
552
|
+
@staticmethod
|
|
553
|
+
def test_stable_version_two_part() -> None:
|
|
554
|
+
"""Two-part stable normalises to three-part."""
|
|
555
|
+
assert pep440_to_semver('1.0') == '1.0.0'
|
|
556
|
+
|
|
557
|
+
@staticmethod
|
|
558
|
+
def test_dev_zero() -> None:
|
|
559
|
+
"""Dev release number zero is preserved."""
|
|
560
|
+
assert pep440_to_semver('0.1.0.dev0') == '0.1.0-dev.0'
|
|
561
|
+
|
|
562
|
+
@staticmethod
|
|
563
|
+
def test_local_segment_stripped() -> None:
|
|
564
|
+
"""Local segment (+gXXXXXXX) is stripped."""
|
|
565
|
+
assert pep440_to_semver('1.2.3.dev10+gabcdef1') == '1.2.3-dev.10'
|
|
566
|
+
|
|
567
|
+
@staticmethod
|
|
568
|
+
def test_semver_input_passthrough() -> None:
|
|
569
|
+
"""SemVer-style pre-release input is normalised via PEP 440."""
|
|
570
|
+
# packaging.version.Version normalises '0.1.0-dev.5' to '0.1.0.dev5'
|
|
571
|
+
assert pep440_to_semver('0.1.0-dev.5') == '0.1.0-dev.5'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '0.0.1.dev45'
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
"""Tests for Client.version property behavior."""
|
|
2
|
-
|
|
3
|
-
import importlib.metadata
|
|
4
|
-
from unittest.mock import patch
|
|
5
|
-
|
|
6
|
-
from packaging.version import Version
|
|
7
|
-
|
|
8
|
-
from synodic_client.client import Client
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class TestClientVersion:
|
|
12
|
-
"""Tests for Client.version property."""
|
|
13
|
-
|
|
14
|
-
@staticmethod
|
|
15
|
-
def test_version_from_metadata() -> None:
|
|
16
|
-
"""Verify version is retrieved from importlib.metadata when available."""
|
|
17
|
-
client = Client()
|
|
18
|
-
|
|
19
|
-
with patch.object(importlib.metadata, 'version', return_value='1.2.3'):
|
|
20
|
-
version = client.version
|
|
21
|
-
|
|
22
|
-
assert version == Version('1.2.3')
|
|
23
|
-
|
|
24
|
-
@staticmethod
|
|
25
|
-
def test_version_is_version_object() -> None:
|
|
26
|
-
"""Verify version property returns a Version object."""
|
|
27
|
-
client = Client()
|
|
28
|
-
version = client.version
|
|
29
|
-
|
|
30
|
-
assert isinstance(version, Version)
|
|
31
|
-
|
|
32
|
-
@staticmethod
|
|
33
|
-
def test_version_dev_format() -> None:
|
|
34
|
-
"""Verify dev versions are parsed correctly."""
|
|
35
|
-
client = Client()
|
|
36
|
-
dev_version = '1.0.0.dev5+gabcdef1'
|
|
37
|
-
expected = Version(dev_version)
|
|
38
|
-
|
|
39
|
-
with patch.object(importlib.metadata, 'version', return_value=dev_version):
|
|
40
|
-
version = client.version
|
|
41
|
-
|
|
42
|
-
assert version == expected
|
|
43
|
-
assert version.dev == expected.dev
|
|
44
|
-
assert version.local == expected.local
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/__init__.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/bootstrap.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/data.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/icon.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/init.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/instance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/screen/card.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/theme.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/synodic_client/application/workers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_gather_packages.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_install_preview.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_preview_model.py
RENAMED
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_tray_window_show.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_update_banner.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_update_controller.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/qt/test_update_feedback.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/windows/test_protocol.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev45 → synodic_client-0.0.1.dev47}/tests/unit/windows/test_startup.py
RENAMED
|
File without changes
|