This repository is a Home Assistant custom integration, not the HA core. These guidelines are adapted from HA core standards for custom component development.
- Compatibility: Python 3.13+
- Language features: Use the newest features when possible (pattern matching, type hints, f-strings, dataclasses, walrus operator)
- Formatting/linting: Ruff (
./scripts/lint) - Type checking: MyPy
- Testing: pytest with
pytest-homeassistant-custom-component - Language: American English, sentence case
- File headers: Short and concise
"""Integration for Moen Smart Water Network.""" - Method/function docstrings: Required for all public methods
- Comments: Explain "why" not "what", keep lines under 80 characters when possible
- No periods at end of messages
- No integration names/domains (added automatically)
- No sensitive data (keys, tokens, passwords)
- Use debug level for non-user-facing messages
- Lazy logging:
_LOGGER.debug("This is a log message with %s", variable)
- All external I/O operations must be async
- Avoid sleeping in loops, awaiting in loops (use
gatherinstead), and blocking calls - Use
asyncio.sleep()instead oftime.sleep() - Use executor for blocking I/O:
await hass.async_add_executor_job(blocking_function, args) @callbackdecorator for event-loop-safe functions
custom_components/moen_smart_water_network/
├── __init__.py # Entry point with async_setup_entry
├── manifest.json # Integration metadata and dependencies
├── const.py # Domain and constants
├── config_flow.py # UI configuration flow
├── coordinator.py # Data update coordinator + MQTT handling
├── entity.py # Base entity class
├── sensor.py # Sensor platform
├── binary_sensor.py # Binary sensor platform
├── switch.py # Switch platform (zone controls)
├── diagnostics.py # Diagnostic data collection
├── services.yaml # Service definitions
├── strings.json # User-facing text and translations
└── moen_api/ # API client library
├── __init__.py
├── auth.py # OAuth2 + AWS Cognito authentication
├── client.py # HTTP API client
├── mqtt.py # AWS IoT MQTT shadow subscriptions
├── models.py # Data models
├── const.py # API constants
└── exceptions.py # Custom exceptions
class MyCoordinator(DataUpdateCoordinator[MyData]):
def __init__(self, hass: HomeAssistant, client: MyClient, config_entry: ConfigEntry) -> None:
super().__init__(
hass,
logger=LOGGER,
name=DOMAIN,
update_interval=timedelta(seconds=15),
config_entry=config_entry,
)
async def _async_update_data(self):
try:
return await self.client.fetch_data()
except ApiError as err:
raise UpdateFailed(f"API communication error: {err}") from err- Use
UpdateFailedfor API errors,ConfigEntryAuthFailedfor auth issues - Always pass
config_entryparameter to coordinator
- Unique IDs: Required for every entity, use device serial numbers or stable identifiers (never IPs, hostnames, or names)
- Entity naming: Set
_attr_has_entity_name = True, use_attr_translation_keyfor translatable names - Device classes: Set appropriate
_attr_device_classwhen available - Entity categories: Use
EntityCategory.DIAGNOSTICfor technical/system entities - State handling: Use
Nonefor unknown values (not "unknown" or "unavailable") - Availability: Implement
availableproperty instead of using "unavailable" state - Event subscriptions: Subscribe in
async_added_to_hassusingself.async_on_remove(), never in__init__
_attr_device_info = DeviceInfo(
identifiers={(DOMAIN, device.id)},
name=device.name,
manufacturer="Moen",
model="Smart Water Network",
sw_version=device.version,
)- Store connection-critical config in
ConfigEntry.data, non-critical settings inConfigEntry.options - Always validate user input and test connection during config flow
- Prevent duplicate configurations with
_abort_if_unique_id_configured()
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)- Always redact sensitive data (tokens, passwords, coordinates)
return async_redact_data(data, {"api_key", "password", "access_token", "refresh_token"})
- Choose specific exceptions:
ServiceValidationError,HomeAssistantError,ConfigEntryNotReady,ConfigEntryAuthFailed,ConfigEntryError - Keep try blocks minimal - process data outside the try/catch
- Avoid bare exceptions except in config flows and background tasks
- Setup failure pattern:
try: await device.async_setup() except (asyncio.TimeoutError, TimeoutException) as ex: raise ConfigEntryNotReady(f"Timeout connecting") from ex except AuthFailed as ex: raise ConfigEntryAuthFailed(f"Credentials expired") from ex
- Location:
tests/ - Framework:
pytest-homeassistant-custom-component - Async mode:
asyncio_mode = auto(configured inpytest.ini) - Mock all external dependencies (API calls, MQTT connections)
- Test through proper integration setup using fixtures, not direct entity construction
- Never access
hass.datadirectly in tests
See CLAUDE.md for setup, lint, test, and develop commands.