SparkleHelper 0.1.0__py3-none-win_amd64.whl

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.
Files changed (31) hide show
  1. sparklehelper-0.1.0.data/purelib/sparklehelper/__init__.py +62 -0
  2. sparklehelper-0.1.0.data/purelib/sparklehelper/_backend/__init__.py +54 -0
  3. sparklehelper-0.1.0.data/purelib/sparklehelper/_backend/_macos/__init__.py +45 -0
  4. sparklehelper-0.1.0.data/purelib/sparklehelper/_backend/_macos/_backend.py +377 -0
  5. sparklehelper-0.1.0.data/purelib/sparklehelper/_backend/_macos/_delegates.py +656 -0
  6. sparklehelper-0.1.0.data/purelib/sparklehelper/_backend/_macos/_loading.py +202 -0
  7. sparklehelper-0.1.0.data/purelib/sparklehelper/_backend/_macos/_runtime.py +263 -0
  8. sparklehelper-0.1.0.data/purelib/sparklehelper/_backend/_windows/__init__.py +20 -0
  9. sparklehelper-0.1.0.data/purelib/sparklehelper/_backend/_windows/_backend.py +335 -0
  10. sparklehelper-0.1.0.data/purelib/sparklehelper/_backend/_windows/_bindings.py +181 -0
  11. sparklehelper-0.1.0.data/purelib/sparklehelper/_backend/_windows/_loading.py +218 -0
  12. sparklehelper-0.1.0.data/purelib/sparklehelper/_backend/base.py +330 -0
  13. sparklehelper-0.1.0.data/purelib/sparklehelper/_framework.py +956 -0
  14. sparklehelper-0.1.0.data/purelib/sparklehelper/_nuitka_plugin.py +139 -0
  15. sparklehelper-0.1.0.data/purelib/sparklehelper/_pyinstaller/__init__.py +265 -0
  16. sparklehelper-0.1.0.data/purelib/sparklehelper/_pyinstaller/hook-sparklehelper.py +124 -0
  17. sparklehelper-0.1.0.data/purelib/sparklehelper/errors.py +68 -0
  18. sparklehelper-0.1.0.data/purelib/sparklehelper/licenses/WinSparkle-LICENSE.txt +25 -0
  19. sparklehelper-0.1.0.data/purelib/sparklehelper/sparklehelper.nuitka-package.config.yml +31 -0
  20. sparklehelper-0.1.0.data/purelib/sparklehelper/types.py +265 -0
  21. sparklehelper-0.1.0.data/purelib/sparklehelper/updater.py +474 -0
  22. sparklehelper-0.1.0.data/purelib/sparklehelper/winsparkle/arm64/WinSparkle.dll +0 -0
  23. sparklehelper-0.1.0.data/purelib/sparklehelper/winsparkle/winsparkle.h +663 -0
  24. sparklehelper-0.1.0.data/purelib/sparklehelper/winsparkle/x64/WinSparkle.dll +0 -0
  25. sparklehelper-0.1.0.data/purelib/sparklehelper/winsparkle/x86/WinSparkle.dll +0 -0
  26. sparklehelper-0.1.0.dist-info/METADATA +371 -0
  27. sparklehelper-0.1.0.dist-info/RECORD +31 -0
  28. sparklehelper-0.1.0.dist-info/WHEEL +5 -0
  29. sparklehelper-0.1.0.dist-info/entry_points.txt +5 -0
  30. sparklehelper-0.1.0.dist-info/licenses/LICENSE +21 -0
  31. sparklehelper-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,62 @@
