Просмотр исходного кода

isolate per-service failures in remote-sync dispatch

ServiceAPI.diff_remote_sync and remote_sync looped over clients directly,
bypassing _gather's per-service exception isolation. A single stale service
(e.g. expired Booking.com session) raised through the diff action and 500'd
the whole /action/diff response with a full traceback.

Route both methods through _gather so a failing service is logged one-line
and skipped while the rest of the diff completes. As a backstop, the actions
route now maps ServiceAuthError to a 503 with the message, matching the chat
and credentials routes.
ZiB 3 месяцев назад
Родитель
Сommit
ad2c43def4
2 измененных файлов с 16 добавлено и 15 удалено
  1. 6 1
      room_cal/api/_routes/actions.py
  2. 10 14
      room_cal/service_api/client.py

+ 6 - 1
room_cal/api/_routes/actions.py

@@ -4,6 +4,7 @@ import pkgutil
 from datetime import date
 from datetime import date
 from fastapi import APIRouter, HTTPException, Body
 from fastapi import APIRouter, HTTPException, Body
 from room_cal.api._routes import _serialize
 from room_cal.api._routes import _serialize
+from room_cal.service_api.types import ServiceAuthError
 import room_cal.actions as _actions_pkg
 import room_cal.actions as _actions_pkg
 
 
 _log = logging.getLogger(__name__)
 _log = logging.getLogger(__name__)
@@ -29,7 +30,11 @@ async def _run(action: str, start_date: date, end_date: date, body: dict) -> obj
   fn = getattr(mod, fn_name)
   fn = getattr(mod, fn_name)
   kwargs = {k: v for k, v in body.items() if k in allowed_keys}
   kwargs = {k: v for k, v in body.items() if k in allowed_keys}
   _log.info('action %s %s..%s', action, start_date, end_date)
   _log.info('action %s %s..%s', action, start_date, end_date)
-  return _serialize(await fn(start_date, end_date, **kwargs))
+  try:
+    return _serialize(await fn(start_date, end_date, **kwargs))
+  except ServiceAuthError as e:
+    _log.warning('action %s: %s', action, e)
+    raise HTTPException(503, str(e))
 
 
 
 
 @router.post('/action/{action}/{start_date}')
 @router.post('/action/{action}/{start_date}')

+ 10 - 14
room_cal/service_api/client.py

@@ -287,21 +287,17 @@ class ServiceAPI:
   async def diff_remote_sync(self, start_date: date, end_date: date) -> dict[str, list[dict]]:
   async def diff_remote_sync(self, start_date: date, end_date: date) -> dict[str, list[dict]]:
     """Return per-service integrity issues without making any changes."""
     """Return per-service integrity issues without making any changes."""
     ds_from, ds_to = start_date.isoformat(), end_date.isoformat()
     ds_from, ds_to = start_date.isoformat(), end_date.isoformat()
-    out: dict[str, list[dict]] = {}
-    for key, client in self._clients.items():
-      if hasattr(client, 'diff_remote_sync'):
-        result = await client.diff_remote_sync(ds_from, ds_to)
-        if result:
-          out[key] = result
-    return out
+    results = await self._gather({
+      key: client.diff_remote_sync(ds_from, ds_to)
+      for key, client in self._clients.items() if hasattr(client, 'diff_remote_sync')
+    })
+    return {key: result for key, result in results.items() if result}
 
 
   async def remote_sync(self, start_date: date, end_date: date) -> dict[str, list[dict]]:
   async def remote_sync(self, start_date: date, end_date: date) -> dict[str, list[dict]]:
     """Fix per-service integrity issues. Returns per-service list of corrections made."""
     """Fix per-service integrity issues. Returns per-service list of corrections made."""
     ds_from, ds_to = start_date.isoformat(), end_date.isoformat()
     ds_from, ds_to = start_date.isoformat(), end_date.isoformat()
-    out: dict[str, list[dict]] = {}
-    for key, client in self._clients.items():
-      if hasattr(client, 'remote_sync'):
-        result = await client.remote_sync(ds_from, ds_to)
-        if result:
-          out[key] = result
-    return out
+    results = await self._gather({
+      key: client.remote_sync(ds_from, ds_to)
+      for key, client in self._clients.items() if hasattr(client, 'remote_sync')
+    })
+    return {key: result for key, result in results.items() if result}