fix(teams): do not call App() when the SDK was never bound
find_spec("microsoft_teams") can be true from sibling namespace packages
while App is still None, so a failed lazy-install crashed connect with
'NoneType' object is not callable instead of a missing-SDK error.
Probe microsoft_teams.apps via the parent first — a dotted find_spec
raises ModuleNotFoundError on 3.11 when the namespace is absent.
This commit is contained in:
@@ -58,11 +58,29 @@ except ImportError:
|
||||
import importlib.util
|
||||
import sys as _sys
|
||||
|
||||
try:
|
||||
TEAMS_SDK_AVAILABLE = importlib.util.find_spec("microsoft_teams") is not None
|
||||
except ValueError:
|
||||
# Test stubs may inject a module without ``__spec__``.
|
||||
TEAMS_SDK_AVAILABLE = "microsoft_teams" in _sys.modules
|
||||
|
||||
def _probe_teams_sdk_available() -> bool:
|
||||
"""True when ``microsoft_teams.apps`` is on sys.path, without importing it.
|
||||
|
||||
Sibling packages (microsoft-teams-api / common / cards) also live under
|
||||
the ``microsoft_teams`` namespace, so ``find_spec("microsoft_teams")``
|
||||
alone can be True while ``App`` is still unbound — connect() then
|
||||
called None and logged ``'NoneType' object is not callable``.
|
||||
|
||||
Probe the parent first: ``find_spec("microsoft_teams.apps")`` raises
|
||||
``ModuleNotFoundError`` on 3.11+ when the parent namespace is absent,
|
||||
which crashed plugin import and unregistered the Teams platform.
|
||||
"""
|
||||
try:
|
||||
if importlib.util.find_spec("microsoft_teams") is None:
|
||||
return False
|
||||
return importlib.util.find_spec("microsoft_teams.apps") is not None
|
||||
except (ValueError, ModuleNotFoundError, ImportError):
|
||||
# Test stubs may inject a module without ``__spec__``.
|
||||
return "microsoft_teams.apps" in _sys.modules
|
||||
|
||||
|
||||
TEAMS_SDK_AVAILABLE = _probe_teams_sdk_available()
|
||||
ClientOptions = None # type: ignore[assignment,misc]
|
||||
App = None # type: ignore[assignment,misc]
|
||||
ActivityContext = None # type: ignore[assignment,misc]
|
||||
@@ -779,8 +797,13 @@ class TeamsAdapter(BasePlatformAdapter):
|
||||
# Defensive re-check: create_adapter() already ran the installer
|
||||
# (ensure_deps_fn) if deps were missing, but connect() can also be
|
||||
# reached via reconnect paths — re-run to bind SDK globals.
|
||||
#
|
||||
# Gate on App, not TEAMS_SDK_AVAILABLE. The latter is a find_spec
|
||||
# probe and can be True from the microsoft_teams namespace without
|
||||
# symbols ever being bound (check_teams_requirements returning
|
||||
# False is ignored if we only inspect the flag).
|
||||
check_teams_requirements()
|
||||
if not TEAMS_SDK_AVAILABLE:
|
||||
if App is None or ClientOptions is None:
|
||||
self._set_fatal_error(
|
||||
"MISSING_SDK",
|
||||
"microsoft-teams-apps could not be installed. "
|
||||
@@ -858,7 +881,7 @@ class TeamsAdapter(BasePlatformAdapter):
|
||||
f"Teams connection failed: {e}",
|
||||
retryable=True,
|
||||
)
|
||||
logger.error("[teams] Failed to connect: %s", e)
|
||||
logger.error("[teams] Failed to connect: %s", e, exc_info=True)
|
||||
return False
|
||||
|
||||
async def disconnect(self) -> None:
|
||||
|
||||
@@ -1230,12 +1230,19 @@ def ensure_and_bind(
|
||||
"""
|
||||
try:
|
||||
ensure(feature, prompt=prompt)
|
||||
except (FeatureUnavailable, Exception):
|
||||
except FeatureUnavailable as exc:
|
||||
logger.warning("%s", exc)
|
||||
return False
|
||||
except Exception as exc:
|
||||
logger.warning("Failed to ensure feature %r: %s", feature, exc)
|
||||
return False
|
||||
|
||||
try:
|
||||
bindings = importer()
|
||||
except ImportError:
|
||||
except ImportError as exc:
|
||||
logger.warning(
|
||||
"Failed to import feature %r after install: %s", feature, exc
|
||||
)
|
||||
return False
|
||||
|
||||
target_globals.update(bindings)
|
||||
|
||||
Reference in New Issue
Block a user