"""Field factory for creating typed Pydantic fields with metadata templates.
This module provides convenience functions for creating Pydantic fields with
standard metadata (device_class, unit_of_measurement, etc.) pre-configured,
reducing boilerplate in models while maintaining type safety.
Each factory function creates a Pydantic Field with metadata for Home Assistant
integration:
- temperature_field: Adds unit_of_measurement, device_class='temperature',
suggested_display_precision
- signal_strength_field: Adds unit_of_measurement,
device_class='signal_strength'
- energy_field: Adds unit_of_measurement, device_class='energy'
- power_field: Adds unit_of_measurement, device_class='power'
Example:
>>> from nwp500.field_factory import temperature_field
>>> class MyModel(BaseModel):
... temp: float = temperature_field("DHW Temperature", unit="°F")
"""
from typing import Any, cast
from pydantic import Field
__all__ = [
"temperature_field",
"signal_strength_field",
"energy_field",
"power_field",
]
def _metadata_field(
description: str,
metadata: dict[str, Any],
default: Any,
kwargs: dict[str, Any],
) -> Any:
"""Build a Pydantic Field with device metadata in json_schema_extra.
Args:
description: Field description
metadata: Base json_schema_extra metadata for this field type
default: Default value or Pydantic default
kwargs: Additional Pydantic Field arguments; a caller-provided
json_schema_extra dict is merged over the base metadata
Returns:
Pydantic Field with the merged metadata
"""
json_schema_extra = dict(metadata)
if "json_schema_extra" in kwargs:
extra = kwargs.pop("json_schema_extra")
if isinstance(extra, dict):
# Explicitly cast to dict[str, Any] for type safety
json_schema_extra.update(cast(dict[str, Any], extra))
return Field(
default=default,
description=description,
json_schema_extra=json_schema_extra,
**kwargs,
)
[docs]
def temperature_field(
description: str,
*,
unit: str = "°F",
default: Any = None,
**kwargs: Any,
) -> Any:
"""Create a temperature field with standard Home Assistant metadata.
The unit parameter is critical for tools consuming this library (e.g.,
Home Assistant) to correctly interpret the values. While the actual
displayed unit is dynamic based on device temperature_type setting
(Celsius or Fahrenheit), the unit parameter in json_schema_extra provides
the default/fallback unit and schema documentation for proper integration.
Args:
description: Field description
unit: Temperature unit (default: °F). Used in json_schema_extra for
Home Assistant and other integrations to understand value units.
Displayed units are dynamic based on device temperature_type.
default: Default value or Pydantic default
**kwargs: Additional Pydantic Field arguments
Returns:
Pydantic Field with temperature metadata
"""
return _metadata_field(
description,
{
"unit_of_measurement": unit,
"device_class": "temperature",
"suggested_display_precision": 1,
},
default,
kwargs,
)
[docs]
def signal_strength_field(
description: str,
*,
unit: str = "dBm",
default: Any = None,
**kwargs: Any,
) -> Any:
"""Create a signal strength field with standard Home Assistant metadata.
Args:
description: Field description
unit: Signal unit (default: dBm)
default: Default value or Pydantic default
**kwargs: Additional Pydantic Field arguments
Returns:
Pydantic Field with signal strength metadata
"""
return _metadata_field(
description,
{"unit_of_measurement": unit, "device_class": "signal_strength"},
default,
kwargs,
)
[docs]
def energy_field(
description: str,
*,
unit: str = "kWh",
default: Any = None,
**kwargs: Any,
) -> Any:
"""Create an energy field with standard Home Assistant metadata.
Args:
description: Field description
unit: Energy unit (default: kWh)
default: Default value or Pydantic default
**kwargs: Additional Pydantic Field arguments
Returns:
Pydantic Field with energy metadata
"""
return _metadata_field(
description,
{"unit_of_measurement": unit, "device_class": "energy"},
default,
kwargs,
)
[docs]
def power_field(
description: str,
*,
unit: str = "W",
default: Any = None,
**kwargs: Any,
) -> Any:
"""Create a power field with standard Home Assistant metadata.
Args:
description: Field description
unit: Power unit (default: W)
default: Default value or Pydantic default
**kwargs: Additional Pydantic Field arguments
Returns:
Pydantic Field with power metadata
"""
return _metadata_field(
description,
{"unit_of_measurement": unit, "device_class": "power"},
default,
kwargs,
)