mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-09-19 19:25:24 +03:00
fix(dataflows): keep vendor API keys out of request errors
- FRED and Alpha Vantage authenticate with a query parameter, and requests quotes the full URL in HTTP, connection and timeout errors, so the key was written into any log or traceback that recorded one - route both vendors through one request helper that re-raises a requests error as the same class with the key removed, carrying no request, response or exception chain, each of which still held the URL #1324
This commit is contained in:
@@ -8,6 +8,7 @@ import unittest
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
import tradingagents.dataflows.config as config_module
|
||||
import tradingagents.default_config as default_config
|
||||
@@ -233,3 +234,53 @@ class FredRoutingTests(unittest.TestCase):
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
_KEY = "abcdef0123456789abcdef0123456789"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestKeyKeptOutOfErrors:
|
||||
"""The key travels as a query parameter, and requests quotes the full URL in
|
||||
its error messages, so any log or traceback would carry it (#1324)."""
|
||||
|
||||
def _raises(self, side_effect):
|
||||
with mock.patch.dict("os.environ", {"FRED_API_KEY": _KEY}), \
|
||||
mock.patch("tradingagents.dataflows.utils.requests.get", side_effect=side_effect), \
|
||||
pytest.raises(requests.RequestException) as caught:
|
||||
fred._request("series", {"series_id": "DGS10"})
|
||||
return caught.value
|
||||
|
||||
def test_http_error_message_carries_no_key(self):
|
||||
response = mock.Mock(status_code=502)
|
||||
response.raise_for_status.side_effect = requests.HTTPError(
|
||||
f"502 Server Error for url: https://api.stlouisfed.org/fred/series?api_key={_KEY}",
|
||||
response=response,
|
||||
)
|
||||
with mock.patch.dict("os.environ", {"FRED_API_KEY": _KEY}), \
|
||||
mock.patch("tradingagents.dataflows.utils.requests.get", return_value=response), \
|
||||
pytest.raises(requests.HTTPError) as caught:
|
||||
fred._request("series", {"series_id": "DGS10"})
|
||||
exc = caught.value
|
||||
assert _KEY not in str(exc) and _KEY not in repr(exc)
|
||||
# The response and request carry the full URL, so they are not attached.
|
||||
assert exc.response is None and exc.request is None
|
||||
assert exc.__cause__ is None and exc.__context__ is None # no chain holds the key
|
||||
|
||||
def test_connection_error_before_any_response_carries_no_key(self):
|
||||
exc = self._raises(requests.ConnectionError(
|
||||
f"Max retries exceeded with url: /fred/series?series_id=DGS10&api_key={_KEY}"))
|
||||
assert isinstance(exc, requests.ConnectionError)
|
||||
assert _KEY not in str(exc) and exc.__context__ is None
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_error_without_the_key_in_its_message_still_drops_the_request():
|
||||
# Some timeout messages omit the URL, but the attached request still has it.
|
||||
import requests as rq
|
||||
req = rq.Request("GET", f"https://api.stlouisfed.org/fred/series?api_key={_KEY}").prepare()
|
||||
with mock.patch.dict("os.environ", {"FRED_API_KEY": _KEY}), \
|
||||
mock.patch("tradingagents.dataflows.utils.requests.get", side_effect=rq.Timeout("Read timed out.", request=req)), \
|
||||
pytest.raises(rq.Timeout) as caught:
|
||||
fred._request("series", {"series_id": "DGS10"})
|
||||
assert caught.value.request is None
|
||||
|
||||
Reference in New Issue
Block a user