Source code for nwp500.unit_system
"""Unit system management for temperature, flow rate, and volume conversions.
This module provides context-based unit system management, allowing applications
to override the device's temperature_type setting and specify a preferred
measurement system (Metric or Imperial).
The unit system preference can be set at library initialization and is used
during model validation to convert device values to the user's preferred units.
"""
from __future__ import annotations
import contextvars
import logging
from typing import Literal
from .enums import TemperatureType
__author__ = "Emmanuel Levijarvi"
__copyright__ = "Emmanuel Levijarvi"
__license__ = "MIT"
_logger = logging.getLogger(__name__)
# Type alias for unit system preference
UnitSystemType = Literal["metric", "us_customary"] | None
# Context variable to store the preferred unit system
# None means auto-detect from device
# "metric" means Celsius, "us_customary" means Fahrenheit
_unit_system_context: contextvars.ContextVar[
Literal["metric", "us_customary"] | None
] = contextvars.ContextVar("unit_system", default=None)
[docs]
def set_unit_system(
unit_system: Literal["metric", "us_customary"] | None,
) -> None:
"""Set preferred unit system for temperature, flow, and volume conversions.
This setting overrides the device's temperature_type setting and applies to
all subsequent model validation operations in the current async context.
Args:
unit_system: Preferred unit system:
- "metric": Use Celsius, LPM, and Liters
- "us_customary": Use Fahrenheit, GPM, and Gallons
- None: Auto-detect from device's temperature_type (default)
Example:
>>> from nwp500 import set_unit_system
>>> set_unit_system("us_customary")
>>> # All values now in F, GPM, Gallons
>>> set_unit_system(None) # Reset to auto-detect
Note:
This is context-aware and works with async code. Each async task
maintains its own unit system preference.
"""
_unit_system_context.set(unit_system)
[docs]
def get_unit_system() -> Literal["metric", "us_customary"] | None:
"""Get the currently configured unit system preference.
Returns:
The current unit system preference:
- "metric": Celsius, LPM, Liters
- "us_customary": Fahrenheit, GPM, Gallons
- None: Auto-detect from device (default)
"""
return _unit_system_context.get()
[docs]
def reset_unit_system() -> None:
"""Reset unit system preference to auto-detect (None).
This is useful for tests or when switching between different
device configurations.
"""
_unit_system_context.set(None)
[docs]
def unit_system_to_temperature_type(
unit_system: Literal["metric", "us_customary"] | None,
) -> TemperatureType | None:
"""Convert unit system preference to TemperatureType enum.
Args:
unit_system: Unit system preference ("metric", "us_customary", or None)
Returns:
- TemperatureType.CELSIUS for "metric"
- TemperatureType.FAHRENHEIT for "us_customary"
- None for None (auto-detect)
"""
match unit_system:
case "metric":
return TemperatureType.CELSIUS
case "us_customary":
return TemperatureType.FAHRENHEIT
case None:
return None
[docs]
def is_metric_preferred(
override: Literal["metric", "us_customary"] | None = None,
) -> bool:
"""Check if metric (Celsius) is preferred.
Checks the override first, then falls back to the context-configured
unit system. Used during validation to determine preferred units.
Args:
override: Optional override value. If provided, this takes precedence
over the context-configured unit system.
Returns:
True if metric (Celsius) is preferred, False if us_customary
(Fahrenheit).
"""
# If override is provided, use it
if override is not None:
return override == "metric"
# Otherwise check context
unit_system = get_unit_system()
if unit_system is not None:
return unit_system == "metric"
# If neither override nor context is set, default to Fahrenheit (US)
return False