Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/frequenz/sdk/timeseries/_quantities.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def __init__(self, value: float, exponent: int = 0) -> None:
value: The value of this quantity in a given exponent of the base unit.
exponent: The exponent of the base unit the given value is in.
"""
self._base_value = value * 10**exponent
self._base_value = value * 10.0**exponent

@classmethod
def _new(cls, value: float, *, exponent: int = 0) -> Self:
Expand All @@ -62,7 +62,7 @@ def _new(cls, value: float, *, exponent: int = 0) -> Self:
A new quantity subclass instance.
"""
self = cls.__new__(cls)
self._base_value = value * 10**exponent
self._base_value = value * 10.0**exponent
return self

def __init_subclass__(cls, exponent_unit_map: dict[int, str]) -> None:
Expand Down Expand Up @@ -93,15 +93,15 @@ def __init_subclass__(cls, exponent_unit_map: dict[int, str]) -> None:

@classmethod
def zero(cls) -> Self:
"""Return a quantity with value 0.
"""Return a quantity with value 0.0.

Returns:
A quantity with value 0.
A quantity with value 0.0.
"""
_zero = cls._zero_cache.get(cls, None)
if _zero is None:
_zero = cls.__new__(cls)
_zero._base_value = 0
_zero._base_value = 0.0
cls._zero_cache[cls] = _zero
assert isinstance(_zero, cls)
return _zero
Expand Down
70 changes: 70 additions & 0 deletions tests/timeseries/test_quantities.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@

"""Tests for quantity types."""

import inspect
from datetime import timedelta
from typing import Callable

import hypothesis
import pytest
from hypothesis import strategies as st

from frequenz.sdk.timeseries import _quantities
from frequenz.sdk.timeseries._quantities import (
Current,
Energy,
Expand Down Expand Up @@ -45,6 +48,48 @@ class Fz2(
"""Frequency quantity with broad exponent unit map."""


_CtorType = Callable[[float], Quantity]

# Thi is the current number of subclasses. This probably will get outdated, but it will
# provide at least some safety against something going really wrong and end up testing
# an empty list. With this we should at least make sure we are not testint less classes
# than before. We don't get the actual number using len(_QUANTITY_SUBCLASSES) because it
# would defeat the purpose of the test.
_SANITFY_NUM_CLASSES = 7
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit late to the review, but it might be better to call this _SANITIFY_NUM_CLASSES.. I just don't see a reason to skip this extra I...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just try to pronounce it with and without :P

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree completely. When making typos, please make sure you don't skimp on the vowels. :D

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea how I ended up typing this. But luckily we have smart completion, so typos are properly propagated and the code never fails!


_QUANTITY_SUBCLASSES = [
cls
for _, cls in inspect.getmembers(
_quantities,
lambda m: inspect.isclass(m) and issubclass(m, Quantity) and m is not Quantity,
)
]

# A very basic sainty check that we are messing up the introspection
assert len(_QUANTITY_SUBCLASSES) >= _SANITFY_NUM_CLASSES

_QUANTITY_BASE_UNIT_STRINGS = [
cls._new(0).base_unit # pylint: disable=protected-access
for cls in _QUANTITY_SUBCLASSES
]
for unit in _QUANTITY_BASE_UNIT_STRINGS:
assert unit is not None

_QUANTITY_CTORS = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So close, we could have called these QTY_CTORS and pronounced them the "cutty cutters"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I made my first meme-PR!

method
for cls in _QUANTITY_SUBCLASSES
for _, method in inspect.getmembers(
cls,
lambda m: inspect.ismethod(m)
and m.__name__.startswith("from_")
and m.__name__ != ("from_string"),
)
]
# A very basic sainty check that we are messing up the introspection. There are actually
# many more constructors than classes, but this still works as a very basic check.
assert len(_QUANTITY_CTORS) >= _SANITFY_NUM_CLASSES


def test_zero() -> None:
"""Test the zero value for quantity."""
assert Quantity(0.0) == Quantity.zero()
Expand Down Expand Up @@ -101,6 +146,31 @@ def test_zero() -> None:
assert Percentage.zero() is Percentage.zero() # It is a "singleton"


@pytest.mark.parametrize("quantity_ctor", _QUANTITY_CTORS)
def test_base_value_from_ctor_is_float(quantity_ctor: _CtorType) -> None:
"""Test that the base value always is a float."""
quantity = quantity_ctor(1)
assert isinstance(quantity.base_value, float)


@pytest.mark.parametrize("quantity_type", _QUANTITY_SUBCLASSES + [Quantity])
def test_base_value_from_zero_is_float(quantity_type: type[Quantity]) -> None:
"""Test that the base value always is a float."""
quantity = quantity_type.zero()
assert isinstance(quantity.base_value, float)


@pytest.mark.parametrize(
"quantity_type, unit", zip(_QUANTITY_SUBCLASSES, _QUANTITY_BASE_UNIT_STRINGS)
)
def test_base_value_from_string_is_float(
quantity_type: type[Quantity], unit: str
) -> None:
"""Test that the base value always is a float."""
quantity = quantity_type.from_string(f"1 {unit}")
assert isinstance(quantity.base_value, float)


def test_string_representation() -> None:
"""Test the string representation of the quantities."""
assert str(Quantity(1.024445, exponent=0)) == "1.024"
Expand Down