Skip to content

feat: assert minimum hyperliquid-python-sdk version at bot startup#155

Merged
keitaj merged 1 commit into
mainfrom
feat/sdk-minimum-version-check
May 31, 2026
Merged

feat: assert minimum hyperliquid-python-sdk version at bot startup#155
keitaj merged 1 commit into
mainfrom
feat/sdk-minimum-version-check

Conversation

@keitaj

@keitaj keitaj commented May 31, 2026

Copy link
Copy Markdown
Owner

Summary

  • Validate the installed hyperliquid-python-sdk version at startup, before any hyperliquid.* import. If it is below the required minimum (currently 0.23.0), exit immediately with a clear remediation message instead of crashing later inside the SDK with an opaque traceback.
  • The minimum lives as a module-scope constant (MINIMUM_HYPERLIQUID_SDK_VERSION) and is bumped in lockstep with the pyproject.toml pin. It is treated as a code-level invariant (analogous to requires-python), not a runtime-tunable parameter, so it is not exposed via config.
  • Motivation: when a deployment's venv is silently out of sync with pyproject.toml, the SDK can fail on upstream API metadata changes deep inside Exchange.__init__. A fast, named failure at startup avoids opaque crash loops and points the operator straight at the fix.

Changes

  • bot.py — Add MINIMUM_HYPERLIQUID_SDK_VERSION, _parse_version_tuple, _assert_sdk_version. The guard runs immediately after setup_logging() and before the first hyperliquid.* import.
  • tests/test_sdk_version_check.py — Unit tests for the version parser (standard, pre-release suffixes, padding, edge cases) and the gate (pass / exit / missing-package). One invariant test asserts the gate's minimum is consistent with the pin in pyproject.toml, so future SDK bumps cannot drift between the two.

Test plan

  • Unit tests added (tests/test_sdk_version_check.py, 16 cases)
  • flake8 passes (full project)
  • pytest passes (full project, 1108 tests)
  • No regressions in existing tests
  • Manual: MINIMUM_HYPERLIQUID_SDK_VERSION matches the hyperliquid-python-sdk==X.Y.Z pin in pyproject.toml

🤖 Generated with Claude Code

If a stale venv ends up with an older SDK than what pyproject.toml
pins, the bot can otherwise crash deep inside the SDK during exchange
initialization with an opaque traceback. Validating the installed
version up front turns that into a single, actionable error message
and prevents watchdog crash loops on a deployment that is silently
out of sync.

The minimum is a code-level invariant (like `requires-python`) rather
than a runtime-tunable parameter, so it is kept as a module-scope
constant in `bot.py` and bumped in lockstep with the pyproject pin.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@keitaj keitaj left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: SDK minimum version startup gate

Small, focused PR with a clear motivation (fail fast on SDK drift instead of dying inside the SDK with an opaque traceback). The gate is properly placed before any hyperliquid.* import, the constant is paired with a test that pins it to pyproject.toml, and the message text gives the operator a directly-usable remediation command. CI is clean (flake8 + 1108 tests). No blockers.


1. Lower-bound construction has unreachable but incorrect branch (nit)

tests/test_sdk_version_check.py L141-L147 (in test_below_minimum_exits_with_status_2):

else:
    lower = f"{major - 1}.0.0" if major > 0 else "0.0.0rc1"  # pragma: no cover

If MINIMUM_HYPERLIQUID_SDK_VERSION ever became (0, 0, 0), the fallback "0.0.0rc1" parses to (0, 0, 0) (suffixes are stripped) — i.e. equal to the minimum, not strictly less. The pragma: no cover keeps coverage clean, but the branch would silently produce a false positive if it ever fired.

Suggestion: since (0, 0, 0) is not a sensible minimum, drop the unreachable arm entirely and assert in the test setup that the minimum is non-zero:

major, minor, patch_ = gate.MINIMUM_HYPERLIQUID_SDK_VERSION
assert (major, minor, patch_) > (0, 0, 0), "minimum must be non-zero for this test"
if patch_ > 0:
    lower = f"{major}.{minor}.{patch_ - 1}"
elif minor > 0:
    lower = f"{major}.{minor - 1}.0"
else:
    lower = f"{major - 1}.0.0"  # major > 0 guaranteed

2. Module-scope invocation imposes an import-time side effect (nit)

bot.py L77:

_assert_sdk_version()

Any tool that imports bot (including future tests or scripts that want to reuse a helper from bot.py) will trip this gate. That's the intended behavior for the bot entry point, and the test file handles it by slicing bot.py's source — but it's a non-obvious constraint.

Suggestion: add a one-liner near the call site noting that the assert runs at import time, so a future contributor importing bot for its helpers isn't surprised:

# Runs at import time so any process that loads bot.py (including the bot
# itself under watchdog) is gated. Tests that need access to the helpers
# without triggering the assert load the gate block standalone.
_assert_sdk_version()

3. Minor DRY: min_str formatted three times (nit)

bot.py L55, L65: min_str = ".".join(str(n) for n in MINIMUM_HYPERLIQUID_SDK_VERSION) appears in both error branches. Not worth a dedicated helper for two call sites, but a module-scope constant MINIMUM_HYPERLIQUID_SDK_VERSION_STR would let the message and the constant stay in lockstep without rebuilding the string each time. Optional.


Verdict: LGTM

Three nits, no blockers and no should-fix. Author may merge as-is or address #1 in a follow-up; the unreachable branch is not exercised under the current minimum. CI passes locally (full flake8 clean, 1108 tests green including 16 new ones).

@keitaj keitaj merged commit 3987ff1 into main May 31, 2026
6 checks passed
@keitaj keitaj deleted the feat/sdk-minimum-version-check branch May 31, 2026 02:11
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