T4: Repository pattern — ModelRepository, JsonFileRepository, ExcelRepository

- application/repositories/model_repository.py: ModelRepository Protocol
  with load(path) and save(model, path)
- infrastructure/repositories/json_file_repository.py: JsonFileRepository
  (вынесен из FinancialModel.save/load)
- infrastructure/repositories/excel_repository.py: ExcelRepository
  (бывший ExcelSync, переименован)
- FinancialModel.save/load удалены — теперь только данные
- infrastructure/sync/ удалён (ExcelSync перенесён в repositories/)
- infrastructure/cli/main.py и config.py используют JsonFileRepository +
  ExcelRepository через локальные singletons
- test_excel_sync.py переименован в test для ExcelRepository
- Новый test_repositories.py: 5 тестов для JsonFileRepository + протокол
- test_model.py: использован JsonFileRepository

Tests: 68/68 pass (было 63 + 5 новых).
This commit is contained in:
2026-10-08 16:50:06 +03:00
parent 363d440d53
commit 3f7268d14c
12 changed files with 190 additions and 92 deletions
+5 -17
View File
@@ -1,6 +1,3 @@
import json
from pathlib import Path
from pydantic import BaseModel, Field
from domain.account import Account
@@ -13,7 +10,11 @@ from domain.transaction import Transaction
class FinancialModel(BaseModel):
"""Корневая модель финансового плана."""
"""Корневая модель финансового плана.
Сериализация (save/load) вынесена в `application.repositories.ModelRepository` —
JsonFileRepository и ExcelRepository. Сам класс хранит только данные.
"""
SCHEMA_VERSION: int = 1 # NB: не Field — это class-level metadata, не pydantic field
@@ -68,16 +69,3 @@ class FinancialModel(BaseModel):
def _from_legacy(cls, data: dict) -> "FinancialModel":
# Legacy: файлы без version. Структура совпадает с v1.
return cls._from_v1(data)
def save(self, path: str | Path) -> None:
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
json.dump(self.to_dict(), f, indent=2, ensure_ascii=False)
@classmethod
def load(cls, path: str | Path) -> "FinancialModel":
path = Path(path)
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
return cls.from_dict(data)