Skip to content

Harden API client resilience and packaging#44

Merged
Xerolux merged 1 commit into
mainfrom
Xe/harden-api-client
Jul 14, 2026
Merged

Harden API client resilience and packaging#44
Xerolux merged 1 commit into
mainfrom
Xe/harden-api-client

Conversation

@Xerolux

@Xerolux Xerolux commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary

  • harden duration, setpoint, climate-key, payload, parser, and numeric validation
  • make retries method-aware and improve authentication, circuit-breaker recovery, and prioritized per-client rate limiting
  • split the large API client into domain mixins while preserving all 51 public methods and signatures
  • modernize pyproject-only packaging, CI type checks, smoke coverage, and user documentation

Verification

  • python -m ruff check .
  • .tox/py312/bin/python -m mypy violet_poolcontroller_api
  • .tox/py312/bin/python -m pytest -q -s -W error::DeprecationWarning (203 passed)
  • .tox/py312/bin/python tests/test_api_smoke.py --user admin --password secret (66 passed, 0 failed, 0 skipped)
  • .tox/py312/bin/python -m build --no-isolation
  • .tox/py312/bin/python -m twine check dist/*

@Xerolux
Xerolux marked this pull request as ready for review July 14, 2026 19:30
@Xerolux
Xerolux merged commit 1a1617f into main Jul 14, 2026
6 checks passed
@Xerolux
Xerolux deleted the Xe/harden-api-client branch July 14, 2026 19:30

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the monolithic VioletPoolAPI client by splitting it into domain-specific mixins (_api_dosing.py, _api_outputs.py, _api_readings.py, and _api_system.py), modernizes the packaging configuration by migrating from setup.py to pyproject.toml, and enhances the RateLimiter with priority-based queuing. Additionally, it updates the CircuitBreaker to prevent concurrent recovery probes, switches basic authentication to use standard Authorization headers, and adds input sanitization for non-finite float values. The review feedback suggests robustly handling different line endings in _api_system.py by using splitlines() instead of split on newline, and validating/clamping the speed parameter in control_pump within _api_outputs.py to prevent sending invalid values.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

priority=API_PRIORITY_NORMAL,
)
text = resp.strip() if resp else ""
lines = text.split("\n") if text else []

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using split("\n") can leave trailing \r characters if the controller returns \r\n line endings. Using splitlines() is more robust as it automatically handles different line endings and strips the line break characters.

Suggested change
lines = text.split("\n") if text else []
lines = text.splitlines() if text else []

Comment on lines +361 to +366
return await self.set_switch_state(
key="PUMP",
action=action,
duration=duration,
last_value=speed,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

In control_pump, the speed parameter is passed directly to set_switch_state without any validation or clamping. If action is "ON" and speed is None or an invalid value (like 5), it could send an invalid speed to the controller. Clamping/validating speed if provided ensures consistency with set_pump_speed.

Suggested change
return await self.set_switch_state(
key="PUMP",
action=action,
duration=duration,
last_value=speed,
)
safe_speed = max(1, min(3, int(speed))) if speed is not None else None
return await self.set_switch_state(
key="PUMP",
action=action,
duration=duration,
last_value=safe_speed,
)

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