Skip to content

Commit 7f1084a

Browse files
committed
chore: migrate from isort/black/pylint to ruff
1 parent 800d764 commit 7f1084a

64 files changed

Lines changed: 695 additions & 1290 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.pre-commit-config.yaml

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,36 +19,39 @@ repos:
1919
- id: trailing-whitespace
2020
- repo: https://github.com/astral-sh/uv-pre-commit
2121
# uv version.
22-
rev: 0.9.5
22+
rev: 0.9.8
2323
hooks:
2424
- id: uv-lock
2525
- repo: https://github.com/codespell-project/codespell
2626
rev: v2.4.1
2727
hooks:
2828
- id: codespell
2929
exclude: uv.lock
30-
- repo: https://github.com/charliermarsh/ruff-pre-commit
31-
rev: v0.14.1
30+
- repo: https://github.com/astral-sh/ruff-pre-commit
31+
# Ruff version
32+
rev: v0.14.4
3233
hooks:
34+
# Run the linter
3335
- id: ruff-check
3436
args: [ --fix ]
37+
# Run the formatter
3538
- id: ruff-format
36-
- repo: https://github.com/PyCQA/isort
37-
rev: 7.0.0
38-
hooks:
39-
- id: isort
40-
- repo: https://github.com/psf/black
41-
rev: 25.9.0
42-
hooks:
43-
- id: black
44-
- repo: local
45-
hooks:
46-
- id: pylint
47-
name: pylint
48-
entry: uv run pylint
49-
language: system
50-
types: [python]
51-
require_serial: true
39+
# - repo: https://github.com/PyCQA/isort # Replacing isort with ruff
40+
# rev: 7.0.0
41+
# hooks:
42+
# - id: isort
43+
# - repo: https://github.com/psf/black # Replacing black with ruff
44+
# rev: 25.9.0
45+
# hooks:
46+
# - id: black
47+
# - repo: local # Replacing pylint with ruff
48+
# hooks:
49+
# - id: pylint
50+
# name: pylint
51+
# entry: uv run pylint
52+
# language: system
53+
# types: [python]
54+
# require_serial: true
5255
- repo: https://github.com/pre-commit/mirrors-mypy
5356
rev: v1.18.2
5457
hooks:

.vscode/extensions.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"recommendations": [
33
"ms-python.python",
4-
"ms-python.black-formatter",
54
"ms-python.mypy-type-checker",
6-
"njpwerner.autodocstring"
5+
"njpwerner.autodocstring",
6+
"charliermarsh.ruff"
77
]
88
}

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"mypy-type-checker.importStrategy": "fromEnvironment",
1212
"python.analysis.typeCheckingMode": "basic",
1313
"[python]": {
14-
"editor.defaultFormatter": "ms-python.black-formatter",
14+
"editor.defaultFormatter": "charliermarsh.ruff",
1515
"editor.formatOnSave": true
1616
}
1717
}

