Skip to content

Refactor Control4 API interaction methods for consistency and clarity#52

Merged
lawtancool merged 20 commits into
masterfrom
2.x-refactor
Feb 20, 2026
Merged

Refactor Control4 API interaction methods for consistency and clarity#52
lawtancool merged 20 commits into
masterfrom
2.x-refactor

Conversation

@lawtancool
Copy link
Copy Markdown
Owner

@lawtancool lawtancool commented Feb 20, 2026

  • Updated method names everywhere to follow Python naming conventions (snake_case).
  • Add type annotations
  • 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.

- 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.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 C4Director to manage temporary aiohttp.ClientSession creation/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.

Comment thread pyControl4/account.py
Comment thread pyControl4/director.py Outdated
Comment thread pyControl4/director.py Outdated
Comment thread pyControl4/director.py Outdated
Comment thread pyControl4/director.py Outdated
Comment thread pyControl4/director.py
Comment thread pyControl4/director.py Outdated
Comment thread pyControl4/websocket.py
Comment thread pyControl4/account.py Outdated
Comment thread pyControl4/account.py Outdated
lawtancool and others added 4 commits February 19, 2026 21:05
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…consistency; update item_callbacks property in C4Websocket to return a read-only view.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment thread pyControl4/websocket.py Outdated
Comment thread pyControl4/director.py Outdated
Comment thread pyControl4/director.py
Comment thread pyControl4/director.py Outdated
Comment thread pyControl4/room.py Outdated
Comment thread pyControl4/alarm.py
Comment thread pyControl4/alarm.py
Comment thread pyControl4/director.py
Comment thread pyControl4/__init__.py
Comment thread pyControl4/alarm.py
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment thread pyControl4/alarm.py
Comment thread pyControl4/alarm.py
Comment thread pyControl4/account.py
Comment thread pyControl4/error_handling.py
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment thread pyControl4/director.py Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment thread pyControl4/room.py
Comment thread pyControl4/room.py
@lawtancool
Copy link
Copy Markdown
Owner Author

lawtancool commented Feb 20, 2026

@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:

  1. Rename all methods to be snake_case instead of camelCase to be more Pythonic
  2. director.py public methods that used to return raw JSON strings now return parsed Python lists and dicts
  3. alarm.py and climate.py public methods that used to return comma-separated lists as raw strings now return parsed Python lists

in addition to the change you introduced in #50 about returning None instead of undefined, which I also considered to be a breaking change.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment thread README.md
Comment thread pyControl4/director.py
Comment thread pyControl4/websocket.py
Comment thread pyControl4/fan.py
Comment thread pyControl4/error_handling.py
Comment thread pyControl4/alarm.py Outdated
Comment thread pyControl4/websocket.py
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment thread pyControl4/websocket.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment thread pyControl4/room.py
Comment thread pyControl4/websocket.py
Comment thread pyControl4/room.py
@davidrecordon
Copy link
Copy Markdown
Contributor

@lawtancool I went through the full diff and cross-referenced against the Director API to make sure types line up. This looks solid. The _get_session context manager, sync error handling, and type annotations are all clear improvements.

A few notes:

Everything checks out on the type casts

I initially worried about the bool(value) calls on variables like ALARM_STATE, LIGHT_STATE, the blind booleans, etc. — but after verifying against the Director API response types, these are all fine:

  • ALARM_STATE, LIGHT_STATE, ContactState, RelayState → JSON int (0/1)bool(0) = False, bool(1) = True
  • IS_ON (fan), Fully Closed, Open, etc. (blinds) → JSON boolbool(True/False)

No issues there.

Couple of things to note in the changelog

  1. C4Entity.__init__ parameter rename from C4Director to director — anyone using keyword arguments (C4Light(C4Director=d, item_id=100)) would get a TypeError. Positional callers are fine, but worth calling out.
  2. Nice touch adding the deprecation note on get_arm_state() pointing to get_partition_state() / get_armed_type().

Happy to write some tests

We have basically zero test coverage beyond the Undefined handling and websocket SSL tests. I'd be happy to write tests for:

  • error_handling.py — pure sync logic after this PR, no mocking needed, and you're rewriting the internals. Highest value for validating the _extract_error_info / _raise_error refactor.
  • director.py — the new _get_session context manager, get_item_variable_value edge cases (empty response, list var_name joining).

Want me to put those together on a branch off yours?

HA integration migration

I looked through the HA Control4 integration and the migration is clean:

  • ~37 method renames across 7 files (mechanical find-and-replace)
  • Remove json.loads() wrappers on get_all_item_info() / get_ui_configuration() calls since those return parsed objects in 2.0
  • Update test fixtures to return parsed dicts instead of JSON strings
  • The FAN_MODES_LIST .split(",") in HA's climate.py is unaffected — HA reads that via the coordinator polling path (get_all_item_variable_value), which still returns raw Director values. Only C4Climate.get_fan_modes() does the list parsing, and HA doesn't call that.

Happy to do the HA PR once 2.0 is published.

@lawtancool
Copy link
Copy Markdown
Owner Author

Thanks for the review @davidrecordon! Yes, I would really appreciate if you could raise a new PR for some tests!

@davidrecordon
Copy link
Copy Markdown
Contributor

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>
@lawtancool lawtancool merged commit 449dec3 into master Feb 20, 2026
12 checks passed
@lawtancool lawtancool deleted the 2.x-refactor branch February 20, 2026 19:21
@lawtancool
Copy link
Copy Markdown
Owner Author

Thanks @davidrecordon, let me know if you'd like a review on the HA PR for 2.0

@davidrecordon
Copy link
Copy Markdown
Contributor

@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.

@lawtancool
Copy link
Copy Markdown
Owner Author

@davidrecordon I just shipped v2.0.2, should be finalized now. Go ahead and open the HA PR when you're ready!

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.

3 participants