Add async support using httpx#150
Conversation
|
If the switch to The changes look good so far, less overhaul needed than i expected. Only thing i'm concerned about is, how do we ensure feature parity between sync and async in future PRs? Perhaps (for a future PR) we need to look into testing the package in both sync and async mode with Github actions so any change will get tested for sync and async functionality. |
|
Good points! The feature parity concern is actually one of the main reasons I lean toward the shared-base approach in this PR over the duplicated-files approach in #151. With the coroutine-passthrough pattern here, ~60 methods live in the shared base class and work for both sync and async automatically. A future contributor adding a new API method just adds it to the base — both modes get it for free. Only the handful of HTTP-touching methods (6 currently) need explicit async overrides. With duplicated files, every new method would need to be added in two places, which is exactly the drift scenario you're worried about. I also considered auto-generating async from sync (like Regarding the breaking change — the main concern is that Home Assistant (and potentially other consumers) currently catch For the transition we could do a deprecation period: have the library exceptions inherit from both the new For testing, I agree we should set up mocked HTTP responses and run the same test suite against both sync and async classes. Happy to put that together in a follow-up PR. |
Replaces requests with httpx and adds async counterparts for all API classes (AsyncGrowattApi, AsyncOpenApiV1, AsyncMin, AsyncSph). Uses a shared base class pattern where regular def methods return self._request(...) — producing a coroutine in async context — so ~60 methods are shared with zero duplication. Only methods that chain async calls need explicit async overrides (6 total). Closes indykoning#149 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap httpx transport errors in GrowattApiError hierarchy so consumers depend on the library API rather than the underlying HTTP library. New exceptions: - GrowattApiError: base for all transport/HTTP errors - GrowattApiConnectionError: connection failures - GrowattApiTimeoutError: request timeouts - GrowattApiStatusError: HTTP 4XX/5XX responses (with status_code attr) For backwards compatibility during the requests-to-httpx transition, GrowattApiError inherits from requests.RequestException when the requests package is installed. A one-time deprecation warning is logged to prompt consumers to migrate their except clauses. Also adds set_classic_inverter_active_power_rate() and set_classic_inverter_on_off() convenience methods from upstream. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
c2f0b26 to
3827ab7
Compare
The method returns a dict (with 'data' and 'totalData' keys) from the API's "back" field, not a list. The annotation incorrectly stated list[dict[str, Any]]. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The method returns a list of devices (confirmed by TLX examples iterating
over the result), not a dict. Fix annotation to list[dict[str, Any]] and
default from {} to [].
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts: # growattServer/base_api.py
…ples - Fix update_inverter_setting passing settings_parameters instead of merged - Fix update_noah_settings passing settings_parameters instead of merged - Wrap v1_request (sync and async) with GrowattApiError exception hierarchy so transport errors are consistently wrapped regardless of code path - Stringify date object in plant_power_overview params - Replace httpx.HTTPError catches in all examples with GrowattApiError - Update docstring Raises sections to reference GrowattApiError - Update docs/README.md migration guidance to reference GrowattApiError Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add .pyi type stubs for async classes (PEP 561) providing strict typing - Fix plant_list/device_list MRO conflict with explicit overrides (non-breaking) - Refactor v1_request to delegate to _request with extract callback - Remove event hooks, use explicit raise_for_status() - Switch to warnings.warn(DeprecationWarning) from logging.warning - Add DEFAULT_TIMEOUT=30s with configurable timeout parameter - Add __all__ to devices package for proper exports - Add async detail()/settings() overrides in async device classes - Change process_response return type to Any - Change abstract_device api type to _OpenApiV1Base Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move Callable import into TYPE_CHECKING block (TC003) - Remove docstrings and __future__ annotations from .pyi stubs (PYI021/PYI044) - Use Self return type for __aenter__ in stubs (PYI034) - Remove unused noqa directives from devices __init__ (RUF100) - Remove unused httpx import from V1 stub (F401) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- pytest + pytest-asyncio test suite using httpx.MockTransport - Tests for _request(), v1_request(), exception mapping, device methods (Min, Sph), coroutine-passthrough parity, and helper functions - CI workflow: pytest on Python 3.12/3.13, stubtest for .pyi sync - Fix _GrowattApiBase._request signature (missing files param) - Move deprecation warning to import-time to avoid firing during except Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both GrowattApi._request and AsyncGrowattApi._request were missing the files keyword argument defined in the base class signature. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
requestswithhttpxfor both sync and async HTTP supportAsyncGrowattApi,AsyncOpenApiV1,AsyncMin,AsyncSphsession(httpx.AsyncClient) for session sharing (e.g. Home Assistant integrations)Trade-offs to discuss
This is a draft PR for discussion — see #149 for the broader conversation.
Breaking change: Replaces
requestswithhttpx, so consumers catchingrequests.exceptions.RequestExceptionneed to switch tohttpx.HTTPError.Complexity: The coroutine-passthrough pattern avoids code duplication but adds architectural complexity (shared base classes, MRO-dependent dispatch). An alternative approach is to duplicate sync files into async copies (how large SDKs like
openaido it), potentially with auto-generation — but that brings its own maintenance trade-offs.Key questions:
requests→httpxbreaking change acceptable, or should we keeprequestsfor sync and only usehttpxfor async?Test plan
requestsexception types)asyncio.run()andasync withcontext managerhttpx.AsyncClient🤖 Generated with Claude Code