diff --git a/README.md b/README.md index 7806043..606cce5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,38 @@ -# BEPP +
+

🏦 Bepp 🧮

+

A tiny tool to manage, clean, and merge transaction exports from Banca Etica and PayPal.

+ PyPI, Status – Shields.io badge + PyPI, Version – Shields.io badge + PyPI, Python Version – Shields.io badge + PyPI, License – Shields.io badge +
-A specific and opinionated helper to merge and clean transactions summaries from [Banca Etica](https://bancaetica.it) and [PayPal](https://paypal.com). +## ℹ️ About -## Notes +I wrote this script to learn and practice with Python and [pandas](https://pandas.pydata.org/), while also getting something I really needed out of it. -- bepp assumes that inside the specified directory all Excel files are Banca Etica’s “estratto conto” files, and all CSV files are PayPal’s transaction summaries. +## ⏬ Install + +1. [Install uv](https://docs.astral.sh/uv/getting-started/installation/ 'Installing uv') +2. [Start a virtual environment](https://docs.astral.sh/uv/pip/environments/ 'Python environments – uv documentation'): `uv venv` +2. Install the package: `uv pip install bepp` + - append `-U` to install at the user level + - append `--system` to install system-wide + +## 📊 Usage + +| argument | type | default | description | +|---|---|---|---| +| **`input/path/`** | string | **Required** | Input directory, containing the source CSV and Excel files. | +| `-d`, `--dry_run` | boolean | False | Run the script without changing or printing anything. | +| `-b`, `--backup` | boolean | False | Save one CSV backup per kind containing all the original transactions, with no modification. | +| `-c`, `--convert_to_eur` | boolean | False | Convert transactions in other currencies to €. **Note**: This *heavily* slows down the process! | +| `-m`, `--merge` | boolean | False | Merge the PayPal’s and Banca Etica’s transaction summaries in one unique CSV. | +| `-o`, `--output_dir` | string | `input/path/`
+
`bepp_export/` | Specify an output directory. | +| `-p`, `--keep_pp_dupes` | boolean | False | Prevent from removing PayPal transactions in Banca Etica’s logs. | + +**Note**: Bepp assumes that inside the specified directory all Excel files are Banca Etica’s “estratto conto” files, and all CSV files are PayPal transaction summaries. + +## ♻️ License + +Everything inside this repository is licensed under the [GNU Affero General Public License, version 3](https://www.gnu.org/licenses/agpl-3.0.txt). diff --git a/pyproject.toml b/pyproject.toml index d996b33..83fd9cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,50 @@ [project] name = 'bepp' -version = '0.0.1' +version = '0.0.2' description = 'An opinionated script to gather, clean up and merge transactions from Banca Etica and PayPal.' +authors = [{ name = 'Tommi', email = 'surfing@tommi.space' }] readme = 'README.md' +license = { file = 'LICENSE' } requires-python = '>=3.13' dependencies = [ 'currencyconverter>=0.17.34', 'pandas>=2.2.3', 'rich-argparse>=1.6.0', 'rich>=13.9.4', - 'xlrd>=2.0.1' + 'xlrd>=2.0.1', ] +keywords = [ + 'Banca Etica', + 'PayPal', + 'Accounting', + 'Personal Finance Management', + 'Bank transfers', + 'Bank', +] +classifiers = [ + 'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)', + 'Development Status :: 4 - Beta', + 'Environment :: Console', + 'Natural Language :: English', + 'Natural Language :: Italian', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.13', + 'Topic :: Database', + 'Topic :: Office/Business :: Financial :: Accounting', + 'Topic :: Office/Business :: Financial :: Spreadsheet', + 'Topic :: Scientific/Engineering :: Information Analysis', + 'Topic :: Utilities', +] + +[project.urls] +Repository = 'https://codeberg.org/tommi/bepp' +Issues = 'https://codeberg.org/tommi/bepp/issues' +Changelog = 'https://codeberg.org/tommi/bepp/commits/branch/main' + +[project.scripts] +bepp = 'bepp:main' + +[build-system] +requires = ['hatchling'] +build-backend = 'hatchling.build' diff --git a/src/bepp/__init__.py b/src/bepp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bepp.py b/src/bepp/bepp.py similarity index 61% rename from bepp.py rename to src/bepp/bepp.py index c2dc215..2766619 100644 --- a/bepp.py +++ b/src/bepp/bepp.py @@ -3,14 +3,17 @@ import glob import argparse import pandas as pd -import xlrd -from rich.console import Console +from rich import print from rich_argparse import RichHelpFormatter from currency_converter import CurrencyConverter -description = """ -[bold magenta]Bepp[/bold magenta] helps you deal with CSV and Excel files exported from PayPal and Banca Etica respectively. -""" +description = ''' +[url=https://codeberg.org/tommi/bepp][bold]Bepp[/bold][/url] helps you deal with transaction summary files exported from PayPal and Banca Etica. +''' + +epilog = ''' +[bold]Note[/bold]: bepp assumes that inside the specified directory all Excel files are Banca Etica’s [italic]estratto conto[/italic] files, and all CSV files are PayPal transaction summaries. +''' def convert_to_eur(date ,amount, currency): c = CurrencyConverter(fallback_on_missing_rate=True) @@ -22,40 +25,44 @@ def main(): parser = argparse.ArgumentParser( prog='Bepp', description=description, + epilog=epilog, formatter_class=RichHelpFormatter ) - parser.add_argument('directory', help='Directory containing the source files.') - parser.add_argument('-m', '--merge', action='store_true', help='Merge the PayPal’s and Banca Etica’s transaction summaries in one unique CSV') - parser.add_argument('-b', '--backup', action='store_true', help='Save two separate backups containing all the merged original transactions') - parser.add_argument('-p', '--pp_duplicates', action='store_false', help='Pass this flag to avoid removing PayPal transactions from Banca Etica') + parser.add_argument('directory', metavar='INPUT_DIR', help='Directory containing the source files.') + parser.add_argument('-b', '--backup', action='store_true', help='Save two separate backups containing all the merged original transactions.') parser.add_argument('-c', '--convert_to_eur', action='store_true', help='Convert transactions in other currencies to €.\n[bold]NOTE[/bold]: This slows things down very heavily!') - parser.add_argument('-o', '--output_dir', metavar='OUTPUT', type=str, help='Specify an output directory (the default is the input directory)') + parser.add_argument('-d', '--dry_run', action='store_false', help='Run the script without changing or printing anything.') + parser.add_argument('-m', '--merge', action='store_true', help='Merge the PayPal’s and Banca Etica’s transaction summaries in one unique CSV.') + parser.add_argument('-o', '--output_dir', metavar='OUTPUT_DIR', type=str, help='Specify an output directory (default: “bepp_export” subdirectory in the input dir).') + parser.add_argument('-p', '--keep_pp_dupes', action='store_true', help='Prevent from removing PayPal transactions from Banca Etica.') args = parser.parse_args() dir = args.directory - if not dir.endswith('/'): - dir = dir + '/' - output_dir = args.output_dir or dir + 'bepp_export/' - if not output_dir.endswith('/'): - output_dir = output_dir + '/' - os.makedirs(output_dir, exist_ok=True) - if not os.path.isdir(dir): - print(f'Error: “{dir}” is not a valid directory.') + print(f'[bold red]Error[/bold red]: “{dir}” is not a valid directory.') sys.exit(1) - be_pattern = os.path.join(dir, '*.xls') - pp_pattern = os.path.join(dir, '*.csv') + output_dir = args.output_dir or os.path.join(dir, 'bepp_export') + if not os.path.isdir(output_dir): + print(f'[bold red]Error[/bold red]: “{output_dir}” is not a valid directory.') + sys.exit(1) + + if not args.dry_run: + os.makedirs(output_dir, exist_ok=True) + + print(f'Reading from {os.path.abspath(dir)}…') - be_files = glob.glob(be_pattern) - pp_files = glob.glob(pp_pattern) + be_files = glob.glob(os.path.join(dir, '*.xls')) + pp_files = glob.glob(os.path.join(dir, '*.CSV')) + glob.glob(os.path.join(dir, '*.csv')) if not be_files or not pp_files: print('No Excel files (Banca Etica’s export format) or CSV files (PayPal’s export format) found in the specified directory.') sys.exit(1) + print('Banca Etica logs found:') be_list = [] for f in be_files: + print(f'\t- \'{os.path.basename(f)}\'') be_file = pd.DataFrame(pd.read_excel( f, parse_dates=[1, 2], @@ -69,7 +76,7 @@ def main(): be = be[['Valuta', 'amount', 'Divisa', 'Descrizione']] be = be.sort_values(by='Valuta', ascending=False) - if args.pp_duplicates: + if not args.keep_pp_dupes: be = be[~be['Descrizione'].str.contains(r'(?i)PayPal', regex=True)] be['Descrizione'] = be['Descrizione'].str.replace(r'(?i)(?:.* FAVORE |(Pagamenti|accredito).* A )(?P.*)(?: IND\.ORD\..* Note: | Valuta.*C\/O )(?P.*)(:? ID\..*| CARTA.*)', r'\g, \g', regex=True) @@ -85,8 +92,10 @@ def main(): be['amount'] = be.apply(lambda row: convert_to_eur(row['date'], row['amount'], row['currency']), axis=1) be.drop('currency', axis='columns') + print('PayPal logs found:') pp_list = [] for f in pp_files: + print(f'\t- \'{os.path.basename(f)}\'') pp_file = pd.DataFrame(pd.read_csv(f)) pp_file = pp_file.dropna(axis='columns', how='all') pp_list.append(pp_file) @@ -114,19 +123,19 @@ def main(): pp['amount'] = pp.apply(lambda row: convert_to_eur(row['date'], row['amount'], row['currency']), axis=1) pp.drop('currency', axis='columns') - if not args.merge: - be.to_csv(output_dir + 'Banca Etica.csv', index=False, date_format='%Y-%m-%d') - pp.to_csv(output_dir + 'PayPal.csv', index=False, date_format='%Y-%m-%d') + if not args.merge and not args.dry_run: + be.to_csv(os.path.join(output_dir, 'Banca Etica.csv'), index=False, date_format='%Y-%m-%d') + pp.to_csv(os.path.join(output_dir, 'PayPal.csv'), index=False, date_format='%Y-%m-%d') all = pd.concat([be, pp], axis=0, ignore_index=True) all = all.sort_values(by='date', ascending=False) - if args.merge: - all.to_csv(output_dir + 'BEPP.csv', index=False, date_format='%Y-%m-%d') + if args.merge and not args.dry_run: + all.to_csv(os.path.join(output_dir, 'BEPP.csv'), index=False, date_format='%Y-%m-%d') - if args.backup: - be_merged.to_csv(output_dir + 'Banca Etica - original.csv', index=False) - pp_merged.to_csv(output_dir + 'Pay Pal - original.csv', index=False) + if args.backup and not args.dry_run: + be_merged.to_csv(os.path.join(output_dir, 'Banca Etica - original.csv'), index=False) + pp_merged.to_csv(os.path.join(output_dir, 'Pay Pal - original.csv'), index=False) if __name__ == '__main__': main()