Skip to content

Commit

Permalink
fix(unit): fix unit bug
Browse files Browse the repository at this point in the history
- Fix bug for `Unit` class when used in Py v2.7
- format `isort`, `black`
- update all tests
  • Loading branch information
ed-p-may committed Jun 17, 2024
1 parent a88c1d2 commit a7cee2e
Show file tree
Hide file tree
Showing 22 changed files with 113 additions and 62 deletions.
22 changes: 14 additions & 8 deletions ph_units/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
"""Functions for converting Values from one unit to another (ie: meter->foot)."""

try:
from typing import Union, Optional, Tuple, Dict, List
from typing import Dict, List, Optional, Tuple, Union
except ImportError:
pass # IronPython 2.7

from copy import copy

from ph_units.unit_types._base import Base_UnitType
from ph_units.unit_types import build_unit_type_dicts
from ph_units.unit_types._base import Base_UnitType

unit_type_dict, unit_type_alias_dict = build_unit_type_dicts()


class UnitTypeNameNotFound(Exception):
def __init__(self, message):
# type: (str) -> None
Expand All @@ -26,21 +27,22 @@ def _find_valid_unit_names_matching_first_letter(_unit_type_alias_dict, _input_s
# type: (Dict[str, str], str) -> List[str]
"""Find all valid unit-type names that start with a given letter."""
matches = []

try:
first_letter = str(_input_string).strip()[0].upper()
except IndexError:
return matches

for k in sorted(_unit_type_alias_dict.keys()):
try:
if k[0] == first_letter:
matches.append(k)
except IndexError:
pass

return matches


def _standardize_unit_name(_input, _unit_type_alias_dict):
# type: (str, Dict[str, str]) -> str
"""Standardize unit nomenclature. ie: 'FT3/M' and 'CFM' both return 'CFM'.
Expand All @@ -61,12 +63,16 @@ def _standardize_unit_name(_input, _unit_type_alias_dict):
if _input_string == "%":
input_unit = "%"
else:
suggested_matches = _find_valid_unit_names_matching_first_letter(_unit_type_alias_dict, _input_string)
suggested_matches = _find_valid_unit_names_matching_first_letter(
_unit_type_alias_dict, _input_string
)
raise UnitTypeNameNotFound(
"\nI do not understand the unit: '{}'? "\
"\nI do not understand the unit: '{}'? "
"\nPerhaps you meant on of these: '{}'?"
"\n\nValid formats include only: {}".format(
_input_string, suggested_matches, sorted(_unit_type_alias_dict.keys())
_input_string,
suggested_matches,
sorted(_unit_type_alias_dict.keys()),
)
)

Expand Down
2 changes: 1 addition & 1 deletion ph_units/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""Function to parse input string from the user in numeric and 'unit' parts."""

try:
from typing import Union, Optional, Tuple
from typing import Optional, Tuple, Union
except ImportError:
pass # IronPython 2.7

Expand Down
80 changes: 51 additions & 29 deletions ph_units/unit_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,27 @@
"""Class to manage numeric values with a unit-type (ie: 0.5 IN)."""

try:
from typing import Any, Iterable, Union, Dict
from typing import Any, Dict, Iterable, Union
except ImportError:
pass # Python 2.7

try:
# If we can, lets try and play well with dataclasses
from dataclasses import _FIELD, Field # type: ignore
from dataclasses import _FIELD, Field # type: ignore
except ImportError:
# If we are in Python 2.7 (Fuck you Rhino 7) then fake it
_FIELD = None

class Field(object):
"""Field Protocol for Unit class so it can approximate a dataclass."""

def __init__(self, *args, **kwargs):
pass


from ph_units.converter import convert


class Unit(object):
"""A numeric value with a unit-type."""

Expand All @@ -30,29 +33,28 @@ class Unit(object):
# -------------------------------------------------------------
# -- This will allow the Unit object to behave like a dataclass
# -- and serialize itself when called as part of 'asdict'
__annotations__ = {'value': float, 'unit': str}
__annotations__ = {"value": float, "unit": str}

try:
field_value = Field(*[None] * 7) # Python 3.7
field_value = Field(*[None] * 7) # Python 3.7
except:
field_value = Field(*[None] * 8) # Python 3.11
field_value.name = "value" # type: ignore
field_value._field_type = _FIELD # type: ignore
field_value = Field(*[None] * 8) # Python 3.11
field_value.name = "value" # type: ignore
field_value._field_type = _FIELD # type: ignore

try:
field_unit = Field(*[None] * 7) # Python 3.7
field_unit = Field(*[None] * 7) # Python 3.7
except:
field_unit = Field(*[None] * 8) # Python 3.11
field_unit.name = "unit"# type: ignore
field_unit._field_type = _FIELD # type: ignore
field_unit = Field(*[None] * 8) # Python 3.11
field_unit.name = "unit" # type: ignore
field_unit._field_type = _FIELD # type: ignore

__dataclass_fields__ = {
'value': field_value,
'unit': field_unit,
}
"value": field_value,
"unit": field_unit,
}
# -------------------------------------------------------------