pyomnilogic_local/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""PyOmniLogic-Local: A Python library for interacting with Hayward OmniLogic Local API."""
2+
13
from __future__ import annotations
24

35
from .collections import EffectsCollection, LightEffectsCollection
@@ -12,9 +14,9 @@
1214
__all__ = [
1315
"EffectsCollection",
1416
"LightEffectsCollection",
17+
"OmniConnectionError",
18+
"OmniEquipmentNotInitializedError",
19+
"OmniEquipmentNotReadyError",
1520
"OmniLogic",
1621
"OmniLogicLocalError",
17-
"OmniEquipmentNotReadyError",
18-
"OmniEquipmentNotInitializedError",
19-
"OmniConnectionError",
2022
]

pyomnilogic_local/_base.py

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
11
from __future__ import annotations
22

33
import logging
4-
from typing import TYPE_CHECKING, Generic, TypeVar, cast
4+
from typing import TYPE_CHECKING, cast
55

6-
from pyomnilogic_local.api.api import OmniLogicAPI
7-
from pyomnilogic_local.models import MSPEquipmentType, Telemetry
6+
from pyomnilogic_local.models import MSPEquipmentType
87
from pyomnilogic_local.models.telemetry import TelemetryType
98
from pyomnilogic_local.omnitypes import BackyardState
109

1110
if TYPE_CHECKING:
11+
from pyomnilogic_local.api.api import OmniLogicAPI
12+
from pyomnilogic_local.models import Telemetry
1213
from pyomnilogic_local.omnilogic import OmniLogic
1314

14-
# Define type variables for generic equipment types
15-
MSPConfigT = TypeVar("MSPConfigT", bound=MSPEquipmentType)
16-
TelemetryT = TypeVar("TelemetryT", bound=TelemetryType | None)
17-
1815

1916
_LOGGER = logging.getLogger(__name__)
2017

2118

22-
class OmniEquipment(Generic[MSPConfigT, TelemetryT]):
19+
class OmniEquipment[MSPConfigT: MSPEquipmentType, TelemetryT: TelemetryType | None]:
2320
"""Base class for all OmniLogic equipment.
2421
2522
This is an abstract base class that provides common functionality for all equipment
@@ -77,7 +74,7 @@ def __init__(self, omni: OmniLogic, mspconfig: MSPConfigT, telemetry: Telemetry
7774
@property
7875
def _api(self) -> OmniLogicAPI:
7976
"""Access the OmniLogic API through the parent controller."""
80-
return self._omni._api # pylint: disable=protected-access
77+
return self._omni._api
8178

8279
@property
8380
def bow_id(self) -> int | None:
@@ -113,13 +110,11 @@ def is_ready(self) -> bool:
113110
"""
114111
# Check if backyard state allows equipment operations
115112
backyard_state = self._omni.backyard.telemetry.state
116-
if backyard_state in (
113+
return backyard_state not in (
117114
BackyardState.SERVICE_MODE,
118115
BackyardState.CONFIG_MODE,
119116
BackyardState.TIMED_SERVICE_MODE,
120-
):
121-
return False
122-
return True
117+
)
123118

124119
def update(self, mspconfig: MSPConfigT, telemetry: Telemetry | None) -> None:
125120
"""Update both the configuration and telemetry data for the equipment."""
@@ -130,14 +125,17 @@ def update(self, mspconfig: MSPConfigT, telemetry: Telemetry | None) -> None:
130125
self._update_equipment(mspconfig, telemetry)
131126

132127
def _update_equipment(self, mspconfig: MSPConfigT, telemetry: Telemetry | None) -> None:
133-
"""Hook to allow classes to trigger updates of sub-equipment."""
128+
"""Allow a class to trigger updates of sub-equipment.
129+
130+
This method can be overridden by subclasses to update any child equipment.
131+
"""
134132

135133
def update_config(self, mspconfig: MSPConfigT) -> None:
136134
"""Update the configuration data for the equipment."""
137135
try:
138136
# If the Equipment has subdevices, we don't store those as part of this device's config
139137
# They will get parsed and stored as their own equipment instances
140-
self.mspconfig = cast(MSPConfigT, mspconfig.without_subdevices())
138+
self.mspconfig = cast("MSPConfigT", mspconfig.without_subdevices())
141139
except AttributeError:
142140
self.mspconfig = mspconfig
143141

@@ -148,9 +146,9 @@ def update_telemetry(self, telemetry: Telemetry) -> None:
148146
# Extract the specific telemetry for this equipment from the full telemetry object
149147
# Note: Some equipment (like sensors) don't have their own telemetry, so this may be None
150148
if (specific_telemetry := telemetry.get_telem_by_systemid(self.mspconfig.system_id)) is not None:
151-
self.telemetry = cast(TelemetryT, specific_telemetry)
149+
self.telemetry = cast("TelemetryT", specific_telemetry)
152150
else:
153-
self.telemetry = cast(TelemetryT, None)
151+
self.telemetry = cast("TelemetryT", None)
154152

155153
def __repr__(self) -> str:
156154
"""Return a string representation of the equipment for debugging.
@@ -162,8 +160,7 @@ def __repr__(self) -> str:
162160
parts = [f"system_id={self.system_id!r}", f"name={self.name!r}"]
163161

164162
# Include state if the equipment has telemetry with a state attribute
165-
if hasattr(self, "telemetry") and self.telemetry is not None:
166-
if (state := getattr(self.telemetry, "state", None)) is not None:
167-
parts.append(f"state={state!r}")
163+
if (hasattr(self, "telemetry") and self.telemetry is not None) and ((state := getattr(self.telemetry, "state", None)) is not None):
164+
parts.append(f"state={state!r}")
168165

169166
return f"{class_name}({', '.join(parts)})"

pyomnilogic_local/api/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
"""API module for interacting with Hayward OmniLogic pool controllers.
2+
3+
This module provides the OmniLogicAPI class, which allows for local
4+
control and monitoring of Hayward OmniLogic and OmniHub pool controllers
5+
over a local network connection via the UDP XML API.
6+
"""
7+
18
from __future__ import annotations
29

310
from .api import OmniLogicAPI

pyomnilogic_local/api/api.py

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
1-
# pylint: disable=too-many-positional-arguments
21
from __future__ import annotations
32

43
import asyncio
54
import logging
65
import xml.etree.ElementTree as ET
7-
from typing import Literal, overload
6+
from typing import TYPE_CHECKING, Literal, overload
87

98
from pyomnilogic_local.models.filter_diagnostics import FilterDiagnostics
109
from pyomnilogic_local.models.mspconfig import MSPConfig
1110
from pyomnilogic_local.models.telemetry import Telemetry
12-
13-
from ..omnitypes import (
11+
from pyomnilogic_local.omnitypes import (
1412
ColorLogicBrightness,
1513
ColorLogicSpeed,
16-
HeaterMode,
17-
LightShows,
1814
MessageType,
1915
)
16+
2017
from .constants import (
2118
DEFAULT_CONTROLLER_PORT,
2219
DEFAULT_RESPONSE_TIMEOUT,
@@ -27,9 +24,12 @@
2724
XML_ENCODING,
2825
XML_NAMESPACE,
2926
)
30-
from .exceptions import OmniValidationException
27+
from .exceptions import OmniValidationError
3128
from .protocol import OmniLogicProtocol
3229

30+
if TYPE_CHECKING:
31+
from pyomnilogic_local.omnitypes import HeaterMode, LightShows
32+
3333
_LOGGER = logging.getLogger(__name__)
3434

3535

@@ -44,9 +44,11 @@ def _validate_temperature(temperature: int, param_name: str = "temperature") ->
4444
OmniValidationException: If temperature is out of range.
4545
"""
4646
if not isinstance(temperature, int):
47-
raise OmniValidationException(f"{param_name} must be an integer, got {type(temperature).__name__}")
47+
msg = f"{param_name} must be an integer, got {type(temperature).__name__}"
48+
raise OmniValidationError(msg)
4849
if not MIN_TEMPERATURE_F <= temperature <= MAX_TEMPERATURE_F:
49-
raise OmniValidationException(f"{param_name} must be between {MIN_TEMPERATURE_F}°F and {MAX_TEMPERATURE_F}°F, got {temperature}°F")
50+
msg = f"{param_name} must be between {MIN_TEMPERATURE_F}°F and {MAX_TEMPERATURE_F}°F, got {temperature}°F"
51+
raise OmniValidationError(msg)
5052

5153

5254
def _validate_speed(speed: int, param_name: str = "speed") -> None:
@@ -60,9 +62,11 @@ def _validate_speed(speed: int, param_name: str = "speed") -> None:
6062
OmniValidationException: If speed is out of range.
6163
"""
6264
if not isinstance(speed, int):
63-
raise OmniValidationException(f"{param_name} must be an integer, got {type(speed).__name__}")
65+
msg = f"{param_name} must be an integer, got {type(speed).__name__}"
66+
raise OmniValidationError(msg)
6467
if not MIN_SPEED_PERCENT <= speed <= MAX_SPEED_PERCENT:
65-
raise OmniValidationException(f"{param_name} must be between {MIN_SPEED_PERCENT} and {MAX_SPEED_PERCENT}, got {speed}")
68+
msg = f"{param_name} must be between {MIN_SPEED_PERCENT} and {MAX_SPEED_PERCENT}, got {speed}"
69+
raise OmniValidationError(msg)
6670

6771

6872
def _validate_id(id_value: int, param_name: str) -> None:
@@ -76,9 +80,11 @@ def _validate_id(id_value: int, param_name: str) -> None:
7680
OmniValidationException: If ID is invalid.
7781
"""
7882
if not isinstance(id_value, int):
79-
raise OmniValidationException(f"{param_name} must be an integer, got {type(id_value).__name__}")
83+
msg = f"{param_name} must be an integer, got {type(id_value).__name__}"
84+
raise OmniValidationError(msg)
8085
if id_value < 0:
81-
raise OmniValidationException(f"{param_name} must be non-negative, got {id_value}")
86+
msg = f"{param_name} must be non-negative, got {id_value}"
87+
raise OmniValidationError(msg)
8288

8389

8490
class OmniLogicAPI:
@@ -96,11 +102,14 @@ def __init__(
96102
OmniValidationException: If parameters are invalid.
97103
"""
98104
if not controller_ip:
99-
raise OmniValidationException("controller_ip cannot be empty")
105+
msg = "controller_ip cannot be empty"
106+
raise OmniValidationError(msg)
100107
if not isinstance(controller_port, int) or controller_port <= 0 or controller_port > 65535:
101-
raise OmniValidationException(f"controller_port must be between 1 and 65535, got {controller_port}")
108+
msg = f"controller_port must be between 1 and 65535, got {controller_port}"
109+
raise OmniValidationError(msg)
102110
if not isinstance(response_timeout, (int, float)) or response_timeout <= 0:
103-
raise OmniValidationException(f"response_timeout must be positive, got {response_timeout}")
111+
msg = f"response_timeout must be positive, got {response_timeout}"
112+
raise OmniValidationError(msg)
104113

105114
self.controller_ip = controller_ip
106115
self.controller_port = controller_port
@@ -179,6 +188,7 @@ async def async_get_filter_diagnostics(self, pool_id: int, equipment_id: int, ra
179188
Args:
180189
pool_id (int): The Pool/BodyOfWater ID that you want to address
181190
equipment_id (int): Which equipment_id within that Pool to address
191+
raw (bool): Do not parse the response into a Pydantic model, just return the raw XML. Defaults to False.
182192
183193
Returns:
184194
FilterDiagnostics|str: Either a parsed .models.mspconfig.FilterDiagnostics object or a str depending on arg raw
@@ -233,7 +243,7 @@ async def async_set_heater(
233243
equipment_id: int,
234244
temperature: int,
235245
) -> None:
236-
"""Set the temperature for a heater on the Omni
246+
"""Set the temperature for a heater on the Omni.
237247
238248
Args:
239249
pool_id (int): The Pool/BodyOfWater ID that you want to address
@@ -332,7 +342,7 @@ async def async_set_heater_enable(
332342
equipment_id: int,
333343
enabled: int | bool,
334344
) -> None:
335-
"""async_set_heater_enable handles sending a SetHeaterEnable XML API call to the Hayward Omni pool controller
345+
"""Send a SetHeaterEnable XML API call to the Hayward Omni pool controller.
336346
337347
Args:
338348
pool_id (int): The Pool/BodyOfWater ID that you want to address
@@ -381,11 +391,11 @@ async def async_set_equipment(
381391
For Variable Speed Pumps, you can optionally provide an int from 0-100 to set the speed percentage with 0 being Off.
382392
The interpretation of value depends on the piece of equipment being targeted.
383393
is_countdown_timer (bool, optional): For potential future use, included to be "API complete". Defaults to False.
384-
startTimeHours (int, optional): For potential future use, included to be "API complete". Defaults to 0.
385-
startTimeMinutes (int, optional): For potential future use, included to be "API complete". Defaults to 0.
386-
endTimeHours (int, optional): For potential future use, included to be "API complete". Defaults to 0.
387-
endTimeMinutes (int, optional): For potential future use, included to be "API complete". Defaults to 0.
388-
daysActive (int, optional): For potential future use, included to be "API complete". Defaults to 0.
394+
start_time_hours (int, optional): For potential future use, included to be "API complete". Defaults to 0.
395+
start_time_minutes (int, optional): For potential future use, included to be "API complete". Defaults to 0.
396+
end_time_hours (int, optional): For potential future use, included to be "API complete". Defaults to 0.
397+
end_time_minutes (int, optional): For potential future use, included to be "API complete". Defaults to 0.
398+
days_active (int, optional): For potential future use, included to be "API complete". Defaults to 0.
389399
recurring (bool, optional): For potential future use, included to be "API complete". Defaults to False.
390400
"""
391401
body_element = ET.Element("Request", {"xmlns": XML_NAMESPACE})
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
11
from __future__ import annotations
22

33

4-
class OmniLogicException(Exception):
4+
class OmniLogicError(Exception):
55
"""Base exception for all OmniLogic errors."""
66

77

8-
class OmniProtocolException(OmniLogicException):
8+
class OmniProtocolError(OmniLogicError):
99
"""Protocol-level errors during communication with the OmniLogic controller."""
1010

1111

12-
class OmniTimeoutException(OmniProtocolException):
12+
class OmniTimeoutError(OmniProtocolError):
1313
"""Timeout occurred while waiting for a response from the controller."""
1414

1515

16-
class OmniMessageFormatException(OmniProtocolException):
16+
class OmniMessageFormatError(OmniProtocolError):
1717
"""Received a malformed or invalid message from the controller."""
1818

1919

20-
class OmniFragmentationException(OmniProtocolException):
20+
class OmniFragmentationError(OmniProtocolError):
2121
"""Error occurred during message fragmentation or reassembly."""
2222

2323

24-
class OmniConnectionException(OmniLogicException):
24+
class OmniConnectionError(OmniLogicError):
2525
"""Network connection error occurred."""
2626

2727

28-
class OmniValidationException(OmniLogicException):
28+
class OmniValidationError(OmniLogicError):
2929
"""Invalid parameter or configuration value provided."""
3030

3131

32-
class OmniCommandException(OmniLogicException):
32+
class OmniCommandError(OmniLogicError):
3333
"""Error occurred while executing a command on the controller."""

0 commit comments

Comments
 (0)