diff --git a/src/blaxel/core/common/sentry.py b/src/blaxel/core/common/sentry.py index d253882..ff50f77 100644 --- a/src/blaxel/core/common/sentry.py +++ b/src/blaxel/core/common/sentry.py @@ -41,6 +41,29 @@ # Optional dependencies that may not be installed - import errors for these are expected _OPTIONAL_DEPENDENCIES = ("opentelemetry",) +# Optional blaxel framework integration subpackages. Importing any of these +# requires installing the matching extra (e.g. ``pip install blaxel[openai]``). +# When the extra -- or one of its transitive dependencies -- is missing, or when +# the integration files are absent from a stripped/partial install, importing the +# integration raises an ImportError. That is an expected environment issue, not an +# SDK bug, so it must not be reported to Sentry. +_OPTIONAL_INTEGRATION_PACKAGES = ( + "blaxel.langgraph", + "blaxel.llamaindex", + "blaxel.openai", + "blaxel.crewai", + "blaxel.googleadk", + "blaxel.livekit", + "blaxel.pydantic", + "blaxel.telemetry", +) + +# Filesystem fragments used to detect an import error raised while loading one of +# the optional integration packages above (covers both POSIX and Windows paths). +_OPTIONAL_INTEGRATION_PATHS = tuple( + f"blaxel/{pkg.rsplit('.', 1)[-1]}/" for pkg in _OPTIONAL_INTEGRATION_PACKAGES +) + tuple(f"blaxel\\{pkg.rsplit('.', 1)[-1]}\\" for pkg in _OPTIONAL_INTEGRATION_PACKAGES) + # SDK path patterns to identify errors originating from our SDK _SDK_PATTERNS = [ "blaxel/", @@ -191,10 +214,48 @@ def _get_exception_key(exc_type, exc_value, frame) -> str: def _is_optional_dependency_error(exc_type, exc_value) -> bool: - """Check if the exception is an import error for an optional dependency.""" - if exc_type and issubclass(exc_type, ImportError): - msg = str(exc_value).lower() - return any(dep in msg for dep in _OPTIONAL_DEPENDENCIES) + """Check if the exception is an import error that is expected when an optional + integration extra is not installed. + + These are environment issues (the user imported, e.g., ``blaxel.openai`` + without ``pip install blaxel[openai]``, or runs a stripped/partial install + that is missing the integration's modules) rather than SDK defects, so they + should not be reported to Sentry. + """ + if not (exc_type and issubclass(exc_type, ImportError)): + return False + + # Name of the module that could not be imported, when available + # (e.g. "blaxel.openai.model", "agents", "opentelemetry.exporter.otlp"). + missing = getattr(exc_value, "name", None) or "" + + # 1) The optional integration subpackage itself is unavailable -- e.g. a + # stripped/partial install missing ``blaxel/openai/model.py``, surfacing as + # ModuleNotFoundError("No module named 'blaxel.openai.model'"). + if any( + missing == pkg or missing.startswith(f"{pkg}.") for pkg in _OPTIONAL_INTEGRATION_PACKAGES + ): + return True + + # 2) A known optional third-party dependency could not be imported. + msg = str(exc_value).lower() + if any(dep in missing for dep in _OPTIONAL_DEPENDENCIES) or any( + dep in msg for dep in _OPTIONAL_DEPENDENCIES + ): + return True + + # 3) A non-blaxel (third-party) import failed while loading an optional + # integration package -- i.e. the matching extra is not installed. Only + # treat non-blaxel modules this way so that genuine SDK import bugs (which + # fail on a "blaxel.*" module) are still captured. + if missing and not missing.startswith("blaxel"): + tb = getattr(exc_value, "__traceback__", None) + while tb: + filename = tb.tb_frame.f_code.co_filename + if any(path in filename for path in _OPTIONAL_INTEGRATION_PATHS): + return True + tb = tb.tb_next + return False diff --git a/tests/core/test_sentry.py b/tests/core/test_sentry.py new file mode 100644 index 0000000..29025fe --- /dev/null +++ b/tests/core/test_sentry.py @@ -0,0 +1,92 @@ +"""Tests for the lightweight Sentry error filter. + +The SDK installs a trace function (``sys.settrace``) that forwards exceptions +originating from ``site-packages/blaxel`` to Sentry. Import errors that happen +because an optional integration extra is not installed (or because a +stripped/partial install is missing the integration's modules) are environment +issues, not SDK defects, and must be filtered out before reaching Sentry. +""" + +from blaxel.core.common.sentry import _is_optional_dependency_error + + +def _raise_in_file(filename: str, code: str) -> Exception: + """Execute ``code`` as if it lived in ``filename`` and return the exception. + + Using ``compile`` with an explicit filename makes the resulting traceback + contain a frame whose ``co_filename`` is ``filename``, which lets us simulate + an error raised from inside a given package path. + """ + try: + exec(compile(code, filename, "exec"), {}) + except Exception as e: # noqa: BLE001 - we want the raised exception object + return e + raise AssertionError("code did not raise") + + +class TestIsOptionalDependencyError: + """Cover the import-error classification used to suppress Sentry noise.""" + + def test_missing_integration_submodule_is_optional(self): + """The exact production symptom: a stripped install missing model.py. + + ``from .model import *`` in ``blaxel/openai/__init__.py`` raises + ``ModuleNotFoundError: No module named 'blaxel.openai.model'``. + """ + exc = ModuleNotFoundError( + "No module named 'blaxel.openai.model'", name="blaxel.openai.model" + ) + assert _is_optional_dependency_error(type(exc), exc) is True + + def test_missing_livekit_submodule_is_optional(self): + exc = ModuleNotFoundError( + "No module named 'blaxel.livekit.model'", name="blaxel.livekit.model" + ) + assert _is_optional_dependency_error(type(exc), exc) is True + + def test_each_optional_integration_package_is_covered(self): + for pkg in ( + "blaxel.langgraph", + "blaxel.llamaindex", + "blaxel.openai", + "blaxel.crewai", + "blaxel.googleadk", + "blaxel.livekit", + "blaxel.pydantic", + "blaxel.telemetry", + ): + exc = ModuleNotFoundError(f"No module named '{pkg}.model'", name=f"{pkg}.model") + assert _is_optional_dependency_error(type(exc), exc) is True, pkg + + def test_opentelemetry_dependency_is_optional(self): + """Existing behavior: opentelemetry import errors are still suppressed.""" + exc = ModuleNotFoundError("No module named 'opentelemetry'", name="opentelemetry") + assert _is_optional_dependency_error(type(exc), exc) is True + + def test_missing_third_party_dep_while_loading_integration_is_optional(self): + """A missing extra dep (e.g. ``agents`` for blaxel[openai]) is expected.""" + exc = _raise_in_file( + "/usr/lib/python3.12/site-packages/blaxel/openai/model.py", + "raise ModuleNotFoundError(\"No module named 'agents'\", name='agents')", + ) + assert _is_optional_dependency_error(type(exc), exc) is True + + def test_missing_third_party_dep_outside_integration_is_not_optional(self): + """A third-party import failure outside any optional integration (e.g. a + genuine missing core dependency) must still be reported.""" + exc = _raise_in_file( + "/usr/lib/python3.12/site-packages/blaxel/core/common/settings.py", + "raise ModuleNotFoundError(\"No module named 'httpx'\", name='httpx')", + ) + assert _is_optional_dependency_error(type(exc), exc) is False + + def test_core_module_import_error_is_not_optional(self): + """A genuine SDK bug failing on a ``blaxel.*`` core module is captured.""" + exc = ModuleNotFoundError( + "No module named 'blaxel.core.missing'", name="blaxel.core.missing" + ) + assert _is_optional_dependency_error(type(exc), exc) is False + + def test_non_import_error_is_not_optional(self): + exc = ValueError("not an import error") + assert _is_optional_dependency_error(type(exc), exc) is False