Expand serializer to more types and test
This commit is contained in:
parent
6057ec59b4
commit
60c7028f0b
@ -30,7 +30,7 @@ class ExportCommand(Command):
|
|||||||
match self.format:
|
match self.format:
|
||||||
case ExportFormat.JSON:
|
case ExportFormat.JSON:
|
||||||
with open(self.fn, "w", newline="") as f:
|
with open(self.fn, "w", newline="") as f:
|
||||||
json.dump([serialize(e) for e in values], f, indent=4, default=str)
|
json.dump([serialize(e) for e in values], f, indent=4)
|
||||||
case ExportFormat.pickle:
|
case ExportFormat.pickle:
|
||||||
with open(self.fn, "wb") as f:
|
with open(self.fn, "wb") as f:
|
||||||
pickle.dump([serialize(e) for e in values], f)
|
pickle.dump(values, f)
|
||||||
|
|||||||
@ -3,19 +3,52 @@ from dataclasses import fields
|
|||||||
from functools import singledispatch
|
from functools import singledispatch
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pfbudget.db.model import Transaction, TransactionCategory
|
from pfbudget.db.model import Transaction, TransactionCategory, TransactionTag, Note
|
||||||
|
|
||||||
|
|
||||||
|
class NotSerializableError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@singledispatch
|
@singledispatch
|
||||||
def serialize(obj: Any) -> Mapping[str, Any]:
|
def serialize(obj: Any) -> Mapping[str, Any]:
|
||||||
raise NotImplementedError
|
return {field.name: getattr(obj, field.name) for field in fields(obj)}
|
||||||
|
|
||||||
|
|
||||||
@serialize.register
|
@serialize.register
|
||||||
def _(obj: Transaction) -> Mapping[str, Any]:
|
def _(obj: Transaction) -> Mapping[str, Any]:
|
||||||
return dict((field.name, getattr(obj, field.name)) for field in fields(obj))
|
category = None
|
||||||
|
if obj.category:
|
||||||
|
category = {
|
||||||
|
"name": obj.category.name,
|
||||||
|
"selector": str(obj.category.selector.selector)
|
||||||
|
if obj.category.selector
|
||||||
|
else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
return dict(
|
||||||
|
id=obj.id,
|
||||||
|
date=obj.date.isoformat(),
|
||||||
|
description=obj.description,
|
||||||
|
amount=str(obj.amount),
|
||||||
|
split=obj.split,
|
||||||
|
category=category if category else None,
|
||||||
|
tags=[{"tag": tag.tag} for tag in obj.tags],
|
||||||
|
note=obj.note,
|
||||||
|
type=obj.type,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@serialize.register
|
@serialize.register
|
||||||
def _(obj: TransactionCategory) -> Mapping[str, Any]:
|
def _(_: TransactionCategory) -> Mapping[str, Any]:
|
||||||
return dict((field.name, getattr(obj, field.name)) for field in fields(obj))
|
raise NotSerializableError("TransactionCategory")
|
||||||
|
|
||||||
|
|
||||||
|
@serialize.register
|
||||||
|
def _(_: TransactionTag) -> Mapping[str, Any]:
|
||||||
|
raise NotSerializableError("TransactionTag")
|
||||||
|
|
||||||
|
|
||||||
|
@serialize.register
|
||||||
|
def _(_: Note) -> Mapping[str, Any]:
|
||||||
|
raise NotSerializableError("Note")
|
||||||
|
|||||||
4
tests/mocks/banks.py
Normal file
4
tests/mocks/banks.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from pfbudget.db.model import AccountType, Bank
|
||||||
|
|
||||||
|
|
||||||
|
checking = Bank("bank", "BANK", AccountType.checking)
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
from pfbudget.extract.nordigen import NordigenCredentials
|
||||||
|
|
||||||
|
|
||||||
id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
|
id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
|
||||||
|
|
||||||
accounts_id = {
|
accounts_id = {
|
||||||
@ -80,3 +83,5 @@ requisitions_id = {
|
|||||||
"account_selection": False,
|
"account_selection": False,
|
||||||
"redirect_immediate": False,
|
"redirect_immediate": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
credentials = NordigenCredentials("ID", "KEY", "TOKEN")
|
||||||
|
|||||||
33
tests/mocks/transactions.py
Normal file
33
tests/mocks/transactions.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
from datetime import date
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from pfbudget.db.model import (
|
||||||
|
CategorySelector,
|
||||||
|
Selector_T,
|
||||||
|
Transaction,
|
||||||
|
TransactionCategory,
|
||||||
|
)
|
||||||
|
|
||||||
|
simple = [
|
||||||
|
Transaction(date(2023, 1, 1), "", Decimal("-10")),
|
||||||
|
Transaction(date(2023, 1, 2), "", Decimal("-50")),
|
||||||
|
]
|
||||||
|
|
||||||
|
simple_transformed = [
|
||||||
|
Transaction(
|
||||||
|
date(2023, 1, 1),
|
||||||
|
"",
|
||||||
|
Decimal("-10"),
|
||||||
|
category=TransactionCategory(
|
||||||
|
"category#1", CategorySelector(Selector_T.algorithm)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Transaction(
|
||||||
|
date(2023, 1, 2),
|
||||||
|
"",
|
||||||
|
Decimal("-50"),
|
||||||
|
category=TransactionCategory(
|
||||||
|
"category#2", CategorySelector(Selector_T.algorithm)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
78
tests/test_command.py
Normal file
78
tests/test_command.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
from collections.abc import Sequence
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import pickle
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import mocks.transactions
|
||||||
|
|
||||||
|
from pfbudget.common.types import ExportFormat
|
||||||
|
from pfbudget.core.command import ExportCommand
|
||||||
|
from pfbudget.db.client import Client
|
||||||
|
from pfbudget.db.model import Transaction
|
||||||
|
from pfbudget.utils.serializer import serialize
|
||||||
|
|
||||||
|
|
||||||
|
class FakeClient(Client):
|
||||||
|
def __init__(self):
|
||||||
|
self._transactions = mocks.transactions.simple
|
||||||
|
|
||||||
|
def select(self, what: Any, *_) -> Sequence[Any]:
|
||||||
|
if what == Transaction:
|
||||||
|
return self.transactions
|
||||||
|
return []
|
||||||
|
|
||||||
|
def insert(self, *_):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def update(self, *_):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete(self, *_):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def transactions(self):
|
||||||
|
return self._transactions
|
||||||
|
|
||||||
|
@transactions.setter
|
||||||
|
def transactions(self, value: Sequence[Transaction]):
|
||||||
|
self._transactions = value
|
||||||
|
|
||||||
|
|
||||||
|
class TestCommand:
|
||||||
|
def test_export_json(self, tmp_path: Path):
|
||||||
|
client = FakeClient()
|
||||||
|
file = tmp_path / "test.json"
|
||||||
|
command = ExportCommand(client, Transaction, file, ExportFormat.JSON)
|
||||||
|
command.execute()
|
||||||
|
|
||||||
|
with open(file, newline="") as f:
|
||||||
|
result = json.load(f)
|
||||||
|
assert result == [serialize(t) for t in mocks.transactions.simple]
|
||||||
|
|
||||||
|
client.transactions = mocks.transactions.simple_transformed
|
||||||
|
command.execute()
|
||||||
|
|
||||||
|
with open(file, newline="") as f:
|
||||||
|
result = json.load(f)
|
||||||
|
assert result == [
|
||||||
|
serialize(t) for t in mocks.transactions.simple_transformed
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_export_pickle(self, tmp_path: Path):
|
||||||
|
client = FakeClient()
|
||||||
|
file = tmp_path / "test.pickle"
|
||||||
|
command = ExportCommand(client, Transaction, file, ExportFormat.pickle)
|
||||||
|
command.execute()
|
||||||
|
|
||||||
|
with open(file, "rb") as f:
|
||||||
|
result = pickle.load(f)
|
||||||
|
assert result == mocks.transactions.simple
|
||||||
|
|
||||||
|
client.transactions = mocks.transactions.simple_transformed
|
||||||
|
command.execute()
|
||||||
|
|
||||||
|
with open(file, "rb") as f:
|
||||||
|
result = pickle.load(f)
|
||||||
|
assert result == mocks.transactions.simple_transformed
|
||||||
@ -88,7 +88,12 @@ class TestExtractPSD2:
|
|||||||
with pytest.raises(requests.Timeout):
|
with pytest.raises(requests.Timeout):
|
||||||
extractor.extract(bank)
|
extractor.extract(bank)
|
||||||
|
|
||||||
def test_extract(self, extractor: Extractor, bank: Bank):
|
def test_extract(
|
||||||
|
self, monkeypatch: pytest.MonkeyPatch, extractor: Extractor, bank: Bank
|
||||||
|
):
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"pfbudget.extract.nordigen.NordigenClient.dump", lambda *args: None
|
||||||
|
)
|
||||||
assert extractor.extract(bank) == [
|
assert extractor.extract(bank) == [
|
||||||
BankTransaction(
|
BankTransaction(
|
||||||
dt.date(2023, 1, 14), "string", Decimal("328.18"), bank="Bank#1"
|
dt.date(2023, 1, 14), "string", Decimal("328.18"), bank="Bank#1"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user