From 8aa8a3b1b52c9e33125562920c0292f0fff88c96 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Fri, 8 Nov 2024 12:16:54 +0100 Subject: [PATCH 01/16] feat: add Length.__add__() --- quantio/__init__.py | 3 ++- quantio/exceptions.py | 5 +++++ quantio/quantities.py | 14 ++++++++++++++ test/test_length.py | 17 ++++++++++++++++- 4 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 quantio/exceptions.py diff --git a/quantio/__init__.py b/quantio/__init__.py index 5419807..624e135 100644 --- a/quantio/__init__.py +++ b/quantio/__init__.py @@ -1,5 +1,6 @@ """The main quantio package.""" +from .exceptions import CanNotAddTypesError from .quantities import Length -__all__ = ["Length"] +__all__ = ["Length", "CanNotAddTypesError"] diff --git a/quantio/exceptions.py b/quantio/exceptions.py new file mode 100644 index 0000000..fb5d94f --- /dev/null +++ b/quantio/exceptions.py @@ -0,0 +1,5 @@ +class CanNotAddTypesError(TypeError): + """Raised when two types are added together, that are not compatible.""" + + def __init__(self, self_type_descriptor: str, other_type_descriptor: str) -> None: + super().__init__(f"Can not add {self_type_descriptor} to {other_type_descriptor}") diff --git a/quantio/quantities.py b/quantio/quantities.py index ed09fd0..b813b32 100644 --- a/quantio/quantities.py +++ b/quantio/quantities.py @@ -2,6 +2,8 @@ from typing import ClassVar +from .exceptions import CanNotAddTypesError + class Length: """The one-dimensional extent of an object or the distance between two points.""" @@ -70,3 +72,15 @@ def millimeters(self) -> float: def micrometers(self) -> float: """The length in micrometers.""" return self._meters / self.UNIT_CONVERSION["micrometers"] + + def __eq__(self, other: object) -> bool: + """Assess if this length is the same as another.""" + if not isinstance(other, Length): + return False + return self._meters == other._meters + + def __add__(self, other: Length) -> Length: + """Add two lengths together.""" + if not isinstance(other, Length): + raise CanNotAddTypesError(self.__class__.__name__, other.__class__.__name__) + return Length(meters=self._meters + other._meters) diff --git a/test/test_length.py b/test/test_length.py index 209af60..0e96922 100644 --- a/test/test_length.py +++ b/test/test_length.py @@ -1,6 +1,6 @@ import pytest -from quantio import Length +from quantio import Length, CanNotAddTypesError def test_construction__multiple_units(): @@ -48,5 +48,20 @@ def test_micrometers(): assert actual.micrometers == 1 +def test_add__success(): + length1 = Length(meters=1) + length2 = Length(meters=2) + + actual = length1 + length2 + assert actual == Length(meters=3) + + +def test_add__false_class(): + length = Length(meters=1) + + with pytest.raises(CanNotAddTypesError): + length += 1 + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From e13a5f369197714c74441c12e71ce07ab54d09aa Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Fri, 8 Nov 2024 12:21:50 +0100 Subject: [PATCH 02/16] feat: add Length.__sub__() --- quantio/__init__.py | 4 ++-- quantio/exceptions.py | 11 +++++++++-- quantio/quantities.py | 8 +++++++- test/test_length.py | 17 ++++++++++++++++- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/quantio/__init__.py b/quantio/__init__.py index 624e135..f51cefd 100644 --- a/quantio/__init__.py +++ b/quantio/__init__.py @@ -1,6 +1,6 @@ """The main quantio package.""" -from .exceptions import CanNotAddTypesError +from .exceptions import CanNotAddTypesError, CanNotSubtractTypesError from .quantities import Length -__all__ = ["Length", "CanNotAddTypesError"] +__all__ = ["Length", "CanNotAddTypesError", "CanNotSubtractTypesError"] diff --git a/quantio/exceptions.py b/quantio/exceptions.py index fb5d94f..cae7dd4 100644 --- a/quantio/exceptions.py +++ b/quantio/exceptions.py @@ -1,5 +1,12 @@ class CanNotAddTypesError(TypeError): - """Raised when two types are added together, that are not compatible.""" + """Raised when two uncompatible quantities are added to one another.""" def __init__(self, self_type_descriptor: str, other_type_descriptor: str) -> None: - super().__init__(f"Can not add {self_type_descriptor} to {other_type_descriptor}") + super().__init__(f"Can not add {other_type_descriptor} to {self_type_descriptor}") + + +class CanNotSubtractTypesError(TypeError): + """Raised when two uncompatible quantities are subtracted from one another.""" + + def __init__(self, self_type_descriptor: str, other_type_descriptor: str) -> None: + super().__init__(f"Can not subtract {other_type_descriptor} from {self_type_descriptor}") diff --git a/quantio/quantities.py b/quantio/quantities.py index b813b32..4c13a2c 100644 --- a/quantio/quantities.py +++ b/quantio/quantities.py @@ -2,7 +2,7 @@ from typing import ClassVar -from .exceptions import CanNotAddTypesError +from .exceptions import CanNotAddTypesError, CanNotSubtractTypesError class Length: @@ -84,3 +84,9 @@ def __add__(self, other: Length) -> Length: if not isinstance(other, Length): raise CanNotAddTypesError(self.__class__.__name__, other.__class__.__name__) return Length(meters=self._meters + other._meters) + + def __sub__(self, other: Length) -> Length: + """Add two lengths together.""" + if not isinstance(other, Length): + raise CanNotSubtractTypesError(self.__class__.__name__, other.__class__.__name__) + return Length(meters=self._meters - other._meters) diff --git a/test/test_length.py b/test/test_length.py index 0e96922..8de3518 100644 --- a/test/test_length.py +++ b/test/test_length.py @@ -1,6 +1,6 @@ import pytest -from quantio import Length, CanNotAddTypesError +from quantio import Length, CanNotAddTypesError, CanNotSubtractTypesError def test_construction__multiple_units(): @@ -63,5 +63,20 @@ def test_add__false_class(): length += 1 +def test_sub__success(): + length1 = Length(meters=1) + length2 = Length(meters=2) + + actual = length2 - length1 + assert actual == Length(meters=1) + + +def test_sub__false_class(): + length = Length(meters=1) + + with pytest.raises(CanNotSubtractTypesError): + length -= 1 + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 6dd9f6dfa04bf4c4cb1bfe5d8dabd006a00bbb00 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Fri, 8 Nov 2024 12:33:37 +0100 Subject: [PATCH 03/16] refactor: extract __init__() into parent class --- quantio/_quantity_base.py | 22 ++++++++++++++++++++ quantio/quantities.py | 42 ++++++++++++++------------------------- 2 files changed, 37 insertions(+), 27 deletions(-) create mode 100644 quantio/_quantity_base.py diff --git a/quantio/_quantity_base.py b/quantio/_quantity_base.py new file mode 100644 index 0000000..856d7d0 --- /dev/null +++ b/quantio/_quantity_base.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod + + +class _QuantityBase(ABC): + """Parent class to all quantities.""" + + _base_value: float + "The base unit of the quantity." + + @property + @abstractmethod + def unit_conversion(self) -> dict[str, float]: + """Dict containing all units of the quantity.""" + + def __init__(self, **kwargs: float) -> None: + """Construct this class with the used units.""" + self._base_value = 0.0 + + for unit, factor in self.unit_conversion.items(): + self._base_value += kwargs.get(unit, 0.0) * factor diff --git a/quantio/quantities.py b/quantio/quantities.py index 4c13a2c..35d84a2 100644 --- a/quantio/quantities.py +++ b/quantio/quantities.py @@ -2,14 +2,15 @@ from typing import ClassVar +from ._quantity_base import _QuantityBase from .exceptions import CanNotAddTypesError, CanNotSubtractTypesError -class Length: +class Length(_QuantityBase): """The one-dimensional extent of an object or the distance between two points.""" - _meters: float - UNIT_CONVERSION: ClassVar[dict[str, float]] = { + _base_value: float + unit_conversion: ClassVar[dict[str, float]] = { "miles": 1609.34, "kilometers": 10**3, "meters": 10**0, @@ -20,73 +21,60 @@ class Length: "micrometers": 10**-6, } - def __init__(self, **kwargs: float) -> None: - """Construct this class with the used units.""" - self._meters = ( - kwargs.get("miles", 0.0) * self.UNIT_CONVERSION["miles"] - + kwargs.get("kilometers", 0.0) * self.UNIT_CONVERSION["kilometers"] - + kwargs.get("meters", 0.0) * self.UNIT_CONVERSION["meters"] - + kwargs.get("feet", 0.0) * self.UNIT_CONVERSION["feet"] - + kwargs.get("inches", 0.0) * self.UNIT_CONVERSION["inches"] - + kwargs.get("centimeters", 0.0) * self.UNIT_CONVERSION["centimeters"] - + kwargs.get("millimeters", 0.0) * self.UNIT_CONVERSION["millimeters"] - + kwargs.get("micrometers", 0.0) * self.UNIT_CONVERSION["micrometers"] - ) - @property def miles(self) -> float: """The length in miles.""" - return self._meters / self.UNIT_CONVERSION["miles"] + return self._base_value / self.unit_conversion["miles"] @property def kilometers(self) -> float: """The length in kilometers.""" - return self._meters / self.UNIT_CONVERSION["kilometers"] + return self._base_value / self.unit_conversion["kilometers"] @property def meters(self) -> float: """The length in meters.""" - return self._meters / self.UNIT_CONVERSION["meters"] + return self._base_value / self.unit_conversion["meters"] @property def feet(self) -> float: """The length in feet.""" - return self._meters / self.UNIT_CONVERSION["feet"] + return self._base_value / self.unit_conversion["feet"] @property def inches(self) -> float: """The length in inches.""" - return self._meters / self.UNIT_CONVERSION["inches"] + return self._base_value / self.unit_conversion["inches"] @property def centimeters(self) -> float: """The length in centimeters.""" - return self._meters / self.UNIT_CONVERSION["centimeters"] + return self._base_value / self.unit_conversion["centimeters"] @property def millimeters(self) -> float: """The length in millimeters.""" - return self._meters / self.UNIT_CONVERSION["millimeters"] + return self._base_value / self.unit_conversion["millimeters"] @property def micrometers(self) -> float: """The length in micrometers.""" - return self._meters / self.UNIT_CONVERSION["micrometers"] + return self._base_value / self.unit_conversion["micrometers"] def __eq__(self, other: object) -> bool: """Assess if this length is the same as another.""" if not isinstance(other, Length): return False - return self._meters == other._meters + return self._base_value == other._base_value def __add__(self, other: Length) -> Length: """Add two lengths together.""" if not isinstance(other, Length): raise CanNotAddTypesError(self.__class__.__name__, other.__class__.__name__) - return Length(meters=self._meters + other._meters) + return Length(meters=self._base_value + other._base_value) def __sub__(self, other: Length) -> Length: """Add two lengths together.""" if not isinstance(other, Length): raise CanNotSubtractTypesError(self.__class__.__name__, other.__class__.__name__) - return Length(meters=self._meters - other._meters) + return Length(meters=self._base_value - other._base_value) From e987a24ee5b135946a44f719c13f6c93a3939093 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Fri, 8 Nov 2024 12:46:20 +0100 Subject: [PATCH 04/16] refactor: extract __eq__() into parent class --- pyproject.toml | 2 ++ quantio/_quantity_base.py | 11 +++++++++-- quantio/quantities.py | 7 ------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 36996b5..c558c15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,8 @@ ignore = [ "ISC001", # conflicts with ruff formatter + "PGH003", # necessary for _QuantityBase.__eq__() + "TCH001", # adds hard to understand compexity without providing a benefit for smaller projects "TCH002", # same as TCH001 "TCH003", # same as TCH001 diff --git a/quantio/_quantity_base.py b/quantio/_quantity_base.py index 856d7d0..c9337e1 100644 --- a/quantio/_quantity_base.py +++ b/quantio/_quantity_base.py @@ -18,5 +18,12 @@ def __init__(self, **kwargs: float) -> None: """Construct this class with the used units.""" self._base_value = 0.0 - for unit, factor in self.unit_conversion.items(): - self._base_value += kwargs.get(unit, 0.0) * factor + for unit_name, factor in self.unit_conversion.items(): + self._base_value += kwargs.get(unit_name, 0.0) * factor + + def __eq__(self, other: object) -> bool: + """Assess if this object is the same as another.""" + if type(self) is not type(other): + return False + + return self._base_value == other._base_value # type: ignore diff --git a/quantio/quantities.py b/quantio/quantities.py index 35d84a2..cfa6881 100644 --- a/quantio/quantities.py +++ b/quantio/quantities.py @@ -9,7 +9,6 @@ class Length(_QuantityBase): """The one-dimensional extent of an object or the distance between two points.""" - _base_value: float unit_conversion: ClassVar[dict[str, float]] = { "miles": 1609.34, "kilometers": 10**3, @@ -61,12 +60,6 @@ def micrometers(self) -> float: """The length in micrometers.""" return self._base_value / self.unit_conversion["micrometers"] - def __eq__(self, other: object) -> bool: - """Assess if this length is the same as another.""" - if not isinstance(other, Length): - return False - return self._base_value == other._base_value - def __add__(self, other: Length) -> Length: """Add two lengths together.""" if not isinstance(other, Length): From d4975670fcdb4e39be30422c34ef53e226e4ef12 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Fri, 8 Nov 2024 12:49:22 +0100 Subject: [PATCH 05/16] refactor: extract __add__() into parent class --- quantio/_quantity_base.py | 10 +++++++++- quantio/quantities.py | 8 +------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/quantio/_quantity_base.py b/quantio/_quantity_base.py index c9337e1..062864f 100644 --- a/quantio/_quantity_base.py +++ b/quantio/_quantity_base.py @@ -2,6 +2,8 @@ from abc import ABC, abstractmethod +from .exceptions import CanNotAddTypesError + class _QuantityBase(ABC): """Parent class to all quantities.""" @@ -16,7 +18,7 @@ def unit_conversion(self) -> dict[str, float]: def __init__(self, **kwargs: float) -> None: """Construct this class with the used units.""" - self._base_value = 0.0 + self._base_value = kwargs.get("_base_value", 0.0) for unit_name, factor in self.unit_conversion.items(): self._base_value += kwargs.get(unit_name, 0.0) * factor @@ -27,3 +29,9 @@ def __eq__(self, other: object) -> bool: return False return self._base_value == other._base_value # type: ignore + + def __add__(self, other: _QuantityBase) -> _QuantityBase: + """Add two lengths together.""" + if type(self) is not type(other): + raise CanNotAddTypesError(self.__class__.__name__, other.__class__.__name__) + return type(self)(_base_value=self._base_value + other._base_value) diff --git a/quantio/quantities.py b/quantio/quantities.py index cfa6881..e5d554c 100644 --- a/quantio/quantities.py +++ b/quantio/quantities.py @@ -3,7 +3,7 @@ from typing import ClassVar from ._quantity_base import _QuantityBase -from .exceptions import CanNotAddTypesError, CanNotSubtractTypesError +from .exceptions import CanNotSubtractTypesError class Length(_QuantityBase): @@ -60,12 +60,6 @@ def micrometers(self) -> float: """The length in micrometers.""" return self._base_value / self.unit_conversion["micrometers"] - def __add__(self, other: Length) -> Length: - """Add two lengths together.""" - if not isinstance(other, Length): - raise CanNotAddTypesError(self.__class__.__name__, other.__class__.__name__) - return Length(meters=self._base_value + other._base_value) - def __sub__(self, other: Length) -> Length: """Add two lengths together.""" if not isinstance(other, Length): From a87a47bcc73ac8211da5a4ee183f09b01430b34c Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Fri, 8 Nov 2024 13:59:48 +0100 Subject: [PATCH 06/16] refactor: extract __sub__() into parent class --- quantio/_quantity_base.py | 10 ++++++++-- quantio/quantities.py | 7 ------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/quantio/_quantity_base.py b/quantio/_quantity_base.py index 062864f..1794671 100644 --- a/quantio/_quantity_base.py +++ b/quantio/_quantity_base.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod -from .exceptions import CanNotAddTypesError +from .exceptions import CanNotAddTypesError, CanNotSubtractTypesError class _QuantityBase(ABC): @@ -31,7 +31,13 @@ def __eq__(self, other: object) -> bool: return self._base_value == other._base_value # type: ignore def __add__(self, other: _QuantityBase) -> _QuantityBase: - """Add two lengths together.""" + """Add two quantities of the same type.""" if type(self) is not type(other): raise CanNotAddTypesError(self.__class__.__name__, other.__class__.__name__) return type(self)(_base_value=self._base_value + other._base_value) + + def __sub__(self, other: _QuantityBase) -> _QuantityBase: + """Subtract two quantities of the same type.""" + if type(self) is not type(other): + raise CanNotSubtractTypesError(self.__class__.__name__, other.__class__.__name__) + return type(self)(_base_value=self._base_value - other._base_value) diff --git a/quantio/quantities.py b/quantio/quantities.py index e5d554c..534c128 100644 --- a/quantio/quantities.py +++ b/quantio/quantities.py @@ -3,7 +3,6 @@ from typing import ClassVar from ._quantity_base import _QuantityBase -from .exceptions import CanNotSubtractTypesError class Length(_QuantityBase): @@ -59,9 +58,3 @@ def millimeters(self) -> float: def micrometers(self) -> float: """The length in micrometers.""" return self._base_value / self.unit_conversion["micrometers"] - - def __sub__(self, other: Length) -> Length: - """Add two lengths together.""" - if not isinstance(other, Length): - raise CanNotSubtractTypesError(self.__class__.__name__, other.__class__.__name__) - return Length(meters=self._base_value - other._base_value) From d2788cbdc324e9f0f838cb723293715c27637ae0 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Fri, 8 Nov 2024 14:03:27 +0100 Subject: [PATCH 07/16] test: extract tests associated with base class --- test/test_length.py | 35 ------------------------------ test/test_quantities_base.py | 42 ++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 35 deletions(-) create mode 100644 test/test_quantities_base.py diff --git a/test/test_length.py b/test/test_length.py index 8de3518..24413ce 100644 --- a/test/test_length.py +++ b/test/test_length.py @@ -3,11 +3,6 @@ from quantio import Length, CanNotAddTypesError, CanNotSubtractTypesError -def test_construction__multiple_units(): - actual = Length(meters=5, kilometers=1, centimeters=7) - assert actual.meters == 1005.07 - - def test_miles(): actual = Length(meters=1) assert actual.miles == 1 / 1609.34 @@ -48,35 +43,5 @@ def test_micrometers(): assert actual.micrometers == 1 -def test_add__success(): - length1 = Length(meters=1) - length2 = Length(meters=2) - - actual = length1 + length2 - assert actual == Length(meters=3) - - -def test_add__false_class(): - length = Length(meters=1) - - with pytest.raises(CanNotAddTypesError): - length += 1 - - -def test_sub__success(): - length1 = Length(meters=1) - length2 = Length(meters=2) - - actual = length2 - length1 - assert actual == Length(meters=1) - - -def test_sub__false_class(): - length = Length(meters=1) - - with pytest.raises(CanNotSubtractTypesError): - length -= 1 - - if __name__ == "__main__": pytest.main([__file__, "-v"]) diff --git a/test/test_quantities_base.py b/test/test_quantities_base.py new file mode 100644 index 0000000..05c4c67 --- /dev/null +++ b/test/test_quantities_base.py @@ -0,0 +1,42 @@ +import pytest + +from quantio import Length, CanNotAddTypesError, CanNotSubtractTypesError + + +def test_construction__multiple_units(): + actual = Length(meters=5, kilometers=1, centimeters=7) + assert actual.meters == 1005.07 + + +def test_add__success(): + length1 = Length(meters=1) + length2 = Length(meters=2) + + actual = length1 + length2 + assert actual == Length(meters=3) + + +def test_add__false_class(): + length = Length(meters=1) + + with pytest.raises(CanNotAddTypesError): + length += 1 + + +def test_sub__success(): + length1 = Length(meters=1) + length2 = Length(meters=2) + + actual = length2 - length1 + assert actual == Length(meters=1) + + +def test_sub__false_class(): + length = Length(meters=1) + + with pytest.raises(CanNotSubtractTypesError): + length -= 1 + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From 05d9de9a1914309ac7554beaacebd4046ef39d42 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Fri, 8 Nov 2024 14:11:39 +0100 Subject: [PATCH 08/16] feat: add Time --- quantio/__init__.py | 4 ++-- quantio/quantities.py | 31 +++++++++++++++++++++++++++++++ test/test_length.py | 34 +++++++++++++++++----------------- test/test_time.py | 27 +++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 19 deletions(-) create mode 100644 test/test_time.py diff --git a/quantio/__init__.py b/quantio/__init__.py index f51cefd..f73c24a 100644 --- a/quantio/__init__.py +++ b/quantio/__init__.py @@ -1,6 +1,6 @@ """The main quantio package.""" from .exceptions import CanNotAddTypesError, CanNotSubtractTypesError -from .quantities import Length +from .quantities import Length, Time -__all__ = ["Length", "CanNotAddTypesError", "CanNotSubtractTypesError"] +__all__ = ["Length", "Time", "CanNotAddTypesError", "CanNotSubtractTypesError"] diff --git a/quantio/quantities.py b/quantio/quantities.py index 534c128..64cc1af 100644 --- a/quantio/quantities.py +++ b/quantio/quantities.py @@ -58,3 +58,34 @@ def millimeters(self) -> float: def micrometers(self) -> float: """The length in micrometers.""" return self._base_value / self.unit_conversion["micrometers"] + + +class Time(_QuantityBase): + """The duration of an event.""" + + unit_conversion: ClassVar[dict[str, float]] = { + "hours": 60 * 60, + "minutes": 60, + "seconds": 1, + "milliseconds": 10**-3, + } + + @property + def hours(self) -> float: + """The time in hours.""" + return self._base_value / self.unit_conversion["hours"] + + @property + def minutes(self) -> float: + """The time in minutes.""" + return self._base_value / self.unit_conversion["minutes"] + + @property + def seconds(self) -> float: + """The time in seconds.""" + return self._base_value / self.unit_conversion["seconds"] + + @property + def milliseconds(self) -> float: + """The time in milliseconds.""" + return self._base_value / self.unit_conversion["milliseconds"] diff --git a/test/test_length.py b/test/test_length.py index 24413ce..2973c33 100644 --- a/test/test_length.py +++ b/test/test_length.py @@ -1,46 +1,46 @@ import pytest -from quantio import Length, CanNotAddTypesError, CanNotSubtractTypesError +from quantio import Length def test_miles(): - actual = Length(meters=1) - assert actual.miles == 1 / 1609.34 + length = Length(meters=1) + assert length.miles == 1 / 1609.34 def test_kilometers(): - actual = Length(kilometers=1) - assert actual.kilometers == 1 + length = Length(kilometers=1) + assert length.kilometers == 1 def test_meters(): - actual = Length(meters=1) - assert actual.meters == 1 + length = Length(meters=1) + assert length.meters == 1 def test_feet(): - actual = Length(feet=1) - assert actual.feet == 1 + length = Length(feet=1) + assert length.feet == 1 def test_inches(): - actual = Length(inches=1) - assert actual.inches == 1 + length = Length(inches=1) + assert length.inches == 1 def test_centimeters(): - actual = Length(centimeters=1) - assert actual.centimeters == 1 + length = Length(centimeters=1) + assert length.centimeters == 1 def test_millimeters(): - actual = Length(millimeters=1) - assert actual.millimeters == 1 + length = Length(millimeters=1) + assert length.millimeters == 1 def test_micrometers(): - actual = Length(micrometers=1) - assert actual.micrometers == 1 + length = Length(micrometers=1) + assert length.micrometers == 1 if __name__ == "__main__": diff --git a/test/test_time.py b/test/test_time.py new file mode 100644 index 0000000..94c0e87 --- /dev/null +++ b/test/test_time.py @@ -0,0 +1,27 @@ +import pytest + +from quantio import Time + + +def test_hours(): + time = Time(hours=1) + assert time.hours == 1 + + +def test_minutes(): + time = Time(minutes=1) + assert time.minutes == 1 + + +def test_seconds(): + time = Time(seconds=1) + assert time.seconds == 1 + + +def test_milliseconds(): + time = Time(milliseconds=1) + assert time.milliseconds == 1 + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From 7fa0bd5f0f7a5b26d6306cfc73ae12e69977c02c Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Fri, 8 Nov 2024 14:44:37 +0100 Subject: [PATCH 09/16] refactor: extract property creation to base class --- pyproject.toml | 4 +++ quantio/_quantity_base.py | 13 ++++++-- quantio/quantities.py | 66 ++------------------------------------- test/test_length.py | 47 ---------------------------- test/test_time.py | 27 ---------------- 5 files changed, 16 insertions(+), 141 deletions(-) delete mode 100644 test/test_length.py delete mode 100644 test/test_time.py diff --git a/pyproject.toml b/pyproject.toml index c558c15..716291c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,8 +32,12 @@ ignore = [ "ISC001", # conflicts with ruff formatter + "N802", # interferes with setting constant abstract properties + "PGH003", # necessary for _QuantityBase.__eq__() + "RUF012", # does not work with constants + "TCH001", # adds hard to understand compexity without providing a benefit for smaller projects "TCH002", # same as TCH001 "TCH003", # same as TCH001 diff --git a/quantio/_quantity_base.py b/quantio/_quantity_base.py index 1794671..587810a 100644 --- a/quantio/_quantity_base.py +++ b/quantio/_quantity_base.py @@ -13,16 +13,23 @@ class _QuantityBase(ABC): @property @abstractmethod - def unit_conversion(self) -> dict[str, float]: - """Dict containing all units of the quantity.""" + def _UNIT_CONVERSION(self) -> dict[str, float]: + """Table used for recording the units with conversion values.""" def __init__(self, **kwargs: float) -> None: """Construct this class with the used units.""" self._base_value = kwargs.get("_base_value", 0.0) - for unit_name, factor in self.unit_conversion.items(): + for unit_name, factor in self._UNIT_CONVERSION.items(): self._base_value += kwargs.get(unit_name, 0.0) * factor + for unit_name, factor in self._UNIT_CONVERSION.items(): + + def make_property(factor: float) -> property: + return property(lambda self: self._base_value / factor) + + setattr(self.__class__, unit_name, make_property(factor)) + def __eq__(self, other: object) -> bool: """Assess if this object is the same as another.""" if type(self) is not type(other): diff --git a/quantio/quantities.py b/quantio/quantities.py index 64cc1af..6399764 100644 --- a/quantio/quantities.py +++ b/quantio/quantities.py @@ -1,14 +1,12 @@ from __future__ import annotations -from typing import ClassVar - from ._quantity_base import _QuantityBase class Length(_QuantityBase): """The one-dimensional extent of an object or the distance between two points.""" - unit_conversion: ClassVar[dict[str, float]] = { + _UNIT_CONVERSION: dict[str, float] = { "miles": 1609.34, "kilometers": 10**3, "meters": 10**0, @@ -19,73 +17,13 @@ class Length(_QuantityBase): "micrometers": 10**-6, } - @property - def miles(self) -> float: - """The length in miles.""" - return self._base_value / self.unit_conversion["miles"] - - @property - def kilometers(self) -> float: - """The length in kilometers.""" - return self._base_value / self.unit_conversion["kilometers"] - - @property - def meters(self) -> float: - """The length in meters.""" - return self._base_value / self.unit_conversion["meters"] - - @property - def feet(self) -> float: - """The length in feet.""" - return self._base_value / self.unit_conversion["feet"] - - @property - def inches(self) -> float: - """The length in inches.""" - return self._base_value / self.unit_conversion["inches"] - - @property - def centimeters(self) -> float: - """The length in centimeters.""" - return self._base_value / self.unit_conversion["centimeters"] - - @property - def millimeters(self) -> float: - """The length in millimeters.""" - return self._base_value / self.unit_conversion["millimeters"] - - @property - def micrometers(self) -> float: - """The length in micrometers.""" - return self._base_value / self.unit_conversion["micrometers"] - class Time(_QuantityBase): """The duration of an event.""" - unit_conversion: ClassVar[dict[str, float]] = { + _UNIT_CONVERSION: dict[str, float] = { "hours": 60 * 60, "minutes": 60, "seconds": 1, "milliseconds": 10**-3, } - - @property - def hours(self) -> float: - """The time in hours.""" - return self._base_value / self.unit_conversion["hours"] - - @property - def minutes(self) -> float: - """The time in minutes.""" - return self._base_value / self.unit_conversion["minutes"] - - @property - def seconds(self) -> float: - """The time in seconds.""" - return self._base_value / self.unit_conversion["seconds"] - - @property - def milliseconds(self) -> float: - """The time in milliseconds.""" - return self._base_value / self.unit_conversion["milliseconds"] diff --git a/test/test_length.py b/test/test_length.py deleted file mode 100644 index 2973c33..0000000 --- a/test/test_length.py +++ /dev/null @@ -1,47 +0,0 @@ -import pytest - -from quantio import Length - - -def test_miles(): - length = Length(meters=1) - assert length.miles == 1 / 1609.34 - - -def test_kilometers(): - length = Length(kilometers=1) - assert length.kilometers == 1 - - -def test_meters(): - length = Length(meters=1) - assert length.meters == 1 - - -def test_feet(): - length = Length(feet=1) - assert length.feet == 1 - - -def test_inches(): - length = Length(inches=1) - assert length.inches == 1 - - -def test_centimeters(): - length = Length(centimeters=1) - assert length.centimeters == 1 - - -def test_millimeters(): - length = Length(millimeters=1) - assert length.millimeters == 1 - - -def test_micrometers(): - length = Length(micrometers=1) - assert length.micrometers == 1 - - -if __name__ == "__main__": - pytest.main([__file__, "-v"]) diff --git a/test/test_time.py b/test/test_time.py deleted file mode 100644 index 94c0e87..0000000 --- a/test/test_time.py +++ /dev/null @@ -1,27 +0,0 @@ -import pytest - -from quantio import Time - - -def test_hours(): - time = Time(hours=1) - assert time.hours == 1 - - -def test_minutes(): - time = Time(minutes=1) - assert time.minutes == 1 - - -def test_seconds(): - time = Time(seconds=1) - assert time.seconds == 1 - - -def test_milliseconds(): - time = Time(milliseconds=1) - assert time.milliseconds == 1 - - -if __name__ == "__main__": - pytest.main([__file__, "-v"]) From 55682aa484f29830eb230cf4f35fa995eeed9c89 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Fri, 8 Nov 2024 14:46:29 +0100 Subject: [PATCH 10/16] feat: add Area --- quantio/quantities.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/quantio/quantities.py b/quantio/quantities.py index 6399764..7cfc22c 100644 --- a/quantio/quantities.py +++ b/quantio/quantities.py @@ -3,6 +3,21 @@ from ._quantity_base import _QuantityBase +class Area(_QuantityBase): + """The two-dimensional extent of an object.""" + + _UNIT_CONVERSION: dict[str, float] = { + "square_miles": 1609.34**2, + "square_kilometers": 10 ** (3 * 2), + "square_meters": 10**0, + "square_feet": 0.3048**2, + "square_inches": 0.0254**2, + "square_centimeters": 10 ** (-2 * 2), + "square_millimeters": 10 ** (-3 * 2), + "square_micrometers": 10 ** (-6 * 2), + } + + class Length(_QuantityBase): """The one-dimensional extent of an object or the distance between two points.""" From 4e20c4947ddee48e98c7a8ec223bae58cb41cb96 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Fri, 8 Nov 2024 16:37:23 +0100 Subject: [PATCH 11/16] refactor: add boilerplate using generation script --- generate/__init__.py | 1 + generate/generate_boilerplate.py | 105 +++++++++++++++++ generate/quantities.py | 27 +++++ pyproject.toml | 4 +- quantio/_quantity_base.py | 37 ++---- quantio/quantities.py | 187 ++++++++++++++++++++++++++----- 6 files changed, 309 insertions(+), 52 deletions(-) create mode 100644 generate/__init__.py create mode 100644 generate/generate_boilerplate.py create mode 100644 generate/quantities.py diff --git a/generate/__init__.py b/generate/__init__.py new file mode 100644 index 0000000..3584e8b --- /dev/null +++ b/generate/__init__.py @@ -0,0 +1 @@ +"""Package used for auto generating the boilerplate in the quantities.""" diff --git a/generate/generate_boilerplate.py b/generate/generate_boilerplate.py new file mode 100644 index 0000000..0f7c2cd --- /dev/null +++ b/generate/generate_boilerplate.py @@ -0,0 +1,105 @@ +from __future__ import annotations + +from pathlib import Path + +TARGET_FILE_PATH = Path(__file__).parent.parent / "quantio" / "quantities.py" + + +def generate_boilerplate(quantities_with_fields: dict[str, dict[str, str]]) -> None: + """Generate the boilerplate parts of the quantity classes.""" + with TARGET_FILE_PATH.open() as target_file: + target_content = target_file.readlines() + + target_content_with_boilerplate = [] + current_class: str | None = None + + current_line_is_part_of_autogenerated_code = False + for line in target_content: + if line.startswith("class "): + current_class = line.split("(")[0][6:] + + if "# --- This part is auto generated. Do not change manually. ---" in line: + current_line_is_part_of_autogenerated_code = True + target_content_with_boilerplate.append(line) + target_content_with_boilerplate.extend( + _generate_init(quantities_with_fields[current_class]) + ) + target_content_with_boilerplate.extend( + _generate_properties(current_class, quantities_with_fields[current_class]) + ) + + if "# --- End of auto generated part. ---" in line: + current_line_is_part_of_autogenerated_code = False + + if current_line_is_part_of_autogenerated_code: + continue + + target_content_with_boilerplate.append(line) + + for line_number, line in enumerate(target_content_with_boilerplate): + if not line.endswith("\n"): + target_content_with_boilerplate[line_number] += "\n" + + with TARGET_FILE_PATH.open("w") as target_file: + target_file.writelines(target_content_with_boilerplate) + + +def _generate_init(units: dict[str, str]) -> list[str]: + code = [" " * 4 + "def __init__(", " " * 8 + "self,"] + + for unit in units: + code.append(" " * 8 + f"{unit}: float = 0.0,") + + code.append(" " * 4 + ") -> None:") + code.append(" " * 8 + "self._base_value = 0.0") + + for unit, factor in units.items(): + code.append(" " * 8 + f"self._base_value += {unit} * {factor}") + + return code + + +def _generate_properties(current_class: str, units: dict[str, str]) -> list[str]: + code = [] + + for unit, factor in units.items(): + code.append("") + code.append(" " * 4 + "@property") + code.append(" " * 4 + f"def {unit}(self) -> float:") + code.append(" " * 8 + f'"""The {current_class.lower()} in {unit.replace("_", " ")}."""') + code.append(" " * 8 + f"return self._base_value / {factor}") + + return code + + +if __name__ == "__main__": + quantities_with_fields = { + "Area": { + "square_miles": "1609.34**2", + "square_kilometers": "10 ** (3 * 2)", + "square_meters": "10**0", + "square_feet": "0.3048**2", + "square_inches": "0.0254**2", + "square_centimeters": "10 ** (-2 * 2)", + "square_millimeters": "10 ** (-3 * 2)", + "square_micrometers": "10 ** (-6 * 2)", + }, + "Length": { + "miles": "1609.34", + "kilometers": "10**3", + "meters": "10**0", + "feet": "0.3048", + "inches": "0.0254", + "centimeters": "10**-2", + "millimeters": "10**-3", + "micrometers": "10**-6", + }, + "Time": { + "hours": "60 * 60", + "minutes": "60", + "seconds": "1", + "milliseconds": "10**-3", + }, + } + + generate_boilerplate(quantities_with_fields) diff --git a/generate/quantities.py b/generate/quantities.py new file mode 100644 index 0000000..43a9ab5 --- /dev/null +++ b/generate/quantities.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from ._quantity_base import _QuantityBase + + +class Area(_QuantityBase): + """The two-dimensional extent of an object.""" + + # --- This part is auto generated. Do not change manually. --- + + # --- End of auto generated part. --- + + +class Length(_QuantityBase): + """The one-dimensional extent of an object or the distance between two points.""" + + # --- This part is auto generated. Do not change manually. --- + + # --- End of auto generated part. --- + + +class Time(_QuantityBase): + """The duration of an event.""" + + # --- This part is auto generated. Do not change manually. --- + + # --- End of auto generated part. --- diff --git a/pyproject.toml b/pyproject.toml index 716291c..45c74a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ pytest = "<9.0" line-length = 101 [tool.ruff.lint] -exclude = ["test/*"] +exclude = ["test/*", "generate/*"] select = ["ALL"] ignore = [ "COM812", # conflicts with ruff formatter @@ -36,6 +36,8 @@ ignore = [ "PGH003", # necessary for _QuantityBase.__eq__() + "PLR0913", # many arguments are necessary for the quantities to enable enough different units + "RUF012", # does not work with constants "TCH001", # adds hard to understand compexity without providing a benefit for smaller projects diff --git a/quantio/_quantity_base.py b/quantio/_quantity_base.py index 587810a..f201f43 100644 --- a/quantio/_quantity_base.py +++ b/quantio/_quantity_base.py @@ -1,6 +1,6 @@ from __future__ import annotations -from abc import ABC, abstractmethod +from abc import ABC from .exceptions import CanNotAddTypesError, CanNotSubtractTypesError @@ -11,40 +11,27 @@ class _QuantityBase(ABC): _base_value: float "The base unit of the quantity." - @property - @abstractmethod - def _UNIT_CONVERSION(self) -> dict[str, float]: - """Table used for recording the units with conversion values.""" - - def __init__(self, **kwargs: float) -> None: - """Construct this class with the used units.""" - self._base_value = kwargs.get("_base_value", 0.0) - - for unit_name, factor in self._UNIT_CONVERSION.items(): - self._base_value += kwargs.get(unit_name, 0.0) * factor - - for unit_name, factor in self._UNIT_CONVERSION.items(): - - def make_property(factor: float) -> property: - return property(lambda self: self._base_value / factor) - - setattr(self.__class__, unit_name, make_property(factor)) - def __eq__(self, other: object) -> bool: """Assess if this object is the same as another.""" - if type(self) is not type(other): - return False + if isinstance(other, type(self)): + return self._base_value == other._base_value - return self._base_value == other._base_value # type: ignore + return False def __add__(self, other: _QuantityBase) -> _QuantityBase: """Add two quantities of the same type.""" if type(self) is not type(other): raise CanNotAddTypesError(self.__class__.__name__, other.__class__.__name__) - return type(self)(_base_value=self._base_value + other._base_value) + + result = type(self)() + result._base_value = self._base_value + other._base_value + return result def __sub__(self, other: _QuantityBase) -> _QuantityBase: """Subtract two quantities of the same type.""" if type(self) is not type(other): raise CanNotSubtractTypesError(self.__class__.__name__, other.__class__.__name__) - return type(self)(_base_value=self._base_value - other._base_value) + + result = type(self)() + result._base_value = self._base_value - other._base_value + return result diff --git a/quantio/quantities.py b/quantio/quantities.py index 7cfc22c..eacdac6 100644 --- a/quantio/quantities.py +++ b/quantio/quantities.py @@ -6,39 +6,174 @@ class Area(_QuantityBase): """The two-dimensional extent of an object.""" - _UNIT_CONVERSION: dict[str, float] = { - "square_miles": 1609.34**2, - "square_kilometers": 10 ** (3 * 2), - "square_meters": 10**0, - "square_feet": 0.3048**2, - "square_inches": 0.0254**2, - "square_centimeters": 10 ** (-2 * 2), - "square_millimeters": 10 ** (-3 * 2), - "square_micrometers": 10 ** (-6 * 2), - } + # --- This part is auto generated. Do not change manually. --- + def __init__( + self, + square_miles: float = 0.0, + square_kilometers: float = 0.0, + square_meters: float = 0.0, + square_feet: float = 0.0, + square_inches: float = 0.0, + square_centimeters: float = 0.0, + square_millimeters: float = 0.0, + square_micrometers: float = 0.0, + ) -> None: + self._base_value = 0.0 + self._base_value += square_miles * 1609.34**2 + self._base_value += square_kilometers * 10 ** (3 * 2) + self._base_value += square_meters * 10**0 + self._base_value += square_feet * 0.3048**2 + self._base_value += square_inches * 0.0254**2 + self._base_value += square_centimeters * 10 ** (-2 * 2) + self._base_value += square_millimeters * 10 ** (-3 * 2) + self._base_value += square_micrometers * 10 ** (-6 * 2) + + @property + def square_miles(self) -> float: + """The area in square miles.""" + return self._base_value / 1609.34**2 + + @property + def square_kilometers(self) -> float: + """The area in square kilometers.""" + return self._base_value / 10 ** (3 * 2) + + @property + def square_meters(self) -> float: + """The area in square meters.""" + return self._base_value / 10**0 + + @property + def square_feet(self) -> float: + """The area in square feet.""" + return self._base_value / 0.3048**2 + + @property + def square_inches(self) -> float: + """The area in square inches.""" + return self._base_value / 0.0254**2 + + @property + def square_centimeters(self) -> float: + """The area in square centimeters.""" + return self._base_value / 10 ** (-2 * 2) + + @property + def square_millimeters(self) -> float: + """The area in square millimeters.""" + return self._base_value / 10 ** (-3 * 2) + + @property + def square_micrometers(self) -> float: + """The area in square micrometers.""" + return self._base_value / 10 ** (-6 * 2) + + # --- End of auto generated part. --- class Length(_QuantityBase): """The one-dimensional extent of an object or the distance between two points.""" - _UNIT_CONVERSION: dict[str, float] = { - "miles": 1609.34, - "kilometers": 10**3, - "meters": 10**0, - "feet": 0.3048, - "inches": 0.0254, - "centimeters": 10**-2, - "millimeters": 10**-3, - "micrometers": 10**-6, - } + # --- This part is auto generated. Do not change manually. --- + def __init__( + self, + miles: float = 0.0, + kilometers: float = 0.0, + meters: float = 0.0, + feet: float = 0.0, + inches: float = 0.0, + centimeters: float = 0.0, + millimeters: float = 0.0, + micrometers: float = 0.0, + ) -> None: + self._base_value = 0.0 + self._base_value += miles * 1609.34 + self._base_value += kilometers * 10**3 + self._base_value += meters * 10**0 + self._base_value += feet * 0.3048 + self._base_value += inches * 0.0254 + self._base_value += centimeters * 10**-2 + self._base_value += millimeters * 10**-3 + self._base_value += micrometers * 10**-6 + + @property + def miles(self) -> float: + """The length in miles.""" + return self._base_value / 1609.34 + + @property + def kilometers(self) -> float: + """The length in kilometers.""" + return self._base_value / 10**3 + + @property + def meters(self) -> float: + """The length in meters.""" + return self._base_value / 10**0 + + @property + def feet(self) -> float: + """The length in feet.""" + return self._base_value / 0.3048 + + @property + def inches(self) -> float: + """The length in inches.""" + return self._base_value / 0.0254 + + @property + def centimeters(self) -> float: + """The length in centimeters.""" + return self._base_value / 10**-2 + + @property + def millimeters(self) -> float: + """The length in millimeters.""" + return self._base_value / 10**-3 + + @property + def micrometers(self) -> float: + """The length in micrometers.""" + return self._base_value / 10**-6 + + # --- End of auto generated part. --- class Time(_QuantityBase): """The duration of an event.""" - _UNIT_CONVERSION: dict[str, float] = { - "hours": 60 * 60, - "minutes": 60, - "seconds": 1, - "milliseconds": 10**-3, - } + # --- This part is auto generated. Do not change manually. --- + def __init__( + self, + hours: float = 0.0, + minutes: float = 0.0, + seconds: float = 0.0, + milliseconds: float = 0.0, + ) -> None: + self._base_value = 0.0 + self._base_value += hours * 60 * 60 + self._base_value += minutes * 60 + self._base_value += seconds * 1 + self._base_value += milliseconds * 10**-3 + + @property + def hours(self) -> float: + """The time in hours.""" + return self._base_value / 60 * 60 + + @property + def minutes(self) -> float: + """The time in minutes.""" + return self._base_value / 60 + + @property + def seconds(self) -> float: + """The time in seconds.""" + return self._base_value / 1 + + @property + def milliseconds(self) -> float: + """The time in milliseconds.""" + return self._base_value / 10**-3 + + # --- End of auto generated part. --- From 8ae9fa161fa3487f1b58d876b0db8e5318abf0ec Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 9 Nov 2024 15:53:10 +0100 Subject: [PATCH 12/16] feat: add Velocity --- generate/generate_boilerplate.py | 5 +++++ quantio/quantities.py | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/generate/generate_boilerplate.py b/generate/generate_boilerplate.py index 0f7c2cd..fb3ded7 100644 --- a/generate/generate_boilerplate.py +++ b/generate/generate_boilerplate.py @@ -94,6 +94,11 @@ def _generate_properties(current_class: str, units: dict[str, str]) -> list[str] "millimeters": "10**-3", "micrometers": "10**-6", }, + "Velocity": { + "meters_per_second": "10**0", + "kilometers_per_hour": "(1 / 3.6)", + "miles_per_hour": "(1 / 2.23694)", + }, "Time": { "hours": "60 * 60", "minutes": "60", diff --git a/quantio/quantities.py b/quantio/quantities.py index eacdac6..ec43b9d 100644 --- a/quantio/quantities.py +++ b/quantio/quantities.py @@ -139,6 +139,39 @@ def micrometers(self) -> float: # --- End of auto generated part. --- +class Velocity(_QuantityBase): + """Distance per time.""" + + # --- This part is auto generated. Do not change manually. --- + def __init__( + self, + meters_per_second: float = 0.0, + kilometers_per_hour: float = 0.0, + miles_per_hour: float = 0.0, + ) -> None: + self._base_value = 0.0 + self._base_value += meters_per_second * 10**0 + self._base_value += kilometers_per_hour * (1 / 3.6) + self._base_value += miles_per_hour * (1 / 2.23694) + + @property + def meters_per_second(self) -> float: + """The velocity in meters per second.""" + return self._base_value / 10**0 + + @property + def kilometers_per_hour(self) -> float: + """The velocity in kilometers per hour.""" + return self._base_value / (1 / 3.6) + + @property + def miles_per_hour(self) -> float: + """The velocity in miles per hour.""" + return self._base_value / (1 / 2.23694) + + # --- End of auto generated part. --- + + class Time(_QuantityBase): """The duration of an event.""" From 24f644931709bb37067d4c40dd60de7c5b4f0de6 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 9 Nov 2024 15:59:31 +0100 Subject: [PATCH 13/16] refactor: change spacing --- generate/generate_boilerplate.py | 11 ++++++----- quantio/quantities.py | 16 ++++++++++------ 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/generate/generate_boilerplate.py b/generate/generate_boilerplate.py index fb3ded7..beae7da 100644 --- a/generate/generate_boilerplate.py +++ b/generate/generate_boilerplate.py @@ -45,7 +45,7 @@ def generate_boilerplate(quantities_with_fields: dict[str, dict[str, str]]) -> N def _generate_init(units: dict[str, str]) -> list[str]: - code = [" " * 4 + "def __init__(", " " * 8 + "self,"] + code = ["", " " * 4 + "def __init__(", " " * 8 + "self,"] for unit in units: code.append(" " * 8 + f"{unit}: float = 0.0,") @@ -56,6 +56,7 @@ def _generate_init(units: dict[str, str]) -> list[str]: for unit, factor in units.items(): code.append(" " * 8 + f"self._base_value += {unit} * {factor}") + code.append("") return code @@ -63,11 +64,11 @@ def _generate_properties(current_class: str, units: dict[str, str]) -> list[str] code = [] for unit, factor in units.items(): - code.append("") code.append(" " * 4 + "@property") code.append(" " * 4 + f"def {unit}(self) -> float:") code.append(" " * 8 + f'"""The {current_class.lower()} in {unit.replace("_", " ")}."""') code.append(" " * 8 + f"return self._base_value / {factor}") + code.append("") return code @@ -77,7 +78,7 @@ def _generate_properties(current_class: str, units: dict[str, str]) -> list[str] "Area": { "square_miles": "1609.34**2", "square_kilometers": "10 ** (3 * 2)", - "square_meters": "10**0", + "square_meters": "1", "square_feet": "0.3048**2", "square_inches": "0.0254**2", "square_centimeters": "10 ** (-2 * 2)", @@ -87,7 +88,7 @@ def _generate_properties(current_class: str, units: dict[str, str]) -> list[str] "Length": { "miles": "1609.34", "kilometers": "10**3", - "meters": "10**0", + "meters": "1", "feet": "0.3048", "inches": "0.0254", "centimeters": "10**-2", @@ -95,7 +96,7 @@ def _generate_properties(current_class: str, units: dict[str, str]) -> list[str] "micrometers": "10**-6", }, "Velocity": { - "meters_per_second": "10**0", + "meters_per_second": "1", "kilometers_per_hour": "(1 / 3.6)", "miles_per_hour": "(1 / 2.23694)", }, diff --git a/quantio/quantities.py b/quantio/quantities.py index ec43b9d..0286560 100644 --- a/quantio/quantities.py +++ b/quantio/quantities.py @@ -7,6 +7,7 @@ class Area(_QuantityBase): """The two-dimensional extent of an object.""" # --- This part is auto generated. Do not change manually. --- + def __init__( self, square_miles: float = 0.0, @@ -21,7 +22,7 @@ def __init__( self._base_value = 0.0 self._base_value += square_miles * 1609.34**2 self._base_value += square_kilometers * 10 ** (3 * 2) - self._base_value += square_meters * 10**0 + self._base_value += square_meters * 1 self._base_value += square_feet * 0.3048**2 self._base_value += square_inches * 0.0254**2 self._base_value += square_centimeters * 10 ** (-2 * 2) @@ -41,7 +42,7 @@ def square_kilometers(self) -> float: @property def square_meters(self) -> float: """The area in square meters.""" - return self._base_value / 10**0 + return self._base_value / 1 @property def square_feet(self) -> float: @@ -75,6 +76,7 @@ class Length(_QuantityBase): """The one-dimensional extent of an object or the distance between two points.""" # --- This part is auto generated. Do not change manually. --- + def __init__( self, miles: float = 0.0, @@ -89,7 +91,7 @@ def __init__( self._base_value = 0.0 self._base_value += miles * 1609.34 self._base_value += kilometers * 10**3 - self._base_value += meters * 10**0 + self._base_value += meters * 1 self._base_value += feet * 0.3048 self._base_value += inches * 0.0254 self._base_value += centimeters * 10**-2 @@ -109,7 +111,7 @@ def kilometers(self) -> float: @property def meters(self) -> float: """The length in meters.""" - return self._base_value / 10**0 + return self._base_value / 1 @property def feet(self) -> float: @@ -143,6 +145,7 @@ class Velocity(_QuantityBase): """Distance per time.""" # --- This part is auto generated. Do not change manually. --- + def __init__( self, meters_per_second: float = 0.0, @@ -150,14 +153,14 @@ def __init__( miles_per_hour: float = 0.0, ) -> None: self._base_value = 0.0 - self._base_value += meters_per_second * 10**0 + self._base_value += meters_per_second * 1 self._base_value += kilometers_per_hour * (1 / 3.6) self._base_value += miles_per_hour * (1 / 2.23694) @property def meters_per_second(self) -> float: """The velocity in meters per second.""" - return self._base_value / 10**0 + return self._base_value / 1 @property def kilometers_per_hour(self) -> float: @@ -176,6 +179,7 @@ class Time(_QuantityBase): """The duration of an event.""" # --- This part is auto generated. Do not change manually. --- + def __init__( self, hours: float = 0.0, From 3e4f8391bc4074ced98f98ff1d29518b48902135 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 9 Nov 2024 16:06:14 +0100 Subject: [PATCH 14/16] feat: add Acceleration --- generate/generate_boilerplate.py | 4 ++++ quantio/quantities.py | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/generate/generate_boilerplate.py b/generate/generate_boilerplate.py index beae7da..84e6962 100644 --- a/generate/generate_boilerplate.py +++ b/generate/generate_boilerplate.py @@ -75,6 +75,10 @@ def _generate_properties(current_class: str, units: dict[str, str]) -> list[str] if __name__ == "__main__": quantities_with_fields = { + "Acceleration": { + "meters_per_square_second": "1", + "g_force": "(1 / 9.8)", + }, "Area": { "square_miles": "1609.34**2", "square_kilometers": "10 ** (3 * 2)", diff --git a/quantio/quantities.py b/quantio/quantities.py index 0286560..bd0cd1a 100644 --- a/quantio/quantities.py +++ b/quantio/quantities.py @@ -3,6 +3,33 @@ from ._quantity_base import _QuantityBase +class Acceleration(_QuantityBase): + """Rate of change of velocity.""" + + # --- This part is auto generated. Do not change manually. --- + + def __init__( + self, + meters_per_square_second: float = 0.0, + g_force: float = 0.0, + ) -> None: + self._base_value = 0.0 + self._base_value += meters_per_square_second * 1 + self._base_value += g_force * (1 / 9.8) + + @property + def meters_per_square_second(self) -> float: + """The acceleration in meters per square second.""" + return self._base_value / 1 + + @property + def g_force(self) -> float: + """The acceleration in g force.""" + return self._base_value / (1 / 9.8) + + # --- End of auto generated part. --- + + class Area(_QuantityBase): """The two-dimensional extent of an object.""" From 464584e18ff07d6f62f8eea4d6787c5e93b43e0d Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 9 Nov 2024 16:13:21 +0100 Subject: [PATCH 15/16] feat: add Mass --- generate/generate_boilerplate.py | 9 +++++ quantio/quantities.py | 62 ++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/generate/generate_boilerplate.py b/generate/generate_boilerplate.py index 84e6962..c30dd89 100644 --- a/generate/generate_boilerplate.py +++ b/generate/generate_boilerplate.py @@ -99,6 +99,15 @@ def _generate_properties(current_class: str, units: dict[str, str]) -> list[str] "millimeters": "10**-3", "micrometers": "10**-6", }, + "Mass": { + "tonnes": "10**3", + "kilograms": "1", + "pounds": "(1 / 2.20462)", + "ounces": "(1 / 35.27396)", + "grams": "10**-3", + "milligrams": "10**-6", + "micrograms": "10**-9", + }, "Velocity": { "meters_per_second": "1", "kilometers_per_hour": "(1 / 3.6)", diff --git a/quantio/quantities.py b/quantio/quantities.py index bd0cd1a..8e2a21e 100644 --- a/quantio/quantities.py +++ b/quantio/quantities.py @@ -168,6 +168,68 @@ def micrometers(self) -> float: # --- End of auto generated part. --- +class Mass(_QuantityBase): + """A measure of resistance to acceleration.""" + + # --- This part is auto generated. Do not change manually. --- + + def __init__( + self, + tonnes: float = 0.0, + kilograms: float = 0.0, + pounds: float = 0.0, + ounces: float = 0.0, + grams: float = 0.0, + milligrams: float = 0.0, + micrograms: float = 0.0, + ) -> None: + self._base_value = 0.0 + self._base_value += tonnes * 10**3 + self._base_value += kilograms * 1 + self._base_value += pounds * (1 / 2.20462) + self._base_value += ounces * (1 / 35.27396) + self._base_value += grams * 10**-3 + self._base_value += milligrams * 10**-6 + self._base_value += micrograms * 10**-9 + + @property + def tonnes(self) -> float: + """The mass in tonnes.""" + return self._base_value / 10**3 + + @property + def kilograms(self) -> float: + """The mass in kilograms.""" + return self._base_value / 1 + + @property + def pounds(self) -> float: + """The mass in pounds.""" + return self._base_value / (1 / 2.20462) + + @property + def ounces(self) -> float: + """The mass in ounces.""" + return self._base_value / (1 / 35.27396) + + @property + def grams(self) -> float: + """The mass in grams.""" + return self._base_value / 10**-3 + + @property + def milligrams(self) -> float: + """The mass in milligrams.""" + return self._base_value / 10**-6 + + @property + def micrograms(self) -> float: + """The mass in micrograms.""" + return self._base_value / 10**-9 + + # --- End of auto generated part. --- + + class Velocity(_QuantityBase): """Distance per time.""" From 9c43ce5cdb331741a8b4be6a7fc70d3c5f034288 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 9 Nov 2024 16:34:21 +0100 Subject: [PATCH 16/16] feat: add Angle --- generate/generate_boilerplate.py | 4 ++++ quantio/quantities.py | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/generate/generate_boilerplate.py b/generate/generate_boilerplate.py index c30dd89..55080e0 100644 --- a/generate/generate_boilerplate.py +++ b/generate/generate_boilerplate.py @@ -79,6 +79,10 @@ def _generate_properties(current_class: str, units: dict[str, str]) -> list[str] "meters_per_square_second": "1", "g_force": "(1 / 9.8)", }, + "Angle": { + "degrees": "(3.141592653589793 / 180)", + "radians": "1", + }, "Area": { "square_miles": "1609.34**2", "square_kilometers": "10 ** (3 * 2)", diff --git a/quantio/quantities.py b/quantio/quantities.py index 8e2a21e..916ca88 100644 --- a/quantio/quantities.py +++ b/quantio/quantities.py @@ -30,6 +30,33 @@ def g_force(self) -> float: # --- End of auto generated part. --- +class Angle(_QuantityBase): + """The figure formed by two rays.""" + + # --- This part is auto generated. Do not change manually. --- + + def __init__( + self, + degrees: float = 0.0, + radians: float = 0.0, + ) -> None: + self._base_value = 0.0 + self._base_value += degrees * (3.141592653589793 / 180) + self._base_value += radians * 1 + + @property + def degrees(self) -> float: + """The angle in degrees.""" + return self._base_value / (3.141592653589793 / 180) + + @property + def radians(self) -> float: + """The angle in radians.""" + return self._base_value / 1 + + # --- End of auto generated part. --- + + class Area(_QuantityBase): """The two-dimensional extent of an object."""