Skip to content

Commit

Permalink
Content Handling Improvement in call_http method (#494)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
nytian authored Oct 8, 2024
1 parent 354ace0 commit 74d9181
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 5 deletions.
25 changes: 20 additions & 5 deletions azure/durable_functions/models/DurableOrchestrationContext.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ def call_activity_with_retry(self,

def call_http(self, method: str, uri: str, content: Optional[str] = None,
headers: Optional[Dict[str, str]] = None,
token_source: TokenSource = None) -> TaskBase:
token_source: TokenSource = None,
is_raw_str: bool = False) -> TaskBase:
"""Schedule a durable HTTP call to the specified endpoint.
Parameters
Expand All @@ -229,17 +230,31 @@ def call_http(self, method: str, uri: str, content: Optional[str] = None,
The HTTP request headers.
token_source: TokenSource
The source of OAuth token to add to the request.
is_raw_str: bool, optional
If True, send string content as-is.
If False (default), serialize content to JSON.
Returns
-------
Task
The durable HTTP request to schedule.
"""
json_content: Optional[str] = None
if content and content is not isinstance(content, str):
json_content = json.dumps(content)
else:
json_content = content

# validate parameters
if (not isinstance(content, str)) and is_raw_str:
raise TypeError(
"Invalid use of 'is_raw_str' parameter: 'is_raw_str' is "
"set to 'True' but 'content' is not an instance of type 'str'. "
"Either set 'is_raw_str' to 'False', or ensure your 'content' "
"is of type 'str'.")

if content is not None:
if isinstance(content, str) and is_raw_str:
# don't serialize the str value - use it as the raw HTTP request payload
json_content = content
else:
json_content = json.dumps(content)

request = DurableHttpRequest(method, uri, json_content, headers, token_source)
action = CallHttpAction(request)
Expand Down
59 changes: 59 additions & 0 deletions tests/orchestrator/test_call_http.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from azure.durable_functions.models.ReplaySchema import ReplaySchema
import json
import pytest
from typing import Dict

from azure.durable_functions.constants import HTTP_ACTION_NAME
Expand Down Expand Up @@ -174,3 +175,61 @@ def test_post_completed_state():
# assert_valid_schema(result)
assert_orchestration_state_equals(expected, result)
validate_result_http_request(result)

@pytest.mark.parametrize("content, expected_content, is_raw_str", [
(None, None, False),
("string data", '"string data"', False),
('{"key": "value"}', '"{\\"key\\": \\"value\\"}"', False),
('["list", "content"]', '"[\\"list\\", \\"content\\"]"', False),
('[]', '"[]"', False),
('42', '"42"', False),
('true', '"true"', False),
# Cases that test actual behavior (not strictly adhering to Optional[str])
({"key": "value"}, '{"key": "value"}', False),
(["list", "content"], '["list", "content"]', False),
([], '[]', False),
(42, '42', False),
(True, 'true', False),
# Cases when is_raw_str is True
("string data", "string data", True),
('{"key": "value"}', '{"key": "value"}', True),
('[]', '[]', True),
])
def test_call_http_content_handling(content, expected_content, is_raw_str):
def orchestrator_function(context):
yield context.call_http("POST", TEST_URI, content, is_raw_str=is_raw_str)

context_builder = ContextBuilder('test_call_http_content_handling')
result = get_orchestration_state_result(context_builder, orchestrator_function)

assert len(result['actions']) == 1
http_action = result['actions'][0][0]['httpRequest']

assert http_action['method'] == "POST"
assert http_action['uri'] == TEST_URI
assert http_action['content'] == expected_content

# Test that call_http raises a TypeError when is_raw_str is True but content is not a string
def test_call_http_non_string_content_with_raw_str():
def orchestrator_function(context):
yield context.call_http("POST", TEST_URI, {"key": "value"}, is_raw_str=True)

context_builder = ContextBuilder('test_call_http_non_string_content_with_raw_str')

try:
result = get_orchestration_state_result(context_builder, orchestrator_function)
assert False
except Exception as e:
error_label = "\n\n$OutOfProcData$:"
error_str = str(e)

expected_state = base_expected_state()
error_msg = "Invalid use of 'is_raw_str' parameter: 'is_raw_str' is "\
"set to 'True' but 'content' is not an instance of type 'str'. "\
"Either set 'is_raw_str' to 'False', or ensure your 'content' "\
"is of type 'str'."
expected_state._error = error_msg
state_str = expected_state.to_json_string()

expected_error_str = f"{error_msg}{error_label}{state_str}"
assert expected_error_str == error_str

0 comments on commit 74d9181

Please sign in to comment.