bankstatementparser-writer-xlsx 0.0.10__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,189 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work.
38
+
39
+ "Derivative Works" shall mean any work, whether in Source or Object
40
+ form, that is based on (or derived from) the Work and for which the
41
+ editorial revisions, annotations, elaborations, or other modifications
42
+ represent, as a whole, an original work of authorship. For the purposes
43
+ of this License, Derivative Works shall not include works that remain
44
+ separable from, or merely link (or bind by name) to the interfaces of,
45
+ the Work and Derivative Works thereof.
46
+
47
+ "Contribution" shall mean any work of authorship, including
48
+ the original version of the Work and any modifications or additions
49
+ to that Work or Derivative Works thereof, that is intentionally
50
+ submitted to the Licensor for inclusion in the Work by the copyright owner
51
+ or by an individual or Legal Entity authorized to submit on behalf of
52
+ the copyright owner. For the purposes of this definition, "submitted"
53
+ means any form of electronic, verbal, or written communication sent
54
+ to the Licensor or its representatives, including but not limited to
55
+ communication on electronic mailing lists, source code control systems,
56
+ and issue tracking systems that are managed by, or on behalf of, the
57
+ Licensor for the purpose of discussing and improving the Work, but
58
+ excluding communication that is conspicuously marked or otherwise
59
+ designated in writing by the copyright owner as "Not a Contribution."
60
+
61
+ "Contributor" shall mean Licensor and any individual or Legal Entity
62
+ on behalf of whom a Contribution has been received by the Licensor and
63
+ subsequently incorporated within the Work.
64
+
65
+ 2. Grant of Copyright License. Subject to the terms and conditions of
66
+ this License, each Contributor hereby grants to You a perpetual,
67
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
68
+ copyright license to reproduce, prepare Derivative Works of,
69
+ publicly display, publicly perform, sublicense, and distribute the
70
+ Work and such Derivative Works in Source or Object form.
71
+
72
+ 3. Grant of Patent License. Subject to the terms and conditions of
73
+ this License, each Contributor hereby grants to You a perpetual,
74
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
75
+ (except as stated in this section) patent license to make, have made,
76
+ use, offer to sell, sell, import, and otherwise transfer the Work,
77
+ where such license applies only to those patent claims licensable
78
+ by such Contributor that are necessarily infringed by their
79
+ Contribution(s) alone or by combination of their Contribution(s)
80
+ with the Work to which such Contribution(s) was submitted. If You
81
+ institute patent litigation against any entity (including a
82
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
83
+ or a Contribution incorporated within the Work constitutes direct
84
+ or contributory patent infringement, then any patent licenses
85
+ granted to You under this License for that Work shall terminate
86
+ as of the date such litigation is filed.
87
+
88
+ 4. Redistribution. You may reproduce and distribute copies of the
89
+ Work or Derivative Works thereof in any medium, with or without
90
+ modifications, and in Source or Object form, provided that You
91
+ meet the following conditions:
92
+
93
+ (a) You must give any other recipients of the Work or
94
+ Derivative Works a copy of this License; and
95
+
96
+ (b) You must cause any modified files to carry prominent notices
97
+ stating that You changed the files; and
98
+
99
+ (c) You must retain, in the Source form of any Derivative Works
100
+ that You distribute, all copyright, patent, trademark, and
101
+ attribution notices from the Source form of the Work,
102
+ excluding those notices that do not pertain to any part of
103
+ the Derivative Works; and
104
+
105
+ (d) If the Work includes a "NOTICE" text file as part of its
106
+ distribution, then any Derivative Works that You distribute must
107
+ include a readable copy of the attribution notices contained
108
+ within such NOTICE file, excluding any notices that do not
109
+ pertain to any part of the Derivative Works, in at least one
110
+ of the following places: within a NOTICE text file distributed
111
+ as part of the Derivative Works; within the Source form or
112
+ documentation, if provided along with the Derivative Works; or,
113
+ within a display generated by the Derivative Works, if and
114
+ wherever such third-party notices normally appear. The contents
115
+ of the NOTICE file are for informational purposes only and
116
+ do not modify the License. You may add Your own attribution
117
+ notices within Derivative Works that You distribute, alongside
118
+ or as an addendum to the NOTICE text from the Work, provided
119
+ that such additional attribution notices cannot be construed
120
+ as modifying the License.
121
+
122
+ You may add Your own copyright statement to Your modifications and
123
+ may provide additional or different license terms and conditions
124
+ for use, reproduction, or distribution of Your modifications, or
125
+ for any such Derivative Works as a whole, provided Your use,
126
+ reproduction, and distribution of the Work otherwise complies with
127
+ the conditions stated in this License.
128
+
129
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
130
+ any Contribution intentionally submitted for inclusion in the Work
131
+ by You to the Licensor shall be under the terms and conditions of
132
+ this License, without any additional terms or conditions.
133
+ Notwithstanding the above, nothing herein shall supersede or modify
134
+ the terms of any separate license agreement you may have executed
135
+ with Licensor regarding such Contributions.
136
+
137
+ 6. Trademarks. This License does not grant permission to use the trade
138
+ names, trademarks, service marks, or product names of the Licensor,
139
+ except as required for reasonable and customary use in describing the
140
+ origin of the Work and reproducing the content of the NOTICE file.
141
+
142
+ 7. Disclaimer of Warranty. Unless required by applicable law or
143
+ agreed to in writing, Licensor provides the Work (and each
144
+ Contributor provides its Contributions) on an "AS IS" BASIS,
145
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
146
+ implied, including, without limitation, any warranties or conditions
147
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
148
+ PARTICULAR PURPOSE. You are solely responsible for determining the
149
+ appropriateness of using or redistributing the Work and assume any
150
+ risks associated with Your exercise of permissions under this License.
151
+
152
+ 8. Limitation of Liability. In no event and under no legal theory,
153
+ whether in tort (including negligence), contract, or otherwise,
154
+ unless required by applicable law (such as deliberate and grossly
155
+ negligent acts) or agreed to in writing, shall any Contributor be
156
+ liable to You for damages, including any direct, indirect, special,
157
+ incidental, or consequential damages of any character arising as a
158
+ result of this License or out of the use or inability to use the
159
+ Work (including but not limited to damages for loss of goodwill,
160
+ work stoppage, computer failure or malfunction, or any and all
161
+ other commercial damages or losses), even if such Contributor
162
+ has been advised of the possibility of such damages.
163
+
164
+ 9. Accepting Warranty or Additional Liability. While redistributing
165
+ the Work or Derivative Works thereof, You may choose to offer,
166
+ and charge a fee for, acceptance of support, warranty, indemnity,
167
+ or other liability obligations and/or rights consistent with this
168
+ License. However, in accepting such obligations, You may act only
169
+ on Your own behalf and on Your sole responsibility, not on behalf
170
+ of any other Contributor, and only if You agree to indemnify,
171
+ defend, and hold each Contributor harmless for any liability
172
+ incurred by, or claims asserted against, such Contributor by reason
173
+ of your accepting any such warranty or additional liability.
174
+
175
+ END OF TERMS AND CONDITIONS
176
+
177
+ Copyright 2023-2026 Sebastien Rousseau
178
+
179
+ Licensed under the Apache License, Version 2.0 (the "License");
180
+ you may not use this file except in compliance with the License.
181
+ You may obtain a copy of the License at
182
+
183
+ http://www.apache.org/licenses/LICENSE-2.0
184
+
185
+ Unless required by applicable law or agreed to in writing, software
186
+ distributed under the License is distributed on an "AS IS" BASIS,
187
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
188
+ See the License for the specific language governing permissions and
189
+ limitations under the License.
@@ -0,0 +1,250 @@
1
+ Metadata-Version: 2.4
2
+ Name: bankstatementparser-writer-xlsx
3
+ Version: 0.0.10
4
+ Summary: Excel (.xlsx) writer for bankstatementparser-parsed bank statements.
5
+ License: Apache-2.0
6
+ License-File: LICENSE
7
+ Keywords: bank-statements,bankstatementparser,xlsx,excel,openpyxl,accounting,reconciliation
8
+ Author: Sebastien Rousseau
9
+ Author-email: sebastian.rousseau@gmail.com
10
+ Requires-Python: >=3.10,<4.0
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Financial and Insurance Industry
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Office/Business :: Financial
22
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
23
+ Requires-Dist: bankstatementparser (>=0.0.9)
24
+ Requires-Dist: openpyxl (>=3.1,<4)
25
+ Requires-Dist: pandas (>=2.0)
26
+ Project-URL: Homepage, https://bankstatementparser.com
27
+ Project-URL: Repository, https://github.com/sebastienrousseau/bankstatementparser-writer-xlsx
28
+ Description-Content-Type: text/markdown
29
+
30
+ # bankstatementparser-writer-xlsx: Excel writer for parsed bank statements
31
+
32
+ [![PyPI Version][pypi-badge]][pypi-url]
33
+ [![Python Versions][python-versions-badge]][pypi-url]
34
+ [![License][license-badge]][license-url]
35
+ [![Coverage][coverage-badge]][ci-url]
36
+
37
+ **An Excel `.xlsx` writer for data parsed by
38
+ [`bankstatementparser`][core]** — turn parsed transactions (a pandas
39
+ `DataFrame`, a list of `Transaction` objects, or a list of plain dicts)
40
+ into a polished workbook that accountants, auditors, and reconciliation
41
+ macros can open directly.
42
+
43
+ > **Latest release: v0.0.10** — single `write_xlsx(data, path, ...)`
44
+ > function, 100% line + branch coverage, 100% docstring coverage,
45
+ > `mypy --strict` clean.
46
+
47
+ ## Contents
48
+
49
+ - [Overview](#overview)
50
+ - [Install](#install)
51
+ - [Quick start](#quick-start)
52
+ - [Input shapes](#input-shapes)
53
+ - [Value coercion](#value-coercion)
54
+ - [The summary sheet](#the-summary-sheet)
55
+ - [Examples](#examples)
56
+ - [When not to use this package](#when-not-to-use-this-package)
57
+ - [Development](#development)
58
+ - [Security](#security)
59
+ - [Documentation](#documentation)
60
+ - [License](#license)
61
+ - [Contributing](#contributing)
62
+ - [Acknowledgements](#acknowledgements)
63
+
64
+ ## Overview
65
+
66
+ `bankstatementparser-writer-xlsx` is a small, focused companion to the
67
+ [`bankstatementparser`][core] library. It does one thing well: given
68
+ already-parsed bank-statement records, write a clean Excel workbook with
69
+ a bold header row, one row per transaction, auto-sized columns, and an
70
+ optional second `Summary` sheet.
71
+
72
+ The package consumes _parsed_ data — it does not read PDFs, CSVs, or
73
+ XML itself. Parsing (and the security surface that comes with untrusted
74
+ input) lives upstream in the [`bankstatementparser`][core] core.
75
+
76
+ ## Install
77
+
78
+ `bankstatementparser-writer-xlsx` runs on macOS, Linux, and Windows and
79
+ requires **Python 3.10+**. It pulls in `bankstatementparser`,
80
+ `openpyxl`, and `pandas` automatically.
81
+
82
+ ```bash
83
+ pip install bankstatementparser-writer-xlsx
84
+ ```
85
+
86
+ ## Quick start
87
+
88
+ ```python
89
+ from bankstatementparser import CsvStatementParser
90
+ from bankstatementparser_writer_xlsx import write_xlsx
91
+
92
+ parser = CsvStatementParser("statement.csv")
93
+ df = parser.parse() # a pandas DataFrame
94
+ write_xlsx(df, "statement.xlsx") # one polished workbook
95
+ ```
96
+
97
+ That's an Excel workbook ready for your accountant. Add a summary sheet
98
+ in one extra argument:
99
+
100
+ ```python
101
+ from bankstatementparser import CsvStatementParser
102
+ from bankstatementparser_writer_xlsx import write_xlsx
103
+
104
+ parser = CsvStatementParser("statement.csv")
105
+ df = parser.parse()
106
+ write_xlsx(df, "statement.xlsx", summary=parser.get_summary())
107
+ ```
108
+
109
+ ## Input shapes
110
+
111
+ `write_xlsx(data, path, *, sheet_name="Transactions", summary=None)`
112
+ accepts three input shapes and normalises each to a flat table:
113
+
114
+ | Input | Column order |
115
+ | :--- | :--- |
116
+ | `pandas.DataFrame` (from a parser's `.parse()`) | the DataFrame's own column order |
117
+ | `list[bankstatementparser.Transaction]` | the stable `Transaction` field order |
118
+ | `list[dict]` | the union of keys, in first-seen order |
119
+
120
+ A header row (bold) is written to the `sheet_name` sheet, followed by
121
+ one row per record. Columns are auto-sized to their widest cell (capped
122
+ so wide descriptions don't run off-screen).
123
+
124
+ **Empty input** is accepted: an empty `list` writes an empty sheet (no
125
+ header), while an empty `DataFrame` that still carries column labels
126
+ writes a header-only sheet.
127
+
128
+ ## Value coercion
129
+
130
+ Spreadsheet cells can't hold arbitrary Python objects, so the writer
131
+ coerces the rich types the parser emits:
132
+
133
+ | Python type | Written as |
134
+ | :--- | :--- |
135
+ | `decimal.Decimal` | `float` (Excel has no decimal type; floats aggregate natively) |
136
+ | `datetime.date` / `datetime.datetime` | native Excel date cell (unchanged) |
137
+ | `str`, `int`, `float`, `bool`, `None` | unchanged |
138
+ | anything else | `str(value)` |
139
+
140
+ ## The summary sheet
141
+
142
+ If you pass `summary=` a mapping (for example a parser's
143
+ `get_summary()` result), the writer adds a second sheet titled
144
+ `Summary` with a bold `Key` / `Value` header and one row per item:
145
+
146
+ ```python
147
+ from decimal import Decimal
148
+
149
+ from bankstatementparser_writer_xlsx import write_xlsx
150
+
151
+ transactions = [
152
+ {"date": "2026-06-01", "description": "Salary", "amount": Decimal("3000.00")},
153
+ {"date": "2026-06-03", "description": "Coffee Shop", "amount": Decimal("-4.20")},
154
+ ]
155
+
156
+ write_xlsx(
157
+ transactions,
158
+ "out.xlsx",
159
+ summary={
160
+ "account_id": "DE89370400440532013000",
161
+ "transaction_count": 128,
162
+ "total_amount": Decimal("12045.67"),
163
+ "currency": "EUR",
164
+ },
165
+ )
166
+ ```
167
+
168
+ ## Examples
169
+
170
+ Five runnable examples live in [`examples/`](examples/) and are
171
+ exercised in CI on every commit. Together they cover every supported
172
+ input shape and option of `write_xlsx`:
173
+
174
+ - [`01_write_dataframe.py`](examples/01_write_dataframe.py) — write a
175
+ pandas `DataFrame` to a single sheet.
176
+ - [`02_write_transactions.py`](examples/02_write_transactions.py) —
177
+ write a list of `Transaction` objects in stable field order.
178
+ - [`03_write_dicts.py`](examples/03_write_dicts.py) — write a list of
179
+ plain `dict` records (union of keys).
180
+ - [`04_write_with_summary.py`](examples/04_write_with_summary.py) —
181
+ write a DataFrame plus a second `Summary` sheet via `summary=`.
182
+ - [`05_custom_sheet_name.py`](examples/05_custom_sheet_name.py) — rename
183
+ the transactions sheet with `sheet_name=`.
184
+
185
+ ## When not to use this package
186
+
187
+ - **You need a custom sheet layout.** The single-sheet (+ optional
188
+ Summary) structure is intentionally simple. Compose your own
189
+ `openpyxl` workbook if you need pivot-ready, multi-sheet layouts.
190
+ - **You need `.xls` (legacy binary).** `openpyxl` writes `.xlsx` only;
191
+ convert downstream if you must.
192
+ - **You need encrypted output.** Out of scope; encrypt the produced
193
+ `.xlsx` downstream with a tool like `msoffcrypto-tool`.
194
+ - **You want to _read_ Excel.** This package is a writer.
195
+
196
+ ## Development
197
+
198
+ ```bash
199
+ git clone https://github.com/sebastienrousseau/bankstatementparser-writer-xlsx
200
+ cd bankstatementparser-writer-xlsx
201
+ poetry env use python3.12
202
+ poetry install
203
+ poetry run pytest # 100% line + branch coverage gate
204
+ poetry run ruff check bankstatementparser_writer_xlsx tests
205
+ poetry run mypy bankstatementparser_writer_xlsx
206
+ poetry run interrogate -c pyproject.toml bankstatementparser_writer_xlsx
207
+ ```
208
+
209
+ ## Security
210
+
211
+ `bankstatementparser-writer-xlsx` consumes already-parsed data, not raw
212
+ statement files — the PDF/CSV/XML parsing surface lives upstream in the
213
+ [`bankstatementparser`][core] core. Reporting practice, supported
214
+ versions, and supply-chain posture are documented in
215
+ [`SECURITY.md`](SECURITY.md).
216
+
217
+ ## Documentation
218
+
219
+ - [`README.md`](README.md) — this file
220
+ - [`ARCHITECTURE.md`](ARCHITECTURE.md) — module map and design decisions
221
+ - [`CHANGELOG.md`](CHANGELOG.md) — release notes
222
+ - [`ROADMAP.md`](ROADMAP.md) — what's next
223
+ - [`SECURITY.md`](SECURITY.md) — disclosure + supported versions
224
+ - [`examples/`](examples/) — runnable scripts, exercised in CI
225
+
226
+ ## License
227
+
228
+ Licensed under the [Apache License, Version 2.0][license-url]. Any
229
+ contribution submitted for inclusion shall be licensed as above, without
230
+ additional terms.
231
+
232
+ ## Contributing
233
+
234
+ Contributions are welcome — open an issue or PR on
235
+ [the repository](https://github.com/sebastienrousseau/bankstatementparser-writer-xlsx).
236
+
237
+ ## Acknowledgements
238
+
239
+ Built on the [`bankstatementparser`][core] library and
240
+ [openpyxl](https://openpyxl.readthedocs.io/).
241
+
242
+ [core]: https://github.com/sebastienrousseau/bankstatementparser
243
+ [pypi-url]: https://pypi.org/project/bankstatementparser-writer-xlsx/
244
+ [license-url]: https://opensource.org/license/apache-2-0/
245
+ [ci-url]: https://github.com/sebastienrousseau/bankstatementparser-writer-xlsx/actions/workflows/ci.yml
246
+ [pypi-badge]: https://img.shields.io/pypi/v/bankstatementparser-writer-xlsx.svg?style=for-the-badge
247
+ [python-versions-badge]: https://img.shields.io/pypi/pyversions/bankstatementparser-writer-xlsx.svg?style=for-the-badge
248
+ [license-badge]: https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge
249
+ [coverage-badge]: https://img.shields.io/badge/Coverage-100%25-brightgreen?style=for-the-badge
250
+
@@ -0,0 +1,220 @@
1
+ # bankstatementparser-writer-xlsx: Excel writer for parsed bank statements
2
+
3
+ [![PyPI Version][pypi-badge]][pypi-url]
4
+ [![Python Versions][python-versions-badge]][pypi-url]
5
+ [![License][license-badge]][license-url]
6
+ [![Coverage][coverage-badge]][ci-url]
7
+
8
+ **An Excel `.xlsx` writer for data parsed by
9
+ [`bankstatementparser`][core]** — turn parsed transactions (a pandas
10
+ `DataFrame`, a list of `Transaction` objects, or a list of plain dicts)
11
+ into a polished workbook that accountants, auditors, and reconciliation
12
+ macros can open directly.
13
+
14
+ > **Latest release: v0.0.10** — single `write_xlsx(data, path, ...)`
15
+ > function, 100% line + branch coverage, 100% docstring coverage,
16
+ > `mypy --strict` clean.
17
+
18
+ ## Contents
19
+
20
+ - [Overview](#overview)
21
+ - [Install](#install)
22
+ - [Quick start](#quick-start)
23
+ - [Input shapes](#input-shapes)
24
+ - [Value coercion](#value-coercion)
25
+ - [The summary sheet](#the-summary-sheet)
26
+ - [Examples](#examples)
27
+ - [When not to use this package](#when-not-to-use-this-package)
28
+ - [Development](#development)
29
+ - [Security](#security)
30
+ - [Documentation](#documentation)
31
+ - [License](#license)
32
+ - [Contributing](#contributing)
33
+ - [Acknowledgements](#acknowledgements)
34
+
35
+ ## Overview
36
+
37
+ `bankstatementparser-writer-xlsx` is a small, focused companion to the
38
+ [`bankstatementparser`][core] library. It does one thing well: given
39
+ already-parsed bank-statement records, write a clean Excel workbook with
40
+ a bold header row, one row per transaction, auto-sized columns, and an
41
+ optional second `Summary` sheet.
42
+
43
+ The package consumes _parsed_ data — it does not read PDFs, CSVs, or
44
+ XML itself. Parsing (and the security surface that comes with untrusted
45
+ input) lives upstream in the [`bankstatementparser`][core] core.
46
+
47
+ ## Install
48
+
49
+ `bankstatementparser-writer-xlsx` runs on macOS, Linux, and Windows and
50
+ requires **Python 3.10+**. It pulls in `bankstatementparser`,
51
+ `openpyxl`, and `pandas` automatically.
52
+
53
+ ```bash
54
+ pip install bankstatementparser-writer-xlsx
55
+ ```
56
+
57
+ ## Quick start
58
+
59
+ ```python
60
+ from bankstatementparser import CsvStatementParser
61
+ from bankstatementparser_writer_xlsx import write_xlsx
62
+
63
+ parser = CsvStatementParser("statement.csv")
64
+ df = parser.parse() # a pandas DataFrame
65
+ write_xlsx(df, "statement.xlsx") # one polished workbook
66
+ ```
67
+
68
+ That's an Excel workbook ready for your accountant. Add a summary sheet
69
+ in one extra argument:
70
+
71
+ ```python
72
+ from bankstatementparser import CsvStatementParser
73
+ from bankstatementparser_writer_xlsx import write_xlsx
74
+
75
+ parser = CsvStatementParser("statement.csv")
76
+ df = parser.parse()
77
+ write_xlsx(df, "statement.xlsx", summary=parser.get_summary())
78
+ ```
79
+
80
+ ## Input shapes
81
+
82
+ `write_xlsx(data, path, *, sheet_name="Transactions", summary=None)`
83
+ accepts three input shapes and normalises each to a flat table:
84
+
85
+ | Input | Column order |
86
+ | :--- | :--- |
87
+ | `pandas.DataFrame` (from a parser's `.parse()`) | the DataFrame's own column order |
88
+ | `list[bankstatementparser.Transaction]` | the stable `Transaction` field order |
89
+ | `list[dict]` | the union of keys, in first-seen order |
90
+
91
+ A header row (bold) is written to the `sheet_name` sheet, followed by
92
+ one row per record. Columns are auto-sized to their widest cell (capped
93
+ so wide descriptions don't run off-screen).
94
+
95
+ **Empty input** is accepted: an empty `list` writes an empty sheet (no
96
+ header), while an empty `DataFrame` that still carries column labels
97
+ writes a header-only sheet.
98
+
99
+ ## Value coercion
100
+
101
+ Spreadsheet cells can't hold arbitrary Python objects, so the writer
102
+ coerces the rich types the parser emits:
103
+
104
+ | Python type | Written as |
105
+ | :--- | :--- |
106
+ | `decimal.Decimal` | `float` (Excel has no decimal type; floats aggregate natively) |
107
+ | `datetime.date` / `datetime.datetime` | native Excel date cell (unchanged) |
108
+ | `str`, `int`, `float`, `bool`, `None` | unchanged |
109
+ | anything else | `str(value)` |
110
+
111
+ ## The summary sheet
112
+
113
+ If you pass `summary=` a mapping (for example a parser's
114
+ `get_summary()` result), the writer adds a second sheet titled
115
+ `Summary` with a bold `Key` / `Value` header and one row per item:
116
+
117
+ ```python
118
+ from decimal import Decimal
119
+
120
+ from bankstatementparser_writer_xlsx import write_xlsx
121
+
122
+ transactions = [
123
+ {"date": "2026-06-01", "description": "Salary", "amount": Decimal("3000.00")},
124
+ {"date": "2026-06-03", "description": "Coffee Shop", "amount": Decimal("-4.20")},
125
+ ]
126
+
127
+ write_xlsx(
128
+ transactions,
129
+ "out.xlsx",
130
+ summary={
131
+ "account_id": "DE89370400440532013000",
132
+ "transaction_count": 128,
133
+ "total_amount": Decimal("12045.67"),
134
+ "currency": "EUR",
135
+ },
136
+ )
137
+ ```
138
+
139
+ ## Examples
140
+
141
+ Five runnable examples live in [`examples/`](examples/) and are
142
+ exercised in CI on every commit. Together they cover every supported
143
+ input shape and option of `write_xlsx`:
144
+
145
+ - [`01_write_dataframe.py`](examples/01_write_dataframe.py) — write a
146
+ pandas `DataFrame` to a single sheet.
147
+ - [`02_write_transactions.py`](examples/02_write_transactions.py) —
148
+ write a list of `Transaction` objects in stable field order.
149
+ - [`03_write_dicts.py`](examples/03_write_dicts.py) — write a list of
150
+ plain `dict` records (union of keys).
151
+ - [`04_write_with_summary.py`](examples/04_write_with_summary.py) —
152
+ write a DataFrame plus a second `Summary` sheet via `summary=`.
153
+ - [`05_custom_sheet_name.py`](examples/05_custom_sheet_name.py) — rename
154
+ the transactions sheet with `sheet_name=`.
155
+
156
+ ## When not to use this package
157
+
158
+ - **You need a custom sheet layout.** The single-sheet (+ optional
159
+ Summary) structure is intentionally simple. Compose your own
160
+ `openpyxl` workbook if you need pivot-ready, multi-sheet layouts.
161
+ - **You need `.xls` (legacy binary).** `openpyxl` writes `.xlsx` only;
162
+ convert downstream if you must.
163
+ - **You need encrypted output.** Out of scope; encrypt the produced
164
+ `.xlsx` downstream with a tool like `msoffcrypto-tool`.
165
+ - **You want to _read_ Excel.** This package is a writer.
166
+
167
+ ## Development
168
+
169
+ ```bash
170
+ git clone https://github.com/sebastienrousseau/bankstatementparser-writer-xlsx
171
+ cd bankstatementparser-writer-xlsx
172
+ poetry env use python3.12
173
+ poetry install
174
+ poetry run pytest # 100% line + branch coverage gate
175
+ poetry run ruff check bankstatementparser_writer_xlsx tests
176
+ poetry run mypy bankstatementparser_writer_xlsx
177
+ poetry run interrogate -c pyproject.toml bankstatementparser_writer_xlsx
178
+ ```
179
+
180
+ ## Security
181
+
182
+ `bankstatementparser-writer-xlsx` consumes already-parsed data, not raw
183
+ statement files — the PDF/CSV/XML parsing surface lives upstream in the
184
+ [`bankstatementparser`][core] core. Reporting practice, supported
185
+ versions, and supply-chain posture are documented in
186
+ [`SECURITY.md`](SECURITY.md).
187
+
188
+ ## Documentation
189
+
190
+ - [`README.md`](README.md) — this file
191
+ - [`ARCHITECTURE.md`](ARCHITECTURE.md) — module map and design decisions
192
+ - [`CHANGELOG.md`](CHANGELOG.md) — release notes
193
+ - [`ROADMAP.md`](ROADMAP.md) — what's next
194
+ - [`SECURITY.md`](SECURITY.md) — disclosure + supported versions
195
+ - [`examples/`](examples/) — runnable scripts, exercised in CI
196
+
197
+ ## License
198
+
199
+ Licensed under the [Apache License, Version 2.0][license-url]. Any
200
+ contribution submitted for inclusion shall be licensed as above, without
201
+ additional terms.
202
+
203
+ ## Contributing
204
+
205
+ Contributions are welcome — open an issue or PR on
206
+ [the repository](https://github.com/sebastienrousseau/bankstatementparser-writer-xlsx).
207
+
208
+ ## Acknowledgements
209
+
210
+ Built on the [`bankstatementparser`][core] library and
211
+ [openpyxl](https://openpyxl.readthedocs.io/).
212
+
213
+ [core]: https://github.com/sebastienrousseau/bankstatementparser
214
+ [pypi-url]: https://pypi.org/project/bankstatementparser-writer-xlsx/
215
+ [license-url]: https://opensource.org/license/apache-2-0/
216
+ [ci-url]: https://github.com/sebastienrousseau/bankstatementparser-writer-xlsx/actions/workflows/ci.yml
217
+ [pypi-badge]: https://img.shields.io/pypi/v/bankstatementparser-writer-xlsx.svg?style=for-the-badge
218
+ [python-versions-badge]: https://img.shields.io/pypi/pyversions/bankstatementparser-writer-xlsx.svg?style=for-the-badge
219
+ [license-badge]: https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge
220
+ [coverage-badge]: https://img.shields.io/badge/Coverage-100%25-brightgreen?style=for-the-badge
@@ -0,0 +1,30 @@
1
+ # Copyright (C) 2023-2026 Sebastien Rousseau.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12
+ # implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """Excel (.xlsx) writer for parsed ``bankstatementparser`` data.
17
+
18
+ Exposes :func:`write_xlsx`, which serialises parsed bank-statement
19
+ records (a pandas DataFrame, a list of
20
+ :class:`bankstatementparser.Transaction` objects, or a list of plain
21
+ dicts) to a polished ``.xlsx`` workbook.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from .writer import write_xlsx
27
+
28
+ __all__ = ["write_xlsx", "__version__"]
29
+
30
+ __version__ = "0.0.10"
@@ -0,0 +1,353 @@
1
+ # Copyright (C) 2023-2026 Sebastien Rousseau.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12
+ # implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """Excel ``.xlsx`` writer for parsed bank-statement data.
17
+
18
+ This module turns the output of a
19
+ [`bankstatementparser`](https://github.com/sebastienrousseau/bankstatementparser)
20
+ parser into a polished Excel workbook via
21
+ [openpyxl](https://openpyxl.readthedocs.io/).
22
+
23
+ The single public entry point is :func:`write_xlsx`, which accepts any
24
+ of three input shapes and normalises them to a flat, rectangular table:
25
+
26
+ * a :class:`pandas.DataFrame` (as returned by a parser's ``.parse()``),
27
+ * a list of :class:`bankstatementparser.Transaction` objects, or
28
+ * a list of plain ``dict`` row records.
29
+
30
+ The resulting workbook has a bold header row followed by one row per
31
+ record on the ``Transactions`` sheet, with column widths auto-sized for
32
+ readability. An optional ``summary`` mapping (e.g. the output of a
33
+ parser's ``get_summary()``) is written to a second ``Summary`` sheet as
34
+ key/value rows.
35
+
36
+ Value coercion
37
+ --------------
38
+ Cells must hold spreadsheet-friendly values, so the writer coerces the
39
+ rich Python types the parser emits:
40
+
41
+ * :class:`decimal.Decimal` is written as a ``float`` (Excel has no
42
+ decimal type; floats sort and aggregate natively in spreadsheets).
43
+ * :class:`datetime.date` and :class:`datetime.datetime` are written
44
+ unchanged — openpyxl serialises them as native Excel date cells.
45
+ * Every other value is written as-is when openpyxl accepts it
46
+ (``str``, ``int``, ``float``, ``bool``, ``None``) and otherwise
47
+ stringified via ``str()``.
48
+ """
49
+
50
+ from __future__ import annotations
51
+
52
+ from collections.abc import Mapping, Sequence
53
+ from datetime import date, datetime
54
+ from decimal import Decimal
55
+ from pathlib import Path
56
+ from typing import Any
57
+
58
+ import pandas as pd
59
+ from openpyxl import Workbook
60
+ from openpyxl.styles import Font
61
+ from openpyxl.utils import get_column_letter
62
+
63
+ __all__ = ["write_xlsx"]
64
+
65
+ # Stable column order for ``Transaction`` rows. Mirrors the field order
66
+ # of ``bankstatementparser.Transaction`` so two runs over the same data
67
+ # always produce the same workbook layout.
68
+ _TRANSACTION_COLUMNS: tuple[str, ...] = (
69
+ "account_id",
70
+ "currency",
71
+ "amount",
72
+ "booking_date",
73
+ "value_date",
74
+ "description",
75
+ "normalized_description",
76
+ "reference",
77
+ "transaction_id",
78
+ "counterparty",
79
+ "source",
80
+ "source_index",
81
+ "source_method",
82
+ "confidence",
83
+ "category",
84
+ "transaction_hash",
85
+ )
86
+
87
+ # Hard upper bound on auto-sized column width, in characters. Wide free
88
+ # text (descriptions) would otherwise push columns off-screen.
89
+ _MAX_COLUMN_WIDTH = 60
90
+
91
+
92
+ def write_xlsx(
93
+ data: Any,
94
+ path: str | Path,
95
+ *,
96
+ sheet_name: str = "Transactions",
97
+ summary: Mapping[str, Any] | None = None,
98
+ ) -> Path:
99
+ """Write parsed bank-statement ``data`` to a ``.xlsx`` workbook.
100
+
101
+ Args:
102
+ data: The records to serialise. One of:
103
+
104
+ * a :class:`pandas.DataFrame` (as returned by a
105
+ ``bankstatementparser`` parser's ``.parse()`` method) —
106
+ its columns define the header order;
107
+ * a list of :class:`bankstatementparser.Transaction`
108
+ objects — serialised with a stable column order;
109
+ * a list of plain ``dict`` records — the header is the
110
+ union of keys in first-seen order.
111
+
112
+ An empty list (or empty DataFrame) is accepted and writes a
113
+ header-only sheet (header omitted entirely when no columns
114
+ can be determined).
115
+ path: The output ``.xlsx`` file path. The parent directory must
116
+ already exist; an existing file is overwritten.
117
+ sheet_name: The title of the sheet holding the transaction rows.
118
+ Defaults to ``"Transactions"``.
119
+ summary: Optional mapping (e.g. a parser's ``get_summary()``
120
+ result) written to a second ``"Summary"`` sheet as one
121
+ ``key``/``value`` row per item, under a bold ``Key``/``Value``
122
+ header.
123
+
124
+ Returns:
125
+ The :class:`~pathlib.Path` of the written workbook.
126
+
127
+ Raises:
128
+ TypeError: If ``data`` is neither a DataFrame, nor a list/tuple
129
+ of ``Transaction`` objects, nor a list/tuple of ``dict``
130
+ records (and is not empty).
131
+ ValueError: If ``data`` is a non-empty sequence whose items mix
132
+ ``dict`` and non-``dict`` types, or contain an unsupported
133
+ item type.
134
+ """
135
+ columns, rows = _normalise(data)
136
+
137
+ workbook = Workbook()
138
+ sheet = workbook.active
139
+ sheet.title = sheet_name
140
+ _write_table(sheet, columns, rows)
141
+
142
+ if summary is not None:
143
+ summary_sheet = workbook.create_sheet("Summary")
144
+ _write_summary(summary_sheet, summary)
145
+
146
+ output = Path(path)
147
+ workbook.save(output)
148
+ return output
149
+
150
+
151
+ def _normalise(data: Any) -> tuple[list[str], list[list[Any]]]:
152
+ """Normalise any supported ``data`` shape to columns and rows.
153
+
154
+ Args:
155
+ data: The input passed to :func:`write_xlsx`.
156
+
157
+ Returns:
158
+ A ``(columns, rows)`` pair where ``columns`` is the ordered
159
+ header and ``rows`` is a list of cell-value lists aligned to it.
160
+
161
+ Raises:
162
+ TypeError: If ``data`` is of an unsupported top-level type.
163
+ ValueError: If a sequence mixes ``dict`` and non-``dict`` items
164
+ or contains an unsupported item type.
165
+ """
166
+ if isinstance(data, pd.DataFrame):
167
+ return _normalise_dataframe(data)
168
+ if isinstance(data, list | tuple):
169
+ return _normalise_sequence(data)
170
+ raise TypeError(
171
+ "write_xlsx() expects a pandas DataFrame, a list of "
172
+ "Transaction objects, or a list of dict records; got "
173
+ f"{type(data).__name__}"
174
+ )
175
+
176
+
177
+ def _normalise_dataframe(frame: Any) -> tuple[list[str], list[list[Any]]]:
178
+ """Normalise a :class:`pandas.DataFrame` to columns and rows.
179
+
180
+ Args:
181
+ frame: The DataFrame to convert. Its column order is preserved.
182
+
183
+ Returns:
184
+ A ``(columns, rows)`` pair with one row per DataFrame record.
185
+ """
186
+ columns = [str(column) for column in frame.columns]
187
+ rows = [
188
+ [_coerce(value) for value in record]
189
+ for record in frame.itertuples(index=False, name=None)
190
+ ]
191
+ return columns, rows
192
+
193
+
194
+ def _normalise_sequence(
195
+ records: Sequence[Any],
196
+ ) -> tuple[list[str], list[list[Any]]]:
197
+ """Normalise a list/tuple of Transactions or dicts to a table.
198
+
199
+ Args:
200
+ records: A sequence of ``Transaction`` objects or ``dict``
201
+ records. May be empty.
202
+
203
+ Returns:
204
+ A ``(columns, rows)`` pair. An empty sequence yields empty
205
+ columns and rows.
206
+
207
+ Raises:
208
+ ValueError: If the items mix ``dict`` and non-``dict`` types or
209
+ an item is neither a ``dict`` nor a ``model_dump``-capable
210
+ object.
211
+ """
212
+ if not records:
213
+ return [], []
214
+
215
+ dict_records = [_to_record(record) for record in records]
216
+ columns: list[str] = []
217
+ seen: set[str] = set()
218
+ for record in dict_records:
219
+ for key in record:
220
+ if key not in seen:
221
+ seen.add(key)
222
+ columns.append(key)
223
+
224
+ rows = [
225
+ [_coerce(record.get(column)) for column in columns]
226
+ for record in dict_records
227
+ ]
228
+ return columns, rows
229
+
230
+
231
+ def _to_record(record: Any) -> dict[str, Any]:
232
+ """Convert a single sequence item to an ordered ``dict`` record.
233
+
234
+ Args:
235
+ record: A ``dict`` or a ``Transaction``-like object exposing a
236
+ ``model_dump()`` method.
237
+
238
+ Returns:
239
+ An ordered ``dict`` of column name to value. ``Transaction``
240
+ objects use the stable column order in
241
+ :data:`_TRANSACTION_COLUMNS`; dicts preserve their own order.
242
+
243
+ Raises:
244
+ ValueError: If ``record`` is neither a ``dict`` nor exposes a
245
+ callable ``model_dump`` attribute.
246
+ """
247
+ if isinstance(record, Mapping):
248
+ return dict(record)
249
+ model_dump = getattr(record, "model_dump", None)
250
+ if callable(model_dump):
251
+ dumped = model_dump()
252
+ ordered = {
253
+ key: dumped[key] for key in _TRANSACTION_COLUMNS if key in dumped
254
+ }
255
+ for key, value in dumped.items():
256
+ if key not in ordered:
257
+ ordered[key] = value
258
+ return ordered
259
+ raise ValueError(
260
+ "Sequence items must be dict records or Transaction objects "
261
+ f"(with a model_dump() method); got {type(record).__name__}"
262
+ )
263
+
264
+
265
+ def _coerce(value: Any) -> Any:
266
+ """Coerce a Python value to a spreadsheet-friendly cell value.
267
+
268
+ Args:
269
+ value: A value drawn from a record.
270
+
271
+ Returns:
272
+ ``float`` for :class:`decimal.Decimal`; the value unchanged for
273
+ ``date``/``datetime`` and for the scalar types openpyxl accepts
274
+ natively (``str``, ``int``, ``float``, ``bool``, ``None``);
275
+ ``str(value)`` for anything else.
276
+ """
277
+ if isinstance(value, bool):
278
+ return value
279
+ if isinstance(value, Decimal):
280
+ return float(value)
281
+ if isinstance(value, datetime | date):
282
+ return value
283
+ if value is None or isinstance(value, str | int | float):
284
+ return value
285
+ return str(value)
286
+
287
+
288
+ def _write_table(
289
+ sheet: Any,
290
+ columns: list[str],
291
+ rows: list[list[Any]],
292
+ ) -> None:
293
+ """Write the header and data rows, then auto-size the columns.
294
+
295
+ Args:
296
+ sheet: The target openpyxl worksheet.
297
+ columns: The ordered header labels. May be empty, in which case
298
+ nothing is written.
299
+ rows: The cell-value rows aligned to ``columns``.
300
+ """
301
+ if not columns:
302
+ return
303
+ sheet.append(columns)
304
+ for cell in sheet[1]:
305
+ cell.font = Font(bold=True)
306
+ for row in rows:
307
+ sheet.append(row)
308
+ _autosize(sheet, columns, rows)
309
+
310
+
311
+ def _write_summary(sheet: Any, summary: Mapping[str, Any]) -> None:
312
+ """Write a ``summary`` mapping as key/value rows on ``sheet``.
313
+
314
+ Args:
315
+ sheet: The target openpyxl worksheet (the ``Summary`` sheet).
316
+ summary: The mapping to serialise, one row per item under a
317
+ bold ``Key``/``Value`` header.
318
+ """
319
+ sheet.append(["Key", "Value"])
320
+ for cell in sheet[1]:
321
+ cell.font = Font(bold=True)
322
+ keys = list(summary)
323
+ for key in keys:
324
+ sheet.append([str(key), _coerce(summary[key])])
325
+ _autosize(
326
+ sheet,
327
+ ["Key", "Value"],
328
+ [[str(key), _coerce(summary[key])] for key in keys],
329
+ )
330
+
331
+
332
+ def _autosize(
333
+ sheet: Any,
334
+ columns: list[str],
335
+ rows: list[list[Any]],
336
+ ) -> None:
337
+ """Set each column width to fit its widest cell, within a cap.
338
+
339
+ Args:
340
+ sheet: The worksheet whose column dimensions are adjusted.
341
+ columns: The header labels, used as the initial width seed.
342
+ rows: The data rows scanned for their widest cell per column.
343
+ """
344
+ for index, header in enumerate(columns):
345
+ width = len(str(header))
346
+ for row in rows:
347
+ cell = row[index]
348
+ if cell is not None:
349
+ width = max(width, len(str(cell)))
350
+ letter = get_column_letter(index + 1)
351
+ sheet.column_dimensions[letter].width = min(
352
+ width + 2, _MAX_COLUMN_WIDTH
353
+ )
@@ -0,0 +1,153 @@
1
+ [tool.poetry]
2
+ name = "bankstatementparser-writer-xlsx"
3
+ version = "0.0.10"
4
+ description = "Excel (.xlsx) writer for bankstatementparser-parsed bank statements."
5
+ authors = ["Sebastien Rousseau <sebastian.rousseau@gmail.com>"]
6
+ license = "Apache-2.0"
7
+ readme = "README.md"
8
+ repository = "https://github.com/sebastienrousseau/bankstatementparser-writer-xlsx"
9
+ homepage = "https://bankstatementparser.com"
10
+ keywords = [
11
+ "bank-statements",
12
+ "bankstatementparser",
13
+ "xlsx",
14
+ "excel",
15
+ "openpyxl",
16
+ "accounting",
17
+ "reconciliation",
18
+ ]
19
+ classifiers = [
20
+ "Development Status :: 3 - Alpha",
21
+ "Intended Audience :: Financial and Insurance Industry",
22
+ "License :: OSI Approved :: Apache Software License",
23
+ "Operating System :: OS Independent",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Topic :: Office/Business :: Financial",
29
+ "Topic :: Office/Business :: Financial :: Accounting",
30
+ ]
31
+ packages = [
32
+ { include = "bankstatementparser_writer_xlsx" },
33
+ ]
34
+
35
+ [tool.poetry.dependencies]
36
+ python = ">=3.10,<4.0"
37
+ bankstatementparser = ">=0.0.9"
38
+ openpyxl = ">=3.1,<4"
39
+ pandas = ">=2.0"
40
+
41
+ [tool.poetry.group.dev.dependencies]
42
+ pytest = ">=9.0.3,<10.0.0"
43
+ pytest-cov = ">=6.0.0,<8.0.0"
44
+ black = "^26.3.1"
45
+ ruff = "^0.1.0"
46
+ mypy = "^1.11.0"
47
+ bandit = "^1.7.0"
48
+ interrogate = "^1.7.0"
49
+
50
+ [build-system]
51
+ requires = ["poetry-core"]
52
+ build-backend = "poetry.core.masonry.api"
53
+
54
+ [tool.black]
55
+ line-length = 79
56
+ target-version = ['py310']
57
+
58
+ [tool.pytest.ini_options]
59
+ testpaths = ["tests"]
60
+ python_files = "test_*.py"
61
+ python_classes = "Test*"
62
+ python_functions = "test_*"
63
+ addopts = ["-ra", "--strict-markers", "--tb=short"]
64
+
65
+ [tool.coverage.run]
66
+ branch = true
67
+ source = ["bankstatementparser_writer_xlsx"]
68
+
69
+ [tool.coverage.report]
70
+ exclude_also = [
71
+ "if __name__ == .__main__.:",
72
+ "pragma: no cover",
73
+ "@overload",
74
+ ]
75
+ fail_under = 100
76
+ show_missing = true
77
+ skip_covered = false
78
+
79
+ [tool.interrogate]
80
+ fail-under = 100
81
+ ignore-init-method = true
82
+ ignore-semiprivate = false
83
+ ignore-private = false
84
+ ignore-magic = true
85
+ exclude = ["tests", "examples"]
86
+
87
+ [tool.ruff]
88
+ line-length = 79
89
+ target-version = "py310"
90
+ extend-exclude = ["*.md", "tmp_env", ".venv", ".eggs"]
91
+
92
+ [tool.ruff.lint]
93
+ select = ["E", "W", "F", "I", "B", "C4", "UP"]
94
+ ignore = ["E501"]
95
+
96
+ [tool.ruff.lint.per-file-ignores]
97
+ "__init__.py" = ["F401"]
98
+
99
+ [tool.ruff.format]
100
+ quote-style = "double"
101
+ indent-style = "space"
102
+ skip-magic-trailing-comma = false
103
+
104
+ [tool.mypy]
105
+ python_version = "3.10"
106
+ strict = true
107
+ warn_return_any = true
108
+ warn_unused_configs = true
109
+ disallow_untyped_defs = true
110
+ disallow_any_unimported = false
111
+ no_implicit_optional = true
112
+ warn_redundant_casts = true
113
+ warn_unused_ignores = true
114
+ warn_no_return = true
115
+ check_untyped_defs = true
116
+ strict_equality = true
117
+ exclude = [
118
+ "tmp_env/",
119
+ ".venv/",
120
+ ".eggs/",
121
+ ]
122
+
123
+ [[tool.mypy.overrides]]
124
+ module = "bankstatementparser.*"
125
+ ignore_missing_imports = true
126
+
127
+ [[tool.mypy.overrides]]
128
+ module = "openpyxl"
129
+ ignore_missing_imports = true
130
+
131
+ [[tool.mypy.overrides]]
132
+ module = "openpyxl.*"
133
+ ignore_missing_imports = true
134
+
135
+ [[tool.mypy.overrides]]
136
+ module = "pandas.*"
137
+ ignore_missing_imports = true
138
+
139
+ [[tool.mypy.overrides]]
140
+ # The writer accepts heterogeneous record shapes (DataFrame, Transaction,
141
+ # dict) normalised through ``Any``-typed cells, so relax the two strict
142
+ # checks that signature would trip while keeping the rest of strict mode.
143
+ module = "bankstatementparser_writer_xlsx.*"
144
+ disallow_any_generics = false
145
+ warn_return_any = false
146
+
147
+ [[tool.mypy.overrides]]
148
+ module = "tests.*"
149
+ disallow_untyped_defs = false
150
+ disallow_incomplete_defs = false
151
+ disallow_untyped_calls = false
152
+ warn_unused_ignores = false
153
+ disable_error_code = ["arg-type", "return-value", "attr-defined", "assignment"]