From afe2478cc704b481c4b627dfe4f0d5d6c2d48f86 Mon Sep 17 00:00:00 2001 From: Mathias Scheider Date: Sun, 13 Sep 2026 09:45:16 +0200 Subject: [PATCH] comdirect stringification --- src/models/comdirect.py | 92 ++++++++++++++++++++++------------- tests/factories.py | 22 +++++++-- tests/test_model_comdirect.py | 24 +++++++++ 3 files changed, 100 insertions(+), 38 deletions(-) create mode 100644 tests/test_model_comdirect.py diff --git a/src/models/comdirect.py b/src/models/comdirect.py index b205f77..4b9f967 100644 --- a/src/models/comdirect.py +++ b/src/models/comdirect.py @@ -1,7 +1,7 @@ from datetime import date from decimal import Decimal -from pydantic import Field, computed_field, field_validator +from pydantic import ConfigDict, Field, computed_field, field_validator from sqlalchemy import Column, Date, String from src.models._common import ( @@ -11,10 +11,12 @@ from src.models._common import ( _parse_date_german, _parse_decimal_german, date2str, + decimal2str, ) class ComdirectBase(ExportBaseModel): + model_config = ConfigDict(populate_by_name=True) date_val: date | None = Field( alias="Wertstellung (Valuta)", examples=["07.09.2026"] ) @@ -22,7 +24,9 @@ class ComdirectBase(ExportBaseModel): @field_validator("date_val", mode="before") @classmethod - def parse_date_val(cls, date_str: str) -> date | None: + def parse_date_val(cls, date_str: str | None) -> date | None: + if date_str is None: + return return _parse_date_german(date_str) @field_validator("amount_eur", mode="before") @@ -30,13 +34,28 @@ class ComdirectBase(ExportBaseModel): def parse_amount(cls, decimal_str: str) -> Decimal: return _parse_decimal_german(decimal_str) - def parameters_to_dict(self) -> dict[str, str]: ... + @classmethod + def _date2str(cls, date_value: date | None) -> str: + return date2str(date_value, default="--", date_format="%d.%m.%Y") - def parameters_to_str(self, sep=", ", str_qualifier=False): ... + def parameters_to_dict(self, by_name=True) -> dict[str, str]: + return { + "date_val" if by_name else "Wertstellung (Valuta)": self._date2str( + self.date_val + ), + "amount_eur" if by_name else "Umsatz in EUR": decimal2str(self.amount_eur), + } - def __repr__(self): ... + def get_orig_headers(self, sep: str = ";", bracket: str = '"'): + return sep.join( + f"{bracket}{txt}{bracket}" for txt in self.parameters_to_dict(by_name=False) + ) - def __str__(self): ... + def get_orig_row(self, sep: str = ";", bracket: str = '"'): + return sep.join( + f"{bracket}{txt}{bracket}" + for txt in self.parameters_to_dict(by_name=False).values() + ) class ComdirectGiro(ComdirectBase): @@ -59,21 +78,18 @@ class ComdirectGiro(ComdirectBase): def parse_date_book(cls, date_str: str) -> date | None: return _parse_date_german(date_str) - def parameters_to_dict(self) -> dict[str, str]: - return { - "Buchungstag": date2str( - self.date_book, default="--", date_format="%d.%m.%Y" - ), - "Vorgang": self.type, - "Text": self.text, - } - - def parameters_to_str(self, sep=", ", str_qualifier=False): - ... - - def __repr__(self): ... - - def __str__(self): ... + def parameters_to_dict(self, by_name=True) -> dict[str, str]: + parameters = dict(**super().parameters_to_dict(by_name=by_name)) + parameters.update( + { + "date_book" if by_name else "Buchungstag": self._date2str( + self.date_book + ), + "type" if by_name else "Vorgang": self.type, + "text" if by_name else "Buchungstext": self.text, + } + ) + return parameters class ComdirectCreditCard(ComdirectGiro): @@ -96,13 +112,17 @@ class ComdirectCreditCard(ComdirectGiro): alias="Umsatz in EUR", examples=["32,43"], default=None ) - def parameters_to_dict(self) -> dict[str, str]: ... - def parameters_to_str(self, sep=", ", str_qualifier=False): ... - def __repr__(self): ... - - def __str__(self): ... + def parameters_to_dict(self) -> dict[str, str]: + parameters = dict(**super().parameters_to_dict()) + parameters.update( + { + "Umsatztag": self._date2str(self.date_val), + "Referenz": self.reference, + } + ) + return parameters class ComdirectDepot(ComdirectBase): @@ -121,13 +141,19 @@ class ComdirectDepot(ComdirectBase): def parse_decimals(cls, decimal_in: str) -> Decimal: return _parse_decimal_german(decimal_in) - def parameters_to_dict(self) -> dict[str, str]: ... - - def parameters_to_str(self, sep=", ", str_qualifier=False): ... - - def __repr__(self): ... - - def __str__(self): ... + def parameters_to_dict(self) -> dict[str, str]: + parameters = dict(**super().parameters_to_dict()) + parameters.update( + { + "Geschäftstag": self._date2str(self.date_val), + "Stück / Nom.": decimal2str(self.num), + "Bezeichnung": self.name, + "WKN": self.wkn, + "Währung": self.currency, + "Ausführungskurs": decimal2str(self.spot_price), + } + ) + return parameters class ComdirectBaseDb(IngestdbBase): diff --git a/tests/factories.py b/tests/factories.py index a46e685..d2d9726 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -1,4 +1,3 @@ -from collections.abc import Callable from decimal import Decimal from typing import Any @@ -27,6 +26,18 @@ def _amount() -> Decimal: ) +def _amount_eur() -> str: + """Generate German-formatted decimal string (e.g., '32,43').""" + val = B4wExportFactory.__faker__.pydecimal( + left_digits=6, + right_digits=2, + positive=True, + min_value=Decimal("0.01"), + ) + # Format as German string with comma decimal separator + return f"{val:.2f}".replace(".", ",").strip() + + def _sentence() -> str: return B4wExportFactory.__faker__.sentence( nb_words=8, @@ -61,16 +72,17 @@ class B4wExportFactory(ModelFactory[B4wExport]): class ComdirectGiroFactory(ModelFactory[ComdirectGiro]): __faker__ = Faker("de_DE") - __use_example__ = True + __use_examples__ = True - Buchungstext = Use(_sentence) + text = Use(_sentence) + amount_eur = Use(_amount_eur) class ComdirectCreditCardFactory(ModelFactory[ComdirectCreditCard]): __faker__ = Faker("de_DE") - __use_example__ = True + __use_examples__ = True class ComdirectDepotFactory(ModelFactory[ComdirectDepot]): __faker__ = Faker("de_DE") - __use_example__ = True + __use_examples__ = True diff --git a/tests/test_model_comdirect.py b/tests/test_model_comdirect.py new file mode 100644 index 0000000..0660caa --- /dev/null +++ b/tests/test_model_comdirect.py @@ -0,0 +1,24 @@ +from src.models.comdirect import ComdirectGiro +from tests.factories import ComdirectGiroFactory + + +def test_comdirect_giro_basic_and_stringification(): + data = ComdirectGiroFactory.build() + assert data + assert isinstance(data, ComdirectGiro) + orig_headers = data.get_orig_headers() + assert orig_headers + assert isinstance(orig_headers, str) + header_list = orig_headers.split(";") + assert len(header_list) > 0 + assert set(header_list) == { + '"Wertstellung (Valuta)"', + '"Umsatz in EUR"', + '"Buchungstag"', + '"Vorgang"', + '"Buchungstext"', + } + orig_row = data.get_orig_row() + assert orig_row + assert isinstance(orig_row, str) + assert len(orig_row.split(";")) > 0