fix(router): stop fallback after primary vendor attempts

Refactor vendor routing logic to improve clarity and handling of primary and fallback vendors. Enhanced debug logging for better traceability.
This commit is contained in:
Kaushik Yadav 2025-11-25 23:44:09 +05:30 committed by GitHub
parent 13b826a31d
commit aedef928ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 49 additions and 71 deletions

View File

@ -138,107 +138,85 @@ def get_vendor(category: str, method: str = None) -> str:
# Fall back to category-level configuration # Fall back to category-level configuration
return config.get("data_vendors", {}).get(category, "default") return config.get("data_vendors", {}).get(category, "default")
# Route method calls to vendors
def route_to_vendor(method: str, *args, **kwargs): def route_to_vendor(method: str, *args, **kwargs):
"""Route method calls to appropriate vendor implementation with fallback support.""" """Route method calls to appropriate vendor implementation with proper primary/fallback behavior."""
category = get_category_for_method(method) category = get_category_for_method(method)
vendor_config = get_vendor(category, method) vendor_config = get_vendor(category, method)
# Handle comma-separated vendors
primary_vendors = [v.strip() for v in vendor_config.split(',')]
if method not in VENDOR_METHODS: if method not in VENDOR_METHODS:
raise ValueError(f"Method '{method}' not supported") raise ValueError(f"Method '{method}' not supported")
# Get all available vendors for this method for fallback # Parse configured vendors
primary_vendors = [v.strip() for v in vendor_config.split(",")]
all_available_vendors = list(VENDOR_METHODS[method].keys()) all_available_vendors = list(VENDOR_METHODS[method].keys())
# Create fallback vendor list: primary vendors first, then remaining vendors as fallbacks # Compute fallback vendors (ones NOT listed as primary)
fallback_vendors = primary_vendors.copy() fallback_vendors = [v for v in all_available_vendors if v not in primary_vendors]
for vendor in all_available_vendors:
if vendor not in fallback_vendors:
fallback_vendors.append(vendor)
# Debug: Print fallback ordering print(f"DEBUG: {method} - Primary vendors: {primary_vendors} | Fallback vendors: {fallback_vendors}")
primary_str = "".join(primary_vendors)
fallback_str = "".join(fallback_vendors)
print(f"DEBUG: {method} - Primary: [{primary_str}] | Full fallback order: [{fallback_str}]")
# Track results and execution state
results = [] results = []
vendor_attempt_count = 0
any_primary_vendor_attempted = False
successful_vendor = None successful_vendor = None
for vendor in fallback_vendors: # Attempt all primary vendors
for vendor in primary_vendors:
if vendor not in VENDOR_METHODS[method]: if vendor not in VENDOR_METHODS[method]:
if vendor in primary_vendors: print(f"INFO: Primary vendor '{vendor}' not supported for method '{method}'")
print(f"INFO: Vendor '{vendor}' not supported for method '{method}', falling back to next vendor")
continue continue
vendor_impl = VENDOR_METHODS[method][vendor] vendor_impl = VENDOR_METHODS[method][vendor]
is_primary_vendor = vendor in primary_vendors vendor_methods = vendor_impl if isinstance(vendor_impl, list) else [vendor_impl]
vendor_attempt_count += 1
# Track if we attempted any primary vendor print(f"DEBUG: Trying PRIMARY vendor '{vendor}' ({len(vendor_methods)} implementations)")
if is_primary_vendor:
any_primary_vendor_attempted = True
# Debug: Print current attempt
vendor_type = "PRIMARY" if is_primary_vendor else "FALLBACK"
print(f"DEBUG: Attempting {vendor_type} vendor '{vendor}' for {method} (attempt #{vendor_attempt_count})")
# Handle list of methods for a vendor
if isinstance(vendor_impl, list):
vendor_methods = [(impl, vendor) for impl in vendor_impl]
print(f"DEBUG: Vendor '{vendor}' has multiple implementations: {len(vendor_methods)} functions")
else:
vendor_methods = [(vendor_impl, vendor)]
# Run methods for this vendor
vendor_results = [] vendor_results = []
for impl_func, vendor_name in vendor_methods: for impl_func in vendor_methods:
try: try:
print(f"DEBUG: Calling {impl_func.__name__} from vendor '{vendor_name}'...") print(f"DEBUG: Calling {impl_func.__name__} from vendor '{vendor}'...")
result = impl_func(*args, **kwargs) result = impl_func(*args, **kwargs)
vendor_results.append(result) vendor_results.append(result)
print(f"SUCCESS: {impl_func.__name__} from vendor '{vendor_name}' completed successfully") print(f"SUCCESS: {impl_func.__name__} succeeded for vendor '{vendor}'")
except AlphaVantageRateLimitError as e: except AlphaVantageRateLimitError as e:
if vendor == "alpha_vantage": print(f"RATE_LIMIT: Vendor '{vendor}' exceeded rate limit, trying next primary vendor")
print(f"RATE_LIMIT: Alpha Vantage rate limit exceeded, falling back to next available vendor") print(f"DEBUG: {e}")
print(f"DEBUG: Rate limit details: {e}") break
# Continue to next vendor for fallback
continue
except Exception as e: except Exception as e:
# Log error but continue with other implementations print(f"FAILED: {impl_func.__name__} for vendor '{vendor}' - {e}")
print(f"FAILED: {impl_func.__name__} from vendor '{vendor_name}' failed: {e}")
continue continue
# Add this vendor's results
if vendor_results: if vendor_results:
results.extend(vendor_results) results.extend(vendor_results)
successful_vendor = vendor successful_vendor = vendor
result_summary = f"Got {len(vendor_results)} result(s)"
print(f"SUCCESS: Vendor '{vendor}' succeeded - {result_summary}")
# Stopping logic: Stop after first successful vendor for single-vendor configs
# Multiple vendor configs (comma-separated) may want to collect from multiple sources
if len(primary_vendors) == 1:
print(f"DEBUG: Stopping after successful vendor '{vendor}' (single-vendor config)")
break
else: else:
print(f"FAILED: Vendor '{vendor}' produced no results") print(f"FAILED: Primary vendor '{vendor}' returned no results")
# Final result summary # If ANY primary vendor succeeded then STOP EARLY
if not results: if successful_vendor:
print(f"FAILURE: All {vendor_attempt_count} vendor attempts failed for method '{method}'") print(f"FINAL: Completed with primary vendor(s). Total results: {len(results)}")
raise RuntimeError(f"All vendor implementations failed for method '{method}'") return results[0] if len(results) == 1 else '\n'.join(str(r) for r in results)
else:
print(f"FINAL: Method '{method}' completed with {len(results)} result(s) from {vendor_attempt_count} vendor attempt(s)")
# Return single result if only one, otherwise concatenate as string print("WARNING: All primary vendors failed. Attempting fallback vendors...")
if len(results) == 1:
return results[0] # Fallback logic
else: for vendor in fallback_vendors:
# Convert all results to strings and concatenate vendor_impl = VENDOR_METHODS[method][vendor]
return '\n'.join(str(result) for result in results) vendor_methods = vendor_impl if isinstance(vendor_impl, list) else [vendor_impl]
print(f"DEBUG: Trying FALLBACK vendor '{vendor}'")
for impl_func in vendor_methods:
try:
result = impl_func(*args, **kwargs)
print(f"SUCCESS: Fallback vendor '{vendor}' succeeded via {impl_func.__name__}")
return result
except Exception as e:
print(f"FAILED: Fallback vendor '{vendor}' - {e}")
continue
# If all vendors fail
raise RuntimeError(f"All vendors failed for method '{method}'")