Skip to content

Commit 41cc26a

Browse files
committed
♻️ Modernize code with Python 3.11 type hints and exception handling
Upgrade codebase with improved type annotations and error handling This comprehensive refactoring embraces Python 3.11 features: - Update type hints using modern union syntax (str | None) and collection types - Improve exception handling with proper error chaining and more specific errors - Replace LRU cache with explicit caching in SignalRGBClient for better control - Enhance logging in example code with structured error handling - Convert assertions to pytest-style in tests for better diagnostics - Rename SignalRGBException to SignalRGBError for consistency - Fix command naming in CLI to use explicit names (next_effect vs next) - Improve error propagation with proper exception context - Add proper file encoding handling for better compatibility
1 parent a10e6a3 commit 41cc26a

File tree

11 files changed

+670
-588
lines changed

11 files changed

+670
-588
lines changed

examples/effect_cycler.py

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,54 @@
1+
import contextlib
2+
import logging
13
import time
24

3-
from signalrgb.client import SignalRGBClient
5+
from signalrgb.client import SignalRGBClient, SignalRGBException
6+
from signalrgb.model import Effect
47

58

6-
def cycle_effects(client, duration=10):
9+
def cycle_effects(client: SignalRGBClient, duration: int = 10) -> None:
710
"""
811
Cycle through all available effects, applying each for a specified duration.
912
1013
Args:
1114
client (SignalRGBClient): An initialized SignalRGBClient object.
1215
duration (int): The duration in seconds to apply each effect. Defaults to 10 seconds.
1316
"""
14-
print("Fetching available effects...")
1517
effects = client.get_effects()
1618

17-
print(f"Found {len(effects)} effects. Starting cycle...")
1819
for effect in effects:
1920
effect_name = effect.attributes.name
20-
print(f"Applying effect: {effect_name}")
2121

2222
try:
2323
client.apply_effect_by_name(effect_name)
24-
print(
25-
f"Effect '{effect_name}' applied successfully. Waiting for {duration} seconds..."
26-
)
2724
time.sleep(duration)
28-
except Exception as e:
29-
print(f"Error applying effect '{effect_name}': {str(e)}")
25+
except SignalRGBException as e:
26+
logging.warning("Failed to apply effect %s: %s", effect_name, e)
3027

3128

32-
def main():
29+
def main() -> None:
3330
# Initialize the SignalRGB client
3431
client = SignalRGBClient(host="hyperia.home", port=16038)
32+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
33+
34+
# Initialize initial_effect to None
35+
initial_effect: Effect | None = None
3536

3637
try:
3738
# Get the initial effect
3839
initial_effect = client.get_current_effect()
39-
print(f"Initial effect: {initial_effect.attributes.name}")
4040

4141
# Cycle through effects
4242
cycle_effects(client, duration=5) # Change duration as needed
4343

44-
except Exception as e:
45-
print(f"An error occurred: {str(e)}")
44+
except SignalRGBException as e:
45+
logging.exception("Error during effect cycling: %s", e)
4646

4747
finally:
4848
# Restore the initial effect
49-
try:
50-
client.apply_effect_by_name(initial_effect.attributes.name)
51-
print(f"Restored initial effect: {initial_effect.attributes.name}")
52-
except Exception as e:
53-
print(f"Error restoring initial effect: {str(e)}")
49+
with contextlib.suppress(SignalRGBException):
50+
if initial_effect:
51+
client.apply_effect_by_name(initial_effect.attributes.name)
5452

5553

5654
if __name__ == "__main__":

pyproject.toml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "signalrgb"
33
version = "0.9.7"
44
description = "A Python client for SignalRGB"
55
readme = "README.md"
6-
requires-python = ">=3.9"
6+
requires-python = ">=3.11"
77
license = { text = "Apache-2.0" }
88
authors = [{ name = "Stefanie Jane", email = "[email protected]" }]
99
dependencies = [
@@ -25,6 +25,7 @@ dev = [
2525
"mkdocstrings[python]>=0.29.0",
2626
"mypy>=1.15.0",
2727
"pytest-cov>=6.0.0",
28+
"types-requests>=2.31.0.6",
2829
"types-setuptools>=75.8.2.20250305",
2930
"semver>=3.0.4",
3031
]
@@ -63,7 +64,7 @@ exclude_lines = [
6364

6465
# Type checking configuration
6566
[tool.mypy]
66-
python_version = "3.9"
67+
python_version = "3.11"
6768
# Basic type checking
6869
warn_return_any = true
6970
warn_unused_configs = true
@@ -110,7 +111,7 @@ check_untyped_defs = false
110111
[tool.ruff]
111112
# General settings
112113
line-length = 120
113-
target-version = "py39"
114+
target-version = "py311"
114115
src = ["signalrgb", "tests"]
115116
extend-exclude = [".venv", "docs"]
116117

@@ -241,7 +242,7 @@ docstring-code-line-length = 80
241242

242243
# Pylint configuration - only for things Ruff can't handle
243244
[tool.pylint]
244-
py-version = "3.9"
245+
py-version = "3.11"
245246
jobs = 2
246247
max-line-length = 120
247248
disable = [
@@ -271,6 +272,7 @@ disable = [
271272
"wrong-import-position",
272273
"import-outside-toplevel",
273274
"too-many-positional-arguments",
275+
"redefined-builtin", # ConnectionError shadows requests.ConnectionError, it's fine
274276
"fixme",
275277

276278
# Additional suppressions for practicality

0 commit comments

Comments
 (0)