Skip to content

Commit

Permalink
✨ Build package, publish to PyPI
Browse files Browse the repository at this point in the history
  • Loading branch information
xplosionmind committed Nov 17, 2024
1 parent 1febdcc commit 495e677
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 37 deletions.
39 changes: 35 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,38 @@
# BEPP
<div align='center'>
<h1>🏦 Bepp 🧮</h1>
<p>A tiny tool to manage, clean, and merge transaction exports from <a href='https://bancaetica.it'>Banca Etica</a> and <a href='https://paypal.com'>PayPal</a>.</p>
<img alt='PyPI, Status – Shields.io badge' src='https://img.shields.io/pypi/status/bepp?style=flat'>
<img alt='PyPI, Version – Shields.io badge' src='https://img.shields.io/pypi/v/bepp?style=flat&logo=pypi'>
<img alt='PyPI, Python Version – Shields.io badge' src='https://img.shields.io/pypi/pyversions/bepp?style=flat&logo=python'>
<img alt='PyPI, License – Shields.io badge' src='https://img.shields.io/pypi/l/bepp?style=flat'>
</div>

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/`<br>+<br>`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).
41 changes: 39 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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'
Empty file added src/bepp/__init__.py
Empty file.
71 changes: 40 additions & 31 deletions bepp.py → src/bepp/bepp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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],
Expand All @@ -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<who>.*)(?: IND\.ORD\..* Note: | Valuta.*C\/O )(?P<what>.*)(:? ID\..*| CARTA.*)', r'\g<who>, \g<what>', regex=True)
Expand All @@ -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)
Expand Down Expand Up @@ -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()

0 comments on commit 495e677

Please sign in to comment.