Skip to content

fix(client,version): add timeout to all requests.get calls - #106

Open
mmclinton wants to merge 2 commits into
gridstatus:mainfrom
mmclinton:fix/requests-missing-timeout
Open

fix(client,version): add timeout to all requests.get calls#106
mmclinton wants to merge 2 commits into
gridstatus:mainfrom
mmclinton:fix/requests-missing-timeout

Conversation

@mmclinton

Copy link
Copy Markdown

Summary

This PR fixes two requests.get() calls that had no timeout set, found via static analysis with Bandit (rule B113: request_without_timeout, CWE-400).

Issues

1. API requests can hang indefinitely (gs_client.py)

_get_with_retry() calls requests.get() with no timeout. If a TCP connection is established but the server stalls, the call blocks forever, there is no OS-level default. requests.exceptions.Timeout is already listed in RETRIABLE_EXCEPTIONS, but it is only raised when a timeout is actually set, so the existing retry logic never fires in this scenario. A caller's process can hang permanently with no recovery path.

2. Import-time PyPI version check can also hang (version.py)

check_for_update() is called on import gridstatusio. The underlying requests.get("https://pypi.org/pypi/gridstatusio/json") had no timeout, so a slow or unreachable PyPI could make the library unimportable until the OS TCP timeout fires (potentially several minutes). GSIO_SKIP_VERSION_CHECK exists as an escape hatch, but a user hitting this cold won't know to use it.

Changes

  • GridStatusClient gains a timeout parameter (default 30.0 seconds) passed directly to requests.get(). The default can be overridden at construction time; set a lower value for latency-sensitive pipelines or None to restore the previous unbounded behavior.
  • The PyPI version check uses a hardcoded timeout=3. Network errors are now swallowed silently since the check is informational only.

Validation

  • bandit -r gridstatusio/ -ll reports no issues after these changes (previously two medium-severity findings).
  • No behavior change for successful requests; the timeout default of 30s is well above normal API response times.

All HTTP requests, the API client and the import-time PyPI version
check, were made without a timeout. A hung TCP connection would block
indefinitely with no recovery.

- Add timeout param (default 30s) to GridStatusClient constructor;
  Timeout exceptions feed into the existing retry logic automatically
- Add timeout=3 to the PyPI version check and swallow network errors
  silently since the check is non-critical
@Kladar
Kladar self-requested a review June 3, 2026 14:57
@mmclinton

Copy link
Copy Markdown
Author

Quick clarification on the failing Tests job (specifically test_version_check_enabled):

This failure is the one genuinely introduced by this PR. In gridstatusio/version.py, get_latest_version() now calls:

requests.get("https://pypi.org/pypi/gridstatusio/json", timeout=3)

but the test in gridstatusio/tests/test_version.py still asserts the old call signature:

mock_get.assert_called_once_with("https://pypi.org/pypi/gridstatusio/json")

which produces:

AssertionError: expected call not found.
Expected: get('https://pypi.org/pypi/gridstatusio/json')
  Actual: get('https://pypi.org/pypi/gridstatusio/json', timeout=3)

The fix is simply to update the assertion to include the new timeout=3 argument:

mock_get.assert_called_once_with(
    "https://pypi.org/pypi/gridstatusio/json",
    timeout=3,
)

I'll push this update shortly.

(Side note: the remaining 401: Missing API Key / No API key provided failures appear to be environmental, caused by the GRIDSTATUS_API_KEY_TEST secret being empty/unavailable in this run, and are not related to the code changes in this PR.)

the previous commit added a 3s timeout to the pypi version-check
request, but the mock assertion still expected the old single-arg
call signature. update the assertion so the test reflects the new
network-call contract.
@mmclinton

Copy link
Copy Markdown
Author

I updated the assertion to reflect the new network-call contract. Tests should no longer fail in this regard.

The only fails now are secret-gated assertions — 401: Missing API Key / No API key provided.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant