Skip to content

Commit 852696d

Browse files
committed
feat: update to bonaparte 1.0.1
- update and cleanup code while we're at it Signed-off-by: Felix Kaechele <felix@kaechele.ca>
1 parent cb12940 commit 852696d

File tree

17 files changed

+2997
-321
lines changed

17 files changed

+2997
-321
lines changed

.github/workflows/lint.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ jobs:
1414
runs-on: "ubuntu-latest"
1515
steps:
1616
- name: "Checkout the repository"
17-
uses: "actions/checkout@v4.2.2"
17+
uses: "actions/checkout@v5"
1818

1919
- name: "Set up Python"
20-
uses: "actions/setup-python@v5.4.0"
20+
uses: "actions/setup-python@v5"
2121
with:
2222
python-version: "3.13"
2323
cache: "pip"

.github/workflows/release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
contents: write
1313

1414
steps:
15-
- uses: actions/checkout@v4
15+
- uses: actions/checkout@v5
1616
with:
1717
fetch-depth: 0
1818

@@ -23,12 +23,12 @@ jobs:
2323
# - Create GitHub release
2424
- name: Python Semantic Release
2525
id: release
26-
uses: python-semantic-release/python-semantic-release@v9.17.0
26+
uses: python-semantic-release/python-semantic-release@v10.3.1
2727
with:
2828
github_token: ${{ secrets.GITHUB_TOKEN }}
2929

3030
- name: Publish package distributions to GitHub Releases
31-
uses: python-semantic-release/upload-to-gh-release@main
31+
uses: python-semantic-release/publish-action@v10.3.1
3232
if: steps.release.outputs.released == 'true'
3333
with:
3434
github_token: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/validate.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
runs-on: "ubuntu-latest"
1818
steps:
1919
- name: "Checkout the repository"
20-
uses: "actions/checkout@v4.2.2"
20+
uses: "actions/checkout@v5"
2121

2222
- name: "Run hassfest validation"
2323
uses: "home-assistant/actions/hassfest@master"
@@ -27,7 +27,7 @@ jobs:
2727
runs-on: "ubuntu-latest"
2828
steps:
2929
- name: "Checkout the repository"
30-
uses: "actions/checkout@v4.2.2"
30+
uses: "actions/checkout@v5"
3131

3232
- name: "Run HACS validation"
3333
uses: "hacs/action@main"

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
repos:
33
- repo: https://github.com/pre-commit/pre-commit-hooks
4-
rev: v5.0.0
4+
rev: v6.0.0
55
hooks:
66
- id: debug-statements
77
- id: check-builtin-literals
@@ -14,12 +14,12 @@ repos:
1414
- id: end-of-file-fixer
1515
- id: trailing-whitespace
1616
- repo: https://github.com/kaechele/pre-commit-mirror-prettier
17-
rev: v3.4.2
17+
rev: v3.6.2
1818
hooks:
1919
- id: prettier
2020
exclude: "(CHANGELOG.md)"
2121
- repo: https://github.com/astral-sh/ruff-pre-commit
22-
rev: v0.8.6
22+
rev: v0.12.9
2323
hooks:
2424
- id: ruff-format
2525
name: ruff (format)

