ha(H2): исправления по ревью — несколько устройств, suggested values, YAML-import, типы ошибок
- critical: listen_port по умолчанию 0 (эфемерный) — несколько ConfigEntry не конфликтуют; тест двух записей на дефолтном порту - manifest: добавлен обязательный ключ codeowners - unload: set_log_handler(None) вынесен в executor (не блокирует loop); рефкаунт лог-пампа без гонки (старт после успешного setup) - config flow: возврат введённого в форму при ошибке (add_suggested_values_to_schema; секреты исключены), YAML-import подключён через async_setup + SOURCE_IMPORT, ProvisionError.kind вместо разбора русского текста, валидация device_port/key_id из JSON - trial: требует получения всех базовых свойств шаблона (не пустой ok), безопасное создание Session - py-ci.sh: отсутствие .venv-ha — ошибка (PY_CI_SKIP_HA=1 для пропуска); pytest.ini testpaths
This commit is contained in:
@@ -9,6 +9,7 @@ from dataclasses import dataclass, field
|
||||
from typing import Iterable
|
||||
|
||||
from pyfglair import Config, Error, Prop, Session, State, Template, Value
|
||||
from pyfglair.templates import template_info
|
||||
|
||||
BASE_PROPS: tuple[Prop, ...] = (
|
||||
Prop.OPERATION_MODE,
|
||||
@@ -56,7 +57,8 @@ def trial_connect(
|
||||
lock = threading.Lock()
|
||||
box = {"state": State.IDLE, "error": Error.NONE}
|
||||
values: dict[Prop, Value] = {}
|
||||
wanted = tuple(props)
|
||||
valid = {info.prop for info in template_info(template)}
|
||||
wanted = tuple(prop for prop in props if prop in valid)
|
||||
|
||||
def on_state(state: State, error: Error) -> None:
|
||||
with lock:
|
||||
@@ -67,21 +69,24 @@ def trial_connect(
|
||||
with lock:
|
||||
values[event.prop] = event.value
|
||||
|
||||
session = Session(
|
||||
Config(
|
||||
host=host,
|
||||
device_port=device_port,
|
||||
dsn=dsn,
|
||||
lanip_key=lanip_key,
|
||||
lanip_key_id=lanip_key_id,
|
||||
template=template,
|
||||
listen_port=listen_port,
|
||||
keepalive_ms=keepalive_ms,
|
||||
),
|
||||
loop=None,
|
||||
on_state=on_state,
|
||||
on_property=on_property,
|
||||
)
|
||||
try:
|
||||
session = Session(
|
||||
Config(
|
||||
host=host,
|
||||
device_port=device_port,
|
||||
dsn=dsn,
|
||||
lanip_key=lanip_key,
|
||||
lanip_key_id=lanip_key_id,
|
||||
template=template,
|
||||
listen_port=listen_port,
|
||||
keepalive_ms=keepalive_ms,
|
||||
),
|
||||
loop=None,
|
||||
on_state=on_state,
|
||||
on_property=on_property,
|
||||
)
|
||||
except Exception: # pragma: no cover - защитный путь
|
||||
return TrialResult(ok=False, reason="start_failed")
|
||||
try:
|
||||
try:
|
||||
started = session.start()
|
||||
@@ -110,20 +115,28 @@ def trial_connect(
|
||||
state=state, error=error,
|
||||
)
|
||||
|
||||
session.batch_begin()
|
||||
for prop in wanted:
|
||||
session.get_prop(prop)
|
||||
session.batch_commit()
|
||||
|
||||
prop_deadline = min(deadline, time.monotonic() + 2.0)
|
||||
while time.monotonic() < prop_deadline:
|
||||
with lock:
|
||||
if all(prop in values for prop in wanted):
|
||||
break
|
||||
time.sleep(0.05)
|
||||
if wanted:
|
||||
session.batch_begin()
|
||||
for prop in wanted:
|
||||
session.get_prop(prop)
|
||||
session.batch_commit()
|
||||
prop_deadline = min(deadline, time.monotonic() + 2.0)
|
||||
while time.monotonic() < prop_deadline:
|
||||
with lock:
|
||||
if all(prop in values for prop in wanted):
|
||||
break
|
||||
time.sleep(0.05)
|
||||
|
||||
with lock:
|
||||
snapshot = dict(values)
|
||||
missing = [prop for prop in wanted if prop not in snapshot]
|
||||
if missing:
|
||||
return TrialResult(
|
||||
ok=False,
|
||||
reason="properties_missing",
|
||||
state=State.ONLINE,
|
||||
properties=snapshot,
|
||||
)
|
||||
return TrialResult(ok=True, state=State.ONLINE, properties=snapshot)
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
Reference in New Issue
Block a user