Skip to content

fix(watsonx): Package Import Error Fix#3737

Closed
elinacse wants to merge 1 commit intotraceloop:mainfrom
elinacse:fix-watsonx-import-warning-clean
Closed

fix(watsonx): Package Import Error Fix#3737
elinacse wants to merge 1 commit intotraceloop:mainfrom
elinacse:fix-watsonx-import-warning-clean

Conversation

@elinacse
Copy link
Contributor

@elinacse elinacse commented Feb 27, 2026

  • I have added tests that cover my changes.
  • If adding a new instrumentation or changing an existing one, I've added screenshots from some observability platform showing the change.
  • PR name follows conventional commits format: feat(instrumentation): ... or fix(instrumentation): ....
  • (If applicable) I have updated the documentation accordingly.

Summary by CodeRabbit

  • Bug Fixes
    • Watsonx instrumentation now automatically checks for module availability before attempting instrumentation. If a module is unavailable, the process gracefully skips it and continues with available modules instead of failing.

@elinacse
Copy link
Contributor Author

Fix for Issue #3735

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

📝 Walkthrough

Walkthrough

The instrumentation now checks module availability before wrapping functions, attempting explicit imports and gracefully skipping instrumentation if modules are unavailable. ImportError exceptions trigger debug logging instead of failures, preserving functionality for present modules while avoiding crashes for missing ones.

Changes

