Skip to content

Commit 59a9e77

Browse files
committed
Update testing GH Action. Add Ruff. Add Python 3.11 and 3.12.
1 parent f96a011 commit 59a9e77

File tree

4 files changed

+85
-72
lines changed

4 files changed

+85
-72
lines changed

.github/workflows/python-app.yml

+6-9
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,22 @@ jobs:
2121

2222
strategy:
2323
matrix:
24-
python-version: ["3.7", "3.8", "3.9", "3.10"]
24+
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
2525

2626
steps:
27-
- uses: actions/checkout@v3
27+
- uses: actions/checkout@v4
2828
- name: Set up Python ${{ matrix.python-version }}
29-
uses: actions/setup-python@v4
29+
uses: actions/setup-python@v5
3030
with:
3131
python-version: ${{ matrix.python-version }}
3232
- name: Install dependencies
3333
run: |
3434
python -m pip install --upgrade pip
35-
pip install flake8
3635
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
37-
- name: Lint with flake8
36+
- name: Lint with Ruff
3837
run: |
39-
# stop the build if there are Python syntax errors or undefined names
40-
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
41-
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
42-
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
38+
pip install ruff
39+
ruff --output-format=github .
4340
- name: Test with example.csv
4441
run: |
4542
python convert.py example.csv -p test.csv

README.md

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# A Charles Schwab CSV Converter for Portfolio Performance
22

