Refactor Control4 API interaction methods for consistency and clarity#52
Conversation
- Updated method names in C4Director, C4Fan, C4Light, C4Relay, C4Room, and C4Websocket classes to follow Python naming conventions (snake_case). - Replaced direct session management with an async context manager in C4Director for better session handling. - Improved error handling in error_handling.py by extracting error information into a dedicated function.
There was a problem hiding this comment.
Pull request overview
This PR refactors the pyControl4 client API to standardize method naming (snake_case), improves HTTP session lifecycle handling in C4Director, and restructures error parsing/raising logic.
Changes:
- Renames public methods across Director + device classes to snake_case and updates tests accordingly.
- Introduces an async context manager in
C4Directorto manage temporaryaiohttp.ClientSessioncreation/closure. - Refactors error handling by extracting error-info parsing and centralized exception raising.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_undefined_handling.py | Updates tests to use the new snake_case Director/device method names. |
| pyControl4/director.py | Adds _get_session() context manager, renames request helpers, and returns parsed JSON for many getters. |
| pyControl4/error_handling.py | Extracts error info parsing and centralizes exception raising via helper functions. |
| pyControl4/account.py | Renames methods to snake_case, improves parsing/KeyError handling, and keeps request helpers consistent. |
| pyControl4/websocket.py | Renames imports + internal fields, adds typing, and adjusts message processing/callback plumbing. |
| pyControl4/light.py | Renames light APIs to snake_case and aligns Director method calls. |
| pyControl4/fan.py | Renames fan APIs to snake_case and aligns Director method calls. |
| pyControl4/relay.py | Renames relay APIs to snake_case and normalizes return typing/conversions. |
| pyControl4/room.py | Renames room APIs to snake_case and now returns parsed JSON for device lists. |
| pyControl4/climate.py | Renames climate APIs to snake_case and normalizes numeric conversions for setpoints/sensors. |
| pyControl4/blind.py | Renames blind APIs to snake_case and aligns Director method calls. |
| pyControl4/alarm.py | Renames security panel/contact sensor APIs to snake_case and adds some None handling. |
| pyControl4/init.py | Updates C4Entity ctor typing and parameter naming; imports C4Director for type usage. |
Comments suppressed due to low confidence (1)
pyControl4/account.py:165
- Spelling: "recieve" → "receive" in this error message.
msg = (
"Did not recieve an account bearer token. "
"Is your username/password correct?"
)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…consistency; update item_callbacks property in C4Websocket to return a read-only view.
…cumentation in C4Room for consistency
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 10 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…riable name types in get_item_variable_value methods; correct volume adjustment comment in C4Room
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
pyControl4/error_handling.py:86
- The _check_response_format function is marked as async but doesn't perform any await operations. Remove the async keyword and update the caller on line 151 to not use await when calling this function. This improves performance by avoiding unnecessary async overhead.
async def _check_response_format(response_text: str) -> str:
"""Known Control4 authentication API error message formats:
```json
{
"C4ErrorResponse": {
"code": 401,
"details": "Permission denied Bad credentials",
"message": "Permission denied",
"subCode": 0
}
}
```
```json
{
"code": 404,
"details": "Account with id:000000 not found in DB",
"message": "Account not found",
"subCode": 0
}```
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<C4ErrorResponse>
<code>401</code>
<details></details>
<message>Permission denied</message>
<subCode>0</subCode>
</C4ErrorResponse>
```
Known Control4 director error message formats:
```json
{
"error": "Unauthorized",
"details": "Expired or invalid token"
}
```
"""
if response_text.startswith("<"):
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@davidrecordon Could you take a look at this PR? This is going to be a major release 2.0.0 that introduces some breaking API changes. The relevant portions for you would be:
in addition to the change you introduced in #50 about returning |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@lawtancool I went through the full diff and cross-referenced against the Director API to make sure types line up. This looks solid. The A few notes: Everything checks out on the type castsI initially worried about the
No issues there. Couple of things to note in the changelog
Happy to write some testsWe have basically zero test coverage beyond the Undefined handling and websocket SSL tests. I'd be happy to write tests for:
Want me to put those together on a branch off yours? HA integration migrationI looked through the HA Control4 integration and the migration is clean:
Happy to do the HA PR once 2.0 is published. |
|
Thanks for the review @davidrecordon! Yes, I would really appreciate if you could raise a new PR for some tests! |
|
https://github.com/lawtancool/pyControl4/pull/55/changes :) Wrote first against 1.6 and then updated to land atop this PR. |
Tests were written first against the 1.x API to verify correctness on
the existing codebase, then adjusted for the 2.0 snake_case renames and
sync error_handling refactor so that this commit applies cleanly on top
of the 2.x-refactor branch.
test_error_handling.py — covers all branches of check_response_for_error:
- JSON and XML happy paths (no error keys)
- C4ErrorResponse format: BadCredentials, Unauthorized, NotFound,
unknown code fallback to C4Exception
- Flat JSON code format: NotFound, BadCredentials (details priority)
- Director error format: BadToken, Unauthorized, InvalidCategory,
unknown error fallback to C4Exception
- XML C4ErrorResponse parsing
- Exception hierarchy and message preservation
test_director.py — covers get_item_variable_value edge cases:
- Return types: int, bool, string, zero (not confused with None)
- Undefined normalization to None
- JSON null passthrough as None
- Empty response and invalid format raise ValueError
- List/tuple var_name joining
- get_all_item_variable_value mixed Undefined normalization
- get_all_item_info returns parsed JSON (2.0 behavior)
Also adds tests/conftest.py with shared director fixture and removes
the duplicate fixture from test_undefined_handling.py.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
|
Thanks @davidrecordon, let me know if you'd like a review on the HA PR for 2.0 |
|
@lawtancool home-assistant/core@dev...davidrecordon:home-assistant-core:upgrade-pycontrol4-2.0 works in my local environment, I'll wait on 2.0 being on PyPi to make a HA PR. But please give me feedback if anything in this diff seems off. |
|
@davidrecordon I just shipped v2.0.2, should be finalized now. Go ahead and open the HA PR when you're ready! |
Uh oh!
There was an error while loading. Please reload this page.