def __init__(self, value=0.0, unit="-"):
# type: (Union[float, str, None], str) -> None
self._value = float(str(value or 0).strip().replace(",", ""))
Expand All @@ -75,7 +77,9 @@ def as_a(self, unit):
try:
new_value = convert(self.value, self.unit, unit)
except Exception as e:
msg = "Error trying to convert '{}' from '{}' to '{}'".format(self.value, self.unit, unit)
msg = "Error trying to convert '{}' from '{}' to '{}'".format(
self.value, self.unit, unit
)
raise ValueError(msg, e)
return Unit(new_value, unit)

Expand Down Expand Up @@ -128,7 +132,9 @@ def __add__(self, other):
# type: (Union[Unit, int, float]) -> Unit
if not isinstance(other, (Unit, int, float)):
raise TypeError(
"Error: Cannot add '{}' to '{}'.".format(type(other), self.__class__.__name__)
"Error: Cannot add '{}' to '{}'.".format(
type(other), self.__class__.__name__
)
)
if isinstance(other, Unit):
if self.unit != other.unit:
Expand All @@ -140,7 +146,7 @@ def __add__(self, other):
return Unit(self.value + other, self.unit)

def __radd__(self, other):
# type: (Union[Unit, int, float]) -> Unit
# type: (Union[Unit, int, float]) -> Unit
if not isinstance(other, Unit):
return self
return self.__add__(other)
Expand All @@ -149,7 +155,9 @@ def __mul__(self, other):
# type: (Union[Unit, int, float]) -> Unit
if not isinstance(other, (Unit, float, int)):
raise TypeError(
"Error: Cannot multiply '{}' by '{}'.".format(type(other), self.__class__.__name__)
"Error: Cannot multiply '{}' by '{}'.".format(
type(other), self.__class__.__name__
)
)
if isinstance(other, Unit):
if self.unit != other.unit:
Expand All @@ -160,7 +168,9 @@ def __mul__(self, other):
return Unit(self.value * other.value, unit_type)
else:
raise TypeError(
"Error: Cannot multiply '{}' by '{}'.".format(self.unit, other.unit)
"Error: Cannot multiply '{}' by '{}'.".format(
self.unit, other.unit
)
)
else:
return Unit(self.value * other.value, self.unit)
Expand All @@ -170,7 +180,9 @@ def __truediv__(self, other):
# type: (Union[Unit, int, float]) -> Unit
if not isinstance(other, (Unit, float, int)):
raise TypeError(
"Error: Cannot divide '{}' by '{}'.".format(type(other), self.__class__.__name__)
"Error: Cannot divide '{}' by '{}'.".format(
type(other), self.__class__.__name__
)
)
if isinstance(other, Unit):
if self.unit != other.unit:
Expand All @@ -181,7 +193,9 @@ def __truediv__(self, other):
return Unit(self.value / other.value, unit_type)
else:
raise TypeError(
"Error: Cannot divide '{}' by '{}'.".format(self.unit, other.unit)
"Error: Cannot divide '{}' by '{}'.".format(
self.unit, other.unit
)
)
else:
return Unit(self.value / other.value, "-")
Expand Down Expand Up @@ -214,32 +228,40 @@ def __le__(self, other):
if not isinstance(other, Unit):
return self.value <= other
if not self.unit == other.unit:
raise TypeError("Cannot compare '{}' to '{}'.".format(self.unit, other.unit))
raise TypeError(
"Cannot compare '{}' to '{}'.".format(self.unit, other.unit)
)
return self.value <= other.value

def __lt__(self, other):
# type: (Any) -> bool
if not isinstance(other, Unit):
return self.value < other
if not self.unit == other.unit:
raise TypeError("Cannot compare '{}' to '{}'.".format(self.unit, other.unit))
raise TypeError(
"Cannot compare '{}' to '{}'.".format(self.unit, other.unit)
)
return self.value < other.value

