mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-02 18:21:56 +00:00
(fix) litellm_logging.py: fix mcp tool call response logging + (fix) responses_bridge: remove unmapped param error mid-stream - allows gpt-5 web search to work via responses api in .completion() (#16946)
* fix: fix getting mcp servers * fix(litellm_logging.py): handle list objects for final response in standard logging payload Fixes issue where mcp tool call response wouldn't show up * fix(litellm_responses_transformation/): remove invalid item error for unmapped objects - breaks stream and there's no real value to this as outside of a few of them, not all can be mapped to chat completions resolves error for web search calls via chat completions to responses api
This commit is contained in:
@@ -32,6 +32,12 @@ from litellm.types.llms.openai import (
|
||||
ResponsesAPIOptionalRequestParams,
|
||||
ResponsesAPIStreamEvents,
|
||||
)
|
||||
from litellm.types.utils import (
|
||||
Delta,
|
||||
GenericStreamingChunk,
|
||||
ModelResponseStream,
|
||||
StreamingChoices,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from openai.types.responses import ResponseInputImageParam
|
||||
@@ -46,7 +52,6 @@ if TYPE_CHECKING:
|
||||
ChatCompletionThinkingBlock,
|
||||
OpenAIMessageContentListBlock,
|
||||
)
|
||||
from litellm.types.utils import GenericStreamingChunk, ModelResponseStream
|
||||
|
||||
|
||||
class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
||||
@@ -606,11 +611,13 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
||||
ResponsesAPIOptionalRequestParams.__annotations__.keys()
|
||||
)
|
||||
# Also include params we handle specially
|
||||
supported_responses_api_params.update({
|
||||
"previous_response_id",
|
||||
"reasoning_effort", # We map this to "reasoning"
|
||||
})
|
||||
|
||||
supported_responses_api_params.update(
|
||||
{
|
||||
"previous_response_id",
|
||||
"reasoning_effort", # We map this to "reasoning"
|
||||
}
|
||||
)
|
||||
|
||||
# Extract supported params from extra_body and merge into optional_params
|
||||
extra_body_copy = extra_body.copy()
|
||||
for key, value in extra_body_copy.items():
|
||||
@@ -620,14 +627,16 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
||||
|
||||
return optional_params
|
||||
|
||||
def _map_reasoning_effort(self, reasoning_effort: Union[str, Dict[str, Any]]) -> Optional[Reasoning]:
|
||||
def _map_reasoning_effort(
|
||||
self, reasoning_effort: Union[str, Dict[str, Any]]
|
||||
) -> Optional[Reasoning]:
|
||||
# If dict is passed, convert it directly to Reasoning object
|
||||
if isinstance(reasoning_effort, dict):
|
||||
return Reasoning(**reasoning_effort) # type: ignore[typeddict-item]
|
||||
|
||||
# If string is passed, map without summary (default)
|
||||
if reasoning_effort == "none":
|
||||
return Reasoning(effort="none") # type: ignore
|
||||
return Reasoning(effort="none") # type: ignore
|
||||
elif reasoning_effort == "high":
|
||||
return Reasoning(effort="high")
|
||||
elif reasoning_effort == "medium":
|
||||
@@ -746,12 +755,6 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator):
|
||||
finish_reason="",
|
||||
usage=None,
|
||||
)
|
||||
elif output_item.get("type") == "message":
|
||||
pass
|
||||
elif output_item.get("type") == "reasoning":
|
||||
pass
|
||||
else:
|
||||
raise ValueError(f"Chat provider: Invalid output_item {output_item}")
|
||||
elif event_type == "response.function_call_arguments.delta":
|
||||
content_part: Optional[str] = parsed_chunk.get("delta", None)
|
||||
if content_part:
|
||||
@@ -813,10 +816,6 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator):
|
||||
return GenericStreamingChunk(
|
||||
finish_reason="stop", is_finished=True, usage=None, text=""
|
||||
)
|
||||
elif output_item.get("type") == "reasoning":
|
||||
pass
|
||||
else:
|
||||
raise ValueError(f"Chat provider: Invalid output_item {output_item}")
|
||||
|
||||
elif event_type == "response.output_text.delta":
|
||||
# Content part added to output
|
||||
|
||||
@@ -4358,12 +4358,12 @@ class StandardLoggingPayloadSetup:
|
||||
"""
|
||||
Get final response object after redacting the message input/output from logging
|
||||
"""
|
||||
if response_obj is not None:
|
||||
if response_obj:
|
||||
final_response_obj: Optional[Union[dict, str, list]] = response_obj
|
||||
elif isinstance(init_response_obj, list) or isinstance(init_response_obj, str):
|
||||
final_response_obj = init_response_obj
|
||||
else:
|
||||
final_response_obj = None
|
||||
final_response_obj = {}
|
||||
|
||||
modified_final_response_obj = redact_message_input_output_from_logging(
|
||||
model_call_details=kwargs,
|
||||
|
||||
@@ -1286,6 +1286,7 @@ if MCP_AVAILABLE:
|
||||
# Allow modifying the MCP tool call response before it is returned to the user
|
||||
#########################################################
|
||||
if litellm_logging_obj:
|
||||
litellm_logging_obj.post_call(original_response=response)
|
||||
end_time = datetime.now()
|
||||
await litellm_logging_obj.async_post_mcp_tool_call_hook(
|
||||
kwargs=litellm_logging_obj.model_call_details,
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -8,9 +8,9 @@ Module responsible for
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
import traceback
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union, cast, overload
|
||||
|
||||
@@ -342,7 +342,7 @@ class DBSpendUpdateWriter:
|
||||
):
|
||||
"""
|
||||
Update spend for all tags in the request.
|
||||
|
||||
|
||||
Args:
|
||||
response_cost: Cost of the request
|
||||
request_tags: JSON string of tags list e.g. '["prod-tag", "test-tag"]'
|
||||
@@ -863,7 +863,7 @@ class DBSpendUpdateWriter:
|
||||
):
|
||||
"""
|
||||
Helper function to update spend for any entity type (team, org, tag, etc).
|
||||
|
||||
|
||||
Args:
|
||||
entity_name: Name of entity for logging (e.g., "Team", "Org", "Tag")
|
||||
transactions: Dictionary of {entity_id: response_cost}
|
||||
@@ -875,9 +875,7 @@ class DBSpendUpdateWriter:
|
||||
"""
|
||||
from litellm.proxy.utils import _raise_failed_update_spend_exception
|
||||
|
||||
verbose_proxy_logger.debug(
|
||||
f"{entity_name} Spend transactions: {transactions}"
|
||||
)
|
||||
verbose_proxy_logger.debug(f"{entity_name} Spend transactions: {transactions}")
|
||||
if transactions is not None and len(transactions.keys()) > 0:
|
||||
for i in range(n_retry_times + 1):
|
||||
start_time = time.time()
|
||||
@@ -1062,17 +1060,19 @@ class DBSpendUpdateWriter:
|
||||
|
||||
# Add cache-related fields if they exist
|
||||
if "cache_read_input_tokens" in transaction:
|
||||
common_data[
|
||||
"cache_read_input_tokens"
|
||||
] = transaction.get("cache_read_input_tokens", 0)
|
||||
common_data["cache_read_input_tokens"] = (
|
||||
transaction.get("cache_read_input_tokens", 0)
|
||||
)
|
||||
if "cache_creation_input_tokens" in transaction:
|
||||
common_data[
|
||||
"cache_creation_input_tokens"
|
||||
] = transaction.get("cache_creation_input_tokens", 0)
|
||||
common_data["cache_creation_input_tokens"] = (
|
||||
transaction.get("cache_creation_input_tokens", 0)
|
||||
)
|
||||
|
||||
if entity_type == "tag" and "request_id" in transaction:
|
||||
common_data["request_id"] = transaction.get("request_id")
|
||||
|
||||
common_data["request_id"] = transaction.get(
|
||||
"request_id"
|
||||
)
|
||||
|
||||
# Create update data structure
|
||||
update_data = {
|
||||
"prompt_tokens": {
|
||||
|
||||
@@ -95,9 +95,9 @@ def _get_spend_logs_metadata(
|
||||
clean_metadata["applied_guardrails"] = applied_guardrails
|
||||
clean_metadata["batch_models"] = batch_models
|
||||
clean_metadata["mcp_tool_call_metadata"] = mcp_tool_call_metadata
|
||||
clean_metadata[
|
||||
"vector_store_request_metadata"
|
||||
] = _get_vector_store_request_for_spend_logs_payload(vector_store_request_metadata)
|
||||
clean_metadata["vector_store_request_metadata"] = (
|
||||
_get_vector_store_request_for_spend_logs_payload(vector_store_request_metadata)
|
||||
)
|
||||
clean_metadata["guardrail_information"] = guardrail_information
|
||||
clean_metadata["usage_object"] = usage_object
|
||||
clean_metadata["model_map_information"] = model_map_information
|
||||
@@ -142,28 +142,26 @@ def get_spend_logs_id(
|
||||
return id
|
||||
|
||||
|
||||
def _extract_usage_for_ocr_call(
|
||||
response_obj: Any, response_obj_dict: dict
|
||||
) -> dict:
|
||||
def _extract_usage_for_ocr_call(response_obj: Any, response_obj_dict: dict) -> dict:
|
||||
"""
|
||||
Extract usage information for OCR/AOCR calls.
|
||||
|
||||
|
||||
OCR responses use usage_info (with pages_processed) instead of token-based usage.
|
||||
|
||||
|
||||
Args:
|
||||
response_obj: The raw response object (can be dict, BaseModel, or other)
|
||||
response_obj_dict: Dictionary representation of the response object
|
||||
|
||||
|
||||
Returns:
|
||||
A dict with prompt_tokens=0, completion_tokens=0, total_tokens=0,
|
||||
and pages_processed from usage_info.
|
||||
"""
|
||||
usage_info = None
|
||||
|
||||
|
||||
# Try to extract usage_info from dict
|
||||
if isinstance(response_obj_dict, dict) and "usage_info" in response_obj_dict:
|
||||
usage_info = response_obj_dict.get("usage_info")
|
||||
|
||||
|
||||
# Try to extract usage_info from object attributes if not found in dict
|
||||
if not usage_info and hasattr(response_obj, "usage_info"):
|
||||
usage_info = response_obj.usage_info
|
||||
@@ -171,7 +169,7 @@ def _extract_usage_for_ocr_call(
|
||||
usage_info = usage_info.model_dump()
|
||||
elif hasattr(usage_info, "__dict__"):
|
||||
usage_info = vars(usage_info)
|
||||
|
||||
|
||||
# For OCR, we track pages instead of tokens
|
||||
if usage_info is not None:
|
||||
# Handle dict or object with attributes
|
||||
@@ -193,7 +191,7 @@ def _extract_usage_for_ocr_call(
|
||||
"prompt_tokens": 0,
|
||||
"completion_tokens": 0,
|
||||
"total_tokens": 0,
|
||||
"pages_processed": 0
|
||||
"pages_processed": 0,
|
||||
}
|
||||
else:
|
||||
return {}
|
||||
@@ -206,17 +204,18 @@ def get_logging_payload( # noqa: PLR0915
|
||||
|
||||
if kwargs is None:
|
||||
kwargs = {}
|
||||
if response_obj is None or (
|
||||
not isinstance(response_obj, BaseModel) and not isinstance(response_obj, dict)
|
||||
):
|
||||
|
||||
if response_obj is None:
|
||||
response_obj = {}
|
||||
elif not isinstance(response_obj, BaseModel) and not isinstance(response_obj, dict):
|
||||
response_obj = {"result": str(response_obj)}
|
||||
# standardize this function to be used across, s3, dynamoDB, langfuse logging
|
||||
litellm_params = kwargs.get("litellm_params", {})
|
||||
metadata = get_litellm_metadata_from_kwargs(kwargs)
|
||||
completion_start_time = kwargs.get("completion_start_time", end_time)
|
||||
call_type = kwargs.get("call_type")
|
||||
cache_hit = kwargs.get("cache_hit", False)
|
||||
|
||||
|
||||
# Convert response_obj to dict first
|
||||
if isinstance(response_obj, dict):
|
||||
response_obj_dict = response_obj
|
||||
@@ -224,7 +223,7 @@ def get_logging_payload( # noqa: PLR0915
|
||||
response_obj_dict = response_obj.model_dump()
|
||||
else:
|
||||
response_obj_dict = {}
|
||||
|
||||
|
||||
# Handle OCR responses which use usage_info instead of usage
|
||||
if call_type in ["ocr", "aocr"]:
|
||||
usage = _extract_usage_for_ocr_call(response_obj, response_obj_dict)
|
||||
@@ -658,6 +657,7 @@ def _get_response_for_spend_logs_payload(
|
||||
sanitized_wrapper = _sanitize_request_body_for_spend_logs_payload(
|
||||
{"response": response_obj}
|
||||
)
|
||||
|
||||
sanitized_response = sanitized_wrapper.get("response", response_obj)
|
||||
|
||||
if sanitized_response is None:
|
||||
|
||||
@@ -348,7 +348,11 @@ def test_openai_image_generation_forwards_organization(mock_get_openai_client):
|
||||
return {
|
||||
"created": 123,
|
||||
"data": [{"url": "http://example.com/image.png"}],
|
||||
"usage": {"input_tokens": 0, "output_tokens": 0, "total_tokens": 0},
|
||||
"usage": {
|
||||
"input_tokens": 0,
|
||||
"output_tokens": 0,
|
||||
"total_tokens": 0,
|
||||
},
|
||||
}
|
||||
|
||||
return _Resp()
|
||||
@@ -797,7 +801,9 @@ async def test_openai_service_tier_parameter():
|
||||
# Verify the request contains the service_tier parameter
|
||||
assert "service_tier" in request_body, "service_tier should be in request body"
|
||||
# Verify service_tier is correctly sent to the API
|
||||
assert request_body["service_tier"] == "priority", "service_tier should be 'priority'"
|
||||
assert (
|
||||
request_body["service_tier"] == "priority"
|
||||
), "service_tier should be 'priority'"
|
||||
|
||||
|
||||
def test_openai_service_tier_parameter_sync():
|
||||
@@ -826,7 +832,9 @@ def test_openai_service_tier_parameter_sync():
|
||||
# Verify the request contains the service_tier parameter
|
||||
assert "service_tier" in request_body, "service_tier should be in request body"
|
||||
# Verify service_tier is correctly sent to the API
|
||||
assert request_body["service_tier"] == "priority", "service_tier should be 'priority'"
|
||||
assert (
|
||||
request_body["service_tier"] == "priority"
|
||||
), "service_tier should be 'priority'"
|
||||
|
||||
|
||||
def test_gpt_5_reasoning_streaming():
|
||||
@@ -1358,7 +1366,7 @@ async def test_streaming_tool_calls_with_n_greater_than_1(model):
|
||||
"""
|
||||
Test that the index field in a choice object is correctly populated
|
||||
when using streaming mode with n>1 and tool calls.
|
||||
|
||||
|
||||
Regression test for: https://github.com/BerriAI/litellm/issues/8977
|
||||
"""
|
||||
tools = [
|
||||
@@ -1386,7 +1394,7 @@ async def test_streaming_tool_calls_with_n_greater_than_1(model):
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
response = litellm.completion(
|
||||
model=model,
|
||||
messages=[
|
||||
@@ -1399,20 +1407,30 @@ async def test_streaming_tool_calls_with_n_greater_than_1(model):
|
||||
stream=True,
|
||||
n=3,
|
||||
)
|
||||
|
||||
|
||||
# Collect all chunks and their indices
|
||||
indices_seen = []
|
||||
for chunk in response:
|
||||
assert len(chunk.choices) == 1, "Each streaming chunk should have exactly 1 choice"
|
||||
assert hasattr(chunk.choices[0], "index"), "Choice should have an index attribute"
|
||||
assert (
|
||||
len(chunk.choices) == 1
|
||||
), "Each streaming chunk should have exactly 1 choice"
|
||||
assert hasattr(
|
||||
chunk.choices[0], "index"
|
||||
), "Choice should have an index attribute"
|
||||
index = chunk.choices[0].index
|
||||
indices_seen.append(index)
|
||||
|
||||
|
||||
# Verify that we got chunks with different indices (0, 1, 2 for n=3)
|
||||
unique_indices = set(indices_seen)
|
||||
assert unique_indices == {0, 1, 2}, f"Should have indices 0, 1, 2 for n=3, got {unique_indices}"
|
||||
|
||||
print(f"✓ Test passed: streaming with n=3 and tool calls correctly populates index field")
|
||||
assert unique_indices == {
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
}, f"Should have indices 0, 1, 2 for n=3, got {unique_indices}"
|
||||
|
||||
print(
|
||||
f"✓ Test passed: streaming with n=3 and tool calls correctly populates index field"
|
||||
)
|
||||
print(f" Indices seen: {indices_seen}")
|
||||
print(f" Unique indices: {unique_indices}")
|
||||
|
||||
@@ -1436,19 +1454,42 @@ async def test_streaming_content_with_n_greater_than_1(model):
|
||||
n=2,
|
||||
max_tokens=10,
|
||||
)
|
||||
|
||||
|
||||
# Collect all chunks and their indices
|
||||
indices_seen = []
|
||||
for chunk in response:
|
||||
assert len(chunk.choices) == 1, "Each streaming chunk should have exactly 1 choice"
|
||||
assert hasattr(chunk.choices[0], "index"), "Choice should have an index attribute"
|
||||
assert (
|
||||
len(chunk.choices) == 1
|
||||
), "Each streaming chunk should have exactly 1 choice"
|
||||
assert hasattr(
|
||||
chunk.choices[0], "index"
|
||||
), "Choice should have an index attribute"
|
||||
index = chunk.choices[0].index
|
||||
indices_seen.append(index)
|
||||
|
||||
|
||||
# Verify that we got chunks with different indices (0, 1 for n=2)
|
||||
unique_indices = set(indices_seen)
|
||||
assert unique_indices == {0, 1}, f"Should have indices 0, 1 for n=2, got {unique_indices}"
|
||||
|
||||
print(f"✓ Test passed: streaming with n=2 and regular content correctly populates index field")
|
||||
assert unique_indices == {
|
||||
0,
|
||||
1,
|
||||
}, f"Should have indices 0, 1 for n=2, got {unique_indices}"
|
||||
|
||||
print(
|
||||
f"✓ Test passed: streaming with n=2 and regular content correctly populates index field"
|
||||
)
|
||||
print(f" Indices seen: {indices_seen}")
|
||||
print(f" Unique indices: {unique_indices}")
|
||||
|
||||
|
||||
def test_gpt_5_web_search():
|
||||
response = litellm.completion(
|
||||
model="openai/responses/gpt-5",
|
||||
messages=[{"role": "user", "content": "get price of nvda"}],
|
||||
stream=True,
|
||||
temperature=1,
|
||||
max_tokens=8192,
|
||||
tools=[{"type": "web_search"}],
|
||||
)
|
||||
|
||||
for chunk in response:
|
||||
print("chunk: ", chunk)
|
||||
|
||||
@@ -568,10 +568,11 @@ async def test_e2e_generate_cold_storage_object_key_successful():
|
||||
start_time = datetime(2025, 1, 15, 10, 30, 45, 123456, timezone.utc)
|
||||
response_id = "chatcmpl-test-12345"
|
||||
team_alias = "test-team"
|
||||
|
||||
with patch("litellm.cold_storage_custom_logger", return_value="s3"), \
|
||||
patch("litellm.integrations.s3.get_s3_object_key") as mock_get_s3_key:
|
||||
|
||||
|
||||
with patch("litellm.cold_storage_custom_logger", return_value="s3"), patch(
|
||||
"litellm.integrations.s3.get_s3_object_key"
|
||||
) as mock_get_s3_key:
|
||||
|
||||
# Mock the S3 object key generation to return a predictable result
|
||||
mock_get_s3_key.return_value = (
|
||||
"2025-01-15/time-10-30-45-123456_chatcmpl-test-12345.json"
|
||||
@@ -613,11 +614,13 @@ async def test_e2e_generate_cold_storage_object_key_with_custom_logger_s3_path()
|
||||
# Create mock custom logger with s3_path
|
||||
mock_custom_logger = MagicMock()
|
||||
mock_custom_logger.s3_path = "storage"
|
||||
|
||||
with patch("litellm.cold_storage_custom_logger", "s3_v2"), \
|
||||
patch("litellm.logging_callback_manager.get_active_custom_logger_for_callback_name") as mock_get_logger, \
|
||||
patch("litellm.integrations.s3.get_s3_object_key") as mock_get_s3_key:
|
||||
|
||||
|
||||
with patch("litellm.cold_storage_custom_logger", "s3_v2"), patch(
|
||||
"litellm.logging_callback_manager.get_active_custom_logger_for_callback_name"
|
||||
) as mock_get_logger, patch(
|
||||
"litellm.integrations.s3.get_s3_object_key"
|
||||
) as mock_get_s3_key:
|
||||
|
||||
# Setup mocks
|
||||
mock_get_logger.return_value = mock_custom_logger
|
||||
mock_get_s3_key.return_value = (
|
||||
@@ -663,11 +666,13 @@ async def test_e2e_generate_cold_storage_object_key_with_logger_no_s3_path():
|
||||
# Create mock custom logger without s3_path
|
||||
mock_custom_logger = MagicMock()
|
||||
mock_custom_logger.s3_path = None # or could be missing attribute
|
||||
|
||||
with patch("litellm.cold_storage_custom_logger", "s3_v2"), \
|
||||
patch("litellm.logging_callback_manager.get_active_custom_logger_for_callback_name") as mock_get_logger, \
|
||||
patch("litellm.integrations.s3.get_s3_object_key") as mock_get_s3_key:
|
||||
|
||||
|
||||
with patch("litellm.cold_storage_custom_logger", "s3_v2"), patch(
|
||||
"litellm.logging_callback_manager.get_active_custom_logger_for_callback_name"
|
||||
) as mock_get_logger, patch(
|
||||
"litellm.integrations.s3.get_s3_object_key"
|
||||
) as mock_get_s3_key:
|
||||
|
||||
# Setup mocks
|
||||
mock_get_logger.return_value = mock_custom_logger
|
||||
mock_get_s3_key.return_value = (
|
||||
@@ -708,7 +713,7 @@ async def test_e2e_generate_cold_storage_object_key_not_configured():
|
||||
team_alias = "another-team"
|
||||
|
||||
# Use patch to ensure test isolation
|
||||
with patch.object(litellm, 'cold_storage_custom_logger', None):
|
||||
with patch.object(litellm, "cold_storage_custom_logger", None):
|
||||
# Call the function
|
||||
result = StandardLoggingPayloadSetup._generate_cold_storage_object_key(
|
||||
start_time=start_time, response_id=response_id, team_alias=team_alias
|
||||
@@ -716,3 +721,41 @@ async def test_e2e_generate_cold_storage_object_key_not_configured():
|
||||
|
||||
# Verify the result is None when cold storage is not configured
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_get_final_response_obj_with_empty_response_obj_and_list_init():
|
||||
"""
|
||||
Test get_final_response_obj when response_obj is empty dict and init_response_obj is a list.
|
||||
|
||||
When response_obj is empty (falsy), the method should return init_response_obj if it's a list.
|
||||
"""
|
||||
from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup
|
||||
|
||||
# Create test objects
|
||||
class TestObject1:
|
||||
def __init__(self):
|
||||
self.name = "Object1"
|
||||
|
||||
class TestObject2:
|
||||
def __init__(self):
|
||||
self.name = "Object2"
|
||||
|
||||
obj1 = TestObject1()
|
||||
obj2 = TestObject2()
|
||||
|
||||
# Test case: empty response_obj, list init_response_obj
|
||||
response_obj = {}
|
||||
init_response_obj = [obj1, obj2]
|
||||
kwargs = {}
|
||||
|
||||
# Call the method
|
||||
result = StandardLoggingPayloadSetup.get_final_response_obj(
|
||||
response_obj=response_obj, init_response_obj=init_response_obj, kwargs=kwargs
|
||||
)
|
||||
|
||||
# Verify the result
|
||||
assert result == [obj1, obj2]
|
||||
assert result is init_response_obj # Should be the exact same list object
|
||||
assert len(result) == 2
|
||||
assert result[0].name == "Object1"
|
||||
assert result[1].name == "Object2"
|
||||
|
||||
Reference in New Issue
Block a user