-
Notifications
You must be signed in to change notification settings - Fork 0
Quality Scale Guide
Guide to achieving Bronze, Silver, Gold, and Platinum tiers in the Home Assistant Integration Quality Scale.
The Integration Quality Scale measures integration quality across four tiers. Higher tiers indicate better reliability, maintainability, and user experience.
Why it matters:
- Users trust higher-tier integrations
- Core integrations must meet minimum standards
- Community integrations benefit from quality benchmarks
- HACS may require Bronze tier minimum
Bronze → Silver → Gold → Platinum
↓ ↓ ↓ ↓
Basic Reliable Robust Excellent
Each tier builds on the previous. You must meet ALL requirements of lower tiers.
Goal: Functional integration with basic quality standards.
Required: UI-based configuration (no YAML)
class MyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle config flow."""
VERSION = 1
async def async_step_user(self, user_input=None):
"""Handle user step."""
# ImplementationFiles needed:
config_flow.py-
strings.json(UI text) translations/en.json
Required: Tests for setup and config flow
Minimum tests:
async def test_setup_entry(hass, mock_config_entry):
"""Test setup from config entry."""
async def test_unload_entry(hass, mock_config_entry):
"""Test unload config entry."""
async def test_config_flow_user_step(hass):
"""Test config flow user step."""Run: pytest tests/ -v
Required: Code passes linting
ruff check custom_components/my_integration/
ruff format custom_components/my_integration/No errors allowed.
Required fields in manifest.json:
{
"domain": "my_integration",
"name": "My Integration",
"version": "1.0.0",
"codeowners": ["@username"],
"config_flow": true,
"documentation": "https://...",
"integration_type": "device|hub|service",
"iot_class": "local_polling|...",
"issue_tracker": "https://..."
}- Config flow implemented (
config_flow.py) -
async_setup_entry()with tests - Ruff linting passes (zero errors)
-
manifest.jsonproperly configured - Unique IDs for all entities
- Basic README.md with setup instructions
- At least one entity platform
# Lint check
ruff check .
# Test check
pytest tests/
# Manual check
python scripts/verify_environment.pyGoal: Handles errors gracefully, provides good user experience.
Connection errors → UpdateFailed:
async def _async_update_data(self):
try:
return await self.client.async_get_data()
except ConnectionError as err:
raise UpdateFailed(f"Connection error: {err}") from errAuthentication errors → ConfigEntryAuthFailed:
except AuthenticationError as err:
raise ConfigEntryAuthFailed("Invalid credentials") from errEntities show unavailable when device offline:
@property
def available(self) -> bool:
"""Return if entity is available."""
return (
super().available
and self._device_id in self.coordinator.data
)Graceful degradation:
async def _async_update_data(self):
try:
data = await self.client.async_get_data()
return data
except UpdateFailed:
# Log once, don't spam
if not self._logged_offline:
_LOGGER.warning("Device offline")
self._logged_offline = True
raiseIn config flow:
errors = {}
try:
await validate_input(user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"In strings.json:
{
"error": {
"cannot_connect": "Failed to connect to device",
"invalid_auth": "Invalid credentials"
}
}README.md should include:
- Common error messages
- How to resolve connection issues
- How to debug authentication
- Where to get logs
- Proper error handling (ConnectionError → UpdateFailed)
- Authentication failures raise ConfigEntryAuthFailed
- Entity availability properly managed
- Coordinator handles offline devices gracefully
- Troubleshooting documentation in README
- Log-once patterns for repeated errors
- Config flow error messages are user-friendly
Goal: Production-ready with comprehensive testing and async architecture.
NO blocking I/O:
# ❌ WRONG
def get_data():
return requests.get(url).json()
# ✅ CORRECT
async def get_data():
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
return await resp.json()If no async library exists:
async def sync_operation():
return await hass.async_add_executor_job(
blocking_function, arg1, arg2
)pytest tests/ --cov=custom_components.my_integration --cov-report=htmlOpen htmlcov/index.html - must show ≥90%
Cover:
- All config flow steps
- All error scenarios
- Setup/unload
- Coordinator updates
- All entity platforms
- Entity availability
All functions typed:
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
) -> bool:
"""Set up from config entry."""Mypy strict mode passes:
mypy custom_components/my_integration/
# 0 errorsNo redundant polls:
- Use DataUpdateCoordinator (centralizes updates)
- Set appropriate update_interval (not too frequent)
- Use
always_update=Falseif data implements__eq__
Batch operations:
# Fetch all device data in one call
data = await client.async_get_all_devices()Group entities by device:
@property
def device_info(self) -> DeviceInfo:
"""Return device information."""
return DeviceInfo(
identifiers={(DOMAIN, self._device_id)},
name=self.coordinator.data[self._device_id]["name"],
manufacturer="Manufacturer",
model="Model",
sw_version="1.0.0",
)Allow reconfiguration:
async def async_get_options_flow(entry):
"""Get options flow handler."""
return MyOptionsFlow(entry)- Full async codebase (no blocking I/O)
- Test coverage ≥ 90%
- Complete type annotations (mypy strict passes)
- Efficient data handling (no redundant polls)
- Device registry integration
- All entity platforms have tests
- Options flow for reconfiguration
Goal: Exemplary code quality and maintainability.
Follows all HA style guidelines:
- Clear, self-documenting code
- Meaningful variable names
- Logical code organization
- No code smells
Comprehensive docstrings:
class MyCoordinator(DataUpdateCoordinator):
"""Coordinator for My Integration.
Fetches data from device API every 30 seconds.
Handles connection errors and authentication failures.
"""
async def _async_update_data(self) -> dict[str, Any]:
"""Fetch data from API endpoint.
Returns:
Dictionary mapping device IDs to device data.
Raises:
ConfigEntryAuthFailed: If authentication fails.
UpdateFailed: If connection fails.
"""Inline comments for complex logic:
# Calculate exponential backoff: 2^retry * base_delay
delay = (2 ** retry_count) * base_delayMinimal coordinator updates:
- Only update when data changes
- Use event-driven updates if possible
- Implement
always_update=False
Memory efficiency:
- Don't store unnecessary data
- Clean up resources properly
Commitment to:
- Respond to issues promptly
- Keep dependencies updated
- Fix bugs quickly
- Add features based on feedback
No security vulnerabilities:
- Credentials stored in ConfigEntry.data
- No credentials in logs
- Input validation
- Secure API communication (HTTPS)
Support multiple languages:
translations/
├── en.json
├── de.json
├── fr.json
└── ...
Provide diagnostic data:
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
) -> dict:
"""Return diagnostics."""
return {
"coordinator_data": coordinator.data,
"device_info": {...},
}- Code follows all HA style guidelines
- Clear docstrings and comments
- Performance optimized (minimal coordinator updates)
- Active maintenance plan documented
- Security review completed
- Localization for multiple languages
- Integration with HA diagnostics system
Use the QUALITY_CHECKLIST.md file to track:
## Bronze Tier
- [x] Config flow implemented
- [x] Tests written
- [ ] Linting passes
...- Start with Bronze - Don't skip fundamentals
- Test as you code - Don't save testing for later
- Use the example integration - It meets Gold tier standards
- Ask for help - Use Discussions for questions
- Iterate - Improve tier by tier, don't rush Platinum
Target: Minimum Bronze, aim for Silver/Gold for best user experience.