in src/psearch/ingestion/services/spanner_service.py [0:0]
def _prepare_product_for_spanner(self, product_data: Dict[str, Any]) -> str:
"""
Prepares product data for storage in Spanner by converting to JSON
and handling non-serializable values
Args:
product_data: Raw product data
Returns:
str: JSON string representation of product data
"""
# Create a copy to avoid modifying the original
product_copy = product_data.copy()
# Recursively sanitize the product data
def sanitize_value(value):
if isinstance(value, dict):
return {k: sanitize_value(v) for k, v in value.items()}
elif isinstance(value, list):
return [sanitize_value(item) for item in value]
elif isinstance(value, (int, bool, str)) or value is None:
return value
elif isinstance(value, float):
# Handle NaN and Infinity which Spanner doesn't accept
if math.isnan(value) or math.isinf(value):
return None
# Handle very large or very small numbers that might cause issues
if abs(value) > 1e15: # Very large numbers
# Round to avoid precision issues
return round(value, 2)
elif 0 < abs(value) < 1e-10: # Very small non-zero numbers
# Round to avoid precision issues with very small numbers
return 0.0
# For price-like values (common in product data), round to 2 decimal places
if 0.01 <= abs(value) <= 10000.0:
return round(value, 2)
# For other normal range numbers, ensure we don't have too many decimal places
return round(value, 6)
else:
# Convert any other type to string
return str(value)
# Sanitize the entire product
sanitized_product = sanitize_value(product_copy)
# Special handling for priceInfo to ensure consistent formatting
if "priceInfo" in sanitized_product:
price_info = sanitized_product["priceInfo"]
if "price" in price_info and isinstance(price_info["price"], float):
price_info["price"] = round(price_info["price"], 2)
if "cost" in price_info and isinstance(price_info["cost"], float):
price_info["cost"] = round(price_info["cost"], 2)
if "originalPrice" in price_info and isinstance(
price_info["originalPrice"], float
):
price_info["originalPrice"] = round(price_info["originalPrice"], 2)
# Convert to JSON string with special handling for Spanner
json_str = json.dumps(sanitized_product, ensure_ascii=True)
return json_str