3-
[![Python CI](https://github.com/rlan/convert-csv-schwab2pp/actions/workflows/python-app.yml/badge.svg)](https://github.com/rlan/convert-csv-schwab2pp/actions)
4-
![Last commit date](https://img.shields.io/github/last-commit/rlan/convert-csv-schwab2pp)
5-
![Eclipse Public License - v 2.0](https://img.shields.io/github/license/rlan/convert-csv-schwab2pp)
3+
[![Testing badge](https://github.com/rlan/convert-csv-schwab2pp/actions/workflows/python-app.yml/badge.svg)](https://github.com/rlan/convert-csv-schwab2pp/actions)
4+
![MIT license](https://img.shields.io/github/license/rlan/convert-csv-schwab2pp)
65

76
Converts a [Charles Schwab](https://www.schwab.com/) transaction CSV file to a ready-to-import CSV file for [Portfolio Performance](https://www.portfolio-performance.info/en/) (PP).
87

@@ -22,7 +21,7 @@ Although Google Colab is free, one will need a [Google account](https://www.goog
2221

2322
Runtime Requirements:
2423

25-
* Python 3.7, 3.8, 3.9 or 3.10[^1]
24+
* Python 3.7, 3.8, 3.9, 3.10, 3.11, 3.12.
2625
* Pandas
2726

2827
Install this tool in a Python virtual environment:
@@ -42,7 +41,7 @@ Command-line options:
4241
python convert.py --help
4342
```
4443

45-
```
44+
```txt
4645
usage: convert.py [-h] [-p PP_CSV] schwab_csv
4746
4847
Converts a Charles Schwab transaction CSV file to a ready-to-import CSV file for Portfolio
@@ -96,5 +95,3 @@ As far as I can test, PP will detect and skip duplicate transactions. So it is s
9695
## License
9796

9897
MIT
99-
100-
[^1]: Python 3.11 not yet officially supported by Pandas, as of 2022-11-15.

convert.py

+58-56
Original file line numberDiff line numberDiff line change
@@ -6,47 +6,48 @@
66

77
try:
88
import pandas as pd
9-
except ImportError:
10-
raise ImportError('Is Pandas installed?')
9+
except ImportError as exc:
10+
raise ImportError("Is Pandas installed?") from exc
1111

1212

1313
parser = argparse.ArgumentParser(
14-
description=('Converts a Charles Schwab transaction CSV file to a'
15-
' ready-to-import CSV file for Portfolio Performance.'))
14+
description=(
15+
"Converts a Charles Schwab transaction CSV file to a"
16+
" ready-to-import CSV file for Portfolio Performance."
17+
)
18+
)
19+
parser.add_argument("schwab_csv", type=str, help="Input Charles Schwab CSV file")
1620
parser.add_argument(
17-
'schwab_csv', type=str,
18-
help='Input Charles Schwab CSV file')
19-
parser.add_argument(
20-
'-p', '--pp_csv', type=str,
21-
default='pp.csv',
22-
help='Resulting CSV file for Portfolio Performance (default: pp.csv)')
21+
"-p",
22+
"--pp_csv",
23+
type=str,
24+
default="pp.csv",
25+
help="Resulting CSV file for Portfolio Performance (default: pp.csv)",
26+
)
2327
args = parser.parse_args()
2428

2529

2630
if not os.path.isfile(args.schwab_csv):
27-
print(f'{args.schwab_csv} not found')
31+
print(f"{args.schwab_csv} not found")
2832
sys.exit(1)
2933

3034

3135
# A Charles Scwab CSV starts with a prefix and a suffix row
3236
# Prefix: "Transactions for account...
3337
# Suffix: "Transactions Total"
34-
df = pd.read_csv(args.schwab_csv,
35-
skiprows=1,
36-
skipfooter=1,
37-
engine='python')
38+
df = pd.read_csv(args.schwab_csv, skiprows=1, skipfooter=1, engine="python")
3839

3940
# Convert dates to datetime objects
40-
df['Date'] = pd.to_datetime(df['Date'], format='%m/%d/%Y')
41+
df["Date"] = pd.to_datetime(df["Date"], format="%m/%d/%Y")
4142

4243
# Rename column names
4344
column_new_names = {
44-
'Action': 'Note',
45-
'Symbol': 'Ticker Symbol',
46-
'Description': 'Security Name',
47-
'Quantity': 'Shares',
48-
'Fees & Comm': 'Fees',
49-
'Amount': 'Value',
45+
"Action": "Note",
46+
"Symbol": "Ticker Symbol",
47+
"Description": "Security Name",
48+
"Quantity": "Shares",
49+
"Fees & Comm": "Fees",
50+
"Amount": "Value",
5051
}
5152
df.rename(columns=column_new_names, inplace=True)
5253

@@ -55,61 +56,62 @@
5556
def remove_currency(text: str):
5657
import re
5758
import locale
58-
decimal_point_char = locale.localeconv()['decimal_point']
59-
clean = re.sub(r'[^0-9'+decimal_point_char+'-'+r']+', '', text)
59+
60+
decimal_point_char = locale.localeconv()["decimal_point"]
61+
clean = re.sub(r"[^0-9" + decimal_point_char + "-" + r"]+", "", text)
6062
return clean
6163

6264

63-
new_value = df['Value'].apply(remove_currency)
64-
df['Value'] = new_value
65+
new_value = df["Value"].apply(remove_currency)
66+
df["Value"] = new_value
6567

6668
# Add a new column with all USD: Transaction Currency
67-
transaction_currency = ["USD" for x in df['Value']]
68-
df['Transaction Currency'] = transaction_currency
69+
transaction_currency = ["USD" for x in df["Value"]]
70+
df["Transaction Currency"] = transaction_currency
6971

7072
# Convert Action to Type
7173
action_to_type = {
72-
'NRA Tax Adj': 'Taxes',
73-
'Credit Interest': 'Interest',
74-
'NRA Withholding': 'Taxes',
75-
'Short Term Cap Gain': 'Dividend',
76-
'Long Term Cap Gain': 'Dividend',
77-
'Cash Dividend': 'Dividend',
78-
'Buy': 'Buy',
79-
'Sell': 'Sell',
80-
'Wire Received': 'Deposit',
81-
'Advisor Fee': 'Fees',
82-
'Reinvest Dividend': 'Dividend',
83-
'Reinvest Shares': 'Buy',
84-
'Bank Interest': 'Dividend',
85-
'Funds Received': 'Deposit',
86-
'MoneyLink Transfer': 'Deposit',
74+
"NRA Tax Adj": "Taxes",
75+
"Credit Interest": "Interest",
76+
"NRA Withholding": "Taxes",
77+
"Short Term Cap Gain": "Dividend",
78+
"Long Term Cap Gain": "Dividend",
79+
"Cash Dividend": "Dividend",
80+
"Buy": "Buy",
81+
"Sell": "Sell",
82+
"Wire Received": "Deposit",
83+
"Advisor Fee": "Fees",
84+
"Reinvest Dividend": "Dividend",
85+
"Reinvest Shares": "Buy",
86+
"Bank Interest": "Dividend",
87+
"Funds Received": "Deposit",
88+
"MoneyLink Transfer": "Deposit",
8789
}
88-
new_type = [action_to_type[x] for x in df['Note']]
89-
df['Type'] = new_type
90+
new_type = [action_to_type[x] for x in df["Note"]]
91+
df["Type"] = new_type
9092

9193
# Delete Price column
92-
df.drop(columns=['Price'], inplace=True)
94+
df.drop(columns=["Price"], inplace=True)
9395

9496
# Add SCHWAB1 INT to Notes
95-
for k, v in df['Security Name'].items():
96-
if v.startswith('SCHWAB1 INT'):
97-
df.at[k, 'Note'] = df.at[k, 'Note'] + ' ' + v
97+
for k, v in df["Security Name"].items():
98+
if v.startswith("SCHWAB1 INT"):
99+
df.at[k, "Note"] = df.at[k, "Note"] + " " + v
98100

99101

100102
# Remove non-security names
101103
def convert_security_name(data: str):
102-
if data.startswith('SCHWAB1 INT'):
103-
return ''
104-
elif data.startswith('WIRED FUNDS RECEIVED'):
105-
return ''
104+
if data.startswith("SCHWAB1 INT"):
105+
return ""
106+
elif data.startswith("WIRED FUNDS RECEIVED"):
107+
return ""
106108
else:
107109
return data
108110

109111

110-
new_security_name = [convert_security_name(v) for v in df['Security Name']]
111-
df['Security Name'] = new_security_name
112+
new_security_name = [convert_security_name(v) for v in df["Security Name"]]
113+
df["Security Name"] = new_security_name
112114

113115
# Write to CSV file
114-
df.to_csv(args.pp_csv, index=False, date_format='%Y-%m-%d')
116+
df.to_csv(args.pp_csv, index=False, date_format="%Y-%m-%d")
115117
print(args.pp_csv)

ruff.toml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[lint]
2+
# 1. Enable flake8-bugbear (`B`) rules, in addition to the defaults.
3+
select = ["E4", "E7", "E9", "F", "B"]
4+
5+
# 2. Avoid enforcing line-length violations (`E501`)
6+
ignore = ["E501"]
7+
8+
# 3. Avoid trying to fix flake8-bugbear (`B`) violations.
9+
unfixable = ["B"]
10+
11+
# 4. Ignore `E402` (import violations) in all `__init__.py` files, and in select subdirectories.
12+
[lint.per-file-ignores]
13+
"__init__.py" = ["E402"]
14+
"**/{tests,docs,tools}/*" = ["E402"]
15+
16+
[format]
17+
docstring-code-format = true

0 commit comments

Comments
 (0)