Conversation
Replaces dataclasses with Pydantic models for better validation and type safety. Updates auth, api_client, and mqtt_client to use new models.
Fixes whitespace, line length, and import sorting issues reported by ruff.
Auto-formatted code to pass CI checks.
There was a problem hiding this comment.
Pull Request Overview
This PR refactors the data models to use Pydantic instead of dataclasses, improving data validation, type safety, and maintainability. The changes replace manual field conversion logic with Pydantic's validator system and leverage automatic camelCase/snake_case aliasing.
Key Changes
- Replaced dataclasses with Pydantic
BaseModelacrossmodels.pyandauth.py - Introduced custom validators for device-specific field conversions (boolean encoding, temperature offsets, etc.)
- Migrated from
from_dict()factory methods to Pydantic'smodel_validate()for consistency
Reviewed Changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
src/nwp500/models.py |
Core refactor: replaced dataclasses with Pydantic models, converted field metadata to validators, simplified data conversion logic |
src/nwp500/auth.py |
Refactored authentication models to Pydantic, replaced __post_init__ with model_post_init, added validator for empty alias handling |
src/nwp500/api_client.py |
Updated model instantiation from from_dict() to model_validate() |
src/nwp500/mqtt_client.py |
Updated docstring examples to use snake_case field names |
src/nwp500/mqtt_subscriptions.py |
Updated field references and docstrings to use snake_case naming |
src/nwp500/__init__.py |
Removed deprecated EnergyUsageData and MonthlyEnergyData exports, added duplicate exports |
setup.cfg |
Added pydantic>=2.0.0 dependency |
src/nwp500/models.py
Outdated
| he_upper_off_diff_temp_setting: Div10 | ||
| he_lower_on_diff_temp_setting: Div10 = Field( | ||
| alias="heLowerOnTDiffempSetting" | ||
| ) # Handle typo |
There was a problem hiding this comment.
The inline comment "# Handle typo" is unclear. Consider expanding it to: "# Handle API typo: heLowerOnTDiffempSetting -> heLowerOnDiffTempSetting" to better document that this is a workaround for an API-level typo.
| ) # Handle typo | |
| ) # Handle API typo: heLowerOnTDiffempSetting -> heLowerOnDiffTempSetting |
src/nwp500/models.py
Outdated
| # Handle the typo field explicitly if needed, though alias handles it | ||
| if "heLowerOnTDiffempSetting" in data: | ||
| # Pydantic alias will handle this, but if we want to be safe | ||
| pass |
There was a problem hiding this comment.
The from_dict method contains dead code (lines 316-318). Since Pydantic's alias mechanism handles the typo field automatically, remove this unnecessary conditional check and simplify to just the docstring and return statement.
| # Handle the typo field explicitly if needed, though alias handles it | |
| if "heLowerOnTDiffempSetting" in data: | |
| # Pydantic alias will handle this, but if we want to be safe | |
| pass |
src/nwp500/models.py
Outdated
| heat_pump_usage: int | ||
| heat_element_usage: int |
There was a problem hiding this comment.
The EnergyUsageTotal model has breaking changes - field names have changed from heUsage/hpUsage to heat_element_usage/heat_pump_usage without aliases. Consider adding Field aliases to maintain backward compatibility: heat_pump_usage: int = Field(alias="hpUsage") and heat_element_usage: int = Field(alias="heUsage").
| heat_pump_usage: int | |
| heat_element_usage: int | |
| heat_pump_usage: int = Field(alias="hpUsage") | |
| heat_element_usage: int = Field(alias="heUsage") |
src/nwp500/models.py
Outdated
| day: int | ||
| total_usage: int | ||
| heat_pump_usage: int | ||
| heat_element_usage: int | ||
| heat_pump_time: int | ||
| heat_element_time: int |
There was a problem hiding this comment.
The EnergyUsageDay model appears to be a new structure replacing EnergyUsageData. If this is meant to parse the same API response data, field aliases may be needed to map from the API's camelCase format (e.g., hpUsage, heUsage, hpTime, heTime) to these snake_case names.
| day: int | |
| total_usage: int | |
| heat_pump_usage: int | |
| heat_element_usage: int | |
| heat_pump_time: int | |
| heat_element_time: int | |
| day: int = Field(alias="day") | |
| total_usage: int = Field(alias="totalUsage") | |
| heat_pump_usage: int = Field(alias="hpUsage") | |
| heat_element_usage: int = Field(alias="heUsage") | |
| heat_pump_time: int = Field(alias="hpTime") | |
| heat_element_time: int = Field(alias="heTime") |
Clarifies that the alias handles an API-level typo.
Removes unnecessary conditional check for typo field as Pydantic alias handles it automatically.
Adds explicit aliases for heat_pump_usage and heat_element_usage to match API keys (hpUsage, heUsage).
- Auth: Add idToken to handle_empty_aliases validator for consistency - Models: Restructure energy usage models to match actual API response - Add MonthlyEnergyData class for monthly grouping - Change EnergyUsageResponse from flat daily list to usage with monthly grouping - Add time fields to EnergyUsageTotal (heat_pump_time, heat_element_time) - Make total_usage computed properties where appropriate - Add get_month_data() method to EnergyUsageResponse - Models: Fix temp_formula_type to accept Union[int, str] for API compatibility - Exports: Remove duplicate MqttRequest/MqttCommand, add new model exports - Docs: Add datetime serialization backward compatibility notes - Examples: Fix 100+ camelCase attribute references to snake_case across all example files - Examples: Fix encoding function imports (decode_week_bitfield, decode_price) All changes verified with actual API responses from Navien device.
- Remove trailing whitespace from blank lines - Apply ruff formatting to modified files
- Fix camelCase attribute usage in CLI commands and monitoring - Fix return type annotation in boolean validator - Update TOUInfo.model_validate signature to match base class - Fix Optional[ClientSession] assignment in NavienAPIClient
- Extract shared topic matching utility to mqtt_utils - Refactor MqttSubscriptionManager to use shared utility - Clean up MqttClient imports and remove dead code - Make save/restore arguments optional in token_restoration_example.py
- Refine configuration.rst to reduce duplication - Move Protocol Reference to Advanced section in index.rst - Add warnings to all protocol docs clarifying internal nature
- Fix deprecated typing.Tuple usage in scripts/bump_version.py - Fix line length issues in scripts/ - Apply ruff formatting to all files
- Updated Energy model fields (heUsage -> heat_element_usage, etc.) - Updated MQTT model fields (clientID -> client_id, etc.) - Updated common fields (deviceType -> device_type, etc.)
Refactor models to use Pydantic
Date: 2025-11-20
Summary
Replaces all legacy dataclass-based models (DeviceInfo, Location, Device, FirmwareInfo, DeviceStatus, DeviceFeature, EnergyUsage*) with Pydantic BaseModel implementations (NavienBaseModel) providing automatic validation, camelCase alias mapping, and declarative field conversions.
Breaking Changes
Migration Guide
Benefits
Testing & Quality
Follow-Up
Reference
See CHANGELOG v6.0.3 entry for full details and examples.