fix(feishu): lazy-load lark sdk during gateway startup
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
"""Feishu/Lark channel implementation using lark-oapi SDK with WebSocket long connection."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import importlib.util
|
||||
import json
|
||||
@@ -11,10 +13,8 @@ import uuid
|
||||
from collections import OrderedDict
|
||||
from contextlib import suppress
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Literal
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
from lark_oapi.api.im.v1.model import MentionEvent, P2ImMessageReceiveV1
|
||||
from lark_oapi.core.const import FEISHU_DOMAIN, LARK_DOMAIN
|
||||
from pydantic import Field
|
||||
|
||||
from nanobot.bus.events import OutboundMessage
|
||||
@@ -25,8 +25,42 @@ from nanobot.config.schema import Base
|
||||
from nanobot.utils.helpers import safe_filename
|
||||
from nanobot.utils.logging_bridge import redirect_lib_logging
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from lark_oapi.api.im.v1.model import MentionEvent, P2ImMessageReceiveV1
|
||||
|
||||
FEISHU_AVAILABLE = importlib.util.find_spec("lark_oapi") is not None
|
||||
|
||||
|
||||
def _load_lark_runtime() -> tuple[Any, str, str]:
|
||||
"""Import the heavy Feishu SDK lazily.
|
||||
|
||||
lark_oapi imports a large generated API surface at module import time, so
|
||||
keep it out of channel discovery and constructor paths.
|
||||
"""
|
||||
import sys
|
||||
|
||||
ws_client_already_imported = "lark_oapi.ws.client" in sys.modules
|
||||
import lark_oapi as lark
|
||||
import lark_oapi.ws.client as lark_ws_client
|
||||
from lark_oapi.core.const import FEISHU_DOMAIN, LARK_DOMAIN
|
||||
|
||||
if (
|
||||
not ws_client_already_imported
|
||||
and threading.current_thread() is not threading.main_thread()
|
||||
):
|
||||
import_loop = getattr(lark_ws_client, "loop", None)
|
||||
if (
|
||||
import_loop is not None
|
||||
and not import_loop.is_running()
|
||||
and not import_loop.is_closed()
|
||||
):
|
||||
import_loop.close()
|
||||
lark_ws_client.loop = None
|
||||
with suppress(Exception):
|
||||
asyncio.set_event_loop(None)
|
||||
|
||||
return lark, FEISHU_DOMAIN, LARK_DOMAIN
|
||||
|
||||
# Message type display mapping
|
||||
MSG_TYPE_MAP = {
|
||||
"image": "[image]",
|
||||
@@ -297,13 +331,11 @@ class FeishuChannel(BaseChannel):
|
||||
return FeishuConfig().model_dump(by_alias=True)
|
||||
|
||||
def __init__(self, config: Any, bus: MessageBus):
|
||||
import lark_oapi as lark
|
||||
|
||||
if isinstance(config, dict):
|
||||
config = FeishuConfig.model_validate(config)
|
||||
super().__init__(config, bus)
|
||||
self.config: FeishuConfig = config
|
||||
self._client: lark.Client = None
|
||||
self._client: Any = None
|
||||
self._ws_client: Any = None
|
||||
self._ws_thread: threading.Thread | None = None
|
||||
self._processed_message_ids: OrderedDict[str, None] = OrderedDict() # Ordered dedup cache
|
||||
@@ -329,7 +361,7 @@ class FeishuChannel(BaseChannel):
|
||||
self.logger.error("app_id and app_secret not configured")
|
||||
return
|
||||
|
||||
import lark_oapi as lark
|
||||
lark, feishu_domain, lark_domain = await asyncio.to_thread(_load_lark_runtime)
|
||||
|
||||
redirect_lib_logging("Lark")
|
||||
|
||||
@@ -337,7 +369,7 @@ class FeishuChannel(BaseChannel):
|
||||
self._loop = asyncio.get_running_loop()
|
||||
|
||||
# Create Lark client for sending messages
|
||||
domain = LARK_DOMAIN if self.config.domain == "lark" else FEISHU_DOMAIN
|
||||
domain = lark_domain if self.config.domain == "lark" else feishu_domain
|
||||
self._client = (
|
||||
lark.Client.builder()
|
||||
.app_id(self.config.app_id)
|
||||
@@ -397,6 +429,7 @@ class FeishuChannel(BaseChannel):
|
||||
|
||||
import lark_oapi.ws.client as _lark_ws_client
|
||||
|
||||
previous_loop = getattr(_lark_ws_client, "loop", None)
|
||||
ws_loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(ws_loop)
|
||||
# Patch the module-level loop used by lark's ws Client.start()
|
||||
@@ -410,6 +443,10 @@ class FeishuChannel(BaseChannel):
|
||||
if self._running:
|
||||
time.sleep(5)
|
||||
finally:
|
||||
if getattr(_lark_ws_client, "loop", None) is ws_loop:
|
||||
_lark_ws_client.loop = previous_loop
|
||||
with suppress(Exception):
|
||||
asyncio.set_event_loop(None)
|
||||
ws_loop.close()
|
||||
|
||||
self._ws_thread = threading.Thread(target=run_ws, daemon=True)
|
||||
|
||||
Reference in New Issue
Block a user