From 32cc357804fbbb8c5282d1a084b3897c67861f0a Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Wed, 22 Apr 2026 10:37:02 -0700 Subject: [PATCH 1/2] remove requests library --- pyproject.toml | 1 - .../cli.py | 51 +++++----- tests/cli_test.py | 93 ++++++++++++------- 3 files changed, 87 insertions(+), 58 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b72961e..5135844 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,6 @@ classifiers = [ ] dependencies = [ "boto3>=1.42.1", - "requests>=2.25.0", "aws_durable_execution_sdk_python>=1.0.0", ] diff --git a/src/aws_durable_execution_sdk_python_testing/cli.py b/src/aws_durable_execution_sdk_python_testing/cli.py index b96cdc5..6c70b97 100644 --- a/src/aws_durable_execution_sdk_python_testing/cli.py +++ b/src/aws_durable_execution_sdk_python_testing/cli.py @@ -21,7 +21,9 @@ import aws_durable_execution_sdk_python import boto3 # type: ignore -import requests +from urllib.error import HTTPError, URLError +from urllib.request import Request, urlopen + from botocore.exceptions import ConnectionError # type: ignore from aws_durable_execution_sdk_python_testing.exceptions import ( @@ -348,35 +350,38 @@ def invoke_command(self, args: argparse.Namespace) -> int: endpoint_url = self.config.local_runner_endpoint url = urljoin(endpoint_url, "/start-durable-execution") - headers = {"Content-Type": "application/json"} payload = start_input.to_dict() + data = json.dumps(payload).encode("utf-8") + req = Request( + url, + data=data, + headers={"Content-Type": "application/json"}, + method="POST", + ) - response = requests.post(url, json=payload, headers=headers, timeout=30) - - if response.status_code == 201: # noqa: PLR2004 - # Success - print the response - result = response.json() - print(json.dumps(result, indent=2)) # noqa: T201 - return 0 - - # Error - print error details try: - error_data = response.json() - logger.exception("HTTP error response") - print( # noqa: T201 - f"Error: {error_data.get('ErrorMessage', 'Unknown error')}", - file=sys.stderr, - ) - except json.JSONDecodeError: - logger.exception("Non-JSON error response") - return 1 # noqa: TRY300 - - except requests.exceptions.ConnectionError: + with urlopen(req, timeout=30) as response: # noqa: S310 + result = json.loads(response.read().decode("utf-8")) + print(json.dumps(result, indent=2)) # noqa: T201 + return 0 + except HTTPError as e: + try: + error_data = json.loads(e.read().decode("utf-8")) + logger.exception("HTTP error response") + print( # noqa: T201 + f"Error: {error_data.get('ErrorMessage', 'Unknown error')}", + file=sys.stderr, + ) + except json.JSONDecodeError: + logger.exception("Non-JSON error response") + return 1 + + except URLError: logger.exception( "Error: Could not connect to the local runner server. Is it running?" ) return 1 - except requests.exceptions.Timeout: + except TimeoutError: logger.exception("Request timeout") return 1 except Exception: diff --git a/tests/cli_test.py b/tests/cli_test.py index 0ea1b3c..0baa9d1 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -7,11 +7,13 @@ import logging import os import sys -from io import StringIO +from http.client import HTTPMessage +from io import StringIO, BytesIO from unittest.mock import Mock, patch import pytest -import requests +from urllib.error import HTTPError, URLError + from botocore.exceptions import ConnectionError # type: ignore from aws_durable_execution_sdk_python_testing.cli import CliApp, CliConfig, main @@ -604,14 +606,21 @@ def test_invoke_command_makes_http_request_to_start_execution_endpoint() -> None """Test that invoke command makes HTTP request to start-durable-execution endpoint.""" app = CliApp() - with patch("requests.post") as mock_post: - # Mock successful response - mock_response = mock_post.return_value - mock_response.status_code = 201 - mock_response.json.return_value = { + response_body = json.dumps( + { "ExecutionArn": "arn:aws:lambda:us-west-2:123456789012:function:test-function:execution:test-execution" } + ).encode("utf-8") + + mock_response = Mock() + mock_response.read.return_value = response_body + mock_response.__enter__ = Mock(return_value=mock_response) + mock_response.__exit__ = Mock(return_value=False) + with patch( + "aws_durable_execution_sdk_python_testing.cli.urlopen", + return_value=mock_response, + ) as mock_urlopen: with patch("sys.stdout", new_callable=StringIO) as mock_stdout: exit_code = app.invoke_command( argparse.Namespace( @@ -622,16 +631,17 @@ def test_invoke_command_makes_http_request_to_start_execution_endpoint() -> None ) assert exit_code == 0 - mock_post.assert_called_once() + mock_urlopen.assert_called_once() # Verify the request details - call_args = mock_post.call_args - assert call_args[0][0].endswith("/start-durable-execution") - assert call_args[1]["headers"]["Content-Type"] == "application/json" + call_args = mock_urlopen.call_args + req = call_args[0][0] + assert req.full_url.endswith("/start-durable-execution") + assert req.get_header("Content-type") == "application/json" assert call_args[1]["timeout"] == 30 # Verify payload structure - payload = call_args[1]["json"] + payload = json.loads(req.data.decode("utf-8")) assert payload["FunctionName"] == "test-function" assert payload["Input"] == '{"key": "value"}' assert payload["ExecutionName"] == "test-execution" @@ -645,11 +655,16 @@ def test_invoke_command_uses_default_execution_name_when_not_provided() -> None: """Test that invoke command generates default execution name when not provided.""" app = CliApp() - with patch("requests.post") as mock_post: - mock_response = mock_post.return_value - mock_response.status_code = 201 - mock_response.json.return_value = {"ExecutionArn": "test-arn"} + response_body = json.dumps({"ExecutionArn": "test-arn"}).encode("utf-8") + mock_response = Mock() + mock_response.read.return_value = response_body + mock_response.__enter__ = Mock(return_value=mock_response) + mock_response.__exit__ = Mock(return_value=False) + with patch( + "aws_durable_execution_sdk_python_testing.cli.urlopen", + return_value=mock_response, + ) as mock_urlopen: app.invoke_command( argparse.Namespace( function_name="my-function", @@ -659,7 +674,8 @@ def test_invoke_command_uses_default_execution_name_when_not_provided() -> None: ) # Verify default execution name is generated - payload = mock_post.call_args[1]["json"] + req = mock_urlopen.call_args[0][0] + payload = json.loads(req.data.decode("utf-8")) assert payload["ExecutionName"] == "my-function-execution" @@ -667,10 +683,8 @@ def test_invoke_command_handles_connection_error() -> None: """Test that invoke command handles connection errors gracefully.""" app = CliApp() - with patch("requests.post") as mock_post: - mock_post.side_effect = requests.exceptions.ConnectionError( - "Connection refused" - ) + with patch("aws_durable_execution_sdk_python_testing.cli.urlopen") as mock_urlopen: + mock_urlopen.side_effect = URLError("Connection refused") exit_code = app.invoke_command( argparse.Namespace( @@ -687,8 +701,8 @@ def test_invoke_command_handles_timeout_error() -> None: """Test that invoke command handles timeout errors gracefully.""" app = CliApp() - with patch("requests.post") as mock_post: - mock_post.side_effect = requests.exceptions.Timeout("Request timed out") + with patch("aws_durable_execution_sdk_python_testing.cli.urlopen") as mock_urlopen: + mock_urlopen.side_effect = TimeoutError("Request timed out") exit_code = app.invoke_command( argparse.Namespace( @@ -705,13 +719,21 @@ def test_invoke_command_handles_http_error_response() -> None: """Test that invoke command handles HTTP error responses.""" app = CliApp() - with patch("requests.post") as mock_post: - mock_response = mock_post.return_value - mock_response.status_code = 400 - mock_response.json.return_value = { + error_body = json.dumps( + { "ErrorMessage": "Invalid parameter value", "ErrorType": "InvalidParameterValueException", } + ).encode("utf-8") + + with patch("aws_durable_execution_sdk_python_testing.cli.urlopen") as mock_urlopen: + mock_urlopen.side_effect = HTTPError( + url="http://0.0.0.0:5000/start-durable-execution", + code=400, + msg="Bad Request", + hdrs=HTTPMessage(), + fp=BytesIO(error_body), + ) with patch("sys.stderr", new_callable=StringIO) as mock_stderr: exit_code = app.invoke_command( @@ -730,11 +752,14 @@ def test_invoke_command_handles_non_json_error_response() -> None: """Test that invoke command handles non-JSON error responses.""" app = CliApp() - with patch("requests.post") as mock_post: - mock_response = mock_post.return_value - mock_response.status_code = 500 - mock_response.json.side_effect = json.JSONDecodeError("Invalid JSON", "", 0) - mock_response.text = "Internal Server Error" + with patch("aws_durable_execution_sdk_python_testing.cli.urlopen") as mock_urlopen: + mock_urlopen.side_effect = HTTPError( + url="http://0.0.0.0:5000/start-durable-execution", + code=500, + msg="Internal Server Error", + hdrs=HTTPMessage(), + fp=BytesIO(b"Internal Server Error"), + ) exit_code = app.invoke_command( argparse.Namespace( @@ -1050,8 +1075,8 @@ def test_invoke_command_handles_general_exception() -> None: """Test that invoke command handles general exceptions.""" app = CliApp() - with patch("requests.post") as mock_post: - mock_post.side_effect = ValueError("Some unexpected error") + with patch("aws_durable_execution_sdk_python_testing.cli.urlopen") as mock_urlopen: + mock_urlopen.side_effect = ValueError("Some unexpected error") exit_code = app.invoke_command( argparse.Namespace( From c317bb9c8d360ce71869f871ff6f336f7a0d43be Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Wed, 22 Apr 2026 12:02:21 -0700 Subject: [PATCH 2/2] update timeout and handling of timeout errors --- .../cli.py | 5 +---- tests/cli_test.py | 20 +------------------ 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/src/aws_durable_execution_sdk_python_testing/cli.py b/src/aws_durable_execution_sdk_python_testing/cli.py index 6c70b97..85dcb7d 100644 --- a/src/aws_durable_execution_sdk_python_testing/cli.py +++ b/src/aws_durable_execution_sdk_python_testing/cli.py @@ -360,7 +360,7 @@ def invoke_command(self, args: argparse.Namespace) -> int: ) try: - with urlopen(req, timeout=30) as response: # noqa: S310 + with urlopen(req, timeout=10) as response: # noqa: S310 result = json.loads(response.read().decode("utf-8")) print(json.dumps(result, indent=2)) # noqa: T201 return 0 @@ -381,9 +381,6 @@ def invoke_command(self, args: argparse.Namespace) -> int: "Error: Could not connect to the local runner server. Is it running?" ) return 1 - except TimeoutError: - logger.exception("Request timeout") - return 1 except Exception: logger.exception("Unexpected error in invoke command") return 1 diff --git a/tests/cli_test.py b/tests/cli_test.py index 0baa9d1..98b53c6 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -638,7 +638,7 @@ def test_invoke_command_makes_http_request_to_start_execution_endpoint() -> None req = call_args[0][0] assert req.full_url.endswith("/start-durable-execution") assert req.get_header("Content-type") == "application/json" - assert call_args[1]["timeout"] == 30 + assert call_args[1]["timeout"] == 10 # Verify payload structure payload = json.loads(req.data.decode("utf-8")) @@ -697,24 +697,6 @@ def test_invoke_command_handles_connection_error() -> None: assert exit_code == 1 -def test_invoke_command_handles_timeout_error() -> None: - """Test that invoke command handles timeout errors gracefully.""" - app = CliApp() - - with patch("aws_durable_execution_sdk_python_testing.cli.urlopen") as mock_urlopen: - mock_urlopen.side_effect = TimeoutError("Request timed out") - - exit_code = app.invoke_command( - argparse.Namespace( - function_name="test-function", - input="{}", - durable_execution_name=None, - ) - ) - - assert exit_code == 1 - - def test_invoke_command_handles_http_error_response() -> None: """Test that invoke command handles HTTP error responses.""" app = CliApp()