From 12202e885a30ec6b6d901bdcbb20888fd2de043d Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Thu, 18 Dec 2025 18:24:58 +0000 Subject: [PATCH 1/2] refactor!: migrate HTTP client from requests to httpx Replace the synchronous HTTP client library from `requests` to `httpx` for improved async support potential and modern API design. Changes include: - Replace `requests>=2.32.3` with `httpx>=0.28.0` in dependencies - Update `stackone_ai/models.py` to use httpx.request() instead of requests.request() - Refactor exception handling to use httpx-specific exceptions: - httpx.HTTPStatusError for HTTP error responses - httpx.RequestError for connection/network errors - Replace test mocking library from `responses` to `respx` - Remove `types-requests` type stubs (no longer needed) - Update all test files to use respx decorators and httpx.Response BREAKING CHANGE: Error handling now uses httpx exceptions instead of requests exceptions. Code catching RequestException should be updated to catch httpx.HTTPStatusError or httpx.RequestError. --- pyproject.toml | 5 +- stackone_ai/models.py | 27 +++-- tests/test_feedback.py | 224 +++++++++++++++++-------------------- tests/test_meta_tools.py | 25 ++--- tests/test_models.py | 12 +- tests/test_tool_calling.py | 71 ++++++------ uv.lock | 34 ++---- 7 files changed, 186 insertions(+), 212 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 58c470f..78f533a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ classifiers = [ ] dependencies = [ "pydantic>=2.10.6", - "requests>=2.32.3", + "httpx>=0.28.0", "langchain-core>=0.1.0", "bm25s>=0.2.2", "numpy>=1.24.0", @@ -62,10 +62,9 @@ dev = [ "pytest-asyncio>=0.25.3", "pytest-cov>=6.0.0", "pytest-snapshot>=0.9.0", - "responses>=0.25.8", + "respx>=0.22.0", "ruff>=0.9.6", "stackone-ai", - "types-requests>=2.31.0.20240311", ] [tool.pytest.ini_options] diff --git a/stackone_ai/models.py b/stackone_ai/models.py index fa6ac73..6d137ce 100644 --- a/stackone_ai/models.py +++ b/stackone_ai/models.py @@ -10,10 +10,9 @@ from typing import Annotated, Any, ClassVar, cast from urllib.parse import quote -import requests +import httpx from langchain_core.tools import BaseTool from pydantic import BaseModel, BeforeValidator, Field, PrivateAttr -from requests.exceptions import RequestException # TODO: Remove when Python 3.9 support is dropped from typing_extensions import TypeAlias @@ -242,7 +241,7 @@ def execute( if query_params: request_kwargs["params"] = query_params - response = requests.request(**request_kwargs) + response = httpx.request(**request_kwargs) response_status = response.status_code response.raise_for_status() @@ -254,15 +253,23 @@ def execute( status = "error" error_message = f"Invalid JSON in arguments: {exc}" raise ValueError(error_message) from exc - except RequestException as exc: + except httpx.HTTPStatusError as exc: + status = "error" + error_message = str(exc) + response_body = None + if exc.response.text: + try: + response_body = exc.response.json() + except json.JSONDecodeError: + response_body = exc.response.text + raise StackOneAPIError( + str(exc), + exc.response.status_code, + response_body, + ) from exc + except httpx.RequestError as exc: status = "error" error_message = str(exc) - if hasattr(exc, "response") and exc.response is not None: - raise StackOneAPIError( - str(exc), - exc.response.status_code, - exc.response.json() if exc.response.text else None, - ) from exc raise StackOneError(f"Request failed: {exc}") from exc finally: datetime.now(timezone.utc) diff --git a/tests/test_feedback.py b/tests/test_feedback.py index b67b31c..f024582 100644 --- a/tests/test_feedback.py +++ b/tests/test_feedback.py @@ -5,9 +5,10 @@ import json import os -from unittest.mock import Mock, patch +import httpx import pytest +import respx from stackone_ai.feedback import create_feedback_tool from stackone_ai.models import StackOneError @@ -55,165 +56,150 @@ def test_multiple_account_ids_validation(self) -> None: with pytest.raises(StackOneError, match="At least one valid account ID is required"): tool.execute({"feedback": "Great tools!", "account_id": ["", " "], "tool_names": ["test_tool"]}) + @respx.mock def test_json_string_input(self) -> None: """Test that JSON string input is properly parsed.""" tool = create_feedback_tool(api_key="test_key") - with patch("requests.request") as mock_request: - mock_response = Mock() - mock_response.status_code = 200 - mock_response.json.return_value = {"message": "Success"} - mock_response.raise_for_status = Mock() - mock_request.return_value = mock_response + route = respx.post("https://api.stackone.com/ai/tool-feedback").mock( + return_value=httpx.Response(200, json={"message": "Success"}) + ) - json_string = json.dumps( - {"feedback": "Great tools!", "account_id": "acc_123456", "tool_names": ["test_tool"]} - ) - result = tool.execute(json_string) - assert result["message"] == "Success" + json_string = json.dumps( + {"feedback": "Great tools!", "account_id": "acc_123456", "tool_names": ["test_tool"]} + ) + result = tool.execute(json_string) + assert result["message"] == "Success" + assert route.called class TestFeedbackToolExecution: """Test suite for feedback tool execution.""" + @respx.mock def test_single_account_execution(self) -> None: """Test execution with single account ID.""" tool = create_feedback_tool(api_key="test_key") api_response = {"message": "Feedback successfully stored", "trace_id": "test-trace-id"} - with patch("requests.request") as mock_request: - mock_response = Mock() - mock_response.status_code = 200 - mock_response.json.return_value = api_response - mock_response.raise_for_status = Mock() - mock_request.return_value = mock_response - - result = tool.execute( - { - "feedback": "Great tools!", - "account_id": "acc_123456", - "tool_names": ["data_export", "analytics"], - } - ) - - assert result == api_response - mock_request.assert_called_once() - call_kwargs = mock_request.call_args[1] - assert call_kwargs["method"] == "POST" - assert call_kwargs["url"] == "https://api.stackone.com/ai/tool-feedback" - assert call_kwargs["json"]["feedback"] == "Great tools!" - assert call_kwargs["json"]["account_id"] == "acc_123456" - assert call_kwargs["json"]["tool_names"] == ["data_export", "analytics"] - + route = respx.post("https://api.stackone.com/ai/tool-feedback").mock( + return_value=httpx.Response(200, json=api_response) + ) + + result = tool.execute( + { + "feedback": "Great tools!", + "account_id": "acc_123456", + "tool_names": ["data_export", "analytics"], + } + ) + + assert result == api_response + assert route.called + assert route.call_count == 1 + request = route.calls[0].request + body = json.loads(request.content) + assert body["feedback"] == "Great tools!" + assert body["account_id"] == "acc_123456" + assert body["tool_names"] == ["data_export", "analytics"] + + @respx.mock def test_call_method_interface(self) -> None: """Test that the .call() method works correctly.""" tool = create_feedback_tool(api_key="test_key") api_response = {"message": "Success", "trace_id": "test-trace-id"} - with patch("requests.request") as mock_request: - mock_response = Mock() - mock_response.status_code = 200 - mock_response.json.return_value = api_response - mock_response.raise_for_status = Mock() - mock_request.return_value = mock_response - - result = tool.call( - feedback="Testing the .call() method interface.", - account_id="acc_test004", - tool_names=["meta_collect_tool_feedback"], - ) + route = respx.post("https://api.stackone.com/ai/tool-feedback").mock( + return_value=httpx.Response(200, json=api_response) + ) - assert result == api_response - mock_request.assert_called_once() + result = tool.call( + feedback="Testing the .call() method interface.", + account_id="acc_test004", + tool_names=["meta_collect_tool_feedback"], + ) + assert result == api_response + assert route.called + assert route.call_count == 1 + + @respx.mock def test_api_error_handling(self) -> None: """Test that API errors are handled properly.""" tool = create_feedback_tool(api_key="test_key") - with patch("requests.request") as mock_request: - mock_response = Mock() - mock_response.status_code = 401 - mock_response.text = '{"error": "Unauthorized"}' - mock_response.json.return_value = {"error": "Unauthorized"} - mock_response.raise_for_status.side_effect = Exception("401 Client Error: Unauthorized") - mock_request.return_value = mock_response - - with pytest.raises(StackOneError): - tool.execute( - { - "feedback": "Great tools!", - "account_id": "acc_123456", - "tool_names": ["test_tool"], - } - ) - - def test_multiple_account_ids_execution(self) -> None: - """Test execution with multiple account IDs - both success and mixed scenarios.""" - tool = create_feedback_tool(api_key="test_key") - api_response = {"message": "Feedback successfully stored", "trace_id": "test-trace-id"} + respx.post("https://api.stackone.com/ai/tool-feedback").mock( + return_value=httpx.Response(401, json={"error": "Unauthorized"}) + ) - # Test all successful case - with patch("requests.request") as mock_request: - mock_response = Mock() - mock_response.status_code = 200 - mock_response.json.return_value = api_response - mock_response.raise_for_status = Mock() - mock_request.return_value = mock_response - - result = tool.execute( + with pytest.raises(StackOneError): + tool.execute( { "feedback": "Great tools!", - "account_id": ["acc_123456", "acc_789012", "acc_345678"], + "account_id": "acc_123456", "tool_names": ["test_tool"], } ) - assert result["message"] == "Feedback sent to 3 account(s)" - assert result["total_accounts"] == 3 - assert result["successful"] == 3 - assert result["failed"] == 0 - assert len(result["results"]) == 3 - assert mock_request.call_count == 3 + @respx.mock + def test_multiple_account_ids_execution(self) -> None: + """Test execution with multiple account IDs - both success and mixed scenarios.""" + tool = create_feedback_tool(api_key="test_key") + api_response = {"message": "Feedback successfully stored", "trace_id": "test-trace-id"} - # Test mixed success/error case - def mock_request_side_effect(*args, **kwargs): - account_id = kwargs.get("json", {}).get("account_id") + # Test all successful case + route = respx.post("https://api.stackone.com/ai/tool-feedback").mock( + return_value=httpx.Response(200, json=api_response) + ) + + result = tool.execute( + { + "feedback": "Great tools!", + "account_id": ["acc_123456", "acc_789012", "acc_345678"], + "tool_names": ["test_tool"], + } + ) + + assert result["message"] == "Feedback sent to 3 account(s)" + assert result["total_accounts"] == 3 + assert result["successful"] == 3 + assert result["failed"] == 0 + assert len(result["results"]) == 3 + assert route.call_count == 3 + + @respx.mock + def test_multiple_account_ids_mixed_success(self) -> None: + """Test execution with multiple account IDs - mixed success and error.""" + tool = create_feedback_tool(api_key="test_key") + + def custom_side_effect(request: httpx.Request) -> httpx.Response: + body = json.loads(request.content) + account_id = body.get("account_id") if account_id == "acc_123456": - mock_response = Mock() - mock_response.status_code = 200 - mock_response.json.return_value = {"message": "Success"} - mock_response.raise_for_status = Mock() - return mock_response + return httpx.Response(200, json={"message": "Success"}) else: - mock_response = Mock() - mock_response.status_code = 401 - mock_response.text = '{"error": "Unauthorized"}' - mock_response.json.return_value = {"error": "Unauthorized"} - mock_response.raise_for_status.side_effect = Exception("401 Client Error: Unauthorized") - return mock_response + return httpx.Response(401, json={"error": "Unauthorized"}) - with patch("requests.request") as mock_request: - mock_request.side_effect = mock_request_side_effect + respx.post("https://api.stackone.com/ai/tool-feedback").mock(side_effect=custom_side_effect) - result = tool.execute( - { - "feedback": "Great tools!", - "account_id": ["acc_123456", "acc_unauthorized"], - "tool_names": ["test_tool"], - } - ) + result = tool.execute( + { + "feedback": "Great tools!", + "account_id": ["acc_123456", "acc_unauthorized"], + "tool_names": ["test_tool"], + } + ) - assert result["total_accounts"] == 2 - assert result["successful"] == 1 - assert result["failed"] == 1 - assert len(result["results"]) == 2 + assert result["total_accounts"] == 2 + assert result["successful"] == 1 + assert result["failed"] == 1 + assert len(result["results"]) == 2 - success_result = next(r for r in result["results"] if r["account_id"] == "acc_123456") - assert success_result["status"] == "success" + success_result = next(r for r in result["results"] if r["account_id"] == "acc_123456") + assert success_result["status"] == "success" - error_result = next(r for r in result["results"] if r["account_id"] == "acc_unauthorized") - assert error_result["status"] == "error" - assert "401 Client Error: Unauthorized" in error_result["error"] + error_result = next(r for r in result["results"] if r["account_id"] == "acc_unauthorized") + assert error_result["status"] == "error" def test_tool_integration(self) -> None: """Test that feedback tool integrates properly with toolset.""" diff --git a/tests/test_meta_tools.py b/tests/test_meta_tools.py index 90ce23f..1ebb07e 100644 --- a/tests/test_meta_tools.py +++ b/tests/test_meta_tools.py @@ -1,7 +1,8 @@ """Tests for meta tools functionality""" +import httpx import pytest -import responses +import respx from stackone_ai import StackOneTool, Tools from stackone_ai.meta_tools import ( @@ -209,24 +210,22 @@ def test_execute_tool_invalid_name(self, tools_collection): } ) + @respx.mock def test_execute_tool_call(self, tools_collection): """Test calling the execute tool with call method""" execute_tool = create_meta_execute_tool(tools_collection) - # Mock the actual tool execution by patching the requests - with responses.RequestsMock() as rsps: - rsps.add( - responses.GET, - "https://api.example.com/hris/employee", - json={"success": True, "employees": []}, - status=200, - ) + # Mock the actual tool execution + route = respx.get("https://api.example.com/hris/employee").mock( + return_value=httpx.Response(200, json={"success": True, "employees": []}) + ) - # Call the meta execute tool - result = execute_tool.call(toolName="hris_list_employee", params={"limit": 10}) + # Call the meta execute tool + result = execute_tool.call(toolName="hris_list_employee", params={"limit": 10}) - assert result is not None - assert "success" in result or "employees" in result + assert result is not None + assert "success" in result or "employees" in result + assert route.called class TestToolsMetaTools: diff --git a/tests/test_models.py b/tests/test_models.py index 834e831..c974979 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -56,9 +56,11 @@ def mock_specs() -> dict: def test_tool_execution(mock_tool): """Test tool execution with parameters""" - with patch("requests.request") as mock_request: + with patch("httpx.request") as mock_request: mock_response = MagicMock() mock_response.json.return_value = {"id": "123", "name": "Test User"} + mock_response.status_code = 200 + mock_response.raise_for_status = MagicMock() mock_request.return_value = mock_response result = mock_tool.execute({"id": "123"}) @@ -69,9 +71,11 @@ def test_tool_execution(mock_tool): def test_tool_execution_with_string_args(mock_tool): """Test tool execution with string arguments""" - with patch("requests.request") as mock_request: + with patch("httpx.request") as mock_request: mock_response = MagicMock() mock_response.json.return_value = {"id": "123", "name": "Test User"} + mock_response.status_code = 200 + mock_response.raise_for_status = MagicMock() mock_request.return_value = mock_response result = mock_tool.execute('{"id": "123"}') @@ -138,9 +142,11 @@ async def test_langchain_tool_execution(mock_tool): langchain_tool = langchain_tools[0] # Mock the HTTP request - with patch("requests.request") as mock_request: + with patch("httpx.request") as mock_request: mock_response = MagicMock() mock_response.json.return_value = {"id": "test_value", "name": "Test User"} + mock_response.status_code = 200 + mock_response.raise_for_status = MagicMock() mock_request.return_value = mock_response # Test sync execution with correct parameter name diff --git a/tests/test_tool_calling.py b/tests/test_tool_calling.py index 8e35aec..736df63 100644 --- a/tests/test_tool_calling.py +++ b/tests/test_tool_calling.py @@ -2,8 +2,9 @@ import json +import httpx import pytest -import responses +import respx from stackone_ai import StackOneTool from stackone_ai.models import ExecuteConfig, ToolParameters @@ -40,15 +41,12 @@ def mock_tool(): class TestToolCalling: """Test tool calling functionality""" - @responses.activate + @respx.mock def test_call_with_kwargs(self, mock_tool): """Test calling a tool with keyword arguments""" # Mock the API response - responses.add( - responses.POST, - "https://api.example.com/test", - json={"success": True, "result": "test_result"}, - status=200, + route = respx.post("https://api.example.com/test").mock( + return_value=httpx.Response(200, json={"success": True, "result": "test_result"}) ) # Call the tool with kwargs @@ -58,19 +56,17 @@ def test_call_with_kwargs(self, mock_tool): assert result == {"success": True, "result": "test_result"} # Verify the request was made correctly - assert len(responses.calls) == 1 - request = responses.calls[0].request - assert json.loads(request.body) == {"name": "test", "value": 42} + assert route.called + assert route.call_count == 1 + request = route.calls[0].request + assert json.loads(request.content) == {"name": "test", "value": 42} - @responses.activate + @respx.mock def test_call_with_dict_arg(self, mock_tool): """Test calling a tool with a dictionary argument""" # Mock the API response - responses.add( - responses.POST, - "https://api.example.com/test", - json={"success": True, "result": "test_result"}, - status=200, + route = respx.post("https://api.example.com/test").mock( + return_value=httpx.Response(200, json={"success": True, "result": "test_result"}) ) # Call the tool with a dict @@ -80,19 +76,17 @@ def test_call_with_dict_arg(self, mock_tool): assert result == {"success": True, "result": "test_result"} # Verify the request - assert len(responses.calls) == 1 - request = responses.calls[0].request - assert json.loads(request.body) == {"name": "test", "value": 42} + assert route.called + assert route.call_count == 1 + request = route.calls[0].request + assert json.loads(request.content) == {"name": "test", "value": 42} - @responses.activate + @respx.mock def test_call_with_json_string(self, mock_tool): """Test calling a tool with a JSON string argument""" # Mock the API response - responses.add( - responses.POST, - "https://api.example.com/test", - json={"success": True, "result": "test_result"}, - status=200, + route = respx.post("https://api.example.com/test").mock( + return_value=httpx.Response(200, json={"success": True, "result": "test_result"}) ) # Call the tool with a JSON string @@ -102,9 +96,10 @@ def test_call_with_json_string(self, mock_tool): assert result == {"success": True, "result": "test_result"} # Verify the request - assert len(responses.calls) == 1 - request = responses.calls[0].request - assert json.loads(request.body) == {"name": "test", "value": 42} + assert route.called + assert route.call_count == 1 + request = route.calls[0].request + assert json.loads(request.content) == {"name": "test", "value": 42} def test_call_with_both_args_and_kwargs_raises_error(self, mock_tool): """Test that providing both args and kwargs raises an error""" @@ -116,15 +111,12 @@ def test_call_with_multiple_args_raises_error(self, mock_tool): with pytest.raises(ValueError, match="Only one positional argument is allowed"): mock_tool.call({"name": "test"}, {"value": 42}) - @responses.activate + @respx.mock def test_call_without_arguments(self, mock_tool): """Test calling a tool without any arguments""" # Mock the API response - responses.add( - responses.POST, - "https://api.example.com/test", - json={"success": True, "result": "no_args"}, - status=200, + route = respx.post("https://api.example.com/test").mock( + return_value=httpx.Response(200, json={"success": True, "result": "no_args"}) ) # Call the tool without arguments @@ -134,10 +126,11 @@ def test_call_without_arguments(self, mock_tool): assert result == {"success": True, "result": "no_args"} # Verify the request body is empty or contains empty JSON - assert len(responses.calls) == 1 - request = responses.calls[0].request + assert route.called + assert route.call_count == 1 + request = route.calls[0].request # Handle case where body might be None for empty POST - if request.body: - assert json.loads(request.body) == {} + if request.content: + assert json.loads(request.content) == {} else: - assert request.body is None or request.body == b"" + assert request.content == b"" diff --git a/uv.lock b/uv.lock index 02b2bf9..4f58dde 100644 --- a/uv.lock +++ b/uv.lock @@ -4204,17 +4204,15 @@ wheels = [ ] [[package]] -name = "responses" -version = "0.25.8" +name = "respx" +version = "0.22.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyyaml" }, - { name = "requests" }, - { name = "urllib3" }, + { name = "httpx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/95/89c054ad70bfef6da605338b009b2e283485835351a9935c7bfbfaca7ffc/responses-0.25.8.tar.gz", hash = "sha256:9374d047a575c8f781b94454db5cab590b6029505f488d12899ddb10a4af1cf4", size = 79320, upload-time = "2025-08-08T19:01:46.709Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/7c/96bd0bc759cf009675ad1ee1f96535edcb11e9666b985717eb8c87192a95/respx-0.22.0.tar.gz", hash = "sha256:3c8924caa2a50bd71aefc07aa812f2466ff489f1848c96e954a5362d17095d91", size = 28439, upload-time = "2024-12-19T22:33:59.374Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/4c/cc276ce57e572c102d9542d383b2cfd551276581dc60004cb94fe8774c11/responses-0.25.8-py3-none-any.whl", hash = "sha256:0c710af92def29c8352ceadff0c3fe340ace27cf5af1bbe46fb71275bcd2831c", size = 34769, upload-time = "2025-08-08T19:01:45.018Z" }, + { url = "https://files.pythonhosted.org/packages/8e/67/afbb0978d5399bc9ea200f1d4489a23c9a1dad4eee6376242b8182389c79/respx-0.22.0-py2.py3-none-any.whl", hash = "sha256:631128d4c9aba15e56903fb5f66fb1eff412ce28dd387ca3a81339e52dbd3ad0", size = 25127, upload-time = "2024-12-19T22:33:57.837Z" }, ] [[package]] @@ -4662,12 +4660,12 @@ source = { editable = "." } dependencies = [ { name = "bm25s" }, { name = "eval-type-backport", marker = "python_full_version < '3.10'" }, + { name = "httpx" }, { name = "langchain-core" }, { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "numpy", version = "2.3.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pydantic" }, - { name = "requests" }, { name = "typing-extensions" }, ] @@ -4690,10 +4688,9 @@ dev = [ { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "pytest-snapshot" }, - { name = "responses" }, + { name = "respx" }, { name = "ruff" }, { name = "stackone-ai" }, - { name = "types-requests" }, ] [package.metadata] @@ -4701,6 +4698,7 @@ requires-dist = [ { name = "bm25s", specifier = ">=0.2.2" }, { name = "crewai", marker = "python_full_version >= '3.10' and extra == 'examples'", specifier = ">=0.102.0" }, { name = "eval-type-backport", marker = "python_full_version < '3.10'" }, + { name = "httpx", specifier = ">=0.28.0" }, { name = "langchain-core", specifier = ">=0.1.0" }, { name = "langchain-openai", marker = "extra == 'examples'", specifier = ">=0.3.6" }, { name = "langgraph", marker = "extra == 'examples'", specifier = ">=0.2.0" }, @@ -4709,7 +4707,6 @@ requires-dist = [ { name = "openai", marker = "extra == 'examples'", specifier = ">=1.63.2" }, { name = "pydantic", specifier = ">=2.10.6" }, { name = "python-dotenv", marker = "extra == 'examples'", specifier = ">=1.0.1" }, - { name = "requests", specifier = ">=2.32.3" }, { name = "typing-extensions", specifier = ">=4.0.0" }, ] provides-extras = ["mcp", "examples"] @@ -4721,10 +4718,9 @@ dev = [ { name = "pytest-asyncio", specifier = ">=0.25.3" }, { name = "pytest-cov", specifier = ">=6.0.0" }, { name = "pytest-snapshot", specifier = ">=0.9.0" }, - { name = "responses", specifier = ">=0.25.8" }, + { name = "respx", specifier = ">=0.22.0" }, { name = "ruff", specifier = ">=0.9.6" }, { name = "stackone-ai" }, - { name = "types-requests", specifier = ">=2.31.0.20240311" }, ] [[package]] @@ -4948,18 +4944,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9", size = 46748, upload-time = "2025-09-23T09:47:46.777Z" }, ] -[[package]] -name = "types-requests" -version = "2.32.4.20250913" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/36/27/489922f4505975b11de2b5ad07b4fe1dca0bca9be81a703f26c5f3acfce5/types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d", size = 23113, upload-time = "2025-09-13T02:40:02.309Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1", size = 20658, upload-time = "2025-09-13T02:40:01.115Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0" From 9b023ede87745e68f91d98a624837d7e7df3d11d Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:05:15 +0000 Subject: [PATCH 2/2] fix: address PR review comments for httpx migration - Remove unused error_message variable in exception handlers - Restore error assertion in test_multiple_account_ids_mixed_success --- stackone_ai/models.py | 2 -- tests/test_feedback.py | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stackone_ai/models.py b/stackone_ai/models.py index 6d137ce..96ccd40 100644 --- a/stackone_ai/models.py +++ b/stackone_ai/models.py @@ -255,7 +255,6 @@ def execute( raise ValueError(error_message) from exc except httpx.HTTPStatusError as exc: status = "error" - error_message = str(exc) response_body = None if exc.response.text: try: @@ -269,7 +268,6 @@ def execute( ) from exc except httpx.RequestError as exc: status = "error" - error_message = str(exc) raise StackOneError(f"Request failed: {exc}") from exc finally: datetime.now(timezone.utc) diff --git a/tests/test_feedback.py b/tests/test_feedback.py index f024582..ecb884d 100644 --- a/tests/test_feedback.py +++ b/tests/test_feedback.py @@ -200,6 +200,8 @@ def custom_side_effect(request: httpx.Request) -> httpx.Response: error_result = next(r for r in result["results"] if r["account_id"] == "acc_unauthorized") assert error_result["status"] == "error" + assert "error" in error_result + assert "401" in error_result["error"] def test_tool_integration(self) -> None: """Test that feedback tool integrates properly with toolset."""