.ruff.toml

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# The contents of this file is based on https://github.com/home-assistant/core/blob/dev/pyproject.toml
2+
3+
target-version = "py313"
4+
5+
[lint]
6+
select = [
7+
"A001", # Variable {name} is shadowing a Python builtin
8+
"ASYNC", # flake8-async
9+
"B002", # Python does not support the unary prefix increment
10+
"B005", # Using .strip() with multi-character strings is misleading
11+
"B007", # Loop control variable {name} not used within loop body
12+
"B009", # Do not call getattr with a constant attribute value. It is not any safer than normal property access.
13+
"B014", # Exception handler with duplicate exception
14+
"B015", # Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it.
15+
"B017", # pytest.raises(BaseException) should be considered evil
16+
"B018", # Found useless attribute access. Either assign it to a variable or remove it.
17+
"B023", # Function definition does not bind loop variable {name}
18+
"B024", # `{name}` is an abstract base class, but it has no abstract methods or properties
19+
"B026", # Star-arg unpacking after a keyword argument is strongly discouraged
20+
"B032", # Possible unintentional type annotation (using :). Did you mean to assign (using =)?
21+
"B035", # Dictionary comprehension uses static key
22+
"B904", # Use raise from to specify exception cause
23+
"B905", # zip() without an explicit strict= parameter
24+
"BLE",
25+
"C", # complexity
26+
"COM818", # Trailing comma on bare tuple prohibited
27+
"D", # docstrings
28+
"DTZ003", # Use datetime.now(tz=) instead of datetime.utcnow()
29+
"DTZ004", # Use datetime.fromtimestamp(ts, tz=) instead of datetime.utcfromtimestamp(ts)
30+
"E", # pycodestyle
31+
"F", # pyflakes/autoflake
32+
"F541", # f-string without any placeholders
33+
"FLY", # flynt
34+
"FURB", # refurb
35+
"G", # flake8-logging-format
36+
"I", # isort
37+
"INP", # flake8-no-pep420
38+
"ISC", # flake8-implicit-str-concat
39+
"ICN001", # import concentions; {name} should be imported as {asname}
40+
"LOG", # flake8-logging
41+
"N804", # First argument of a class method should be named cls
42+
"N805", # First argument of a method should be named self
43+
"N815", # Variable {name} in class scope should not be mixedCase
44+
"PERF", # Perflint
45+
"PGH", # pygrep-hooks
46+
"PIE", # flake8-pie
47+
"PL", # pylint
48+
"PT", # flake8-pytest-style
49+
"PTH", # flake8-pathlib
50+
"PYI", # flake8-pyi
51+
"RET", # flake8-return
52+
"RSE", # flake8-raise
53+
"RUF005", # Consider iterable unpacking instead of concatenation
54+
"RUF006", # Store a reference to the return value of asyncio.create_task
55+
"RUF007", # Prefer itertools.pairwise() over zip() when iterating over successive pairs
56+
"RUF008", # Do not use mutable default values for dataclass attributes
57+
"RUF010", # Use explicit conversion flag
58+
"RUF013", # PEP 484 prohibits implicit Optional
59+
"RUF016", # Slice in indexed access to type {value_type} uses type {index_type} instead of an integer
60+
"RUF017", # Avoid quadratic list summation
61+
"RUF018", # Avoid assignment expressions in assert statements
62+
"RUF019", # Unnecessary key check before dictionary access
63+
"RUF020", # {never_like} | T is equivalent to T
64+
"RUF021", # Parenthesize a and b expressions when chaining and and or together, to make the precedence clear
65+
"RUF022", # Sort __all__
66+
"RUF023", # Sort __slots__
67+
"RUF024", # Do not pass mutable objects as values to dict.fromkeys
68+
"RUF026", # default_factory is a positional-only argument to defaultdict
69+
"RUF030", # print() call in assert statement is likely unintentional
70+
"RUF032", # Decimal() called with float literal argument
71+
"RUF033", # __post_init__ method with argument defaults
72+
"RUF034", # Useless if-else condition
73+
"RUF100", # Unused `noqa` directive
74+
"RUF101", # noqa directives that use redirected rule codes
75+
"RUF200", # Failed to parse pyproject.toml: {message}
76+
"S102", # Use of exec detected
77+
"S103", # bad-file-permissions
78+
"S108", # hardcoded-temp-file
79+
"S306", # suspicious-mktemp-usage
80+
"S307", # suspicious-eval-usage
81+
"S313", # suspicious-xmlc-element-tree-usage
82+
"S314", # suspicious-xml-element-tree-usage
83+
"S315", # suspicious-xml-expat-reader-usage
84+
"S316", # suspicious-xml-expat-builder-usage
85+
"S317", # suspicious-xml-sax-usage
86+
"S318", # suspicious-xml-mini-dom-usage
87+
"S319", # suspicious-xml-pull-dom-usage
88+
"S601", # paramiko-call
89+
"S602", # subprocess-popen-with-shell-equals-true
90+
"S604", # call-with-shell-equals-true
91+
"S608", # hardcoded-sql-expression
92+
"S609", # unix-command-wildcard-injection
93+
"SIM", # flake8-simplify
94+
"SLF", # flake8-self
95+
"SLOT", # flake8-slots
96+
"T100", # Trace found: {name} used
97+
"T20", # flake8-print
98+
"TC", # flake8-type-checking
99+
"TID", # Tidy imports
100+
"TRY", # tryceratops
101+
"UP", # pyupgrade
102+
"UP031", # Use format specifiers instead of percent format
103+
"UP032", # Use f-string instead of `format` call
104+
"W", # pycodestyle
105+
]
106+
107+
ignore = [
108+
"ASYNC109", # Async function definition with a `timeout` parameter Use `asyncio.timeout` instead
109+
"ASYNC110", # Use `asyncio.Event` instead of awaiting `asyncio.sleep` in a `while` loop
110+
"D202", # No blank lines allowed after function docstring
111+
"D203", # 1 blank line required before class docstring
112+
"D213", # Multi-line docstring summary should start at the second line
113+
"D406", # Section name should end with a newline
114+
"D407", # Section name underlining
115+
"E501", # line too long
116+
117+
"PLC1901", # {existing} can be simplified to {replacement} as an empty string is falsey; too many false positives
118+
"PLR0911", # Too many return statements ({returns} > {max_returns})
119+
"PLR0912", # Too many branches ({branches} > {max_branches})
120+
"PLR0913", # Too many arguments to function call ({c_args} > {max_args})
121+
"PLR0915", # Too many statements ({statements} > {max_statements})
122+
"PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable
123+
"PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target
124+
"PT011", # pytest.raises({exception}) is too broad, set the `match` parameter or use a more specific exception
125+
"PT018", # Assertion should be broken down into multiple parts
126+
"RUF001", # String contains ambiguous unicode character.
127+
"RUF002", # Docstring contains ambiguous unicode character.
128+
"RUF003", # Comment contains ambiguous unicode character.
129+
#"RUF015", # Prefer next(...) over single element slice
130+
#"SIM102", # Use a single if statement instead of nested if statements
131+
"SIM103", # Return the condition {condition} directly
132+
"SIM108", # Use ternary operator {contents} instead of if-else-block
133+
"SIM115", # Use context handler for opening files
134+
135+
# Moving imports into type-checking blocks can mess with pytest.patch()
136+
"TC001", # Move application import {} into a type-checking block
137+
"TC002", # Move third-party import {} into a type-checking block
138+
"TC003", # Move standard library import {} into a type-checking block
139+
# Quotes for typing.cast generally not necessary, only for performance critical paths
140+
"TC006", # Add quotes to type expression in typing.cast()
141+
142+
"TRY003", # Avoid specifying long messages outside the exception class
143+
"TRY400", # Use `logging.exception` instead of `logging.error`
144+
# Ignored due to performance: https://github.com/charliermarsh/ruff/issues/2923
145+
"UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)`
146+
147+
# May conflict with the formatter, https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
148+
"W191",
149+
"E111",
150+
"E114",
151+
"E117",
152+
"D206",
153+
"D300",
154+
"Q",
155+
"COM812",
156+
"COM819",
157+
158+
# Disabled because ruff does not understand type of __all__ generated by a function
159+
"PLE0605",
160+
]
161+
162+
[lint.flake8-pytest-style]
163+
fixture-parentheses = false
164+
165+
[lint.mccabe]
166+
max-complexity = 25

