T2: Pydantic v2 — replace @dataclass with BaseModel
- All domain models now use pydantic.BaseModel
- Removed manual to_dict()/from_dict() — use model_dump(mode='json')/model_validate()
- Added field validators:
- Account.balance >= 0
- Transaction.amount != 0
- RecurringCashflow.amount != 0, frequency in {daily, weekly, monthly, yearly}
- Liability.interest, payment >= 0
- ExchangeRate.rate > 0
- ForecastScenario.multipliers >= 0
- pydantic v2 native UUID handling (auto str in JSON)
- Updated excel_sync.py to use model_validate()
- Updated tests/test_model.py and tests/test_currency.py
- Added pydantic>=2.0 to pyproject.toml
Tests: 63/63 pass.
This commit is contained in:
+9
-19
@@ -1,27 +1,17 @@
|
|||||||
from dataclasses import dataclass, field
|
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Account:
|
class Account(BaseModel):
|
||||||
id: UUID = field(default_factory=uuid4)
|
id: UUID = Field(default_factory=uuid4)
|
||||||
name: str = ""
|
name: str = ""
|
||||||
currency: str = "USD"
|
currency: str = "USD"
|
||||||
balance: float = 0.0
|
balance: float = 0.0
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
@field_validator("balance")
|
||||||
return {
|
|
||||||
"id": str(self.id),
|
|
||||||
"name": self.name,
|
|
||||||
"currency": self.currency,
|
|
||||||
"balance": self.balance,
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: dict) -> "Account":
|
def _balance_non_negative(cls, v: float) -> float:
|
||||||
return cls(
|
if v < 0:
|
||||||
id=UUID(data["id"]),
|
raise ValueError("balance must be non-negative")
|
||||||
name=data["name"],
|
return v
|
||||||
currency=data.get("currency", "USD"),
|
|
||||||
balance=data.get("balance", 0.0),
|
|
||||||
)
|
|
||||||
|
|||||||
+4
-21
@@ -1,27 +1,10 @@
|
|||||||
from dataclasses import dataclass, field
|
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Asset:
|
class Asset(BaseModel):
|
||||||
id: UUID = field(default_factory=uuid4)
|
id: UUID = Field(default_factory=uuid4)
|
||||||
name: str = ""
|
name: str = ""
|
||||||
value: float = 0.0
|
value: float = 0.0
|
||||||
growth_rate: float = 0.0
|
growth_rate: float = 0.0
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
|
||||||
return {
|
|
||||||
"id": str(self.id),
|
|
||||||
"name": self.name,
|
|
||||||
"value": self.value,
|
|
||||||
"growth_rate": self.growth_rate,
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls, data: dict) -> "Asset":
|
|
||||||
return cls(
|
|
||||||
id=UUID(data["id"]),
|
|
||||||
name=data["name"],
|
|
||||||
value=data.get("value", 0.0),
|
|
||||||
growth_rate=data.get("growth_rate", 0.0),
|
|
||||||
)
|
|
||||||
|
|||||||
+7
-16
@@ -1,4 +1,4 @@
|
|||||||
from dataclasses import dataclass
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
CURRENCY_SYMBOLS = {
|
CURRENCY_SYMBOLS = {
|
||||||
"RUB": "₽",
|
"RUB": "₽",
|
||||||
@@ -12,26 +12,17 @@ CURRENCY_SYMBOLS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
class ExchangeRate(BaseModel):
|
||||||
class ExchangeRate:
|
|
||||||
from_currency: str = "USD"
|
from_currency: str = "USD"
|
||||||
to_currency: str = "RUB"
|
to_currency: str = "RUB"
|
||||||
rate: float = 80.0
|
rate: float = 80.0
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
@field_validator("rate")
|
||||||
return {
|
|
||||||
"from_currency": self.from_currency,
|
|
||||||
"to_currency": self.to_currency,
|
|
||||||
"rate": self.rate,
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: dict) -> "ExchangeRate":
|
def _rate_positive(cls, v: float) -> float:
|
||||||
return cls(
|
if v <= 0:
|
||||||
from_currency=data.get("from_currency", "USD"),
|
raise ValueError("rate must be positive")
|
||||||
to_currency=data.get("to_currency", "RUB"),
|
return v
|
||||||
rate=data.get("rate", 80.0),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_RATES: list[ExchangeRate] = [
|
DEFAULT_RATES: list[ExchangeRate] = [
|
||||||
|
|||||||
+9
-21
@@ -1,30 +1,18 @@
|
|||||||
from dataclasses import dataclass, field
|
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Liability:
|
class Liability(BaseModel):
|
||||||
id: UUID = field(default_factory=uuid4)
|
id: UUID = Field(default_factory=uuid4)
|
||||||
name: str = ""
|
name: str = ""
|
||||||
balance: float = 0.0
|
balance: float = 0.0
|
||||||
interest: float = 0.0
|
interest: float = 0.0
|
||||||
payment: float = 0.0
|
payment: float = 0.0
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
@field_validator("interest", "payment")
|
||||||
return {
|
|
||||||
"id": str(self.id),
|
|
||||||
"name": self.name,
|
|
||||||
"balance": self.balance,
|
|
||||||
"interest": self.interest,
|
|
||||||
"payment": self.payment,
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: dict) -> "Liability":
|
def _non_negative(cls, v: float) -> float:
|
||||||
return cls(
|
if v < 0:
|
||||||
id=UUID(data["id"]),
|
raise ValueError("must be non-negative")
|
||||||
name=data["name"],
|
return v
|
||||||
balance=data.get("balance", 0.0),
|
|
||||||
interest=data.get("interest", 0.0),
|
|
||||||
payment=data.get("payment", 0.0),
|
|
||||||
)
|
|
||||||
|
|||||||
+34
-29
@@ -1,7 +1,8 @@
|
|||||||
import json
|
import json
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from domain.account import Account
|
from domain.account import Account
|
||||||
from domain.asset import Asset
|
from domain.asset import Asset
|
||||||
from domain.currency import DEFAULT_RATES, ExchangeRate
|
from domain.currency import DEFAULT_RATES, ExchangeRate
|
||||||
@@ -11,37 +12,41 @@ from domain.scenario import ForecastScenario
|
|||||||
from domain.transaction import Transaction
|
from domain.transaction import Transaction
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
class FinancialModel(BaseModel):
|
||||||
class FinancialModel:
|
"""Корневая модель финансового плана."""
|
||||||
base_currency: str = "RUB"
|
|
||||||
accounts: list[Account] = field(default_factory=list)
|
|
||||||
transactions: list[Transaction] = field(default_factory=list)
|
|
||||||
recurring: list[RecurringCashflow] = field(default_factory=list)
|
|
||||||
assets: list[Asset] = field(default_factory=list)
|
|
||||||
liabilities: list[Liability] = field(default_factory=list)
|
|
||||||
scenarios: list[ForecastScenario] = field(default_factory=list)
|
|
||||||
exchange_rates: list[ExchangeRate] = field(default_factory=lambda: DEFAULT_RATES.copy())
|
|
||||||
|
|
||||||
SCHEMA_VERSION = 1
|
SCHEMA_VERSION: int = 1 # NB: не Field — это class-level metadata, не pydantic field
|
||||||
|
|
||||||
|
base_currency: str = "RUB"
|
||||||
|
accounts: list[Account] = Field(default_factory=list)
|
||||||
|
transactions: list[Transaction] = Field(default_factory=list)
|
||||||
|
recurring: list[RecurringCashflow] = Field(default_factory=list)
|
||||||
|
assets: list[Asset] = Field(default_factory=list)
|
||||||
|
liabilities: list[Liability] = Field(default_factory=list)
|
||||||
|
scenarios: list[ForecastScenario] = Field(default_factory=list)
|
||||||
|
exchange_rates: list[ExchangeRate] = Field(
|
||||||
|
default_factory=lambda: [r.model_copy() for r in DEFAULT_RATES]
|
||||||
|
)
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
|
"""Сериализация в dict с version и UUID-as-str (для JSON)."""
|
||||||
return {
|
return {
|
||||||
"version": self.SCHEMA_VERSION,
|
"version": self.SCHEMA_VERSION,
|
||||||
"base_currency": self.base_currency,
|
"base_currency": self.base_currency,
|
||||||
"accounts": [a.to_dict() for a in self.accounts],
|
"accounts": [a.model_dump(mode="json") for a in self.accounts],
|
||||||
"transactions": [t.to_dict() for t in self.transactions],
|
"transactions": [t.model_dump(mode="json") for t in self.transactions],
|
||||||
"recurring": [r.to_dict() for r in self.recurring],
|
"recurring": [r.model_dump(mode="json") for r in self.recurring],
|
||||||
"assets": [a.to_dict() for a in self.assets],
|
"assets": [a.model_dump(mode="json") for a in self.assets],
|
||||||
"liabilities": [li.to_dict() for li in self.liabilities],
|
"liabilities": [li.model_dump(mode="json") for li in self.liabilities],
|
||||||
"scenarios": [s.to_dict() for s in self.scenarios],
|
"scenarios": [s.model_dump(mode="json") for s in self.scenarios],
|
||||||
"exchange_rates": [r.to_dict() for r in self.exchange_rates],
|
"exchange_rates": [e.model_dump(mode="json") for e in self.exchange_rates],
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: dict) -> "FinancialModel":
|
def from_dict(cls, data: dict) -> "FinancialModel":
|
||||||
version = data.get("version", 0)
|
version = data.get("version", 0)
|
||||||
if version == 0:
|
if version == 0:
|
||||||
return cls._from_v0(data)
|
return cls._from_legacy(data)
|
||||||
if version == 1:
|
if version == 1:
|
||||||
return cls._from_v1(data)
|
return cls._from_v1(data)
|
||||||
raise ValueError(f"Unsupported FinancialModel version: {version}")
|
raise ValueError(f"Unsupported FinancialModel version: {version}")
|
||||||
@@ -50,18 +55,18 @@ class FinancialModel:
|
|||||||
def _from_v1(cls, data: dict) -> "FinancialModel":
|
def _from_v1(cls, data: dict) -> "FinancialModel":
|
||||||
return cls(
|
return cls(
|
||||||
base_currency=data.get("base_currency", "RUB"),
|
base_currency=data.get("base_currency", "RUB"),
|
||||||
accounts=[Account.from_dict(a) for a in data.get("accounts", [])],
|
accounts=[Account.model_validate(a) for a in data.get("accounts", [])],
|
||||||
transactions=[Transaction.from_dict(t) for t in data.get("transactions", [])],
|
transactions=[Transaction.model_validate(t) for t in data.get("transactions", [])],
|
||||||
recurring=[RecurringCashflow.from_dict(r) for r in data.get("recurring", [])],
|
recurring=[RecurringCashflow.model_validate(r) for r in data.get("recurring", [])],
|
||||||
assets=[Asset.from_dict(a) for a in data.get("assets", [])],
|
assets=[Asset.model_validate(a) for a in data.get("assets", [])],
|
||||||
liabilities=[Liability.from_dict(li) for li in data.get("liabilities", [])],
|
liabilities=[Liability.model_validate(li) for li in data.get("liabilities", [])],
|
||||||
scenarios=[ForecastScenario.from_dict(s) for s in data.get("scenarios", [])],
|
scenarios=[ForecastScenario.model_validate(s) for s in data.get("scenarios", [])],
|
||||||
exchange_rates=[ExchangeRate.from_dict(r) for r in data.get("exchange_rates", [])],
|
exchange_rates=[ExchangeRate.model_validate(r) for r in data.get("exchange_rates", [])],
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_v0(cls, data: dict) -> "FinancialModel":
|
def _from_legacy(cls, data: dict) -> "FinancialModel":
|
||||||
# Legacy: файлы, сохранённые до введения version. Совпадает по структуре с v1.
|
# Legacy: файлы без version. Структура совпадает с v1.
|
||||||
return cls._from_v1(data)
|
return cls._from_v1(data)
|
||||||
|
|
||||||
def save(self, path: str | Path) -> None:
|
def save(self, path: str | Path) -> None:
|
||||||
|
|||||||
+16
-23
@@ -1,33 +1,26 @@
|
|||||||
from dataclasses import dataclass, field
|
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class RecurringCashflow:
|
class RecurringCashflow(BaseModel):
|
||||||
id: UUID = field(default_factory=uuid4)
|
id: UUID = Field(default_factory=uuid4)
|
||||||
start_date: str = ""
|
start_date: str = ""
|
||||||
end_date: str = ""
|
end_date: str = ""
|
||||||
frequency: str = "monthly"
|
frequency: str = "monthly"
|
||||||
amount: float = 0.0
|
amount: float = 0.0
|
||||||
category: str = ""
|
category: str = ""
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
@field_validator("frequency")
|
||||||
return {
|
|
||||||
"id": str(self.id),
|
|
||||||
"start_date": self.start_date,
|
|
||||||
"end_date": self.end_date,
|
|
||||||
"frequency": self.frequency,
|
|
||||||
"amount": self.amount,
|
|
||||||
"category": self.category,
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: dict) -> "RecurringCashflow":
|
def _frequency_known(cls, v: str) -> str:
|
||||||
return cls(
|
if v not in {"daily", "weekly", "monthly", "yearly"}:
|
||||||
id=UUID(data["id"]),
|
raise ValueError(f"unknown frequency: {v}")
|
||||||
start_date=data.get("start_date", ""),
|
return v
|
||||||
end_date=data.get("end_date", ""),
|
|
||||||
frequency=data.get("frequency", "monthly"),
|
@field_validator("amount")
|
||||||
amount=data.get("amount", 0.0),
|
@classmethod
|
||||||
category=data.get("category", ""),
|
def _amount_nonzero(cls, v: float) -> float:
|
||||||
)
|
if v == 0:
|
||||||
|
raise ValueError("amount must be non-zero")
|
||||||
|
return v
|
||||||
|
|||||||
+9
-23
@@ -1,33 +1,19 @@
|
|||||||
from dataclasses import dataclass, field
|
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ForecastScenario:
|
class ForecastScenario(BaseModel):
|
||||||
id: UUID = field(default_factory=uuid4)
|
id: UUID = Field(default_factory=uuid4)
|
||||||
name: str = "baseline"
|
name: str = "baseline"
|
||||||
income_multiplier: float = 1.0
|
income_multiplier: float = 1.0
|
||||||
expense_multiplier: float = 1.0
|
expense_multiplier: float = 1.0
|
||||||
growth_multiplier: float = 1.0
|
growth_multiplier: float = 1.0
|
||||||
description: str = ""
|
description: str = ""
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
@field_validator("income_multiplier", "expense_multiplier", "growth_multiplier")
|
||||||
return {
|
|
||||||
"id": str(self.id),
|
|
||||||
"name": self.name,
|
|
||||||
"income_multiplier": self.income_multiplier,
|
|
||||||
"expense_multiplier": self.expense_multiplier,
|
|
||||||
"growth_multiplier": self.growth_multiplier,
|
|
||||||
"description": self.description,
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: dict) -> "ForecastScenario":
|
def _non_negative(cls, v: float) -> float:
|
||||||
return cls(
|
if v < 0:
|
||||||
id=UUID(data["id"]),
|
raise ValueError("multiplier must be non-negative")
|
||||||
name=data["name"],
|
return v
|
||||||
income_multiplier=data.get("income_multiplier", 1.0),
|
|
||||||
expense_multiplier=data.get("expense_multiplier", 1.0),
|
|
||||||
growth_multiplier=data.get("growth_multiplier", 1.0),
|
|
||||||
description=data.get("description", ""),
|
|
||||||
)
|
|
||||||
|
|||||||
+9
-23
@@ -1,33 +1,19 @@
|
|||||||
from dataclasses import dataclass, field
|
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Transaction:
|
class Transaction(BaseModel):
|
||||||
id: UUID = field(default_factory=uuid4)
|
id: UUID = Field(default_factory=uuid4)
|
||||||
date: str = ""
|
date: str = ""
|
||||||
account: str = ""
|
account: str = ""
|
||||||
category: str = ""
|
category: str = ""
|
||||||
amount: float = 0.0
|
amount: float = 0.0
|
||||||
description: str = ""
|
description: str = ""
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
@field_validator("amount")
|
||||||
return {
|
|
||||||
"id": str(self.id),
|
|
||||||
"date": self.date,
|
|
||||||
"account": self.account,
|
|
||||||
"category": self.category,
|
|
||||||
"amount": self.amount,
|
|
||||||
"description": self.description,
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: dict) -> "Transaction":
|
def _amount_nonzero(cls, v: float) -> float:
|
||||||
return cls(
|
if v == 0:
|
||||||
id=UUID(data["id"]),
|
raise ValueError("amount must be non-zero")
|
||||||
date=data["date"],
|
return v
|
||||||
account=data.get("account", ""),
|
|
||||||
category=data.get("category", ""),
|
|
||||||
amount=data.get("amount", 0.0),
|
|
||||||
description=data.get("description", ""),
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -119,17 +119,19 @@ class ExcelSync:
|
|||||||
|
|
||||||
def _add_to_model(self, model: FinancialModel, sheet_name: str, data: dict) -> None:
|
def _add_to_model(self, model: FinancialModel, sheet_name: str, data: dict) -> None:
|
||||||
try:
|
try:
|
||||||
|
cls = _SHEET_CONFIG[sheet_name]["cls"]
|
||||||
|
instance = cls.model_validate(data)
|
||||||
if sheet_name == "Accounts":
|
if sheet_name == "Accounts":
|
||||||
model.accounts.append(Account.from_dict(data))
|
model.accounts.append(instance)
|
||||||
elif sheet_name == "Transactions":
|
elif sheet_name == "Transactions":
|
||||||
model.transactions.append(Transaction.from_dict(data))
|
model.transactions.append(instance)
|
||||||
elif sheet_name == "Recurring":
|
elif sheet_name == "Recurring":
|
||||||
model.recurring.append(RecurringCashflow.from_dict(data))
|
model.recurring.append(instance)
|
||||||
elif sheet_name == "Assets":
|
elif sheet_name == "Assets":
|
||||||
model.assets.append(Asset.from_dict(data))
|
model.assets.append(instance)
|
||||||
elif sheet_name == "Liabilities":
|
elif sheet_name == "Liabilities":
|
||||||
model.liabilities.append(Liability.from_dict(data))
|
model.liabilities.append(instance)
|
||||||
elif sheet_name == "ExchangeRates":
|
elif sheet_name == "ExchangeRates":
|
||||||
model.exchange_rates.append(ExchangeRate.from_dict(data))
|
model.exchange_rates.append(instance)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise SyncError(f"Failed to parse row in {sheet_name}: {e}") from e
|
raise SyncError(f"Failed to parse row in {sheet_name}: {e}") from e
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ dependencies = [
|
|||||||
"openpyxl>=3.1",
|
"openpyxl>=3.1",
|
||||||
"typer>=0.9",
|
"typer>=0.9",
|
||||||
"rich>=13.0",
|
"rich>=13.0",
|
||||||
|
"pydantic>=2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ from domain import CurrencyConverter, CurrencyError, ExchangeRate
|
|||||||
class TestExchangeRate:
|
class TestExchangeRate:
|
||||||
def test_to_dict_roundtrip(self):
|
def test_to_dict_roundtrip(self):
|
||||||
rate = ExchangeRate(from_currency="USD", to_currency="RUB", rate=80.0)
|
rate = ExchangeRate(from_currency="USD", to_currency="RUB", rate=80.0)
|
||||||
d = rate.to_dict()
|
d = rate.model_dump(mode="json")
|
||||||
r2 = ExchangeRate.from_dict(d)
|
r2 = ExchangeRate.model_validate(d)
|
||||||
assert r2.from_currency == "USD"
|
assert r2.from_currency == "USD"
|
||||||
assert r2.to_currency == "RUB"
|
assert r2.to_currency == "RUB"
|
||||||
assert r2.rate == 80.0
|
assert r2.rate == 80.0
|
||||||
|
|||||||
+4
-4
@@ -21,8 +21,8 @@ class TestAccount:
|
|||||||
|
|
||||||
def test_to_dict_roundtrip(self):
|
def test_to_dict_roundtrip(self):
|
||||||
a = Account(name="Test", balance=100.0)
|
a = Account(name="Test", balance=100.0)
|
||||||
d = a.to_dict()
|
d = a.model_dump(mode="json")
|
||||||
a2 = Account.from_dict(d)
|
a2 = Account.model_validate(d)
|
||||||
assert a2.name == a.name
|
assert a2.name == a.name
|
||||||
assert a2.balance == a.balance
|
assert a2.balance == a.balance
|
||||||
assert a2.currency == a.currency
|
assert a2.currency == a.currency
|
||||||
@@ -35,8 +35,8 @@ class TestTransaction:
|
|||||||
|
|
||||||
def test_roundtrip(self):
|
def test_roundtrip(self):
|
||||||
t = Transaction(amount=-100.0, category="rent", description="test")
|
t = Transaction(amount=-100.0, category="rent", description="test")
|
||||||
d = t.to_dict()
|
d = t.model_dump(mode="json")
|
||||||
t2 = Transaction.from_dict(d)
|
t2 = Transaction.model_validate(d)
|
||||||
assert t2.amount == t.amount
|
assert t2.amount == t.amount
|
||||||
assert t2.category == t.category
|
assert t2.category == t.category
|
||||||
assert t2.description == t.description
|
assert t2.description == t.description
|
||||||
|
|||||||
Reference in New Issue
Block a user