Skip to content

Commit 74d9181

Browse files
authored
Content Handling Improvement in call_http method (#494)
* initial commit * remove unused pkg * improve call_http for clarity * add content handling test for call_http * add import * update test * update test * update typo * remove unused using * update by comments * add space * update unit tests as we update call_http logic * update by comment * update description * update by comment * re-arrange the error message as it's too long * re-arrange error msg * update error msg * update test * updatetest
1 parent 354ace0 commit 74d9181

File tree

2 files changed

+79
-5
lines changed

2 files changed

+79
-5
lines changed

azure/durable_functions/models/DurableOrchestrationContext.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@ def call_activity_with_retry(self,
214214

215215
def call_http(self, method: str, uri: str, content: Optional[str] = None,
216216
headers: Optional[Dict[str, str]] = None,
217-
token_source: TokenSource = None) -> TaskBase:
217+
token_source: TokenSource = None,
218+
is_raw_str: bool = False) -> TaskBase:
218219
"""Schedule a durable HTTP call to the specified endpoint.
219220
220221
Parameters
@@ -229,17 +230,31 @@ def call_http(self, method: str, uri: str, content: Optional[str] = None,
229230
The HTTP request headers.
230231
token_source: TokenSource
231232
The source of OAuth token to add to the request.
233+
is_raw_str: bool, optional
234+
If True, send string content as-is.
235+
If False (default), serialize content to JSON.
232236
233237
Returns
234238
-------
235239
Task
236240
The durable HTTP request to schedule.
237241
"""
238242
json_content: Optional[str] = None
239-
if content and content is not isinstance(content, str):
240-
json_content = json.dumps(content)
241-
else:
242-
json_content = content
243+
244+
# validate parameters
245+
if (not isinstance(content, str)) and is_raw_str:
246+
raise TypeError(
247+
"Invalid use of 'is_raw_str' parameter: 'is_raw_str' is "
248+
"set to 'True' but 'content' is not an instance of type 'str'. "
249+
"Either set 'is_raw_str' to 'False', or ensure your 'content' "
250+
"is of type 'str'.")
251+
252+
if content is not None:
253+
if isinstance(content, str) and is_raw_str:
254+
# don't serialize the str value - use it as the raw HTTP request payload
255+
json_content = content
256+
else:
257+
json_content = json.dumps(content)
243258

244259
request = DurableHttpRequest(method, uri, json_content, headers, token_source)
245260
action = CallHttpAction(request)

tests/orchestrator/test_call_http.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from azure.durable_functions.models.ReplaySchema import ReplaySchema
22
import json
3+
import pytest
34
from typing import Dict
45

56
from azure.durable_functions.constants import HTTP_ACTION_NAME
@@ -174,3 +175,61 @@ def test_post_completed_state():
174175
# assert_valid_schema(result)
175176
assert_orchestration_state_equals(expected, result)
176177
validate_result_http_request(result)
178+
179+
@pytest.mark.parametrize("content, expected_content, is_raw_str", [
180+
(None, None, False),
181+
("string data", '"string data"', False),
182+
('{"key": "value"}', '"{\\"key\\": \\"value\\"}"', False),
183+
('["list", "content"]', '"[\\"list\\", \\"content\\"]"', False),
184+
('[]', '"[]"', False),
185+
('42', '"42"', False),
186+
('true', '"true"', False),
187+
# Cases that test actual behavior (not strictly adhering to Optional[str])
188+
({"key": "value"}, '{"key": "value"}', False),
189+
(["list", "content"], '["list", "content"]', False),
190+
([], '[]', False),
191+
(42, '42', False),
192+
(True, 'true', False),
193+
# Cases when is_raw_str is True
194+
("string data", "string data", True),
195+
('{"key": "value"}', '{"key": "value"}', True),
196+
('[]', '[]', True),
197+
])
198+
def test_call_http_content_handling(content, expected_content, is_raw_str):
199+
def orchestrator_function(context):
200+
yield context.call_http("POST", TEST_URI, content, is_raw_str=is_raw_str)
201+
202+
context_builder = ContextBuilder('test_call_http_content_handling')
203+
result = get_orchestration_state_result(context_builder, orchestrator_function)
204+
205+
assert len(result['actions']) == 1
206+
http_action = result['actions'][0][0]['httpRequest']
207+
208+
assert http_action['method'] == "POST"
209+
assert http_action['uri'] == TEST_URI
210+
assert http_action['content'] == expected_content
211+
212+
# Test that call_http raises a TypeError when is_raw_str is True but content is not a string
213+
def test_call_http_non_string_content_with_raw_str():
214+
def orchestrator_function(context):
215+
yield context.call_http("POST", TEST_URI, {"key": "value"}, is_raw_str=True)
216+
217+
context_builder = ContextBuilder('test_call_http_non_string_content_with_raw_str')
218+
219+
try:
220+
result = get_orchestration_state_result(context_builder, orchestrator_function)
221+
assert False
222+
except Exception as e:
223+
error_label = "\n\n$OutOfProcData$:"
224+
error_str = str(e)
225+
226+
expected_state = base_expected_state()
227+
error_msg = "Invalid use of 'is_raw_str' parameter: 'is_raw_str' is "\
228+
"set to 'True' but 'content' is not an instance of type 'str'. "\
229+
"Either set 'is_raw_str' to 'False', or ensure your 'content' "\
230+
"is of type 'str'."
231+
expected_state._error = error_msg
232+
state_str = expected_state.to_json_string()
233+
234+
expected_error_str = f"{error_msg}{error_label}{state_str}"
235+
assert expected_error_str == error_str

0 commit comments

Comments
 (0)