custom_components/napoleon_efire/__init__.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,28 @@
33
from __future__ import annotations
44

55
import logging
6+
from typing import TYPE_CHECKING
67

78
from bonaparte import Fireplace
8-
99
from homeassistant.components import bluetooth
1010
from homeassistant.components.bluetooth.match import ADDRESS, BluetoothCallbackMatcher
11-
from homeassistant.config_entries import ConfigEntry
1211
from homeassistant.const import (
1312
CONF_ADDRESS,
1413
CONF_PASSWORD,
1514
EVENT_HOMEASSISTANT_STOP,
1615
Platform,
1716
)
18-
from homeassistant.core import Event, HomeAssistant, callback
17+
from homeassistant.core import callback
1918
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
2019

2120
from .const import CONF_FEATURES, DOMAIN
2221
from .coordinator import NapoleonEfireDataUpdateCoordinator
2322
from .models import FireplaceData
2423

24+
if TYPE_CHECKING:
25+
from homeassistant.config_entries import ConfigEntry
26+
from homeassistant.core import Event, HomeAssistant
27+
2528
PLATFORMS: list[Platform] = [
2629
Platform.FAN,
2730
Platform.LIGHT,
@@ -35,11 +38,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
3538
"""Set up Napoleon eFIRE from a config entry."""
3639
address: str = entry.data[CONF_ADDRESS]
3740
password: str = entry.data[CONF_PASSWORD]
38-
ble_device = bluetooth.async_ble_device_from_address(hass, address.upper(), True)
41+
ble_device = bluetooth.async_ble_device_from_address(
42+
hass, address.upper(), connectable=True
43+
)
3944
if not ble_device:
40-
raise ConfigEntryNotReady(
41-
f"Could not find eFIRE fireplace controller with address {address}"
42-
)
45+
msg = f"Could not find eFIRE fireplace controller with address {address}"
46+
raise ConfigEntryNotReady(msg)
4347

4448
fireplace = Fireplace(ble_device, compatibility_mode=False)
4549
fireplace.set_features(set(entry.data[CONF_FEATURES]))

custom_components/napoleon_efire/config_flow.py

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,34 @@
33
from __future__ import annotations
44

55
import logging
6-
from typing import Any
6+
from typing import TYPE_CHECKING
77

8-
from bleak.backends.device import BLEDevice
8+
import voluptuous as vol
99
from bluetooth_data_tools import human_readable_name
1010
from bonaparte import Fireplace
1111
from bonaparte.const import Feature
12-
import voluptuous as vol
13-
14-
from homeassistant import config_entries
1512
from homeassistant.components.bluetooth import (
1613
BluetoothServiceInfoBleak,
1714
async_discovered_service_info,
1815
)
19-
from homeassistant.const import CONF_ADDRESS, CONF_PASSWORD
20-
from homeassistant.data_entry_flow import FlowResult
16+
from homeassistant.config_entries import ConfigFlow
17+
from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_PASSWORD
2118
from homeassistant.helpers import selector
2219

2320
from .const import CONF_FEATURES, DOMAIN, LOCAL_NAME_PREFIX, UNSUPPORTED_FEATURES
2421

22+
if TYPE_CHECKING:
23+
from bleak.backends.device import BLEDevice
24+
from homeassistant.config_entries import ConfigFlowResult
25+
2526
AVAILABLE_FEATURES = [f.value for f in Feature if f.value not in UNSUPPORTED_FEATURES]
2627

2728
FEATURES_SCHEMA = {
2829
# replace selector.BooleanSelector() with bool once
2930
# https://github.com/home-assistant/frontend/issues/15536 is fixed
30-
vol.Required(feature, default=False): selector.BooleanSelector()
31+
vol.Required(feature, default=False): selector.BooleanSelector(
32+
selector.BackupLocationSelectorConfig()
33+
)
3134
for feature in AVAILABLE_FEATURES
3235
}
3336

@@ -46,7 +49,7 @@ async def async_validate_fireplace_or_error(
4649
return {}
4750

4851

49-
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
52+
class EfireConfigFlow(ConfigFlow, domain=DOMAIN):
5053
"""Handle a config flow for Napoleon eFIRE."""
5154

5255
VERSION = 1
@@ -56,11 +59,11 @@ def __init__(self) -> None:
5659
super().__init__()
5760
self._discovery_info: BluetoothServiceInfoBleak | None = None
5861
self._discovered_devices: dict[str, BluetoothServiceInfoBleak] = {}
59-
self._init_info: dict[str, Any] | None = None
62+
self._init_info: dict[str, str] | None = None
6063

6164
async def async_step_bluetooth(
6265
self, discovery_info: BluetoothServiceInfoBleak
63-
) -> FlowResult:
66+
) -> ConfigFlowResult:
6467
"""Handle the bluetooth discovery step."""
6568
await self.async_set_unique_id(discovery_info.address)
6669
self._abort_if_unique_id_configured()
@@ -73,8 +76,8 @@ async def async_step_bluetooth(
7376
return await self.async_step_user()
7477

7578
async def async_step_user(
76-
self, user_input: dict[str, Any] | None = None
77-
) -> FlowResult:
79+
self, user_input: dict[str, str] | None = None
80+
) -> ConfigFlowResult:
7881
"""Handle the user step to pick discovered device."""
7982
errors: dict[str, str] = {}
8083

@@ -93,7 +96,7 @@ async def async_step_user(
9396
)
9497
):
9598
self._init_info = {
96-
"name": discovery_info.name,
99+
CONF_NAME: discovery_info.name,
97100
CONF_ADDRESS: discovery_info.address,
98101
CONF_PASSWORD: password,
99102
}
@@ -135,8 +138,8 @@ async def async_step_user(
135138
)
136139

137140
async def async_step_select_features(
138-
self, user_input: dict[str, Any] | None = None
139-
) -> FlowResult:
141+
self, user_input: dict[str, bool] | None = None
142+
) -> ConfigFlowResult:
140143
"""Allow the user to select the features available on the fireplace."""
141144
if user_input is not None and self._init_info is not None:
142145
supported_features = [

0 commit comments

Comments
 (0)