diff --git a/.gitignore b/.gitignore index 9e32c83..e8cb269 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.pyc *egg-info* +.mypy_cache/ .Python bin/* build/ diff --git a/measurement/base.py b/measurement/base.py index 236d30e..7b1df04 100644 --- a/measurement/base.py +++ b/measurement/base.py @@ -5,17 +5,24 @@ import inspect import warnings from functools import total_ordering -from typing import Any, Dict, Iterable, List, Optional, Tuple, Type, Union +from typing import Any, Dict, Iterable, List, Mapping, Optional, Tuple, Type, TypeVar, \ + Union, Generic + +T = TypeVar("T", bound="AbstractMeasure") def qualname(obj: Any) -> str: return obj.__qualname__ if inspect.isclass(obj) else type(obj).__qualname__ -class ImmutableKeyDict(Dict): +K = TypeVar("K") +V = TypeVar("V") + + +class ImmutableKeyDict(Generic[K, V], Dict[K, V]): """Like :class:`.dict` but any key may only assigned to a value once.""" - def __setitem__(self, key, value): + def __setitem__(self, key: K, value: V) -> None: """ Map item to key once and raise error if the same key is set twice. @@ -46,10 +53,10 @@ def from_si(self, value: decimal.Decimal) -> decimal.Decimal: """Return measure in the unit defined by this class based on given SI measure.""" @abc.abstractmethod - def get_symbols(self) -> Iterable[Tuple[str, Type["AbstractUnit"]]]: + def get_symbols(self) -> Iterable[Tuple[str, "AbstractUnit"]]: """Return list of symbol names and their :class:`.AbstractUnit` representation.""" - def __str__(self): + def __str__(self) -> str: return str(self.name) @@ -90,17 +97,17 @@ class Distance(AbstractMeasure): symbols: List[str] = dataclasses.field(default_factory=list) """Symbols used to describe this unit.""" - def __post_init__(self): + def __post_init__(self) -> None: if self.factor is not None: self.factor = decimal.Decimal(self.factor) - def to_si(self, value): + def to_si(self, value: decimal.Decimal) -> decimal.Decimal: return value * self.factor - def from_si(self, value): + def from_si(self, value: decimal.Decimal) -> decimal.Decimal: return value / self.factor - def get_symbols(self): + def get_symbols(self) -> Iterable[Tuple[str, "Unit"]]: yield self.name.replace("_", " "), Unit(self.factor) yield from ((name, Unit(self.factor)) for name in self.symbols) @@ -168,7 +175,7 @@ class MetricUnit(Unit): "yotta": decimal.Decimal("1e24"), } - def get_symbols(self): + def get_symbols(self) -> Iterable[Tuple[str, "Unit"]]: yield from super().get_symbols() yield from ( (f"{prefix}{s}", Unit(factor=self.factor * factor)) @@ -203,7 +210,7 @@ class attributes to a Measure. This metaclass removes to attributes and def __new__(mcs, name, bases, attrs): mcs.freeze_org_units(attrs) - symbols = ImmutableKeyDict() + symbols: ImmutableKeyDict[str, AbstractUnit] = ImmutableKeyDict() new_attr = {} for attr_name, attr in attrs.items(): if isinstance(attr, AbstractUnit): @@ -219,7 +226,7 @@ def __new__(mcs, name, bases, attrs): return cls @staticmethod - def freeze_org_units(attrs: Dict[str, Any]): + def freeze_org_units(attrs: Dict[str, Any]) -> None: if "_org_units" in attrs: return @@ -258,7 +265,7 @@ def __init__( self.unit.org_name = unit self.si_value = self.unit.to_si(value) - def __getattr__(self, name): + def __getattr__(self, name: str) -> decimal.Decimal: try: unit = self._units[self._attr_to_unit(name)] except KeyError as e: @@ -268,7 +275,7 @@ def __getattr__(self, name): else: return unit.from_si(self.si_value) - def __getitem__(self, item): + def __getitem__(self, item: str) -> decimal.Decimal: try: unit = self._units[self._attr_to_unit(item)] except KeyError as e: @@ -280,7 +287,7 @@ def __getitem__(self, item): def _attr_to_unit(cls, name: str) -> str: return name.replace("_", " ") - unit = None + unit: AbstractUnit """Return :class:`~Unit` initially given to construct the measure.""" @property @@ -288,13 +295,13 @@ def _value(self) -> decimal.Decimal: """Return :class:`~Decimal` value of measure in the given :attr:`.unit`.""" return getattr(self, self.unit.name) - def __repr__(self): + def __repr__(self) -> str: return f'{qualname(self)}({self.unit.name}="{getattr(self, self.unit.name)}")' - def __str__(self): + def __str__(self) -> str: return "%s %s" % (getattr(self, self.unit.org_name), self.unit.org_name) - def __format__(self, format_spec): + def __format__(self, format_spec: str) -> str: decimal_format = getattr(self, self.unit.org_name).__format__(format_spec) return f"{decimal_format} {self.unit.org_name}" @@ -303,27 +310,27 @@ def __eq__(self, other): return NotImplemented return self.si_value == other.si_value - def __lt__(self, other): + def __lt__(self, other: Any) -> Optional[bool]: if not isinstance(other, type(self)): return NotImplemented return self.si_value < other.si_value - def __gt__(self, other): + def __gt__(self, other: Any) -> Optional[bool]: if not isinstance(other, type(self)): return NotImplemented return self.si_value > other.si_value - def __add__(self, other): + def __add__(self: T, other: T) -> T: if not isinstance(other, type(self)): raise TypeError(f"can't add type '{qualname(self)}' to '{qualname(other)}'") return type(self)( value=self._value + getattr(other, self.unit.name), unit=self.unit.name ) - def __iadd__(self, other): + def __iadd__(self: T, other: T) -> T: return self + other - def __sub__(self, other): + def __sub__(self: T, other: T) -> T: if not isinstance(other, type(self)): raise TypeError( f"can't substract type '{qualname(other)}' from '{qualname(self)}'" @@ -333,7 +340,7 @@ def __sub__(self, other): value=self._value - getattr(other, self.unit.name), unit=self.unit.name ) - def __isub__(self, other): + def __isub__(self: T, other: T) -> T: return self - other def __mul__(self, other): @@ -369,5 +376,5 @@ def __itruediv__(self, other): def __rtruediv__(self, other): return self / other - def __bool__(self): + def __bool__(self) -> bool: return bool(self.si_value) diff --git a/measurement/utils.py b/measurement/utils.py index 568599f..67ff49a 100644 --- a/measurement/utils.py +++ b/measurement/utils.py @@ -1,4 +1,10 @@ -def guess(value, unit, measures=None): +import decimal +from typing import List, Optional, Type + +from measurement.base import AbstractMeasure + + +def guess(value: decimal.Decimal, unit: str, measures: Optional[List[Type[AbstractMeasure]]] = None) -> AbstractMeasure: """ Return measurement instance based on given unit. @@ -6,14 +12,14 @@ def guess(value, unit, measures=None): ValueError: If measurement type cannot be guessed. Returns: - MeasureBase: Measurement instance based on given unit. + AbstractMeasure: Measurement instance based on given unit. """ - from measurement.base import AbstractMeasure + all_measures: List[Type[AbstractMeasure]] = measures or AbstractMeasure.__subclasses__() - for measure in measures or AbstractMeasure.__subclasses__(): + for measure in all_measures: try: - return measure(**{unit: value}) + return measure(value=None, unit=None, **{unit: value}) except KeyError: pass raise ValueError(f"can't guess measure for '{value} {unit}'") diff --git a/mypy.log b/mypy.log new file mode 100644 index 0000000..bd93474 --- /dev/null +++ b/mypy.log @@ -0,0 +1,150 @@ +measurement/base.py:15: error: Returning Any from function declared to return "str" +measurement/base.py:89: error: Incompatible types in assignment (expression has type "None", variable has type "Union[str, Decimal]") +measurement/base.py:105: error: Unsupported operand types for * ("Decimal" and "str") +measurement/base.py:105: note: Right operand is of type "Union[str, Decimal]" +measurement/base.py:108: error: Unsupported operand types for / ("Decimal" and "str") +measurement/base.py:108: note: Right operand is of type "Union[str, Decimal]" +measurement/base.py:111: error: "None" has no attribute "replace" +measurement/base.py:181: error: Unsupported operand types for * ("str" and "Decimal") +measurement/base.py:181: note: Left operand is of type "Union[str, Decimal]" +measurement/base.py:186: error: Unsupported operand types for * ("str" and "Decimal") +measurement/base.py:186: note: Left operand is of type "Union[str, Decimal]" +measurement/base.py:191: error: Unsupported operand types for * ("str" and "Decimal") +measurement/base.py:191: note: Left operand is of type "Union[str, Decimal]" +measurement/base.py:211: error: Function is missing a type annotation +measurement/base.py:225: error: "type" has no attribute "_units" +measurement/base.py:253: error: Item "Decimal" of "Union[str, Decimal, int, None]" has no attribute "split" +measurement/base.py:253: error: Item "int" of "Union[str, Decimal, int, None]" has no attribute "split" +measurement/base.py:253: error: Item "None" of "Union[str, Decimal, int, None]" has no attribute "split" +measurement/base.py:261: error: Argument 1 to "Decimal" has incompatible type "Union[str, Decimal, int, None]"; expected "Union[Decimal, float, str, Tuple[int, Sequence[int], int]]" +measurement/base.py:264: error: Value of type "Decimal" is not indexable +measurement/base.py:265: error: "AbstractUnit" has no attribute "org_name" +measurement/base.py:270: error: Value of type "Decimal" is not indexable +measurement/base.py:276: error: Returning Any from function declared to return "Decimal" +measurement/base.py:280: error: Value of type "Decimal" is not indexable +measurement/base.py:284: error: Returning Any from function declared to return "Decimal" +measurement/base.py:296: error: Returning Any from function declared to return "Decimal" +measurement/base.py:296: error: Argument 2 to "getattr" has incompatible type "None"; expected "str" +measurement/base.py:299: error: Argument 2 to "getattr" has incompatible type "None"; expected "str" +measurement/base.py:302: error: "AbstractUnit" has no attribute "org_name" +measurement/base.py:305: error: "AbstractUnit" has no attribute "org_name" +measurement/base.py:306: error: "AbstractUnit" has no attribute "org_name" +measurement/base.py:308: error: Function is missing a type annotation +measurement/base.py:327: error: Argument 2 to "getattr" has incompatible type "None"; expected "str" +measurement/base.py:340: error: Argument 2 to "getattr" has incompatible type "None"; expected "str" +measurement/base.py:346: error: Function is missing a type annotation +measurement/base.py:348: error: "AbstractUnit" has no attribute "org_name" +measurement/base.py:349: error: "AbstractUnit" has no attribute "org_name" +measurement/base.py:355: error: Function is missing a type annotation +measurement/base.py:358: error: Function is missing a type annotation +measurement/base.py:361: error: Function is missing a type annotation +measurement/base.py:365: error: "AbstractUnit" has no attribute "org_name" +measurement/base.py:371: error: "AbstractUnit" has no attribute "org_name" +measurement/base.py:373: error: Function is missing a type annotation +measurement/base.py:376: error: Function is missing a type annotation +measurement/measures/time.py:33: error: Function is missing a type annotation +measurement/measures/time.py:36: error: Call to untyped function "__mul__" in typed context +measurement/measures/temperature.py:10: error: Only concrete class can be given where "Type[DegreeUnit]" is expected +measurement/measures/temperature.py:15: error: Function is missing a return type annotation +measurement/measures/temperature.py:16: error: "None" has no attribute "replace" +measurement/measures/temperature.py:21: error: Function is missing a type annotation +measurement/measures/temperature.py:24: error: Function is missing a type annotation +measurement/measures/temperature.py:29: error: Function is missing a type annotation +measurement/measures/temperature.py:33: error: Function is missing a type annotation +measurement/measures/geometry.py:75: error: Function is missing a type annotation +measurement/measures/geometry.py:82: error: Call to untyped function "__mul__" in typed context +measurement/measures/geometry.py:84: error: Function is missing a type annotation +measurement/measures/geometry.py:93: error: Function is missing a type annotation +measurement/measures/geometry.py:97: error: Call to untyped function "square" in typed context +measurement/measures/geometry.py:99: error: Call to untyped function "__new__" in typed context +measurement/measures/geometry.py:103: error: Function is missing a type annotation +measurement/measures/geometry.py:106: error: Incompatible types in assignment (expression has type "str", variable has type "None") +measurement/measures/geometry.py:139: error: Function is missing a type annotation +measurement/measures/geometry.py:144: error: Function is missing a type annotation +measurement/measures/geometry.py:148: error: Call to untyped function "__truediv__" in typed context +measurement/measures/geometry.py:150: error: Function is missing a type annotation +measurement/measures/geometry.py:154: error: Call to untyped function "__mul__" in typed context +measurement/measures/geometry.py:158: error: Function is missing a type annotation +measurement/measures/geometry.py:162: error: Call to untyped function "cubic" in typed context +measurement/measures/geometry.py:163: error: Call to untyped function "__new__" in typed context +measurement/measures/geometry.py:167: error: Function is missing a type annotation +measurement/measures/geometry.py:170: error: Incompatible types in assignment (expression has type "str", variable has type "None") +measurement/measures/geometry.py:246: error: Function is missing a type annotation +measurement/measures/geometry.py:251: error: Function is missing a type annotation +measurement/measures/geometry.py:258: error: Call to untyped function "__truediv__" in typed context +measurement/measures/electromagnetism.py:20: error: Function is missing a type annotation +measurement/measures/electromagnetism.py:23: error: Call to untyped function "__mul__" in typed context +measurement/measures/electromagnetism.py:33: error: Function is missing a type annotation +measurement/measures/electromagnetism.py:36: error: Call to untyped function "__mul__" in typed context +measurement/measures/electromagnetism.py:71: error: Function is missing a type annotation +measurement/measures/electromagnetism.py:78: error: Call to untyped function "__truediv__" in typed context +measurement/measures/mechanics.py:12: error: Function is missing a type annotation +measurement/measures/mechanics.py:16: error: Call to untyped function "div" in typed context +measurement/measures/mechanics.py:18: error: Call to untyped function "__new__" in typed context +measurement/measures/mechanics.py:22: error: Function is missing a type annotation +measurement/measures/mechanics.py:28: error: Incompatible types in assignment (expression has type "str", variable has type "None") +measurement/measures/mechanics.py:39: error: Function is missing a type annotation +measurement/measures/mechanics.py:51: error: Function is missing a type annotation +tests/measures/test_speed.py:119: error: "Type[Speed]" has no attribute "_units" +tests/measures/test_speed.py:122: error: "Type[Speed]" has no attribute "_units" +tests/test_utils.py:8: error: Argument 1 to "guess" has incompatible type "int"; expected "Decimal" +tests/test_utils.py:12: error: Argument 1 to "guess" has incompatible type "int"; expected "Decimal" +tests/test_utils.py:16: error: Argument 1 to "guess" has incompatible type "int"; expected "Decimal" +tests/test_utils.py:21: error: Argument 1 to "guess" has incompatible type "int"; expected "Decimal" +tests/test_base.py:32: error: Incompatible types in assignment (expression has type "str", variable has type "None") +tests/test_base.py:53: error: Incompatible types in assignment (expression has type "str", variable has type "None") +tests/test_base.py:73: error: Incompatible types in assignment (expression has type "str", variable has type "None") +tests/test_base.py:90: error: Argument "km" to "Distance" has incompatible type "float"; expected "Union[str, Decimal, int, None]" +tests/test_base.py:106: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:109: error: Call to untyped function "__eq__" in typed context +tests/test_base.py:109: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:112: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:113: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:116: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:119: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:120: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:123: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:126: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:127: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:128: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:132: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:132: error: Unsupported operand types for + ("Distance" and "str") +tests/test_base.py:136: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:137: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:138: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:141: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:142: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:143: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:147: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:147: error: Unsupported operand types for - ("Distance" and "str") +tests/test_base.py:154: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:155: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:156: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:159: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:163: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:174: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:176: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:178: error: Incompatible types in assignment (expression has type "int", variable has type "Distance") +tests/test_base.py:179: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:180: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:183: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:186: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:187: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:191: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:195: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:197: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:199: error: Incompatible types in assignment (expression has type "int", variable has type "Distance") +tests/test_base.py:200: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:201: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:203: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:204: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:208: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:211: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:212: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/test_base.py:213: error: Argument 1 has incompatible type "**Dict[str, int]"; expected "Optional[str]" +tests/measures/test_temperature.py:11: error: Argument "celsius" to "Temperature" has incompatible type "float"; expected "Union[str, Decimal, int, None]" +tests/measures/test_temperature.py:16: error: Argument "celsius" to "Temperature" has incompatible type "float"; expected "Union[str, Decimal, int, None]" +tests/measures/test_geometry.py:28: error: Argument "meter" to "Distance" has incompatible type "float"; expected "Union[str, Decimal, int, None]" +tests/measures/test_geometry.py:77: error: Call to untyped function "_attr_to_unit" of "Area" in typed context +tests/measures/test_geometry.py:78: error: Call to untyped function "_attr_to_unit" of "Area" in typed context +Found 144 errors in 11 files (checked 22 source files) diff --git a/setup.cfg b/setup.cfg index 3f5202f..702411b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -85,3 +85,19 @@ use_parentheses=True known_first_party = measurement, tests default_section=THIRDPARTY combine_as_imports = true + +[mypy] +warn_unused_configs = True +warn_redundant_casts = True +disallow_subclassing_any = True +disallow_any_generics = True +disallow_untyped_calls = True +disallow_untyped_defs = True +disallow_incomplete_defs = True +check_untyped_defs = True +disallow_untyped_decorators = True +no_implicit_optional = True +warn_unused_ignores = True +warn_return_any = True +strict_optional = True +ignore_missing_imports = True diff --git a/tests/measures/test_electromagnetism.py b/tests/measures/test_electromagnetism.py index f94cf7d..6d13900 100644 --- a/tests/measures/test_electromagnetism.py +++ b/tests/measures/test_electromagnetism.py @@ -2,35 +2,35 @@ class TestCurrent: - def test_mul(self): + def test_mul(self) -> None: assert measures.Current("2 A") * measures.Voltage( "12 V" ) == measures.ElectricPower("24 W") - def test_mul__super(self): + def test_mul__super(self) -> None: assert measures.Current("2 A") * 2 == measures.Current("4 A") class TestVoltage: - def test_mul(self): + def test_mul(self) -> None: assert measures.Voltage("12 V") * measures.Current( "2 A" ) == measures.ElectricPower("24 W") - def test_mul__super(self): + def test_mul__super(self) -> None: assert measures.Voltage("6 V") * 2 == measures.Voltage("12 V") class TestElectricPower: - def test_truediv__voltage(self): + def test_truediv__voltage(self) -> None: assert measures.ElectricPower("24 W") / measures.Voltage( "12 V" ) == measures.Current("2 A") - def test_truediv__current(self): + def test_truediv__current(self) -> None: assert measures.ElectricPower("24 W") / measures.Current( "4 A" ) == measures.Voltage("6 V") - def test_truediv__super(self): + def test_truediv__super(self) -> None: assert measures.ElectricPower("24 W") / 2 == measures.ElectricPower("12 W") diff --git a/tests/measures/test_energy.py b/tests/measures/test_energy.py index f1326de..1cb5fc5 100644 --- a/tests/measures/test_energy.py +++ b/tests/measures/test_energy.py @@ -2,7 +2,7 @@ class TestEnergy: - def test_dietary_calories_kwarg(self): + def test_dietary_calories_kwarg(self) -> None: calories = Energy(Calorie=2000) kilojoules = Energy(kJ=8368) diff --git a/tests/measures/test_geometry.py b/tests/measures/test_geometry.py index adc9610..f6b2d95 100644 --- a/tests/measures/test_geometry.py +++ b/tests/measures/test_geometry.py @@ -6,53 +6,53 @@ class TestDistance: - def test_conversion_equivalence(self): + def test_conversion_equivalence(self) -> None: miles = Distance(mi=1) kilometers = Distance(km=decimal.Decimal("1.609344")) assert miles.km == kilometers.km - def test_attrib_conversion(self): + def test_attrib_conversion(self) -> None: kilometers = Distance(km=1) expected_meters = 1000 assert kilometers.m == expected_meters - def test_identity_conversion(self): + def test_identity_conversion(self) -> None: expected_miles = 10 miles = Distance(mi=expected_miles) assert miles.mi == expected_miles - def test_auto_si_kwargs(self): + def test_auto_si_kwargs(self) -> None: meters = Distance(meter=1e6) megameters = Distance(megameter=1) assert meters == megameters - def test_auto_si_attrs(self): + def test_auto_si_attrs(self) -> None: one_meter = Distance(m=1) micrometers = one_meter.um assert one_meter.si_value * 10 ** 6 == micrometers - def test_area_sq_km(self): + def test_area_sq_km(self) -> None: one_sq_km = Area(sq_km=10) miles_sqd = Area(sq_mi=decimal.Decimal("3.861021585424458472628811394")) assert one_sq_km.si_value == miles_sqd.si_value - def test_mul__distance(self): + def test_mul__distance(self) -> None: assert Distance(m=1) * Distance(m=1) == Area("1 m²") - def test_mul__area(self): + def test_mul__area(self) -> None: assert Distance(m=1) * Area(sq_m=1) == Volume("1 m³") - def test_mul__super(self): + def test_mul__super(self) -> None: assert Distance(m=1) * 3 == Distance("3 m") - def test_pow(self): + def test_pow(self) -> None: assert Distance(m=1) ** 2 == Area("1 m²") assert Distance(m=1) ** 3 == Volume("1 m³") @@ -61,39 +61,39 @@ def test_pow(self): class TestArea: - def test_truediv(self): + def test_truediv(self) -> None: assert Area("1 m²") / Distance("1 m") == Distance("1 m") - def test_truediv__super(self): + def test_truediv__super(self) -> None: assert Area("1 m²") / 2 == Area("0.5 m²") - def test_mul(self): + def test_mul(self) -> None: assert Area("1 m²") * Distance("1 m") == Volume("1 m³") - def test_mul__super(self): + def test_mul__super(self) -> None: assert Area("1 m²") * 2 == Area("2 m²") - def test_attr_to_unit(self): + def test_attr_to_unit(self) -> None: assert Area._attr_to_unit("sq_m") == "m²" assert Area._attr_to_unit("m²") == "m²" class TestVolume: - def test_truediv__distance(self): + def test_truediv__distance(self) -> None: assert Volume("1 m³") / Distance("1 m") == Area("1 m²") - def test_truediv__area(self): + def test_truediv__area(self) -> None: assert Volume("1 m³") / Area("1 m²") == Distance("1 m") - def test_truediv__super(self): + def test_truediv__super(self) -> None: assert Volume("1 m³") / 2 == Volume("0.5 m³") - def test_litre(self): + def test_litre(self) -> None: assert Volume("1 cubic metre") == Volume("1000 L") assert Volume("1 mL") == Volume("1e-6 m³") - def test_us_fluid_ounce(self): + def test_us_fluid_ounce(self) -> None: assert Volume("29.57353 mL") == Volume("1 US fl oz") - def test_imperial_flud_ounce(self): + def test_imperial_flud_ounce(self) -> None: assert Volume("28.41306 mL") == Volume("1 imp fl oz") diff --git a/tests/measures/test_speed.py b/tests/measures/test_speed.py index 51ca1c1..18a6ba5 100644 --- a/tests/measures/test_speed.py +++ b/tests/measures/test_speed.py @@ -2,10 +2,10 @@ class TestSpeed: - def test_attrconversion(self): + def test_attrconversion(self) -> None: assert Speed("10 m/s") == Speed("36 km/h") - def test_addition(self): + def test_addition(self) -> None: train_1 = Speed(mile__hour=10) train_2 = Speed(mile__hour=5) @@ -14,7 +14,7 @@ def test_addition(self): assert actual_value == expected_value - def test_iadd(self): + def test_iadd(self) -> None: train_1 = Speed(mile__hour=10) train_2 = Speed(mile__hour=5) @@ -24,7 +24,7 @@ def test_iadd(self): assert actual_value == expected_value - def test_sub(self): + def test_sub(self) -> None: train_1 = Speed(mile__hour=10) train_2 = Speed(mile__hour=5) @@ -33,7 +33,7 @@ def test_sub(self): assert expected_value == actual_value - def test_isub(self): + def test_isub(self) -> None: train_1 = Speed(mile__hour=10) train_2 = Speed(mile__hour=5) @@ -43,7 +43,7 @@ def test_isub(self): assert expected_value == actual_value - def test_mul(self): + def test_mul(self) -> None: train_1 = Speed(mile__hour=10) multiplier = 2 @@ -52,7 +52,7 @@ def test_mul(self): assert expected_value == actual_value - def test_imul(self): + def test_imul(self) -> None: train_1 = Speed(mile__hour=10) multiplier = 2 @@ -62,7 +62,7 @@ def test_imul(self): assert expected_value == actual_value - def test_div(self): + def test_div(self) -> None: train_1 = Speed(mile__hour=10) divider = 2 @@ -71,7 +71,7 @@ def test_div(self): assert expected_value == actual_value - def test_idiv(self): + def test_idiv(self) -> None: train_1 = Speed(mile__hour=10) divider = 2 @@ -81,42 +81,42 @@ def test_idiv(self): assert expected_value == actual_value - def test_equals(self): + def test_equals(self) -> None: train_1 = Speed(mile__hour=10) train_2 = Speed(mile__hour=10) assert train_1 == train_2 - def test_lt(self): + def test_lt(self) -> None: train_1 = Speed(mile__hour=5) train_2 = Speed(mile__hour=10) assert train_1 < train_2 - def test_bool_true(self): + def test_bool_true(self) -> None: train_1 = Speed(mile__hour=5) assert train_1 - def test_bool_false(self): + def test_bool_false(self) -> None: train_1 = Speed(mile__hour=0) assert not train_1 - def test_abbreviations(self): + def test_abbreviations(self) -> None: train_1 = Speed(mph=4) train_2 = Speed(mile__hour=4) assert train_1 == train_2 - def test_different_units_addition(self): + def test_different_units_addition(self) -> None: train = Speed(km__h=1) increase = Speed(m__h=10) assert train + increase == Speed("1.01 km/h") - def test_mph(self): + def test_mph(self) -> None: assert Speed(mph=10) == Speed(mi__h=10), Speed._units["mi__h"].factor - def test_kph(self): + def test_kph(self) -> None: assert Speed(kph=10) == Speed(km__h=10), Speed._units["km__h"].factor diff --git a/tests/measures/test_temperature.py b/tests/measures/test_temperature.py index 7411b46..e305d60 100644 --- a/tests/measures/test_temperature.py +++ b/tests/measures/test_temperature.py @@ -6,18 +6,18 @@ class TestTemperature: - def test_sanity(self): + def test_sanity(self) -> None: fahrenheit = Temperature(fahrenheit=70) celsius = Temperature(celsius=21.1111111) assert fahrenheit.K == pytest.approx(celsius.K) - def test_conversion_to_non_si(self): + def test_conversion_to_non_si(self) -> None: celsius = Temperature(celsius=21.1111111) expected_farenheit = decimal.Decimal("70") assert celsius.fahrenheit == pytest.approx(expected_farenheit) - def test_ensure_that_we_always_output_float(self): + def test_ensure_that_we_always_output_float(self) -> None: kelvin = Temperature(kelvin=10) assert isinstance(kelvin.celsius, decimal.Decimal) diff --git a/tests/measures/test_time.py b/tests/measures/test_time.py index 5832cde..26e129d 100644 --- a/tests/measures/test_time.py +++ b/tests/measures/test_time.py @@ -2,8 +2,8 @@ class TestFrequency: - def test_mul(self): + def test_mul(self) -> None: assert measures.Frequency("60 Hz") * measures.Time("2 s") == 120 - def test_mul__super(self): + def test_mul__super(self) -> None: assert measures.Frequency("60 Hz") * 2 == measures.Frequency("120 Hz") diff --git a/tests/test_base.py b/tests/test_base.py index dce1951..57a40e1 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -6,14 +6,14 @@ from measurement.measures import Distance, Mass -def test_qualname(): +def test_qualname() -> None: assert qualname(Distance) == "Distance" assert qualname(Distance("1 m")) == "Distance" class TestImmutableKeyDict: - def test_setitem(self): - d = ImmutableKeyDict() + def test_setitem(self) -> None: + d: ImmutableKeyDict[str, str] = ImmutableKeyDict() d["foo"] = "bar" d["foo"] = "bar" with pytest.raises(KeyError) as e: @@ -23,11 +23,11 @@ def test_setitem(self): class TestUnit: - def test_post_init(self): + def test_post_init(self) -> None: inch = Unit("0.0254", ["in", "inches"]) assert inch.factor == decimal.Decimal("0.0254") - def test_get_symbols(self): + def test_get_symbols(self) -> None: inch = Unit("0.0254", ["in", "inches"]) inch.name = "inch" assert list(inch.get_symbols()) == [ @@ -36,19 +36,19 @@ def test_get_symbols(self): ("inches", Unit("0.0254")), ] - def test_to_si(self): + def test_to_si(self) -> None: assert Unit("1").to_si(decimal.Decimal("10")) == decimal.Decimal("10") assert Unit("10").to_si(decimal.Decimal("10")) == decimal.Decimal("100") assert Unit("1E-3").to_si(decimal.Decimal("10")) == decimal.Decimal("1E-2") - def test_from_si(self): + def test_from_si(self) -> None: assert Unit("1").from_si(decimal.Decimal("10")) == decimal.Decimal("10") assert Unit("10").from_si(decimal.Decimal("10")) == decimal.Decimal("1") assert Unit("1E-3").from_si(decimal.Decimal("10")) == decimal.Decimal("1E+4") class TestMetricUnit: - def test_get_symbols(self): + def test_get_symbols(self) -> None: metre = MetricUnit("1", ["m", "meter"], ["m"], ["metre", "meter"]) metre.name = "metre" symbols = list(metre.get_symbols()) @@ -68,7 +68,7 @@ def test_get_symbols(self): assert ("nanometer", Unit("1E-9")) in symbols - def test_get_symbols__unique_names(self): + def test_get_symbols__unique_names(self) -> None: metre = MetricUnit("1", ["m", "meter"], ["m"], ["metre", "meter"]) metre.name = "metre" symbols = list(metre.get_symbols()) @@ -79,70 +79,70 @@ class TestAbstractMeasure: measure = Distance unit = "m" - def test_repr(self): + def test_repr(self) -> None: assert repr(Distance("1 km")) == 'Distance(metre="1E+3")' - def test_str(self): + def test_str(self) -> None: assert str(Distance("1 km")) == "1 km" - def test_format(self): + def test_format(self) -> None: assert f"{Distance('1 km') / 3:5.3f}" == "0.333 km" assert f"{Distance(km=1/3):5.3f}" == "0.333 km" with pytest.raises(ValueError): f"{Distance('1 km') / 3:5.3x}" - def test_getitem(self): + def test_getitem(self) -> None: assert Distance("1 km")["m"] == decimal.Decimal("1000") with pytest.raises(KeyError) as e: Distance("1 km")["does not exist"] assert "Distance object has no key 'does not exist'" in str(e.value) - def test_custom_string(self): + def test_custom_string(self) -> None: m = Distance("1 km") / 3 assert f"{m._value:.3f} {m.unit}" == "333.333 metre" - def test_eq(self): + def test_eq(self) -> None: assert self.measure(**{self.unit: 1}) == self.measure(**{self.unit: 1}) - def test_eq__not_implemented(self): + def test_eq__not_implemented(self) -> None: assert self.measure(**{self.unit: 1}).__eq__("not-valid") is NotImplemented - def test_tl(self): + def test_tl(self) -> None: assert self.measure(**{self.unit: 1}) < self.measure(**{self.unit: 2}) assert not self.measure(**{self.unit: 2}) < self.measure(**{self.unit: 1}) - def test_lt__not_implemented(self): + def test_lt__not_implemented(self) -> None: assert self.measure(**{self.unit: 1}).__lt__("not-valid") is NotImplemented - def test_gt(self): + def test_gt(self) -> None: assert self.measure(**{self.unit: 2}) > self.measure(**{self.unit: 1}) assert not self.measure(**{self.unit: 1}) > self.measure(**{self.unit: 1}) - def test_gt__not_implemented(self): + def test_gt__not_implemented(self) -> None: assert self.measure(**{self.unit: 1}).__gt__("not-valid") is NotImplemented - def test_add(self): + def test_add(self) -> None: assert self.measure(**{self.unit: 2}) + self.measure( **{self.unit: 1} ) == self.measure(**{self.unit: 3}) - def test_add__raise__type_error(self): + def test_add__raise__type_error(self) -> None: with pytest.raises(TypeError) as e: self.measure(**{self.unit: 2}) + "not-allowed" assert str(e.value) == f"can't add type '{qualname(self.measure)}' to 'str'" - def test_iadd(self): + def test_iadd(self) -> None: d = self.measure(**{self.unit: 2}) d += self.measure(**{self.unit: 1}) assert d == self.measure(**{self.unit: 3}) - def test_sub(self): + def test_sub(self) -> None: assert self.measure(**{self.unit: 2}) - self.measure( **{self.unit: 1} ) == self.measure(**{self.unit: 1}) - def test_sub__raise__type_error(self): + def test_sub__raise__type_error(self) -> None: with pytest.raises(TypeError) as e: self.measure(**{self.unit: 2}) - "not-allowed" assert ( @@ -150,27 +150,27 @@ def test_sub__raise__type_error(self): == f"can't substract type 'str' from '{qualname(self.measure)}'" ) - def test_isub(self): + def test_isub(self) -> None: d = self.measure(**{self.unit: 2}) d -= self.measure(**{self.unit: 1}) assert d == self.measure(**{self.unit: 1}) - def test_mul(self): + def test_mul(self) -> None: assert self.measure(**{self.unit: 2}) * 2 == self.measure(**{self.unit: 4}) - def test_mul__raise__type_error(self): + def test_mul__raise__type_error(self) -> None: with pytest.raises(TypeError) as e: self.measure(**{self.unit: 2}) * "not-allowed" assert ( str(e.value) == f"can't multiply type '{qualname(self.measure)}' and 'str'" ) - def test_mul__raise_for_same_type(self): + def test_mul__raise_for_same_type(self) -> None: with pytest.raises(TypeError) as e: Mass("1 kg") * Mass("1 kg") assert str(e.value) == f"can't multiply type 'Mass' and 'Mass'" - def test_imul(self): + def test_imul(self) -> None: d = self.measure(**{self.unit: 2}) d *= 2 assert d == self.measure(**{self.unit: 4}) @@ -179,19 +179,19 @@ def test_imul(self): d *= self.measure(**{self.unit: 2}) assert d == self.measure(**{self.unit: 4}) - def test_rmul(self): + def test_rmul(self) -> None: assert 2 * self.measure(**{self.unit: 2}) == self.measure(**{self.unit: 4}) - def test_truediv(self): + def test_truediv(self) -> None: assert self.measure(**{self.unit: 2}) / 2 == self.measure(**{self.unit: 1}) assert self.measure(**{self.unit: 2}) / self.measure(**{self.unit: 2}) == 1 - def test_truediv__raise__type_error(self): + def test_truediv__raise__type_error(self) -> None: with pytest.raises(TypeError) as e: self.measure(**{self.unit: 2}) / "not-allowed" assert str(e.value) == f"can't devide type '{qualname(self.measure)}' by 'str'" - def test_itruediv(self): + def test_itruediv(self) -> None: d = self.measure(**{self.unit: 2}) d /= 2 assert d == self.measure(**{self.unit: 1}) @@ -204,15 +204,15 @@ def test_itruediv(self): d /= self.measure(**{self.unit: 2}) assert d == 1 - def test_rtruediv(self): + def test_rtruediv(self) -> None: assert 2 / self.measure(**{self.unit: 2}) == self.measure(**{self.unit: 1}) - def test_bool(self): + def test_bool(self) -> None: assert self.measure(**{self.unit: 1}) assert self.measure(**{self.unit: -11}) assert not self.measure(**{self.unit: 0}) - def test_getattr(self): + def test_getattr(self) -> None: assert Distance(m=1).inch == pytest.approx(decimal.Decimal("40"), abs=1) with pytest.raises(AttributeError): Distance(m=1).does_not_exist diff --git a/tests/test_utils.py b/tests/test_utils.py index b6eb467..d11aed3 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -4,19 +4,19 @@ from measurement.utils import guess -def test_guess_weight(): +def test_guess_weight() -> None: assert guess(23, "g") == Mass(g=23) -def test_guess_distance(): +def test_guess_distance() -> None: assert guess(30, "mi") == Distance(mi=30) -def test_guess_temperature(): +def test_guess_temperature() -> None: assert guess(98, "°F") == Temperature(fahrenheit=98) -def test_guess__raise__value_error(): +def test_guess__raise__value_error() -> None: with pytest.raises(ValueError) as e: guess(98, "does-not-exist") assert str(e.value) == "can't guess measure for '98 does-not-exist'"