chore: add CORE-003 roadmap task for mypy type safety

- Remove '|| true' from CI type-check job to enforce strict checking
- Begin type narrowing pattern in units.py with _typed property accessors
- Document all 42 type errors across 15 files in roadmap backlog
- Priority: medium, estimated 4-6 hours to complete

Type errors fall into categories:
- Union types not narrowed after __post_init__ coercion
- float() on object types
- Duplicate method definitions
- Provider interface type mismatches
This commit is contained in:
Bu5hm4nn
2026-03-29 23:40:55 +02:00
parent 1ad369727d
commit 367960772b
4 changed files with 165 additions and 27 deletions

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
from dataclasses import dataclass
from decimal import Decimal
from enum import StrEnum
from typing import TYPE_CHECKING
class BaseCurrency(StrEnum):
@@ -99,6 +100,11 @@ class Money:
object.__setattr__(self, "amount", to_decimal(self.amount))
object.__setattr__(self, "currency", _coerce_currency(self.currency))
@property
def _currency_typed(self) -> BaseCurrency:
"""Type-narrowed currency accessor for internal use."""
return self.currency # type: ignore[return-value]
@classmethod
def zero(cls, currency: BaseCurrency) -> Money:
return cls(amount=Decimal("0"), currency=currency)
@@ -147,6 +153,15 @@ class Money:
return Money(amount=-self.amount, currency=self.currency)
if TYPE_CHECKING:
# Type narrowing: after __post_init__, these are the actual types
Money_amount: Decimal
Money_currency: BaseCurrency
else:
Money_amount = Decimal
Money_currency = BaseCurrency | str
@dataclass(frozen=True, slots=True)
class Weight:
amount: Decimal
@@ -156,49 +171,54 @@ class Weight:
object.__setattr__(self, "amount", to_decimal(self.amount))
object.__setattr__(self, "unit", _coerce_weight_unit(self.unit))
@property
def _unit_typed(self) -> WeightUnit:
"""Type-narrowed unit accessor for internal use."""
return self.unit # type: ignore[return-value]
def to_unit(self, unit: WeightUnit) -> Weight:
return Weight(amount=convert_weight(self.amount, self.unit, unit), unit=unit)
return Weight(amount=convert_weight(self.amount, self._unit_typed, unit), unit=unit)
def __add__(self, other: object) -> Weight:
if not isinstance(other, Weight):
return NotImplemented
other_converted = other.to_unit(self.unit)
return Weight(amount=self.amount + other_converted.amount, unit=self.unit)
other_converted = other.to_unit(self._unit_typed)
return Weight(amount=self.amount + other_converted.amount, unit=self._unit_typed)
def __sub__(self, other: object) -> Weight:
if not isinstance(other, Weight):
return NotImplemented
other_converted = other.to_unit(self.unit)
return Weight(amount=self.amount - other_converted.amount, unit=self.unit)
other_converted = other.to_unit(self._unit_typed)
return Weight(amount=self.amount - other_converted.amount, unit=self._unit_typed)
def __mul__(self, other: object) -> Weight | Money:
if isinstance(other, bool):
return NotImplemented
if isinstance(other, Decimal):
return Weight(amount=self.amount * other, unit=self.unit)
return Weight(amount=self.amount * other, unit=self._unit_typed)
if isinstance(other, int):
return Weight(amount=self.amount * Decimal(other), unit=self.unit)
return Weight(amount=self.amount * Decimal(other), unit=self._unit_typed)
if isinstance(other, PricePerWeight):
adjusted_weight = self.to_unit(other.per_unit)
return Money(amount=adjusted_weight.amount * other.amount, currency=other.currency)
adjusted_weight = self.to_unit(other._per_unit_typed)
return Money(amount=adjusted_weight.amount * other.amount, currency=other._currency_typed)
return NotImplemented
def __rmul__(self, other: object) -> Weight:
if isinstance(other, bool):
return NotImplemented
if isinstance(other, Decimal):
return Weight(amount=self.amount * other, unit=self.unit)
return Weight(amount=self.amount * other, unit=self._unit_typed)
if isinstance(other, int):
return Weight(amount=self.amount * Decimal(other), unit=self.unit)
return Weight(amount=self.amount * Decimal(other), unit=self._unit_typed)
return NotImplemented
def __truediv__(self, other: object) -> Weight:
if isinstance(other, bool):
return NotImplemented
if isinstance(other, Decimal):
return Weight(amount=self.amount / other, unit=self.unit)
return Weight(amount=self.amount / other, unit=self._unit_typed)
if isinstance(other, int):
return Weight(amount=self.amount / Decimal(other), unit=self.unit)
return Weight(amount=self.amount / Decimal(other), unit=self._unit_typed)
return NotImplemented
@@ -216,10 +236,20 @@ class PricePerWeight:
object.__setattr__(self, "currency", _coerce_currency(self.currency))
object.__setattr__(self, "per_unit", _coerce_weight_unit(self.per_unit))
@property
def _currency_typed(self) -> BaseCurrency:
"""Type-narrowed currency accessor for internal use."""
return self.currency # type: ignore[return-value]
@property
def _per_unit_typed(self) -> WeightUnit:
"""Type-narrowed unit accessor for internal use."""
return self.per_unit # type: ignore[return-value]
def to_unit(self, unit: WeightUnit) -> PricePerWeight:
return PricePerWeight(
amount=convert_price_per_weight(self.amount, self.per_unit, unit),
currency=self.currency,
amount=convert_price_per_weight(self.amount, self._per_unit_typed, unit),
currency=self._currency_typed,
per_unit=unit,
)
@@ -227,19 +257,19 @@ class PricePerWeight:
if isinstance(other, bool):
return NotImplemented
if isinstance(other, Weight):
adjusted_weight = other.to_unit(self.per_unit)
return Money(amount=adjusted_weight.amount * self.amount, currency=self.currency)
adjusted_weight = other.to_unit(self._per_unit_typed)
return Money(amount=adjusted_weight.amount * self.amount, currency=self._currency_typed)
if isinstance(other, Decimal):
return PricePerWeight(amount=self.amount * other, currency=self.currency, per_unit=self.per_unit)
return PricePerWeight(amount=self.amount * other, currency=self._currency_typed, per_unit=self._per_unit_typed)
if isinstance(other, int):
return PricePerWeight(amount=self.amount * Decimal(other), currency=self.currency, per_unit=self.per_unit)
return PricePerWeight(amount=self.amount * Decimal(other), currency=self._currency_typed, per_unit=self._per_unit_typed)
return NotImplemented
def __rmul__(self, other: object) -> PricePerWeight:
if isinstance(other, bool):
return NotImplemented
if isinstance(other, Decimal):
return PricePerWeight(amount=self.amount * other, currency=self.currency, per_unit=self.per_unit)
return PricePerWeight(amount=self.amount * other, currency=self._currency_typed, per_unit=self._per_unit_typed)
if isinstance(other, int):
return PricePerWeight(amount=self.amount * Decimal(other), currency=self.currency, per_unit=self.per_unit)
return PricePerWeight(amount=self.amount * Decimal(other), currency=self._currency_typed, per_unit=self._per_unit_typed)
return NotImplemented