ofxstatement-nordigen 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ include README.rst
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: ofxstatement-nordigen
3
+ Version: 0.2.2
4
+ Summary: ofxstatement plugin for Nordigen bank statements
5
+ Author-email: Jimmy Stammers <jimmy.stammers@gmail.com>
6
+ Project-URL: Homepage, https://github.com/jstammers/ofxstatement-nordigen/
7
+ Keywords: ofx,banking,statement,plugin,ofxstatement
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Natural Language :: English
11
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
12
+ Classifier: Topic :: Utilities
13
+ Classifier: Environment :: Console
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/x-rst
18
+ Requires-Dist: ofxstatement
19
+ Requires-Dist: pydantic
20
+ Requires-Dist: requests
21
+ Requires-Dist: requests-cache
22
+ Requires-Dist: wheel>=0.45.1
23
+
24
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
25
+ ofxstatement-nordigen
26
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
27
+
28
+ A plugin for `ofxstatement`_ to parse transaction data from GoCardless (previously known as Nordigen).
29
+
30
+ `ofxstatement`_ is a tool to convert proprietary bank statement to OFX format,
31
+ suitable for importing to GnuCash. Plugin for ofxstatement parses a
32
+ particular proprietary bank statement format and produces common data
33
+ structure, that is then formatted into an OFX file.
34
+
35
+ .. _ofxstatement: https://github.com/kedder/ofxstatement
36
+
37
+
38
+ Installation
39
+ ================
40
+
41
+ To install the plugin, you can use `pip`_:
42
+
43
+ .. _pip: https://pypi.org/project/pip/
44
+
45
+ .. code-block:: shell
46
+
47
+ pip install ofxstatement-nordigen
48
+
49
+ or, if you want to install it in editable mode (for development), use:
50
+
51
+ .. code-block:: shell
52
+
53
+ pip install -e ./
54
+
55
+ To verify that the plugin is installed correctly, you can run:
56
+
57
+ .. code-block:: shell
58
+
59
+ ofxstatement --list-plugins
60
+
61
+ This should list the ``nordigen`` plugin among other plugins.
62
+
63
+ Usage
64
+ ================
65
+
66
+ To use the plugin, you can run the ``ofxstatement`` command with the ``--plugin`` option:
67
+
68
+ .. code-block:: shell
69
+
70
+ ofxstatement convert -t nordigen <input_file> <output_file>
71
+
72
+ Replace ``<input_file>`` with the path to your input file and ``<output_file>`` with the desired output file name.
73
+
74
+ The input file should be a JSON of transactions from GoCardless that has the schema defined `here`_.
75
+
76
+ .. _here: https://developer.gocardless.com/bank-account-data/transactions
77
+
78
+ The output file will be an OFX file that can be imported into GnuCash or other financial software.
79
+
80
+ Configuration
81
+ ================
82
+
83
+ Configuration can be edited using the ``ofxstatement edit-config`` command.
84
+ The following parameters are available:
85
+
86
+ - ``account_id``: The account ID to use for the transactions. This is required.
87
+ - ``currency``: The currency to use for the account. If not specified, the currency will be determined from the transactions.
88
+
89
+ After you are done
90
+ ==================
91
+
92
+ After your plugin is ready, feel free to open an issue on `ofxstatement`_
93
+ project to include your plugin in "known plugin list". That would hopefully
94
+ make life of other clients of your bank easier.
@@ -0,0 +1,71 @@
1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2
+ ofxstatement-nordigen
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
+
5
+ A plugin for `ofxstatement`_ to parse transaction data from GoCardless (previously known as Nordigen).
6
+
7
+ `ofxstatement`_ is a tool to convert proprietary bank statement to OFX format,
8
+ suitable for importing to GnuCash. Plugin for ofxstatement parses a
9
+ particular proprietary bank statement format and produces common data
10
+ structure, that is then formatted into an OFX file.
11
+
12
+ .. _ofxstatement: https://github.com/kedder/ofxstatement
13
+
14
+
15
+ Installation
16
+ ================
17
+
18
+ To install the plugin, you can use `pip`_:
19
+
20
+ .. _pip: https://pypi.org/project/pip/
21
+
22
+ .. code-block:: shell
23
+
24
+ pip install ofxstatement-nordigen
25
+
26
+ or, if you want to install it in editable mode (for development), use:
27
+
28
+ .. code-block:: shell
29
+
30
+ pip install -e ./
31
+
32
+ To verify that the plugin is installed correctly, you can run:
33
+
34
+ .. code-block:: shell
35
+
36
+ ofxstatement --list-plugins
37
+
38
+ This should list the ``nordigen`` plugin among other plugins.
39
+
40
+ Usage
41
+ ================
42
+
43
+ To use the plugin, you can run the ``ofxstatement`` command with the ``--plugin`` option:
44
+
45
+ .. code-block:: shell
46
+
47
+ ofxstatement convert -t nordigen <input_file> <output_file>
48
+
49
+ Replace ``<input_file>`` with the path to your input file and ``<output_file>`` with the desired output file name.
50
+
51
+ The input file should be a JSON of transactions from GoCardless that has the schema defined `here`_.
52
+
53
+ .. _here: https://developer.gocardless.com/bank-account-data/transactions
54
+
55
+ The output file will be an OFX file that can be imported into GnuCash or other financial software.
56
+
57
+ Configuration
58
+ ================
59
+
60
+ Configuration can be edited using the ``ofxstatement edit-config`` command.
61
+ The following parameters are available:
62
+
63
+ - ``account_id``: The account ID to use for the transactions. This is required.
64
+ - ``currency``: The currency to use for the account. If not specified, the currency will be determined from the transactions.
65
+
66
+ After you are done
67
+ ==================
68
+
69
+ After your plugin is ready, feel free to open an issue on `ofxstatement`_
70
+ project to include your plugin in "known plugin list". That would hopefully
71
+ make life of other clients of your bank easier.
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ofxstatement-nordigen"
7
+ version = "0.2.2"
8
+ authors = [
9
+ { name="Jimmy Stammers", email="jimmy.stammers@gmail.com" },
10
+ ]
11
+ description = "ofxstatement plugin for Nordigen bank statements"
12
+ readme = "README.rst"
13
+ requires-python = ">=3.12"
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Programming Language :: Python :: 3",
17
+ "Natural Language :: English",
18
+ "Topic :: Office/Business :: Financial :: Accounting",
19
+ "Topic :: Utilities",
20
+ "Environment :: Console",
21
+ "Operating System :: OS Independent",
22
+ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
23
+ ]
24
+ keywords = ["ofx", "banking", "statement", "plugin", "ofxstatement"]
25
+ dependencies = [
26
+ "ofxstatement",
27
+ "pydantic",
28
+ "requests",
29
+ "requests-cache",
30
+ "wheel>=0.45.1",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/jstammers/ofxstatement-nordigen/"
35
+
36
+ [project.entry-points."ofxstatement"]
37
+ nordigen = "ofxstatement_nordigen.plugin:NordigenPlugin"
38
+
39
+ [dependency-groups]
40
+ dev = [
41
+ "black>=25.11.0",
42
+ "build>=1.4.0",
43
+ "exceptiongroup>=1.3.1",
44
+ "mypy>=1.19.1",
45
+ "pytest>=8.4.2",
46
+ "pytest-cov>=7.0.0",
47
+ "ruff>=0.14.11",
48
+ "tomli>=2.4.0",
49
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,93 @@
1
+ import json
2
+ from typing import Iterable, Optional
3
+ from datetime import datetime
4
+ from ofxstatement.plugin import Plugin
5
+ from ofxstatement.parser import StatementParser
6
+ from ofxstatement.statement import Statement, StatementLine
7
+
8
+ from ofxstatement_nordigen.schemas import NordigenTransactionModel
9
+
10
+
11
+ class NordigenPlugin(Plugin):
12
+ """Retrieves Nordigen transactions and converts them to OFX format."""
13
+
14
+ def get_parser(self, filename: str) -> "NordigenParser":
15
+ default_ccy = self.settings.get("currency")
16
+ account_id = self.settings.get("account")
17
+ return NordigenParser(filename, default_ccy, account_id)
18
+
19
+
20
+ class NordigenParser(StatementParser[str]):
21
+ def __init__(
22
+ self,
23
+ filename: str,
24
+ currency: Optional[str] = None,
25
+ account_id: Optional[str] = None,
26
+ ) -> None:
27
+ super().__init__()
28
+ if not filename.endswith(".json"):
29
+ raise ValueError("Only JSON files are supported")
30
+ self.filename = filename
31
+ self.currency = currency
32
+ self.account_id = account_id
33
+
34
+ def parse(self) -> Statement:
35
+ """Main entry point for parsers
36
+
37
+ super() implementation will call to split_records and parse_record to
38
+ process the file.
39
+ """
40
+ with open(self.filename, "r"):
41
+ statement = super().parse()
42
+ dates = [
43
+ line.date for line in statement.lines if isinstance(line.date, datetime)
44
+ ]
45
+ if len(dates) > 0:
46
+ statement.start_date = min(dates)
47
+ statement.end_date = max(dates)
48
+ statement.account_id = self.account_id
49
+ statement.currency = self.currency or statement.currency
50
+ return statement
51
+
52
+ def split_records(self) -> Iterable[str]:
53
+ """Return iterable object consisting of a line per transaction"""
54
+ data = json.load(open(self.filename, "r"))
55
+ transactions = data.get("transactions", {})
56
+ booked_transactions = transactions.get("booked", [])
57
+ return [json.dumps(transaction) for transaction in booked_transactions]
58
+
59
+ def parse_record(self, line: str) -> StatementLine:
60
+ """Parse given transaction line and return StatementLine object"""
61
+
62
+ # TODO: Infer transaction type from transaction data
63
+ statement = StatementLine()
64
+ transaction = json.loads(line)
65
+ transaction_data = NordigenTransactionModel(**transaction)
66
+ statement.id = (
67
+ transaction_data.transactionId or transaction_data.internalTransactionId
68
+ )
69
+ # Use bookingDateTime if available, otherwise convert bookingDate to datetime
70
+ if transaction_data.bookingDateTime:
71
+ statement.date = transaction_data.bookingDateTime
72
+ elif transaction_data.bookingDate:
73
+ statement.date = datetime.combine(
74
+ transaction_data.bookingDate, datetime.min.time()
75
+ )
76
+ statement.amount = transaction_data.transactionAmount.amount
77
+ # Handle different types of remittance information
78
+ if transaction_data.remittanceInformationUnstructured:
79
+ statement.memo = transaction_data.remittanceInformationUnstructured
80
+ elif transaction_data.remittanceInformationUnstructuredArray:
81
+ statement.memo = " ".join(
82
+ transaction_data.remittanceInformationUnstructuredArray
83
+ )
84
+ statement.payee = transaction_data.creditorName or transaction_data.debtorName
85
+ statement.date_user = transaction_data.valueDateTime
86
+ statement.check_no = transaction_data.checkId
87
+ statement.refnum = transaction_data.internalTransactionId
88
+ statement.currency = transaction_data.transactionAmount.currency
89
+ if transaction_data.currencyExchange and hasattr(
90
+ transaction_data.currencyExchange, "sourceCurrency"
91
+ ):
92
+ statement.orig_currency = transaction_data.currencyExchange.sourceCurrency
93
+ return statement
@@ -0,0 +1,111 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from ofxstatement.statement import Currency
5
+ from decimal import Decimal
6
+ from typing import List, Optional
7
+
8
+ from pydantic import BaseModel, field_validator, ConfigDict
9
+ from pydantic.alias_generators import to_snake
10
+
11
+
12
+ class Amount(BaseModel):
13
+ amount: Decimal
14
+ currency: Currency
15
+
16
+ @field_validator("currency", mode="before")
17
+ def validate_currency(cls, value):
18
+ if isinstance(value, str):
19
+ return Currency(value)
20
+ return value
21
+
22
+ model_config = ConfigDict(arbitrary_types_allowed=True)
23
+
24
+
25
+ class Account(BaseModel):
26
+ bban: Optional[str] = None
27
+
28
+
29
+ class ReportExchangeRate(BaseModel):
30
+ sourceCurrency: Optional[Currency] = None
31
+ targetCurrency: Optional[Currency] = None
32
+ unitCurrency: Optional[Currency] = None
33
+ exchangeRate: Optional[float] = None
34
+ quotationDate: Optional[datetime.date] = None
35
+
36
+ model_config = ConfigDict(arbitrary_types_allowed=True)
37
+
38
+ @field_validator("sourceCurrency", "targetCurrency", "unitCurrency", mode="before")
39
+ def validate_currency(cls, value):
40
+ if isinstance(value, str):
41
+ return Currency(value)
42
+ return value
43
+
44
+
45
+ class CurrencyExchangeAmex(BaseModel):
46
+ """
47
+ Context: Issue #11
48
+
49
+ Temporary Fix:
50
+ This addresses the lack of normalization of American Express data to the GoCardless specification.
51
+
52
+ Note: This class should be removed once the normalization process is completed.
53
+ """
54
+
55
+ sourceCurrency: Optional[Currency] = None
56
+ targetCurrency: Optional[Currency] = None
57
+ unitCurrency: Optional[Currency] = None
58
+ exchangeRate: Optional[float] = None
59
+ instructedAmount: Optional[Amount] = None
60
+
61
+ @field_validator("sourceCurrency", "targetCurrency", "unitCurrency", mode="before")
62
+ def validate_currency(cls, value):
63
+ if isinstance(value, str):
64
+ return Currency(value)
65
+ return value
66
+
67
+ model_config = ConfigDict(arbitrary_types_allowed=True)
68
+
69
+
70
+ class NordigenTransactionModel(BaseModel):
71
+ """
72
+ Nordigen data transaction model.
73
+ """
74
+
75
+ balanceAfterTransaction: Optional[float] = None
76
+ bankTransactionCode: Optional[str] = None
77
+ bookingDate: Optional[datetime.date] = None
78
+ bookingDateTime: Optional[datetime.datetime] = None
79
+ checkId: Optional[str] = None
80
+ creditorAccount: Optional[Account] = None
81
+ creditorAgent: Optional[str] = None
82
+ creditorId: Optional[str] = None
83
+ creditorName: Optional[str] = None
84
+ currencyExchange: Optional[List[ReportExchangeRate] | CurrencyExchangeAmex] = None
85
+ debtorAccount: Optional[Account] = None
86
+ debtorAgent: Optional[str] = None
87
+ debtorName: Optional[str] = None
88
+ endToEndId: Optional[str] = None
89
+ entryReference: Optional[str] = None
90
+ internalTransactionId: Optional[str] = None
91
+ mandateId: Optional[str] = None
92
+ merchantCategoryCode: Optional[str] = None
93
+ proprietaryBankTransactionCode: Optional[str] = None
94
+ purposeCode: Optional[str] = None
95
+ remittanceInformationStructured: Optional[str] = None
96
+ remittanceInformationStructuredArray: Optional[List[str]] = None
97
+ remittanceInformationUnstructured: Optional[str] = None
98
+ remittanceInformationUnstructuredArray: Optional[List[str]] = None
99
+ transactionAmount: Amount
100
+ transactionId: Optional[str] = None
101
+ ultimateCreditor: Optional[str] = None
102
+ ultimateDebtor: Optional[str] = None
103
+ valueDate: Optional[datetime.date] = None
104
+ valueDateTime: Optional[datetime.datetime] = None
105
+
106
+ # class Config:
107
+ # alias_generator = to_snake
108
+
109
+ model_config = ConfigDict(
110
+ arbitrary_types_allowed=True, alias_generator=to_snake, populate_by_name=True
111
+ )
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: ofxstatement-nordigen
3
+ Version: 0.2.2
4
+ Summary: ofxstatement plugin for Nordigen bank statements
5
+ Author-email: Jimmy Stammers <jimmy.stammers@gmail.com>
6
+ Project-URL: Homepage, https://github.com/jstammers/ofxstatement-nordigen/
7
+ Keywords: ofx,banking,statement,plugin,ofxstatement
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Natural Language :: English
11
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
12
+ Classifier: Topic :: Utilities
13
+ Classifier: Environment :: Console
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/x-rst
18
+ Requires-Dist: ofxstatement
19
+ Requires-Dist: pydantic
20
+ Requires-Dist: requests
21
+ Requires-Dist: requests-cache
22
+ Requires-Dist: wheel>=0.45.1
23
+
24
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
25
+ ofxstatement-nordigen
26
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
27
+
28
+ A plugin for `ofxstatement`_ to parse transaction data from GoCardless (previously known as Nordigen).
29
+
30
+ `ofxstatement`_ is a tool to convert proprietary bank statement to OFX format,
31
+ suitable for importing to GnuCash. Plugin for ofxstatement parses a
32
+ particular proprietary bank statement format and produces common data
33
+ structure, that is then formatted into an OFX file.
34
+
35
+ .. _ofxstatement: https://github.com/kedder/ofxstatement
36
+
37
+
38
+ Installation
39
+ ================
40
+
41
+ To install the plugin, you can use `pip`_:
42
+
43
+ .. _pip: https://pypi.org/project/pip/
44
+
45
+ .. code-block:: shell
46
+
47
+ pip install ofxstatement-nordigen
48
+
49
+ or, if you want to install it in editable mode (for development), use:
50
+
51
+ .. code-block:: shell
52
+
53
+ pip install -e ./
54
+
55
+ To verify that the plugin is installed correctly, you can run:
56
+
57
+ .. code-block:: shell
58
+
59
+ ofxstatement --list-plugins
60
+
61
+ This should list the ``nordigen`` plugin among other plugins.
62
+
63
+ Usage
64
+ ================
65
+
66
+ To use the plugin, you can run the ``ofxstatement`` command with the ``--plugin`` option:
67
+
68
+ .. code-block:: shell
69
+
70
+ ofxstatement convert -t nordigen <input_file> <output_file>
71
+
72
+ Replace ``<input_file>`` with the path to your input file and ``<output_file>`` with the desired output file name.
73
+
74
+ The input file should be a JSON of transactions from GoCardless that has the schema defined `here`_.
75
+
76
+ .. _here: https://developer.gocardless.com/bank-account-data/transactions
77
+
78
+ The output file will be an OFX file that can be imported into GnuCash or other financial software.
79
+
80
+ Configuration
81
+ ================
82
+
83
+ Configuration can be edited using the ``ofxstatement edit-config`` command.
84
+ The following parameters are available:
85
+
86
+ - ``account_id``: The account ID to use for the transactions. This is required.
87
+ - ``currency``: The currency to use for the account. If not specified, the currency will be determined from the transactions.
88
+
89
+ After you are done
90
+ ==================
91
+
92
+ After your plugin is ready, feel free to open an issue on `ofxstatement`_
93
+ project to include your plugin in "known plugin list". That would hopefully
94
+ make life of other clients of your bank easier.
@@ -0,0 +1,16 @@
1
+ MANIFEST.in
2
+ README.rst
3
+ pyproject.toml
4
+ src/ofxstatement_nordigen/__init__.py
5
+ src/ofxstatement_nordigen/plugin.py
6
+ src/ofxstatement_nordigen/schemas.py
7
+ src/ofxstatement_nordigen.egg-info/PKG-INFO
8
+ src/ofxstatement_nordigen.egg-info/SOURCES.txt
9
+ src/ofxstatement_nordigen.egg-info/dependency_links.txt
10
+ src/ofxstatement_nordigen.egg-info/entry_points.txt
11
+ src/ofxstatement_nordigen.egg-info/requires.txt
12
+ src/ofxstatement_nordigen.egg-info/top_level.txt
13
+ tests/test_CAISSEDEPARGNE_ILE_DE_FRANCE_CEPAFRPP751.py
14
+ tests/test_banquepopulaire_rives_de_paris.py
15
+ tests/test_sample.py
16
+ tests/test_schemas.py
@@ -0,0 +1,2 @@
1
+ [ofxstatement]
2
+ nordigen = ofxstatement_nordigen.plugin:NordigenPlugin
@@ -0,0 +1,5 @@
1
+ ofxstatement
2
+ pydantic
3
+ requests
4
+ requests-cache
5
+ wheel>=0.45.1
@@ -0,0 +1,48 @@
1
+ import os
2
+ import json
3
+ import pytest
4
+ from datetime import datetime, date
5
+ from decimal import Decimal
6
+
7
+ from ofxstatement.ui import UI
8
+ from ofxstatement import ofx
9
+ from ofxstatement.statement import StatementLine, Currency
10
+
11
+ from ofxstatement_nordigen.plugin import NordigenPlugin, NordigenParser
12
+ from ofxstatement_nordigen.schemas import NordigenTransactionModel
13
+
14
+
15
+ @pytest.mark.parametrize("filename", ["CAISSEDEPARGNE_ILE_DE_FRANCE_CEPAFRPP751.json"])
16
+ def test_CAISSEDEPARGNE_ILE_DE_FRANCE(filename: str) -> None:
17
+ """Test parsing the CAISSEDEPARGNE_ILE_DE_FRANCE_CEPAFRPP751.json file."""
18
+ here = os.path.dirname(__file__)
19
+ sample_filename = os.path.join(here, "data", filename)
20
+ expected_filename = sample_filename.replace(".json", ".ofx")
21
+
22
+ parser = NordigenParser(sample_filename)
23
+ statement = parser.parse()
24
+
25
+ # Verify the statement properties
26
+ assert len(statement.lines) == 1
27
+
28
+ # Verify the transaction details
29
+ transaction = statement.lines[0]
30
+ assert transaction.id == "6666666"
31
+
32
+ assert (
33
+ transaction.date is not None
34
+ ) # Fix for mypy: Check that date is not None before accessing date() method
35
+ assert transaction.date.date() == date(2025, 5, 13)
36
+
37
+ assert transaction.amount == Decimal("-1")
38
+
39
+ assert (
40
+ transaction.currency is not None
41
+ ) # Fix for mypy: Check that currency is not None before accessing symbol attribute
42
+ assert transaction.currency.symbol == "EUR"
43
+
44
+ # Check if the memo contains the combined information from remittanceInformationUnstructuredArray
45
+ assert (
46
+ transaction.memo is not None
47
+ ) # Fix for mypy: Check that memo is not None before using 'in' operator
48
+ assert "PRLV assurance" in transaction.memo
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+ import json
5
+ import pytest
6
+ from datetime import datetime, date
7
+ from decimal import Decimal
8
+
9
+ from ofxstatement.ui import UI
10
+ from ofxstatement import ofx
11
+ from ofxstatement.statement import StatementLine, Currency
12
+
13
+ from ofxstatement_nordigen.plugin import NordigenPlugin, NordigenParser
14
+ from ofxstatement_nordigen.schemas import NordigenTransactionModel
15
+
16
+
17
+ @pytest.mark.parametrize(
18
+ "filename", ["BANQUEPOPULAIRE_RIVES_DE_PARIS_CCBPFRPPMTG.json"]
19
+ )
20
+ def test_banquepopulaire_rives_de_paris(filename: str) -> None:
21
+ """Test parsing the BANQUEPOPULAIRE_RIVES_DE_PARIS_CCBPFRPPMTG.json file."""
22
+ here = os.path.dirname(__file__)
23
+ sample_filename = os.path.join(here, "data", filename)
24
+ expected_filename = sample_filename.replace(".json", ".ofx")
25
+
26
+ parser = NordigenParser(sample_filename)
27
+ statement = parser.parse()
28
+
29
+ # Verify the statement properties
30
+ assert len(statement.lines) == 1
31
+
32
+ # Verify the transaction details
33
+ transaction = statement.lines[0]
34
+ assert transaction.id == "202500400015"
35
+
36
+ # Fix for mypy: Check that date is not None before accessing date() method
37
+ assert transaction.date is not None
38
+ assert transaction.date.date() == date(2025, 5, 2)
39
+
40
+ assert transaction.amount == Decimal("-8.43")
41
+
42
+ # Fix for mypy: Check that currency is not None before accessing symbol attribute
43
+ assert transaction.currency is not None
44
+ assert transaction.currency.symbol == "EUR"
45
+
46
+ assert transaction.refnum == "YYYYYYYYYYYYYY"
47
+
48
+ # Check if the memo contains the combined information from remittanceInformationUnstructuredArray
49
+ # Fix for mypy: Check that memo is not None before using 'in' operator
50
+ assert transaction.memo is not None
51
+ assert "CB****2222" in transaction.memo
52
+ assert "Food Restaurant" in transaction.memo
53
+
54
+ # Compare with expected OFX output
55
+ expected = open(expected_filename, "r").read()
56
+ writer = ofx.OfxWriter(statement)
57
+ result = writer.toxml(pretty=True)
58
+
59
+ # Get everything between the <STMTTRN> and </STMTTRN> tags ignoring \r characters
60
+ result = result[
61
+ result.index("<STMTTRN>") : result.index("</STMTTRN>") + len("</STMTTRN>")
62
+ ].replace("\r", "")
63
+ expected = expected[
64
+ expected.index("<STMTTRN>") : expected.index("</STMTTRN>") + len("</STMTTRN>")
65
+ ].replace("\r", "")
66
+
67
+ assert result == expected
@@ -0,0 +1,61 @@
1
+ import os
2
+ import pytest
3
+ from ofxstatement.ui import UI
4
+
5
+ from ofxstatement_nordigen.plugin import NordigenPlugin, NordigenParser
6
+ from ofxstatement import ofx
7
+
8
+
9
+ def test_config_sets_account_and_currency() -> None:
10
+ """Test that account and currency are set correctly when provided in config."""
11
+ # Create a plugin with specific account and currency settings
12
+ settings = {"account": "test-account-123", "currency": "EUR"}
13
+ plugin = NordigenPlugin(UI(), settings)
14
+
15
+ here = os.path.dirname(__file__)
16
+ sample_filename = os.path.join(here, "sample-statement.json")
17
+ parser = plugin.get_parser(sample_filename)
18
+
19
+ assert parser.account_id == settings["account"]
20
+ assert parser.currency == settings["currency"]
21
+
22
+ statement = parser.parse()
23
+ assert statement.account_id == settings["account"]
24
+ assert statement.currency == settings["currency"]
25
+
26
+
27
+ def test_sample() -> None:
28
+ plugin = NordigenPlugin(UI(), {})
29
+ here = os.path.dirname(__file__)
30
+ for filename in os.listdir(here):
31
+ if filename.endswith(".json"):
32
+ sample_filename = os.path.join(here, filename)
33
+ parser = plugin.get_parser(sample_filename)
34
+ statement = parser.parse()
35
+ assert len(statement.lines) > 0
36
+ assert statement.start_date is not None
37
+ assert statement.end_date is not None
38
+
39
+
40
+ @pytest.mark.parametrize("filename", ["test_date.json", "test_snake_case.json"])
41
+ def test_parse_record(filename: str) -> None:
42
+ here = os.path.dirname(__file__)
43
+ sample_filename = os.path.join(here, "data", filename)
44
+ expected_filename = sample_filename.replace(".json", ".ofx")
45
+
46
+ parser = NordigenParser(sample_filename)
47
+ statement = parser.parse()
48
+
49
+ expected = open(expected_filename, "r").read()
50
+ writer = ofx.OfxWriter(statement)
51
+ result = writer.toxml(pretty=True)
52
+
53
+ # Get everything between the <STMTTRN> and </STMTTRN> tags ignoring \r characters
54
+ result = result[
55
+ result.index("<STMTTRN>") : result.index("</STMTTRN>") + len("</STMTTRN>")
56
+ ].replace("\r", "")
57
+ expected = expected[
58
+ expected.index("<STMTTRN>") : expected.index("</STMTTRN>") + len("</STMTTRN>")
59
+ ].replace("\r", "")
60
+
61
+ assert result == expected
@@ -0,0 +1,68 @@
1
+ import pytest
2
+
3
+ from ofxstatement_nordigen.schemas import NordigenTransactionModel
4
+
5
+
6
+ @pytest.mark.parametrize(
7
+ "data",
8
+ [
9
+ {
10
+ "transactionId": "123456789",
11
+ "transactionAmount": {"amount": 100.0, "currency": "EUR"},
12
+ "valueDate": "2023-10-01",
13
+ "valueDateTime": "2023-10-01T12:00:00Z",
14
+ "remittanceInformationStructured": "Payment for invoice #12345",
15
+ },
16
+ {
17
+ "transactionId": "987654321",
18
+ "entryReference": "REF123456",
19
+ "bookingDate": "2025-03-31",
20
+ "bookingDateTime": "2025-03-31T00:00:00+00:00",
21
+ "transactionAmount": {"amount": "-1521.00", "currency": "GBP"},
22
+ "remittanceInformationUnstructured": "Payment for invoice #67890",
23
+ "additionalInformation": "Payment received",
24
+ "proprietaryBankTransactionCode": "BP",
25
+ "internalTransactionId": "INT123456",
26
+ },
27
+ {
28
+ "transactionId": "anonymized_transaction_id",
29
+ "entryReference": "anonymized_entry_reference",
30
+ "bookingDate": "2025-04-05",
31
+ "valueDate": "2025-04-05",
32
+ "bookingDateTime": "2025-04-05T00:00:00+00:00",
33
+ "valueDateTime": "2025-04-05T00:00:00+00:00",
34
+ "transactionAmount": {"amount": "0.00", "currency": "XXX"},
35
+ "currencyExchange": [{"sourceCurrency": "XXX", "exchangeRate": "0.0"}],
36
+ "remittanceInformationUnstructured": "anonymized_remittance_information",
37
+ "additionalInformation": "anonymized_additional_information",
38
+ "additionalDataStructured": {
39
+ "CardSchemeName": "anonymized_card_scheme",
40
+ "Name": "anonymized_name",
41
+ "Identification": "anonymized_identification",
42
+ },
43
+ "internalTransactionId": "anonymized_internal_transaction_id",
44
+ },
45
+ {
46
+ "transactionId": "anonymized_transaction_id",
47
+ "bookingDate": "2025-04-05",
48
+ "bookingDateTime": "2025-05-05T07:20:42.19Z",
49
+ "transactionAmount": {"amount": "-100.0000", "currency": "GBP"},
50
+ "currencyExchange": [
51
+ {
52
+ "quotationDate": "2025-04-05",
53
+ "sourceCurrency": "AUD",
54
+ "exchangeRate": "2.04501",
55
+ "unitCurrency": "GBP",
56
+ "targetCurrency": "GBP",
57
+ }
58
+ ],
59
+ "remittanceInformationUnstructured": "anonymized_remittance_information",
60
+ "proprietaryBankTransactionCode": "anonymized_code",
61
+ "internalTransactionId": "anonymized_internal_transaction_id",
62
+ },
63
+ ],
64
+ )
65
+ def test_go_cardless_transaction_model(data):
66
+ validated = NordigenTransactionModel(**data)
67
+ print(validated)
68
+ assert validated is not None