1
+ """sparklehelper —— Sparkle / WinSparkle 原生更新框架的 Python 运行时接口。
2
+
3
+ 快速上手::
4
+
5
+ from sparklehelper import Updater
6
+
7
+ updater = Updater() # 读 .app 的 Info.plist 配置
8
+ updater.check_for_updates() # 弹出平台原生更新窗口
9
+
10
+ 详见 :class:`sparklehelper.updater.Updater`。
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from . import errors, types
16
+ from ._backend._macos import Decision, UpdaterDelegate
17
+ from .errors import (
18
+ ConfigurationError,
19
+ NotABundleError,
20
+ SparkleError,
21
+ SparkleNotAvailableError,
22
+ UpdateCheckError,
23
+ WrongThreadError,
24
+ )
25
+ from .types import (
26
+ SystemProfileEntry,
27
+ UpdateCheckKind,
28
+ UpdateCheckResult,
29
+ UpdateInfo,
30
+ UserUpdateChoice,
31
+ UserUpdateStage,
32
+ UserUpdateState,
33
+ )
34
+ from .updater import Subscription, Updater, ensure_runnable
35
+
36
+ __version__ = "0.1.0"
37
+
38
+ __all__ = [
39
+ "Updater",
40
+ "UpdaterDelegate",
41
+ "Decision",
42
+ "Subscription",
43
+ "ensure_runnable",
44
+ "types",
45
+ "errors",
46
+ # dataclass 快捷导出
47
+ "UpdateInfo",
48
+ "SystemProfileEntry",
49
+ "UpdateCheckKind",
50
+ "UpdateCheckResult",
51
+ "UserUpdateChoice",
52
+ "UserUpdateStage",
53
+ "UserUpdateState",
54
+ # 异常快捷导出
55
+ "SparkleError",
56
+ "SparkleNotAvailableError",
57
+ "NotABundleError",
58
+ "ConfigurationError",
59
+ "UpdateCheckError",
60
+ "WrongThreadError",
61
+ "__version__",
62
+ ]
@@ -0,0 +1,54 @@
1
+ """平台后端选择器。
2
+
3
+ 按 ``sys.platform`` 选择并返回一个 :class:`~sparklehelper._backend.base.UpdateBackend`
4
+ 实现。后端类的 import 放在函数体内(惰性),保证:
5
+
6
+ - 非 darwin / 非 win32 平台 ``import sparklehelper._backend`` 本身不失败;
7
+ - 目标平台才 import 对应后端模块(macOS 不 import ctypes win32,Windows
8
+ 不 import PyObjC),避免拉起无关的平台依赖。
9
+
10
+ 后端类尚未全部实现时,调用 ``get_backend`` 在对应平台会抛
11
+ :class:`~sparklehelper.errors.SparkleNotAvailableError`,这是预期行为。
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import sys
17
+
18
+ from ..errors import SparkleNotAvailableError
19
+
20
+ from .base import (
21
+ Callbacks,
22
+ SparkleExtras,
23
+ UpdateBackend,
24
+ UpdateConfig,
25
+ WinSparkleExtras,
26
+ )
27
+
28
+ __all__ = [
29
+ "get_backend",
30
+ "UpdateBackend",
31
+ "UpdateConfig",
32
+ "Callbacks",
33
+ "SparkleExtras",
34
+ "WinSparkleExtras",
35
+ ]
36
+
37
+
38
+ def get_backend() -> UpdateBackend:
39
+ """返回当前平台的更新后端实例。
40
+
41
+ :raises SparkleNotAvailableError: 当前平台不支持,或对应后端模块缺失。
42
+ """
43
+ platform = sys.platform
44
+ if platform == "darwin":
45
+ from ._macos import MacOSBackend
46
+
47
+ return MacOSBackend()
48
+ if platform == "win32":
49
+ from ._windows import WindowsBackend
50
+
51
+ return WindowsBackend()
52
+ raise SparkleNotAvailableError(
53
+ f"sparklehelper 不支持当前平台: {platform}(仅支持 macOS / Windows)"
54
+ )
@@ -0,0 +1,45 @@
1
+ """macOS 后端包:Sparkle.framework 的加载与 ObjC 桥接。
2
+
3
+ 把原 ``_objc`` / ``_bridge`` / ``delegates`` 三个模块的逻辑按功能边界拆分到
4
+ 子模块,由 :class:`MacOSBackend` 组合,对外只暴露统一契约:
5
+
6
+ - :mod:`._loading`:Sparkle.framework 运行时加载(进程级缓存)
7
+ - :mod:`._runtime`:主线程约束 / bundle 解析 / KVO observer(进程级缓存)
8
+ - :mod:`._delegates`:``SPUUpdaterDelegate`` Python 抽象与 ObjC 适配器
9
+ - :mod:`._backend`::class:`MacOSBackend` 类(组合上述三者)
10
+
11
+ 非 darwin 安全
12
+ --------------
13
+ 本包顶层不 import PyObjC / Foundation。所有 ObjC import 延迟到函数体内
14
+ 惰性执行,保证非 darwin 平台 ``import sparklehelper._backend._macos``
15
+ 不会触发 ObjC 类注册或导入失败。
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ # MacOSBackend + 公共符号(运行时函数重导出,供 Updater / 测试直接用)。
21
+ from ._backend import MacOSBackend
22
+ from ._delegates import Decision, UpdaterDelegate, make_delegate_adapter
23
+ from ._runtime import (
24
+ Subscription,
25
+ assert_main_thread,
26
+ bundle_info_plist,
27
+ get_kvo_observer_class,
28
+ host_bundle_path,
29
+ in_app_bundle,
30
+ on_main_thread,
31
+ )
32
+
33
+ __all__ = [
34
+ "MacOSBackend",
35
+ "UpdaterDelegate",
36
+ "Decision",
37
+ "Subscription",
38
+ "make_delegate_adapter",
39
+ "assert_main_thread",
40
+ "on_main_thread",
41
+ "host_bundle_path",
42
+ "in_app_bundle",
43
+ "bundle_info_plist",
44
+ "get_kvo_observer_class",
45
+ ]
@@ -0,0 +1,377 @@
1
+ """macOS 更新后端:组合 Sparkle 加载、运行时基础设施与 delegate 桥接。
2
+
3
+ :class:`MacOSBackend` 实现
4
+ :class:`~sparklehelper._backend.base.UpdateBackend` 契约 + macOS 独有能力
5
+ (KVO / system_profile / 24+ delegate 方法)。它本身只持有 controller /
6
+ updater 实例状态与 ObjC selector 调用,底层逻辑由同包的
7
+ :mod:`._loading` / :mod:`._runtime` / :mod:`._delegates` 提供。
8
+
9
+ 所有方法**必须在主线程**调用(``Updater`` Facade 统一断言)。
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from datetime import datetime, timezone
15
+ from typing import Any, Callable, Optional
16
+
17
+ from ...errors import SparkleNotAvailableError
18
+ from ...types import SystemProfileEntry, from_system_profile
19
+ from ..base import Callbacks, UpdateConfig
20
+ from . import _delegates, _loading, _runtime
21
+
22
+ # Sparkle 2.9 明确声明为 KVO-compliant 的属性。
23
+ _KVO_PROPERTIES: dict[str, tuple[str, Callable[[Any], Any]]] = {
24
+ "can_check_for_updates": ("canCheckForUpdates", bool),
25
+ "automatically_checks_for_updates": ("automaticallyChecksForUpdates", bool),
26
+ "update_check_interval": ("updateCheckInterval", float),
27
+ "automatically_downloads_updates": ("automaticallyDownloadsUpdates", bool),
28
+ "allows_automatic_updates": ("allowsAutomaticUpdates", bool),
29
+ "sends_system_profile": ("sendsSystemProfile", bool),
30
+ }
31
+
32
+
33
+ class MacOSBackend:
34
+ """macOS 更新后端:Sparkle.framework 的加载与 ObjC 桥接。
35
+
36
+ 实现 :class:`~sparklehelper._backend.base.UpdateBackend` 契约 +
37
+ macOS 独有能力(KVO / system_profile / 24+ delegate 方法)。
38
+
39
+ 所有方法**必须在主线程**调用(``Updater`` Facade 统一断言)。
40
+ """
41
+
42
+ # ------------------------------------------------------------------
43
+ # 进程级缓存管理(类方法,供测试与诊断)
44
+ # ------------------------------------------------------------------
45
+
46
+ @classmethod
47
+ def is_loaded(cls) -> bool:
48
+ """Sparkle.framework 是否已成功加载。"""
49
+ return _loading.is_loaded()
50
+
51
+ @classmethod
52
+ def loaded_path(cls) -> Optional[str]:
53
+ """已加载 framework 的磁盘路径;未加载时为 None。"""
54
+ return _loading.loaded_path()
55
+
56
+ @classmethod
57
+ def _reset_for_test(cls) -> None:
58
+ """仅供测试:清空所有进程级缓存。"""
59
+ _loading.reset_for_test()
60
+
61
+ # 透传给 _loading,便于测试 patch(与原 _objc 模块函数签名一致)。
62
+ _main_bundle_frameworks_path = staticmethod(_loading.main_bundle_frameworks_path)
63
+ _resolve_framework_path = staticmethod(_loading.resolve_framework_path)
64
+ load_sparkle = staticmethod(_loading.load_sparkle)
65
+ get_sparkle = staticmethod(_loading.get_sparkle)
66
+
67
+ # ------------------------------------------------------------------
68
+ # ObjC 工具
69
+ # ------------------------------------------------------------------
70
+
71
+ @staticmethod
72
+ def _look_up(sparkle_module: Any, class_name: str) -> Any:
73
+ """从已加载的 Sparkle module 取出类,先查 globals 再 lookUpClass 兜底。"""
74
+ cls = getattr(sparkle_module, class_name, None)
75
+ if cls is not None:
76
+ return cls
77
+ try:
78
+ import objc
79
+
80
+ return objc.lookUpClass(class_name)
81
+ except Exception as exc: # noqa: BLE001
82
+ raise SparkleNotAvailableError(
83
+ f"Sparkle.framework loaded but cannot get class {class_name}: {exc}"
84
+ ) from exc
85
+
86
+ # ------------------------------------------------------------------
87
+ # 构造与生命周期(UpdateBackend Protocol)
88
+ # ------------------------------------------------------------------
89
+
90
+ def __init__(self) -> None:
91
+ self._controller: Any = None
92
+ self._updater: Any = None
93
+ self._delegate: Any = None
94
+ self._delegate_adapter: Any = None
95
+ self._started: bool = False
96
+
97
+ def configure(self, config: UpdateConfig) -> None:
98
+ """加载 Sparkle、创建 controller(复刻原 Updater.__init__ 创建链)。
99
+
100
+ 顺序:load_sparkle → make_delegate_adapter → alloc().initWith...
101
+ selector 名 ``initWithStartingUpdater_updaterDelegate_userDriverDelegate_``
102
+ 来自 framework introspection,不可改名。
103
+
104
+ ``delegate`` 通过 :attr:`UpdateConfig.delegate` 传入。
105
+ """
106
+ _runtime.assert_main_thread()
107
+
108
+ sparkle = self.get_sparkle()
109
+ SPUStandardUpdaterController = self._look_up(
110
+ sparkle, "SPUStandardUpdaterController"
111
+ )
112
+
113
+ delegate = config.delegate
114
+ self._delegate = delegate
115
+ delegate_adapter = _delegates.make_delegate_adapter(delegate)
116
+ self._delegate_adapter = delegate_adapter
117
+
118
+ self._controller = (
119
+ SPUStandardUpdaterController.alloc()
120
+ .initWithStartingUpdater_updaterDelegate_userDriverDelegate_(
121
+ False, delegate_adapter, None
122
+ )
123
+ )
124
+ if self._controller is None:
125
+ raise SparkleNotAvailableError(
126
+ "SPUStandardUpdaterController 初始化失败(返回 nil)。"
127
+ "请检查 Info.plist 是否包含 SUFeedURL、SUPublicEDKey、"
128
+ "CFBundleVersion 等必需字段。"
129
+ )
130
+ self._updater = self._controller.updater()
131
+
132
+ def register_callbacks(self, callbacks: Callbacks) -> None:
133
+ """注册跨平台回调集合。
134
+
135
+ macOS 后端通过 :class:`~._delegates.UpdaterDelegate` 体系接收回调。
136
+ 粗粒度回调(error/found/no_update/cancelled)由 delegate 的对应方法
137
+ 承载;此方法当前为 Protocol 完整性保留。
138
+ """
139
+ return
140
+
141
+ def start(self) -> None:
142
+ """启动更新检查调度器:``[controller startUpdater]``。幂等。"""
143
+ _runtime.assert_main_thread()
144
+ if self._controller is None:
145
+ raise SparkleNotAvailableError(
146
+ "后端尚未配置,请先调用 configure()。"
147
+ )
148
+ if self._started:
149
+ return
150
+ self._controller.startUpdater()
151
+ self._started = True
152
+
153
+ def cleanup(self) -> None:
154
+ """释放资源。macOS 下 Sparkle 无显式清理 API,空操作。"""
155
+ return
156
+
157
+ # ------------------------------------------------------------------
158
+ # 手动检查(UpdateBackend Protocol)
159
+ # ------------------------------------------------------------------
160
+
161
+ def check_for_updates(self) -> None:
162
+ """弹出标准更新窗口:``[controller checkForUpdates:nil]``。"""
163
+ _runtime.assert_main_thread()
164
+ self._controller.checkForUpdates_(None)
165
+
166
+ def check_for_updates_in_background(self) -> None:
167
+ """后台静默检查:``[updater checkForUpdatesInBackground]``。"""
168
+ _runtime.assert_main_thread()
169
+ self._updater.checkForUpdatesInBackground()
170
+
171
+ # ------------------------------------------------------------------
172
+ # macOS 独有检查方法
173
+ # ------------------------------------------------------------------
174
+
175
+ def check_for_update_information(self) -> None:
176
+ """仅拉取 appcast 信息,不触发起安装流程。"""
177
+ _runtime.assert_main_thread()
178
+ self._updater.checkForUpdateInformation()
179
+
180
+ def reset_update_cycle(self) -> None:
181
+ """重置自动检查计时,立即开始新一轮调度。"""
182
+ _runtime.assert_main_thread()
183
+ self._updater.resetUpdateCycle()
184
+
185
+ def reset_update_cycle_after_short_delay(self) -> None:
186
+ """短暂延迟后重置自动检查计时。"""
187
+ _runtime.assert_main_thread()
188
+ self._updater.resetUpdateCycleAfterShortDelay()
189
+
190
+ def clear_feed_url_from_user_defaults(self) -> Optional[str]:
191
+ """清除此前持久化的 feed URL,返回被清除的值。"""
192
+ _runtime.assert_main_thread()
193
+ url = self._updater.clearFeedURLFromUserDefaults()
194
+ if url is None:
195
+ return None
196
+ absolute_string = getattr(url, "absoluteString", None)
197
+ if callable(absolute_string):
198
+ value = absolute_string()
199
+ return str(value) if value else None
200
+ return str(url) or None
201
+
202
+ # ------------------------------------------------------------------
203
+ # 只读状态(UpdateBackend Protocol + SparkleExtras)
204
+ # ------------------------------------------------------------------
205
+
206
+ @property
207
+ def can_check_for_updates(self) -> bool:
208
+ _runtime.assert_main_thread()
209
+ return bool(self._updater.canCheckForUpdates())
210
+
211
+ @property
212
+ def session_in_progress(self) -> bool:
213
+ _runtime.assert_main_thread()
214
+ return bool(self._updater.sessionInProgress())
215
+
216
+ @property
217
+ def feed_url(self) -> Optional[str]:
218
+ _runtime.assert_main_thread()
219
+ url = self._updater.feedURL()
220
+ if url is None:
221
+ return None
222
+ if hasattr(url, "absoluteString"):
223
+ abs_str = url.absoluteString()
224
+ return str(abs_str) if abs_str else None
225
+ return str(url) or None
226
+
227
+ @property
228
+ def host_bundle_path(self) -> str:
229
+ _runtime.assert_main_thread()
230
+ bundle = self._updater.hostBundle()
231
+ path = bundle.bundlePath() if bundle is not None else None
232
+ return str(path) if path else ""
233
+
234
+ @property
235
+ def last_update_check_date(self) -> Optional[datetime]:
236
+ _runtime.assert_main_thread()
237
+ date = self._updater.lastUpdateCheckDate()
238
+ if date is None:
239
+ return None
240
+ try:
241
+ ts = float(date.timeIntervalSince1970())
242
+ except (AttributeError, TypeError, ValueError):
243
+ return None
244
+ return datetime.fromtimestamp(ts, tz=timezone.utc)
245
+
246
+ @property
247
+ def system_profile(self) -> list[SystemProfileEntry]:
248
+ _runtime.assert_main_thread()
249
+ entries = self._updater.systemProfileArray()
250
+ return from_system_profile(entries)
251
+
252
+ @property
253
+ def allows_automatic_updates(self) -> bool:
254
+ _runtime.assert_main_thread()
255
+ return bool(self._updater.allowsAutomaticUpdates())
256
+
257
+ @property
258
+ def automatically_downloads_updates(self) -> bool:
259
+ _runtime.assert_main_thread()
260
+ return bool(self._updater.automaticallyDownloadsUpdates())
261
+
262
+ @automatically_downloads_updates.setter
263
+ def automatically_downloads_updates(self, value: bool) -> None:
264
+ _runtime.assert_main_thread()
265
+ self._updater.setAutomaticallyDownloadsUpdates_(bool(value))
266
+
267
+ # ------------------------------------------------------------------
268
+ # 可读写状态(UpdateBackend Protocol + SparkleExtras)
269
+ # ------------------------------------------------------------------
270
+
271
+ @property
272
+ def automatically_checks_for_updates(self) -> bool:
273
+ _runtime.assert_main_thread()
274
+ return bool(self._updater.automaticallyChecksForUpdates())
275
+
276
+ @automatically_checks_for_updates.setter
277
+ def automatically_checks_for_updates(self, value: bool) -> None:
278
+ _runtime.assert_main_thread()
279
+ self._updater.setAutomaticallyChecksForUpdates_(bool(value))
280
+
281
+ @property
282
+ def update_check_interval(self) -> float:
283
+ _runtime.assert_main_thread()
284
+ return float(self._updater.updateCheckInterval())
285
+
286
+ @update_check_interval.setter
287
+ def update_check_interval(self, seconds: float) -> None:
288
+ _runtime.assert_main_thread()
289
+ self._updater.setUpdateCheckInterval_(float(seconds))
290
+
291
+ @property
292
+ def http_headers(self) -> Optional[dict[str, str]]:
293
+ _runtime.assert_main_thread()
294
+ headers = self._updater.httpHeaders()
295
+ if headers is None:
296
+ return None
297
+ return {str(k): str(v) for k, v in headers.items()}
298
+
299
+ @http_headers.setter
300
+ def http_headers(self, headers: Optional[dict[str, str]]) -> None:
301
+ _runtime.assert_main_thread()
302
+ if headers is None:
303
+ self._updater.setHttpHeaders_(None)
304
+ return
305
+ from Foundation import NSDictionary
306
+
307
+ ns_dict = NSDictionary.dictionaryWithDictionary_(
308
+ {str(k): str(v) for k, v in headers.items()}
309
+ )
310
+ self._updater.setHttpHeaders_(ns_dict)
311
+
312
+ @property
313
+ def user_agent_string(self) -> str:
314
+ _runtime.assert_main_thread()
315
+ value = self._updater.userAgentString()
316
+ return str(value) if value else ""
317
+
318
+ @user_agent_string.setter
319
+ def user_agent_string(self, value: str) -> None:
320
+ _runtime.assert_main_thread()
321
+ self._updater.setUserAgentString_(str(value))
322
+
323
+ @property
324
+ def sends_system_profile(self) -> bool:
325
+ _runtime.assert_main_thread()
326
+ return bool(self._updater.sendsSystemProfile())
327
+
328
+ @sends_system_profile.setter
329
+ def sends_system_profile(self, value: bool) -> None:
330
+ _runtime.assert_main_thread()
331
+ self._updater.setSendsSystemProfile_(bool(value))
332
+
333
+ # ------------------------------------------------------------------
334
+ # KVO 订阅
335
+ # ------------------------------------------------------------------
336
+
337
+ def observe(
338
+ self, property_name: str, callback: Callable[[Any], None]
339
+ ) -> "_runtime.Subscription":
340
+ """订阅 Sparkle 公开的 KVO 属性。订阅建立后立即回调一次。"""
341
+ _runtime.assert_main_thread()
342
+ try:
343
+ key_path, converter = _KVO_PROPERTIES[property_name]
344
+ except KeyError as exc:
345
+ supported = ", ".join(sorted(_KVO_PROPERTIES))
346
+ raise ValueError(
347
+ f"unsupported observable property {property_name!r}; "
348
+ f"available: {supported}"
349
+ ) from exc
350
+
351
+ target = self._updater
352
+ observer_cls = _runtime.get_kvo_observer_class()
353
+
354
+ def wrapped(new_value: Any) -> None:
355
+ if new_value is not None:
356
+ callback(converter(new_value))
357
+
358
+ observer = observer_cls.alloc().initWithCallback_target_keyPath_(
359
+ wrapped, target, key_path
360
+ )
361
+ from Foundation import (
362
+ NSKeyValueObservingOptionInitial,
363
+ NSKeyValueObservingOptionNew,
364
+ )
365
+
366
+ options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
367
+ target.addObserver_forKeyPath_options_context_(observer, key_path, options, 0)
368
+ return _runtime.Subscription(observer=observer, target=target, key_path=key_path)
369
+
370
+ def observe_can_check_for_updates(
371
+ self, callback: Callable[[bool], None]
372
+ ) -> "_runtime.Subscription":
373
+ """订阅 ``canCheckForUpdates`` 变化(便捷封装)。"""
374
+ return self.observe("can_check_for_updates", callback)
375
+
376
+
377
+ __all__ = ["MacOSBackend"]