Cohort / File(s) Summary
Module Availability Guard
packages/opentelemetry-instrumentation-watsonx/opentelemetry/instrumentation/watsonx/__init__.py
Added conditional checks and explicit module imports with error handling. Replaced unconditional function wrapping with guarded logic that logs debug messages and skips instrumentation when modules are unavailable.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A module may be missing, it's true,
No need to crash or fail through and through,
We check with grace, import with care,
And skip with logs floating in the air,
Graceful degradation—hooray! Hooray!
The code moves forward, come what may. 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(watsonx): Package Import Error Fix' is directly related to the main change—it addresses import handling in the watsonx instrumentation to prevent package import failures.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/opentelemetry-instrumentation-watsonx/opentelemetry/instrumentation/watsonx/__init__.py`:
- Around line 714-735: Separate the module-probing from the wrapping call: first
attempt to import the module and only catch ModuleNotFoundError (not
ImportError) to decide to skip instrumentation (use __import__(wrap_module) or
importlib.util.find_spec and on ModuleNotFoundError call logger.debug(f"Module
{wrap_module} not found, skipping instrumentation") and continue); then, outside
that import check, call wrap_function_wrapper(wrap_module,
f"{wrap_object}.{wrap_method}", _wrap(...)) so that any other import-related
exceptions or errors raised during wrapping aren’t masked—optionally add a
distinct try/except around wrap_function_wrapper to log and re-raise unexpected
errors rather than treating them as "module not found."
- Around line 711-735: _instrument currently skips modules that fail to import
but _uninstrument blindly unwraps every entry in WATSON_MODULES causing
ImportError on missing modules; fix by tracking successful wraps (e.g., add a
set or list like self._wrapped_pairs or self._wrapped_modules and append the
tuple (wrap_module, f"{wrap_object}.{wrap_method}") when wrap_function_wrapper
succeeds) and change _uninstrument to iterate only over that tracked collection
and call unwrap on those entries (or guard import_module calls with try/except).
Update references to wrap_function_wrapper and unwrap to use the tracked names
so only actually-wrapped modules are unwrapped.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a78de64 and 2158f8f.

⛔ Files ignored due to path filters (1)
  • packages/opentelemetry-instrumentation-watsonx/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • packages/opentelemetry-instrumentation-watsonx/opentelemetry/instrumentation/watsonx/__init__.py

Comment on lines +711 to +735
if not wrap_module:
continue

try:
# Try to import the module first to check if it exists
__import__(wrap_module)
wrap_function_wrapper(
wrap_module,
f"{wrap_object}.{wrap_method}",
_wrap(
tracer,
wrapped_method,
token_histogram,
response_counter,
duration_histogram,
exception_counter,
event_logger,
),
)
except ImportError:
# Module not installed, skip instrumentation for this module
logger.debug(
f"Module {wrap_module} not found, skipping instrumentation"
)
continue
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd packages/opentelemetry-instrumentation-watsonx && wc -l opentelemetry/instrumentation/watsonx/__init__.py

Repository: traceloop/openllmetry

Length of output: 118


🏁 Script executed:

cd packages/opentelemetry-instrumentation-watsonx && sed -n '1,50p' opentelemetry/instrumentation/watsonx/__init__.py

Repository: traceloop/openllmetry

Length of output: 1714


🏁 Script executed:

cd packages/opentelemetry-instrumentation-watsonx && sed -n '700,750p' opentelemetry/instrumentation/watsonx/__init__.py

Repository: traceloop/openllmetry

Length of output: 1882


🏁 Script executed:

cd packages/opentelemetry-instrumentation-watsonx && grep -n "_uninstrument" opentelemetry/instrumentation/watsonx/__init__.py | head -20

Repository: traceloop/openllmetry

Length of output: 107


🏁 Script executed:

cd packages/opentelemetry-instrumentation-watsonx && grep -r "def unwrap" .

Repository: traceloop/openllmetry

Length of output: 47


🏁 Script executed:

# Search for unwrap in OpenTelemetry utils to understand its behavior
fd -t f "utils.py" | xargs grep -l "def unwrap" 2>/dev/null | head -5

Repository: traceloop/openllmetry

Length of output: 47


🏁 Script executed:

python3 << 'EOF'
# Let's try to understand the unwrap function from opentelemetry
# by checking if it exists and examining its signature
try:
    from opentelemetry.instrumentation.utils import unwrap
    import inspect
    print("unwrap function signature:")
    print(inspect.signature(unwrap))
    print("\nDocstring:")
    print(inspect.getdoc(unwrap))
except Exception as e:
    print(f"Error: {e}")
EOF

Repository: traceloop/openllmetry

Length of output: 445


🌐 Web query:

OpenTelemetry unwrap function behavior when module not installed site:github.com/open-telemetry

💡 Result:

In OpenTelemetry JS, the unwrap you’re seeing is the “shimmer” helper used to remove monkey-patches from an object’s function property.

What happens if the target module/function isn’t present?

  • unwrap(nodule, name) does not throw if nodule is falsy or nodule[name] doesn’t exist; it just logs "no function to unwrap." (and a stack trace via the configured shimmer logger) and returns. [1]
  • If nodule[name] exists but isn’t a shimmer-wrapped function (i.e., it has no __unwrap), unwrap again does not throw; it logs "no original to unwrap to -- has <name> already been unwrapped?" and returns. [1]

What happens if the module package isn’t installed?

In the Node InstrumentationBase, the code that checks whether a module was preloaded uses require.resolve(name) inside a try/catch. If require.resolve fails (e.g., module not installed / not resolvable), it silently skips (no warning, no crash). [2]

Sources:
[1] experimental/packages/opentelemetry-instrumentation/src/shimmer.ts (shimmer unwrap implementation) (raw.githubusercontent.com)
[2] experimental/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts (_warnOnPreloadedModules handling of missing modules) (raw.githubusercontent.com)

Citations:


🏁 Script executed:

python3 << 'EOF'
# Let's inspect the actual unwrap implementation
from opentelemetry.instrumentation.utils import unwrap
import inspect

# Get the source code
try:
    source = inspect.getsource(unwrap)
    print(source)
except Exception as e:
    print(f"Could not get source: {e}")
    # Try to get the file location
    print(f"File location: {inspect.getfile(unwrap)}")
EOF

Repository: traceloop/openllmetry

Length of output: 1115


🏁 Script executed:

# Let's find the instrumentation utils file and examine unwrap
find /usr -name "utils.py" -path "*/instrumentation/*" 2>/dev/null | head -3

Repository: traceloop/openllmetry

Length of output: 143


🏁 Script executed:

# Try to examine the installed opentelemetry-instrumentation package
python3 -c "import opentelemetry.instrumentation.utils as u; import os; print(os.path.dirname(u.__file__))"

Repository: traceloop/openllmetry

Length of output: 134


🏁 Script executed:

# List files in the instrumentation utils directory
python3 << 'EOF'
import opentelemetry.instrumentation.utils as u
import os
utils_dir = os.path.dirname(u.__file__)
print(f"Utils directory: {utils_dir}")
if os.path.isdir(utils_dir):
    for f in os.listdir(utils_dir):
        print(f)
EOF

Repository: traceloop/openllmetry

Length of output: 378


🏁 Script executed:

cd packages/opentelemetry-instrumentation-watsonx && sed -n '737,742p' opentelemetry/instrumentation/watsonx/__init__.py

Repository: traceloop/openllmetry

Length of output: 404


🏁 Script executed:

# Check if there's any try/catch around the _uninstrument unwrap calls
cd packages/opentelemetry-instrumentation-watsonx && sed -n '730,742p' opentelemetry/instrumentation/watsonx/__init__.py

Repository: traceloop/openllmetry

Length of output: 691


🏁 Script executed:

# Check the import_module function to confirm it raises on missing modules
python3 << 'EOF'
from importlib import import_module

# Try importing a non-existent module to see what happens
try:
    import_module("nonexistent_module_xyz")
except Exception as e:
    print(f"Exception type: {type(e).__name__}")
    print(f"Exception message: {e}")
EOF

Repository: traceloop/openllmetry

Length of output: 160


Track successful wraps so uninstrumenting stays safe.

During _instrument(), modules that fail to import are gracefully skipped with a debug log (lines 723–729). However, _uninstrument() unconditionally unwraps all items from the static WATSON_MODULES list. The unwrap() function will raise an ImportError when attempting to unwrap a module that doesn't exist, as it internally calls import_module() which raises ModuleNotFoundError. In partial-install environments where some Watson ML submodules are unavailable, this causes _uninstrument() to fail.

Proposed fix
 class WatsonxInstrumentor(BaseInstrumentor):
@@
     def __init__(self, exception_logger=None, use_legacy_attributes: bool = True):
         super().__init__()
         Config.exception_logger = exception_logger
         Config.use_legacy_attributes = use_legacy_attributes
+        self._wrapped_targets = []
@@
                 wrap_function_wrapper(
@@
                 )
+                self._wrapped_targets.append(
+                    (wrap_module, wrap_object, wrap_method)
+                )
@@
     def _uninstrument(self, **kwargs):
-        for wrapped_methods in WATSON_MODULES:
-            for wrapped_method in wrapped_methods:
-                wrap_module = wrapped_method.get("module")
-                wrap_object = wrapped_method.get("object")
-                unwrap(f"{wrap_module}.{wrap_object}", wrapped_method.get("method"))
+        for wrap_module, wrap_object, wrap_method in self._wrapped_targets:
+            unwrap(f"{wrap_module}.{wrap_object}", wrap_method)
+        self._wrapped_targets.clear()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/opentelemetry-instrumentation-watsonx/opentelemetry/instrumentation/watsonx/__init__.py`
around lines 711 - 735, _instrument currently skips modules that fail to import
but _uninstrument blindly unwraps every entry in WATSON_MODULES causing
ImportError on missing modules; fix by tracking successful wraps (e.g., add a
set or list like self._wrapped_pairs or self._wrapped_modules and append the
tuple (wrap_module, f"{wrap_object}.{wrap_method}") when wrap_function_wrapper
succeeds) and change _uninstrument to iterate only over that tracked collection
and call unwrap on those entries (or guard import_module calls with try/except).
Update references to wrap_function_wrapper and unwrap to use the tracked names
so only actually-wrapped modules are unwrapped.

Comment on lines +714 to +735
try:
# Try to import the module first to check if it exists
__import__(wrap_module)
wrap_function_wrapper(
wrap_module,
f"{wrap_object}.{wrap_method}",
_wrap(
tracer,
wrapped_method,
token_histogram,
response_counter,
duration_histogram,
exception_counter,
event_logger,
),
)
except ImportError:
# Module not installed, skip instrumentation for this module
logger.debug(
f"Module {wrap_module} not found, skipping instrumentation"
)
continue
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

head -n 50 packages/opentelemetry-instrumentation-watsonx/opentelemetry/instrumentation/watsonx/__init__.py

Repository: traceloop/openllmetry

Length of output: 1714


🏁 Script executed:

wc -l packages/opentelemetry-instrumentation-watsonx/opentelemetry/instrumentation/watsonx/__init__.py

Repository: traceloop/openllmetry

Length of output: 165


🏁 Script executed:

sed -n '700,750p' packages/opentelemetry-instrumentation-watsonx/opentelemetry/instrumentation/watsonx/__init__.py

Repository: traceloop/openllmetry

Length of output: 1882


🏁 Script executed:

# Find the _instrument method to see full context
ast-grep --pattern 'def _instrument($_) { $$$ }'

Repository: traceloop/openllmetry

Length of output: 47


🏁 Script executed:

# Search for _uninstrument method
rg -A 20 "def _uninstrument" packages/opentelemetry-instrumentation-watsonx/

Repository: traceloop/openllmetry

Length of output: 986


🏁 Script executed:

# Check what wrap_function_wrapper is imported from
rg "from.*wrap_function_wrapper|import.*wrap_function_wrapper" packages/opentelemetry-instrumentation-watsonx/

Repository: traceloop/openllmetry

Length of output: 201


🏁 Script executed:

# Check what exceptions wrap_function_wrapper raises
rg -i "wrap_function_wrapper" --type py | head -20

Repository: traceloop/openllmetry

Length of output: 2805


🏁 Script executed:

# Look at opentelemetry.instrumentation.utils to understand unwrap behavior
python3 << 'EOF'
import sys
try:
    from opentelemetry.instrumentation.utils import unwrap
    import inspect
    print("unwrap signature:", inspect.signature(unwrap))
    print("\nunwrap source:")
    print(inspect.getsource(unwrap))
except Exception as e:
    print(f"Error: {e}")
    print("unwrap may not be available in this environment")
EOF

Repository: traceloop/openllmetry

Length of output: 1178


🏁 Script executed:

# Check if wrap_function_wrapper can raise ImportError by examining its behavior
python3 << 'EOF'
try:
    from wrapt import wrap_function_wrapper
    import inspect
    print("wrap_function_wrapper signature:")
    print(inspect.signature(wrap_function_wrapper))
except Exception as e:
    print(f"Error: {e}")
EOF

Repository: traceloop/openllmetry

Length of output: 121


🏁 Script executed:

# Look for any other instrumentation patterns in opentelemetry to understand best practices
rg "wrap_function_wrapper" packages/ -A 5 -B 2 | head -60

Repository: traceloop/openllmetry

Length of output: 7395


🏁 Script executed:

# Check if there are tests that show what happens when wrap_function_wrapper fails
find packages/opentelemetry-instrumentation-watsonx -name "*test*" -type f

Repository: traceloop/openllmetry

Length of output: 899


Separate module availability check from wrapping to avoid masking unrelated import failures.

At lines 714–735, the single try/except ImportError block combines module probing with wrap_function_wrapper(). This masks import failures unrelated to a missing module (e.g., errors in module dependencies or incorrect target attributes) and logs them misleadingly as "Module not found."

The proposed fix correctly separates module existence verification (which catches only ModuleNotFoundError) from the wrapping call, ensuring unrelated import errors are not silently skipped.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/opentelemetry-instrumentation-watsonx/opentelemetry/instrumentation/watsonx/__init__.py`
around lines 714 - 735, Separate the module-probing from the wrapping call:
first attempt to import the module and only catch ModuleNotFoundError (not
ImportError) to decide to skip instrumentation (use __import__(wrap_module) or
importlib.util.find_spec and on ModuleNotFoundError call logger.debug(f"Module
{wrap_module} not found, skipping instrumentation") and continue); then, outside
that import check, call wrap_function_wrapper(wrap_module,
f"{wrap_object}.{wrap_method}", _wrap(...)) so that any other import-related
exceptions or errors raised during wrapping aren’t masked—optionally add a
distinct try/except around wrap_function_wrapper to log and re-raise unexpected
errors rather than treating them as "module not found."

@galkleinman galkleinman changed the title Fix(Watsonx) PackageImport Error Fix fix(watsonx): Package Import Error Fix Mar 1, 2026
Comment on lines +716 to +731
__import__(wrap_module)
wrap_function_wrapper(
wrap_module,
f"{wrap_object}.{wrap_method}",
_wrap(
tracer,
wrapped_method,
token_histogram,
response_counter,
duration_histogram,
exception_counter,
event_logger,
),
)
except ImportError:
# Module not installed, skip instrumentation for this module
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't import here. There's code in traceloop-sdk which is checking which packages are available and turns on their instrumentations accordingly/

@elinacse elinacse closed this Mar 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants