fix: surface underlying cause when MiOT device refresh fails - #369
fix: surface underlying cause when MiOT device refresh fails#369mvanhorn wants to merge 3 commits into
Conversation
|
👋 感谢提交 PR @mvanhorn!维护者会尽快 review。 提交前请确认:
|
PR #369: fix: surface underlying cause when MiOT device refresh fails作者: mvanhorn
修改方案要解决的问题: 整体方案:
关键设计原则:
基线对账(Step 7.0):
测试覆盖:
问题🔵 建议(可选优化)
结论LGTM — 修复方向正确、错误可见性钉得扎实,前两轮 review 的全部 🟡 与可回归 🔵 均已消化,测试覆盖到位(含两处新暴露路径),全部 由 review-pr skill v1.6 生成 |
|
修复方向是对的。原来那句的 本 PR 的修法: 但目前看起来只完成了一半——底层复用路径没跟着处理,把原来的优雅降级悄悄改成了硬失败。以下需要补: 问题🟡 重要(应当修复)
🔵 建议(可选优化)
结论需要修改 — 错误可见性修得对,但 |
|
Restored the graceful degradation in both places you flagged: get_home_info(refresh=True) now returns partial data when device refresh fails but scenes/cameras succeed, and the lazy-refresh chain raises the clean domain exception instead of hard-failing callers that previously tolerated it. Added the two regression tests you outlined in test_miot_refresh_errors.py (lazy-refresh failure surfaces the domain exception, and partial home info on device-refresh failure). ruff passes; CI will run the full pytest. |
|
Thanks @hlccd - agreed with your analysis. The branch already surfaces the underlying cause: refresh_devices re-raises instead of returning None, so the real exception propagates instead of the doubled message. Happy to adjust the error wording too if you'd like. |
|
麻烦老师空闲时修正一下 |
7a16b89 to
9c5395c
Compare
|
Done - signed the CLA and rebased onto main to clear the conflict. The reconstructed |
…-underlying-cause-when-miot-d # Conflicts: # backend/miloco/src/miloco/miot/service.py
Summary
/api/miot/refresh_miot_devicesand/api/miot/device_listfailed with the opaque, doubled messageFailed to refresh MiOT devices: Failed to refresh MiOT devicesand no underlying cause in the logs, so the real failure (timeout, auth scope, unsupported device type) was invisible even at ERROR level. The refresh paths now attach the actual underlying exception as the cause.Why this matters
The reporter in #341 pointed out that the exception was raised bare, with no wrapped cause: the proxy layer caught the real exception, logged a single line, and returned
None, so the service layer only ever saw a falsy result and raised a genericMiotServiceExceptionwith the original error already discarded.MiotProxy.refresh_devices()inbackend/miloco/src/miloco/miot/client.pycaught the exception fromget_devices_async(), logged it, and returnedNone.MiotService.refresh_miot_devices()then hitif not result: raise MiotServiceException("Failed to refresh MiOT devices")inside its owntry, which the surroundingexcept Exception as e: raise MiotServiceException(f"...: {str(e)}") from ere-wrapped into the doubled message and chained__cause__to that generic inner exception instead of the real fault.get_miot_device_list()had the same shape.Changes
refresh_devices()re-raises the caught exception instead of returningNone, so the existing... from ehandlers inrefresh_miot_devices()andget_miot_device_list()attach the real cause. The genuinely-empty-but-successful refresh now raises a distinctNo MiOT devices foundmessage rather than masking a fault as a refresh failure.refresh_devices, which the SDK schedules viaasyncio.ensure_futurewithout reading its result. A small_refresh_devices_on_reconnectshim now swallows and logs reconnect-time failures, so a transient reconnect does not surface as an unretrieved task exception while awaited API callers still receive the raised cause.get_device_spec()andlist_cameras_with_state()call the lazyget_devices()without a guard. They now wrap it inMiotServiceException(...) from e, matching the handler idiom already used byget_miot_device_list()andget_device_status(), so a lazy-refresh failure returns a structured error instead of a raw 500.Testing
Added
backend/miloco/tests/test_miot_refresh_errors.py. The error paths assert__cause__is the original exception and the message carries the underlying text (and is not the doubled string); the happy path still returnsTrue; the genuinely-empty refresh reportsNo MiOT devices found after refreshwith no cause;refresh_miot_info()still records the per-label error in itserrorslist whenrefresh_devices()raises; andget_device_spec()surfaces the wrapped cause.Running
pytestover the touched MiOT suites: 97 passed.Fixes #341