def __setattr__(self, name, value):
# type: (str, Any) -> None
if self._frozen and hasattr(self, name):
raise AttributeError(
"Modifying '{}' of '{}' is not allowed.".format(name,self.__class__.__name__)
"Modifying '{}' of '{}' is not allowed.".format(
name, self.__class__.__name__
)
)
super().__setattr__(name, value)
super(Unit, self).__setattr__(name, value)

def __repr__(self):
# type: () -> str
return "{}(value={!r}, unit={!r})".format(self.__class__.__name__, self.value, self.unit)
return "{}(value={!r}, unit={!r})".format(
self.__class__.__name__, self.value, self.unit
)

def __str__(self):
# type: () -> str
return "{:,.3f} ({})".format(self.value, self.unit)

def ToString(self):
return str(self)
return str(self)
29 changes: 15 additions & 14 deletions ph_units/unit_types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,26 @@
"""Unit-Type Conversion Factors."""

try:
from typing import Dict, Tuple
from types import ModuleType
from typing import Dict, Tuple
except ImportError:
pass # Python 2.7

from ph_units.unit_types import (
area,
density,
emissions_factors,
energy,
envelope,
length,
moisture_vapor_resistance,
power,
speed,
temperature,
volume,
volume_flow,
)
from ph_units.unit_types._base import Base_UnitType
from ph_units.unit_types import area
from ph_units.unit_types import energy
from ph_units.unit_types import envelope
from ph_units.unit_types import length
from ph_units.unit_types import power
from ph_units.unit_types import speed
from ph_units.unit_types import temperature
from ph_units.unit_types import volume_flow
from ph_units.unit_types import volume
from ph_units.unit_types import density
from ph_units.unit_types import emissions_factors
from ph_units.unit_types import moisture_vapor_resistance


UNIT_TYPE_MODULES = (
area,
Expand Down
2 changes: 1 addition & 1 deletion ph_units/unit_types/area.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ class FootSquarePerPerson(Base_UnitType):
"FT2-PERSON",
"FT²/PERSON",
]
__factors__ = { "M2/PERSON": "{}*0.09290304", "FT2/PERSON": "{}*1"}
__factors__ = {"M2/PERSON": "{}*0.09290304", "FT2/PERSON": "{}*1"}
4 changes: 1 addition & 3 deletions ph_units/unit_types/energy.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class WattHour(Base_UnitType):
"KJ": "{}*3.6",
}


class KiloWattHour(Base_UnitType):
"""KWH"""

Expand Down Expand Up @@ -93,7 +94,6 @@ class KiloJoule(Base_UnitType):
}



# ----------------- Energy Per Person ---------------


Expand Down Expand Up @@ -127,8 +127,6 @@ class KiloBTUPerPerson(Base_UnitType):
}




# ----------------- Energy Per Area -----------------


Expand Down
1 change: 1 addition & 0 deletions tests/test_areas.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# -*- Python Version: 2.7 -*-

import pytest

from ph_units.converter import convert


Expand Down
1 change: 1 addition & 0 deletions tests/test_as_a.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from ph_units.unit_type import Unit


def test_basic_use() -> None:
assert Unit(1.0, "KWH").as_a("KBTU") == Unit(3.41214, "KBTU")

Expand Down
11 changes: 10 additions & 1 deletion tests/test_converter.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
# -*- coding: utf-8 -*-
# -*- Python Version: 2.7 -*-
import pytest
from ph_units.converter import _standardize_unit_name, unit_type_dict, unit_type_alias_dict, UnitTypeNameNotFound

from ph_units.converter import (
UnitTypeNameNotFound,
_standardize_unit_name,
unit_type_alias_dict,
unit_type_dict,
)


def test_standardize_unit_name_simple() -> None:
assert _standardize_unit_name("FT", unit_type_alias_dict) == "FT"
assert _standardize_unit_name("FT", unit_type_alias_dict) == "FT"


def test_standardize_unit_name_percent() -> None:
assert _standardize_unit_name("%", unit_type_alias_dict) == "%"


def test_standardize_unit_name_alias() -> None:
assert _standardize_unit_name("FT3/M", unit_type_alias_dict) == "CFM"
assert _standardize_unit_name("CFM", unit_type_alias_dict) == "CFM"


def test_standardize_gives_error() -> None:
with pytest.raises(UnitTypeNameNotFound):
_standardize_unit_name("Not/A-Unit", unit_type_alias_dict)
1 change: 1 addition & 0 deletions tests/test_density.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# -*- Python Version: 2.7 -*-

import pytest

from ph_units.converter import convert


Expand Down
Loading

0 comments on commit a7cee2e

Please sign in to comment.