synodic-client 0.0.1.dev23__tar.gz → 0.0.1.dev24__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.dev23 → synodic_client-0.0.1.dev24}/PKG-INFO +1 -1
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/pyproject.toml +1 -1
- synodic_client-0.0.1.dev24/synodic_client/_version.py +1 -0
- synodic_client-0.0.1.dev24/synodic_client/application/bootstrap.py +39 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/application/screen/tray.py +12 -4
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/cli.py +2 -1
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/logging.py +9 -19
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/updater.py +3 -3
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/qt/test_logging.py +10 -10
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/test_cli.py +3 -3
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/test_updater.py +3 -3
- synodic_client-0.0.1.dev23/synodic_client/_version.py +0 -1
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/LICENSE.md +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/README.md +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/__init__.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/__main__.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/application/__init__.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/application/icon.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/application/instance.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/application/qt.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/application/screen/__init__.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/application/screen/install.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/application/screen/log_panel.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/application/screen/screen.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/application/theme.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/application/uri.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/client.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/config.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/protocol.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/py.typed +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/resolution.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/__init__.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/conftest.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/__init__.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/qt/__init__.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/qt/conftest.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/qt/test_install_preview.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/qt/test_log_panel.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/test_client_updater.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/test_client_version.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/test_config.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/test_examples.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/test_install.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/test_install_preview.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/test_resolution.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/test_uri.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/windows/__init__.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/windows/conftest.py +0 -0
- {synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/windows/test_protocol.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.0.1.dev24'
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Bootstrap entry point for PyInstaller builds.
|
|
2
|
+
|
|
3
|
+
Runs the lightweight startup preamble — logging, Velopack hooks, and
|
|
4
|
+
protocol registration — **before** importing heavy modules (PySide6,
|
|
5
|
+
porringer). Velopack's install/uninstall/update hooks have strict
|
|
6
|
+
timeouts (15–30 s) and must complete before the process is killed.
|
|
7
|
+
|
|
8
|
+
Import order matters:
|
|
9
|
+
1. stdlib + config (pure-Python, fast)
|
|
10
|
+
2. configure_logging() — now Qt-free
|
|
11
|
+
3. initialize_velopack() — hooks run with logging active
|
|
12
|
+
4. register_protocol() — stdlib only
|
|
13
|
+
5. import qt.application — PySide6 / porringer loaded here
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
from synodic_client.config import set_dev_mode
|
|
19
|
+
from synodic_client.logging import configure_logging
|
|
20
|
+
from synodic_client.protocol import register_protocol
|
|
21
|
+
from synodic_client.updater import initialize_velopack
|
|
22
|
+
|
|
23
|
+
_PROTOCOL_SCHEME = 'synodic'
|
|
24
|
+
|
|
25
|
+
# Parse --dev flag early so logging uses the right filename.
|
|
26
|
+
_dev_mode = '--dev' in sys.argv[1:]
|
|
27
|
+
set_dev_mode(_dev_mode)
|
|
28
|
+
|
|
29
|
+
configure_logging()
|
|
30
|
+
initialize_velopack()
|
|
31
|
+
|
|
32
|
+
if not _dev_mode:
|
|
33
|
+
register_protocol(sys.executable)
|
|
34
|
+
|
|
35
|
+
# Heavy imports happen here — PySide6, porringer, etc.
|
|
36
|
+
from synodic_client.application.qt import application # noqa: E402
|
|
37
|
+
|
|
38
|
+
_uri = next((a for a in sys.argv[1:] if a.lower().startswith(f'{_PROTOCOL_SCHEME}://')), None)
|
|
39
|
+
application(uri=_uri, dev_mode=_dev_mode)
|
{synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/application/screen/tray.py
RENAMED
|
@@ -6,8 +6,8 @@ from pathlib import Path
|
|
|
6
6
|
|
|
7
7
|
from porringer.api import API
|
|
8
8
|
from porringer.schema import SetupParameters, SyncStrategy
|
|
9
|
-
from PySide6.QtCore import QThread, QTimer, Signal
|
|
10
|
-
from PySide6.QtGui import QAction
|
|
9
|
+
from PySide6.QtCore import QThread, QTimer, QUrl, Signal
|
|
10
|
+
from PySide6.QtGui import QAction, QDesktopServices
|
|
11
11
|
from PySide6.QtWidgets import (
|
|
12
12
|
QApplication,
|
|
13
13
|
QDialog,
|
|
@@ -29,7 +29,7 @@ from synodic_client.application.screen.screen import MainWindow
|
|
|
29
29
|
from synodic_client.application.theme import UPDATE_SOURCE_DIALOG_MIN_WIDTH
|
|
30
30
|
from synodic_client.client import Client
|
|
31
31
|
from synodic_client.config import GlobalConfiguration
|
|
32
|
-
from synodic_client.logging import
|
|
32
|
+
from synodic_client.logging import log_path
|
|
33
33
|
from synodic_client.resolution import resolve_config, resolve_enabled_plugins, resolve_update_config, update_and_resolve
|
|
34
34
|
from synodic_client.updater import GITHUB_REPO_URL, UpdateChannel, UpdateInfo
|
|
35
35
|
|
|
@@ -281,7 +281,7 @@ class TrayScreen:
|
|
|
281
281
|
self.settings_menu.addSeparator()
|
|
282
282
|
|
|
283
283
|
self.open_log_action = QAction('Open Log...', self.settings_menu)
|
|
284
|
-
self.open_log_action.triggered.connect(
|
|
284
|
+
self.open_log_action.triggered.connect(self._open_log)
|
|
285
285
|
self.settings_menu.addAction(self.open_log_action)
|
|
286
286
|
|
|
287
287
|
self.menu.addSeparator()
|
|
@@ -294,6 +294,14 @@ class TrayScreen:
|
|
|
294
294
|
|
|
295
295
|
# -- Config helpers --
|
|
296
296
|
|
|
297
|
+
@staticmethod
|
|
298
|
+
def _open_log() -> None:
|
|
299
|
+
"""Open the log file in the system's default editor."""
|
|
300
|
+
path = log_path()
|
|
301
|
+
if not path.exists():
|
|
302
|
+
path.touch()
|
|
303
|
+
QDesktopServices.openUrl(QUrl.fromLocalFile(str(path)))
|
|
304
|
+
|
|
297
305
|
def _resolve_config(self) -> GlobalConfiguration:
|
|
298
306
|
"""Return the injected config or resolve from disk."""
|
|
299
307
|
if self._config is not None:
|
|
@@ -5,7 +5,6 @@ from typing import Annotated
|
|
|
5
5
|
import typer
|
|
6
6
|
|
|
7
7
|
from synodic_client import __version__
|
|
8
|
-
from synodic_client.application.qt import application
|
|
9
8
|
|
|
10
9
|
app = typer.Typer(
|
|
11
10
|
name='synodic-c',
|
|
@@ -38,4 +37,6 @@ def main(
|
|
|
38
37
|
] = False,
|
|
39
38
|
) -> None:
|
|
40
39
|
"""Launch the Synodic Client GUI application."""
|
|
40
|
+
from synodic_client.application.qt import application # noqa: PLC0415
|
|
41
|
+
|
|
41
42
|
application(uri=uri, dev_mode=dev)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Centralised logging configuration for the Synodic Client.
|
|
2
2
|
|
|
3
|
-
Provides a rotating file handler with eager flushing
|
|
4
|
-
the current log file in the system's default editor.
|
|
3
|
+
Provides a rotating file handler with eager flushing.
|
|
5
4
|
"""
|
|
6
5
|
|
|
7
6
|
import logging
|
|
@@ -9,9 +8,6 @@ import tempfile
|
|
|
9
8
|
from logging.handlers import RotatingFileHandler
|
|
10
9
|
from pathlib import Path
|
|
11
10
|
|
|
12
|
-
from PySide6.QtCore import QUrl
|
|
13
|
-
from PySide6.QtGui import QDesktopServices
|
|
14
|
-
|
|
15
11
|
from synodic_client.config import is_dev_mode
|
|
16
12
|
|
|
17
13
|
_LOG_FILENAME = 'synodic.log'
|
|
@@ -52,7 +48,15 @@ def configure_logging() -> None:
|
|
|
52
48
|
Attaches a :class:`EagerRotatingFileHandler` to the ``synodic_client``
|
|
53
49
|
and ``porringer`` loggers and configures :func:`logging.basicConfig`
|
|
54
50
|
for ``INFO`` level output on *stderr*.
|
|
51
|
+
|
|
52
|
+
Safe to call more than once — subsequent calls are no-ops.
|
|
55
53
|
"""
|
|
54
|
+
app_logger = logging.getLogger('synodic_client')
|
|
55
|
+
|
|
56
|
+
# Guard: skip if already configured (e.g. by bootstrap.py)
|
|
57
|
+
if any(isinstance(h, EagerRotatingFileHandler) for h in app_logger.handlers):
|
|
58
|
+
return
|
|
59
|
+
|
|
56
60
|
logging.basicConfig(level=logging.INFO)
|
|
57
61
|
|
|
58
62
|
handler = EagerRotatingFileHandler(
|
|
@@ -63,22 +67,8 @@ def configure_logging() -> None:
|
|
|
63
67
|
)
|
|
64
68
|
handler.setFormatter(logging.Formatter(_FORMAT))
|
|
65
69
|
|
|
66
|
-
app_logger = logging.getLogger('synodic_client')
|
|
67
70
|
app_logger.addHandler(handler)
|
|
68
71
|
|
|
69
72
|
porringer_logger = logging.getLogger('porringer')
|
|
70
73
|
porringer_logger.addHandler(handler)
|
|
71
74
|
porringer_logger.setLevel(logging.INFO)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def open_log() -> None:
|
|
75
|
-
"""Open the log file in the system's default editor.
|
|
76
|
-
|
|
77
|
-
Creates an empty file if one does not yet exist so that the OS always
|
|
78
|
-
has something to open.
|
|
79
|
-
"""
|
|
80
|
-
path = log_path()
|
|
81
|
-
if not path.exists():
|
|
82
|
-
path.touch()
|
|
83
|
-
|
|
84
|
-
QDesktopServices.openUrl(QUrl.fromLocalFile(str(path)))
|
|
@@ -33,7 +33,7 @@ _PLATFORM_SUFFIXES: dict[str, str] = {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def
|
|
36
|
+
def platform_suffix() -> str:
|
|
37
37
|
"""Return the Velopack channel suffix for the current platform."""
|
|
38
38
|
try:
|
|
39
39
|
return _PLATFORM_SUFFIXES[sys.platform]
|
|
@@ -104,8 +104,8 @@ class UpdateConfig:
|
|
|
104
104
|
so each OS has its own release manifest and nupkg files.
|
|
105
105
|
"""
|
|
106
106
|
base = 'dev' if self.channel == UpdateChannel.DEVELOPMENT else 'stable'
|
|
107
|
-
|
|
108
|
-
return f'{base}-{
|
|
107
|
+
suffix = platform_suffix()
|
|
108
|
+
return f'{base}-{suffix}'
|
|
109
109
|
|
|
110
110
|
|
|
111
111
|
class Updater:
|
|
@@ -5,12 +5,12 @@ import tempfile
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from unittest.mock import patch
|
|
7
7
|
|
|
8
|
+
from synodic_client.application.screen.tray import TrayScreen
|
|
8
9
|
from synodic_client.config import set_dev_mode
|
|
9
10
|
from synodic_client.logging import (
|
|
10
11
|
EagerRotatingFileHandler,
|
|
11
12
|
configure_logging,
|
|
12
13
|
log_path,
|
|
13
|
-
open_log,
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
|
|
@@ -106,31 +106,31 @@ class TestConfigureLogging:
|
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
class TestOpenLog:
|
|
109
|
-
"""Tests for
|
|
109
|
+
"""Tests for TrayScreen._open_log()."""
|
|
110
110
|
|
|
111
111
|
@staticmethod
|
|
112
112
|
def test_creates_file_if_missing(tmp_path: Path) -> None:
|
|
113
|
-
"""
|
|
113
|
+
"""_open_log() should create the log file when it does not exist."""
|
|
114
114
|
log_file = tmp_path / 'synodic.log'
|
|
115
115
|
assert not log_file.exists()
|
|
116
116
|
|
|
117
117
|
with (
|
|
118
|
-
patch('synodic_client.
|
|
119
|
-
patch('synodic_client.
|
|
118
|
+
patch('synodic_client.application.screen.tray.log_path', return_value=log_file),
|
|
119
|
+
patch('synodic_client.application.screen.tray.QDesktopServices') as mock_ds,
|
|
120
120
|
):
|
|
121
|
-
|
|
121
|
+
TrayScreen._open_log()
|
|
122
122
|
assert log_file.exists()
|
|
123
123
|
mock_ds.openUrl.assert_called_once()
|
|
124
124
|
|
|
125
125
|
@staticmethod
|
|
126
126
|
def test_opens_existing_file(tmp_path: Path) -> None:
|
|
127
|
-
"""
|
|
127
|
+
"""_open_log() should open an existing log file without error."""
|
|
128
128
|
log_file = tmp_path / 'synodic.log'
|
|
129
129
|
log_file.write_text('existing content', encoding='utf-8')
|
|
130
130
|
|
|
131
131
|
with (
|
|
132
|
-
patch('synodic_client.
|
|
133
|
-
patch('synodic_client.
|
|
132
|
+
patch('synodic_client.application.screen.tray.log_path', return_value=log_file),
|
|
133
|
+
patch('synodic_client.application.screen.tray.QDesktopServices') as mock_ds,
|
|
134
134
|
):
|
|
135
|
-
|
|
135
|
+
TrayScreen._open_log()
|
|
136
136
|
mock_ds.openUrl.assert_called_once()
|
|
@@ -33,7 +33,7 @@ class TestCli:
|
|
|
33
33
|
@staticmethod
|
|
34
34
|
def test_launches_application_without_uri() -> None:
|
|
35
35
|
"""Verify invoking with no args calls application(uri=None, dev_mode=False)."""
|
|
36
|
-
with patch('synodic_client.
|
|
36
|
+
with patch('synodic_client.application.qt.application') as mock_app:
|
|
37
37
|
result = runner.invoke(app, [])
|
|
38
38
|
assert result.exit_code == 0
|
|
39
39
|
mock_app.assert_called_once_with(uri=None, dev_mode=False)
|
|
@@ -42,7 +42,7 @@ class TestCli:
|
|
|
42
42
|
def test_launches_application_with_uri() -> None:
|
|
43
43
|
"""Verify invoking with a URI passes it to application()."""
|
|
44
44
|
test_uri = 'synodic://install?manifest=https://example.com/foo.json'
|
|
45
|
-
with patch('synodic_client.
|
|
45
|
+
with patch('synodic_client.application.qt.application') as mock_app:
|
|
46
46
|
result = runner.invoke(app, [test_uri])
|
|
47
47
|
assert result.exit_code == 0
|
|
48
48
|
mock_app.assert_called_once_with(uri=test_uri, dev_mode=False)
|
|
@@ -50,7 +50,7 @@ class TestCli:
|
|
|
50
50
|
@staticmethod
|
|
51
51
|
def test_launches_application_with_dev_flag() -> None:
|
|
52
52
|
"""Verify --dev flag sets dev_mode=True."""
|
|
53
|
-
with patch('synodic_client.
|
|
53
|
+
with patch('synodic_client.application.qt.application') as mock_app:
|
|
54
54
|
result = runner.invoke(app, ['--dev'])
|
|
55
55
|
assert result.exit_code == 0
|
|
56
56
|
mock_app.assert_called_once_with(uri=None, dev_mode=True)
|
|
@@ -14,8 +14,8 @@ from synodic_client.updater import (
|
|
|
14
14
|
UpdateInfo,
|
|
15
15
|
Updater,
|
|
16
16
|
UpdateState,
|
|
17
|
-
_platform_suffix,
|
|
18
17
|
initialize_velopack,
|
|
18
|
+
platform_suffix,
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
|
|
@@ -127,13 +127,13 @@ class TestUpdateConfig:
|
|
|
127
127
|
def test_channel_name_stable() -> None:
|
|
128
128
|
"""Verify STABLE channel returns platform-specific 'stable' name."""
|
|
129
129
|
config = UpdateConfig(channel=UpdateChannel.STABLE)
|
|
130
|
-
assert config.channel_name == f'stable-{
|
|
130
|
+
assert config.channel_name == f'stable-{platform_suffix()}'
|
|
131
131
|
|
|
132
132
|
@staticmethod
|
|
133
133
|
def test_channel_name_development() -> None:
|
|
134
134
|
"""Verify DEVELOPMENT channel returns platform-specific 'dev' name."""
|
|
135
135
|
config = UpdateConfig(channel=UpdateChannel.DEVELOPMENT)
|
|
136
|
-
assert config.channel_name == f'dev-{
|
|
136
|
+
assert config.channel_name == f'dev-{platform_suffix()}'
|
|
137
137
|
|
|
138
138
|
|
|
139
139
|
@pytest.fixture
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '0.0.1.dev23'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/application/__init__.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/application/icon.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/application/instance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/synodic_client/application/theme.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
|
{synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/qt/test_install_preview.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.dev23 → synodic_client-0.0.1.dev24}/tests/unit/test_install_preview.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev23 → synodic_client-0.0.1.dev24}/tests/unit/windows/test_protocol.py
RENAMED
|
File without changes
|