test3 / tests /code_coverage_tests /check_spanattributes_value_usage.py
DesertWolf's picture
Upload folder using huggingface_hub
447ebeb verified
"""
SpanAttributes Value Usage Checker
This script ensures that all SpanAttributes enum references in the OpenTelemetry integration
are properly accessed with the .value property. This is important because:
1. Without .value, the enum object itself is used instead of its string value
2. This can cause type errors or unexpected behavior in OpenTelemetry exporters
3. It's a consistent pattern that should be followed for all enum usage
Example of correct usage:
span.set_attribute(key=SpanAttributes.LLM_USER.value, value="user123")
Example of incorrect usage:
span.set_attribute(key=SpanAttributes.LLM_USER, value="user123")
The script checks both through AST parsing (for accurate code analysis) and regex
(for backup coverage) to find any violations.
Usage:
python tests/code_coverage_tests/check_spanattributes_value_usage.py
python tests/code_coverage_tests/check_spanattributes_value_usage.py --debug
"""
import argparse
import ast
import os
import re
from typing import List, Tuple
import sys
# Add parent directory to path so we can import litellm
sys.path.insert(0, os.path.abspath("../.."))
import litellm
class SpanAttributesUsageChecker(ast.NodeVisitor):
"""
Checks if SpanAttributes is used without .value when setting attributes in safe_set_attribute calls
and other attribute setting methods in opentelemetry.py.
This is important to ensure consistent enum value access and prevent type errors
when sending data to OpenTelemetry exporters.
"""
def __init__(self, debug=False):
self.violations = []
self.debug = debug
def visit_Call(self, node):
# Check if this is a call to safe_set_attribute or set_attribute
if isinstance(node.func, ast.Attribute) and node.func.attr in ['safe_set_attribute', 'set_attribute']:
# Look for the 'key' parameter
for keyword in node.keywords:
if keyword.arg == 'key':
# Check if the value is a SpanAttributes member without .value
if isinstance(keyword.value, ast.Attribute) and \
isinstance(keyword.value.value, ast.Name) and \
keyword.value.value.id == 'SpanAttributes':
# Get the source code for this attribute
try:
attr_source = ast.unparse(keyword.value)
if not attr_source.endswith('.value'):
if self.debug:
print(f"AST found violation: {node.lineno}: {attr_source}")
self.violations.append((node.lineno, f"{attr_source} used without .value"))
except AttributeError:
# For Python < 3.9, ast.unparse doesn't exist
# Fallback to our best guess
if keyword.value.attr != 'value' and not hasattr(keyword.value, 'value'):
violation_msg = f"SpanAttributes.{keyword.value.attr} used without .value"
if self.debug:
print(f"AST found violation: {node.lineno}: {violation_msg}")
self.violations.append((node.lineno, violation_msg))
# Continue the visit
self.generic_visit(node)
def check_file(file_path: str, debug: bool = False) -> List[Tuple[int, str]]:
"""
Analyze a Python file to check for SpanAttributes usage without .value
Args:
file_path: Path to the Python file to check
debug: Whether to print debug information
Returns:
List of (line_number, message) tuples identifying violations
"""
with open(file_path, 'r') as file:
content = file.read()
# First try AST parsing for accurate code structure analysis
try:
tree = ast.parse(content)
checker = SpanAttributesUsageChecker(debug=debug)
checker.visit(tree)
violations = checker.violations
# Also do a regex check for backup/extra coverage
# This catches cases that might be missed by AST parsing
# Split content into lines for more precise analysis
lines = content.splitlines()
for i, line in enumerate(lines, 1):
# Skip lines that contain ".value" after "SpanAttributes."
# This prevents false positives for correct usage
if re.search(r"SpanAttributes\.[A-Z_][A-Z0-9_]*\.value", line):
if debug:
print(f"Line {i} skipped - contains .value: {line.strip()}")
continue
# Pattern: Looking for "key=SpanAttributes.ENUM_NAME" without .value at the end
pattern = r"key\s*=\s*SpanAttributes\.[A-Z_][A-Z0-9_]*(?!\.value)"
match = re.search(pattern, line)
if match:
# Check if this violation was already found by AST
if not any(i == line_num for line_num, _ in violations):
if debug:
print(f"Regex found violation: {i}: {match.group(0)}")
violations.append((i, f"SpanAttributes used without .value: {match.group(0)}"))
return violations
except SyntaxError:
print(f"Syntax error in {file_path}")
return []
def main():
"""
Main function to run the SpanAttributes usage check on the OpenTelemetry integration file.
Exits with code 1 if violations are found, 0 otherwise.
"""
parser = argparse.ArgumentParser(description='Check for SpanAttributes used without .value')
parser.add_argument('--debug', action='store_true', help='Enable debug output')
args = parser.parse_args()
# Path to the OpenTelemetry integration file
target_file = os.path.join("litellm", "integrations", "opentelemetry.py")
if not os.path.exists(target_file):
# Try alternate path for local development
target_file = os.path.join("..", "..", "litellm", "integrations", "opentelemetry.py")
if not os.path.exists(target_file):
print(f"Error: Could not find file at {target_file}")
exit(1)
violations = check_file(target_file, debug=args.debug)
if violations:
print(f"Found {len(violations)} SpanAttributes without .value in {target_file}:")
# Sort violations by line number for better readability
violations.sort(key=lambda x: x[0])
for line, message in violations:
print(f" Line {line}: {message}")
print("\nDirect enum reference can cause errors. Always use .value with SpanAttributes enums.")
exit(1)
else:
print(f"All SpanAttributes are used correctly with .value in {target_file}")
exit(0)
if __name__ == "__main__":
main()