Coding Guidelines
Note: For complete coding standards, see
AGENTS.md.
Code Style
- Formatter/Linter: Ruff (replaces Black, Flake8, isort)
- Max line length: 120 characters
- Max complexity: 25 (McCabe)
- Target: Python 3.13
Run before committing:
./scripts/lint # Auto-fix issues
./scripts/release/hassfest # Validate integration structure
Naming Conventions
Class Names
All public classes MUST use the integration name as prefix.
This is a Home Assistant standard to avoid naming conflicts between integrations.
# ✅ CORRECT
class TibberPricesApiClient:
class TibberPricesDataUpdateCoordinator:
class TibberPricesSensor:
# ❌ WRONG - Missing prefix
class ApiClient:
class DataFetcher:
class TimeService:
When prefix is required:
- Public classes used across multiple modules
- All exception classes
- All coordinator and entity classes
- Data classes (dataclasses, NamedTuples) used as public APIs
When prefix can be omitted:
- Private helper classes within a single module (prefix with
_underscore) - Type aliases and callbacks (e.g.,
TimeServiceCallback) - Small internal NamedTuples for function returns
Private Classes:
If a helper class is ONLY used within a single module file, prefix it with underscore:
# ✅ Private class - used only in this file
class _InternalHelper:
"""Helper used only within this module."""
pass
# ❌ Wrong - no prefix but used across modules
class DataFetcher: # Should be TibberPricesDataFetcher
pass
Note: Currently (Nov 2025), this project has NO private classes - all classes are used across module boundaries.
Current Technical Debt:
Many existing classes lack the TibberPrices prefix. Before refactoring:
- Document the plan in
/planning/class-naming-refactoring.md - Use
multi_replace_string_in_filefor bulk renames - Test thoroughly after each module
See AGENTS.md for complete list of classes needing rename.
Import Order
- Python stdlib (specific types only)
- Third-party (
homeassistant.*,aiohttp) - Local (
.api,.const)
Critical Patterns
Time Handling
Always use dt_util from homeassistant.util:
from homeassistant.util import dt as dt_util
price_time = dt_util.parse_datetime(starts_at)
price_time = dt_util.as_local(price_time) # Convert to HA timezone
now = dt_util.now()
Translation Loading
# In __init__.py async_setup_entry:
await async_load_translations(hass, "en")
await async_load_standard_translations(hass, "en")
Price Data Enrichment
Always enrich raw API data:
from .price_utils import enrich_price_info_with_differences
enriched = enrich_price_info_with_differences(
price_info_data,
thresholds,
)
See AGENTS.md for complete guidelines.