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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user