tariochbctools 0.38__py2.py3-none-any.whl → 1.0.0__py2.py3-none-any.whl

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.
Files changed (31) hide show
  1. tariochbctools/importers/bcge/importer.py +4 -3
  2. tariochbctools/importers/bitst/importer.py +23 -19
  3. tariochbctools/importers/blockchain/importer.py +9 -8
  4. tariochbctools/importers/cembrastatement/importer.py +44 -28
  5. tariochbctools/importers/general/mailAdapterImporter.py +12 -13
  6. tariochbctools/importers/general/mt940importer.py +18 -19
  7. tariochbctools/importers/general/priceLookup.py +6 -6
  8. tariochbctools/importers/ibkr/importer.py +21 -18
  9. tariochbctools/importers/neon/importer.py +17 -15
  10. tariochbctools/importers/netbenefits/importer.py +24 -16
  11. tariochbctools/importers/nordigen/importer.py +14 -10
  12. tariochbctools/importers/nordigen/nordigen_config.py +12 -11
  13. tariochbctools/importers/postfinance/importer.py +20 -17
  14. tariochbctools/importers/quickfile/importer.py +11 -11
  15. tariochbctools/importers/raiffeisench/importer.py +3 -7
  16. tariochbctools/importers/revolut/importer.py +23 -18
  17. tariochbctools/importers/schedule/importer.py +11 -8
  18. tariochbctools/importers/swisscard/importer.py +17 -15
  19. tariochbctools/importers/transferwise/importer.py +15 -14
  20. tariochbctools/importers/truelayer/importer.py +25 -16
  21. tariochbctools/importers/viseca/importer.py +24 -16
  22. tariochbctools/importers/zak/importer.py +51 -44
  23. tariochbctools/importers/zkb/importer.py +3 -2
  24. tariochbctools/plugins/prices/ibkr.py +6 -3
  25. {tariochbctools-0.38.dist-info → tariochbctools-1.0.0.dist-info}/METADATA +6 -3
  26. tariochbctools-1.0.0.dist-info/RECORD +55 -0
  27. {tariochbctools-0.38.dist-info → tariochbctools-1.0.0.dist-info}/WHEEL +1 -1
  28. tariochbctools-0.38.dist-info/RECORD +0 -55
  29. {tariochbctools-0.38.dist-info → tariochbctools-1.0.0.dist-info}/LICENSE.txt +0 -0
  30. {tariochbctools-0.38.dist-info → tariochbctools-1.0.0.dist-info}/entry_points.txt +0 -0
  31. {tariochbctools-0.38.dist-info → tariochbctools-1.0.0.dist-info}/top_level.txt +0 -0
@@ -1,24 +1,23 @@
1
1
  import csv
2
+ import re
2
3
  from collections.abc import Iterable
3
4
  from datetime import date
4
- from io import StringIO
5
5
 
6
+ import beangulp
6
7
  from beancount.core import amount, data
7
8
  from beancount.core.number import D
8
9
  from beancount.core.position import CostSpec
9
- from beancount.ingest import importer
10
- from beancount.ingest.importers.mixins import identifier
11
10
  from dateutil.parser import parse
12
11
 
13
12
  from tariochbctools.importers.general.priceLookup import PriceLookup
14
13
 
15
14
 
16
- class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
15
+ class Importer(beangulp.Importer):
17
16
  """An importer for Fidelity Netbenefits Activity CSV files."""
18
17
 
19
18
  def __init__(
20
19
  self,
21
- regexps: str | Iterable[str],
20
+ filepattern: str,
22
21
  cashAccount: str,
23
22
  investmentAccount: str,
24
23
  dividendAccount: str,
@@ -28,7 +27,7 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
28
27
  ignoreTypes: Iterable[str],
29
28
  baseCcy: str,
30
29
  ):
31
- identifier.IdentifyMixin.__init__(self, matchers=[("filename", regexps)])
30
+ self.filepattern = filepattern
32
31
  self.cashAccount = cashAccount
33
32
  self.investmentAccount = investmentAccount
34
33
  self.dividendAccount = dividendAccount
@@ -38,18 +37,21 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
38
37
  self.ignoreTypes = ignoreTypes
39
38
  self.baseCcy = baseCcy
40
39
 
41
- def name(self):
40
+ def name(self) -> str:
42
41
  return super().name() + self.cashAccount
43
42
 
44
- def file_account(self, file):
43
+ def identify(self, filepath: str) -> bool:
44
+ return re.search(self.filepattern, filepath) is not None
45
+
46
+ def account(self, filepath: str) -> data.Account:
45
47
  return self.cashAccount
46
48
 
47
- def extract(self, file, existing_entries):
49
+ def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
48
50
  entries = []
49
51
 
50
- self.priceLookup = PriceLookup(existing_entries, self.baseCcy)
52
+ self.priceLookup = PriceLookup(existing, self.baseCcy)
51
53
 
52
- with StringIO(file.contents()) as csvfile:
54
+ with open(filepath) as csvfile:
53
55
  reader = csv.DictReader(
54
56
  csvfile,
55
57
  [
@@ -76,12 +78,12 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
76
78
  if row["Shares"] != "-":
77
79
  shares = amount.Amount(D(row["Shares"]), self.symbol)
78
80
 
79
- metakv = {}
81
+ metakv: data.Meta = {}
80
82
 
81
83
  if not amt and not shares:
82
84
  continue
83
85
 
84
- meta = data.new_metadata(file.name, 0, metakv)
86
+ meta = data.new_metadata(filepath, 0, metakv)
85
87
  description = row["Transaction type"].strip()
86
88
 
87
89
  if "TAX" in description:
@@ -120,7 +122,9 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
120
122
 
121
123
  return entries
122
124
 
123
- def __createBuy(self, amt: amount, shares: amount, book_date: date):
125
+ def __createBuy(
126
+ self, amt: amount, shares: amount, book_date: date
127
+ ) -> list[data.Posting]:
124
128
  price = self.priceLookup.fetchPrice("USD", book_date)
125
129
  cost = CostSpec(
126
130
  number_per=None,
@@ -137,7 +141,9 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
137
141
 
138
142
  return postings
139
143
 
140
- def __createSell(self, amt: amount, shares: amount, book_date: date):
144
+ def __createSell(
145
+ self, amt: amount, shares: amount, book_date: date
146
+ ) -> list[data.Posting]:
141
147
  price = self.priceLookup.fetchPrice("USD", book_date)
142
148
  cost = CostSpec(
143
149
  number_per=None,
@@ -155,7 +161,9 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
155
161
 
156
162
  return postings
157
163
 
158
- def __createDividend(self, amt: amount, book_date: date, incomeAccount: str):
164
+ def __createDividend(
165
+ self, amt: amount, book_date: date, incomeAccount: str
166
+ ) -> list[data.Posting]:
159
167
  price = self.priceLookup.fetchPrice("USD", book_date)
160
168
  postings = [
161
169
  data.Posting(
@@ -1,28 +1,28 @@
1
1
  from datetime import date
2
2
  from os import path
3
3
 
4
+ import beangulp
4
5
  import requests
5
6
  import yaml
6
7
  from beancount.core import amount, data
7
8
  from beancount.core.number import D
8
- from beancount.ingest import importer
9
9
 
10
10
 
11
11
  class HttpServiceException(Exception):
12
12
  pass
13
13
 
14
14
 
15
- class Importer(importer.ImporterProtocol):
15
+ class Importer(beangulp.Importer):
16
16
  """An importer for Nordigen API (e.g. for Revolut)."""
17
17
 
18
- def identify(self, file):
19
- return path.basename(file.name).endswith("nordigen.yaml")
18
+ def identify(self, filepath: str) -> bool:
19
+ return path.basename(filepath).endswith("nordigen.yaml")
20
20
 
21
- def file_account(self, file):
21
+ def account(self, filepath: str) -> data.Entries:
22
22
  return ""
23
23
 
24
- def extract(self, file, existing_entries):
25
- with open(file.name, "r") as f:
24
+ def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
25
+ with open(filepath, "r") as f:
26
26
  config = yaml.safe_load(f)
27
27
 
28
28
  r = requests.post(
@@ -57,9 +57,13 @@ class Importer(importer.ImporterProtocol):
57
57
  r.json()["transactions"]["booked"], key=lambda trx: trx["bookingDate"]
58
58
  )
59
59
  for trx in transactions:
60
- metakv = {
61
- "nordref": trx["transactionId"],
62
- }
60
+ if "transactionId" in trx:
61
+ metakv = {
62
+ "nordref": trx["transactionId"],
63
+ }
64
+ else:
65
+ metakv = {}
66
+
63
67
  if "creditorName" in trx:
64
68
  metakv["creditorName"] = trx["creditorName"]
65
69
  if "debtorName" in trx:
@@ -1,21 +1,22 @@
1
1
  import argparse
2
2
  import sys
3
+ from typing import Any
3
4
 
4
5
  import requests
5
6
 
6
7
 
7
- def build_header(token):
8
+ def build_header(token: str) -> dict[str, str]:
8
9
  return {"Authorization": "Bearer " + token}
9
10
 
10
11
 
11
- def check_result(result):
12
+ def check_result(result: requests.Response) -> None:
12
13
  try:
13
14
  result.raise_for_status()
14
15
  except requests.exceptions.HTTPError as e:
15
16
  raise Exception(e, e.response.text)
16
17
 
17
18
 
18
- def get_token(secret_id, secret_key):
19
+ def get_token(secret_id: str, secret_key: str) -> str:
19
20
  r = requests.post(
20
21
  "https://bankaccountdata.gocardless.com/api/v2/token/new/",
21
22
  data={
@@ -28,7 +29,7 @@ def get_token(secret_id, secret_key):
28
29
  return r.json()["access"]
29
30
 
30
31
 
31
- def list_bank(token, country):
32
+ def list_bank(token: str, country: str) -> None:
32
33
  r = requests.get(
33
34
  "https://bankaccountdata.gocardless.com/api/v2/institutions/",
34
35
  params={"country": country},
@@ -40,7 +41,7 @@ def list_bank(token, country):
40
41
  print(asp["name"] + ": " + asp["id"]) # noqa: T201
41
42
 
42
43
 
43
- def create_link(token, reference, bank):
44
+ def create_link(token: str, reference: str, bank: str) -> None:
44
45
  if not bank:
45
46
  raise Exception("Please specify --bank it is required for create_link")
46
47
  requisitionId = _find_requisition_id(token, reference)
@@ -61,7 +62,7 @@ def create_link(token, reference, bank):
61
62
  print(f"Go to {link} for connecting to your bank.") # noqa: T201
62
63
 
63
64
 
64
- def list_accounts(token):
65
+ def list_accounts(token: str) -> None:
65
66
  headers = build_header(token)
66
67
  r = requests.get(
67
68
  "https://bankaccountdata.gocardless.com/api/v2/requisitions/", headers=headers
@@ -93,7 +94,7 @@ def list_accounts(token):
93
94
  print(f"{account}: {asp} {owner} {iban} {currency}") # noqa: T201
94
95
 
95
96
 
96
- def delete_link(token, reference):
97
+ def delete_link(token: str, reference: str) -> None:
97
98
  requisitionId = _find_requisition_id(token, reference)
98
99
  if requisitionId:
99
100
  r = requests.delete(
@@ -103,7 +104,7 @@ def delete_link(token, reference):
103
104
  check_result(r)
104
105
 
105
106
 
106
- def _find_requisition_id(token, userId):
107
+ def _find_requisition_id(token: str, userId: str) -> str | None:
107
108
  headers = build_header(token)
108
109
  r = requests.get(
109
110
  "https://bankaccountdata.gocardless.com/api/v2/requisitions/", headers=headers
@@ -116,7 +117,7 @@ def _find_requisition_id(token, userId):
116
117
  return None
117
118
 
118
119
 
119
- def parse_args(args):
120
+ def parse_args(args: Any) -> Any:
120
121
  parser = argparse.ArgumentParser(description="nordigen-config")
121
122
  parser.add_argument(
122
123
  "--secret_id",
@@ -154,7 +155,7 @@ def parse_args(args):
154
155
  return parser.parse_args(args)
155
156
 
156
157
 
157
- def main(args):
158
+ def main(args: Any) -> None:
158
159
  args = parse_args(args)
159
160
 
160
161
  token = get_token(args.secret_id, args.secret_key)
@@ -169,6 +170,6 @@ def main(args):
169
170
  delete_link(token, args.reference)
170
171
 
171
172
 
172
- def run():
173
+ def run() -> None:
173
174
  """Entry point for console_scripts"""
174
175
  main(sys.argv[1:])
@@ -1,49 +1,52 @@
1
1
  import csv
2
2
  import logging
3
+ import re
3
4
  from datetime import datetime, timedelta
4
5
  from decimal import Decimal
5
6
 
7
+ import beangulp
6
8
  from beancount.core import data
7
- from beancount.ingest.importer import ImporterProtocol
8
- from beancount.ingest.importers.mixins.identifier import IdentifyMixin
9
9
 
10
10
 
11
- class Importer(IdentifyMixin, ImporterProtocol):
11
+ class Importer(beangulp.Importer):
12
12
  """An importer for PostFinance CSV."""
13
13
 
14
- def __init__(self, regexps, account, currency="CHF"):
15
- IdentifyMixin.__init__(self, matchers=[("filename", regexps)])
16
- self.account = account
14
+ def __init__(self, filepattern: str, account: data.Account, currency: str = "CHF"):
15
+ self._filepattern = filepattern
16
+ self._account = account
17
17
  self.currency = currency
18
18
 
19
- def file_account(self, file):
20
- return self.account
19
+ def identify(self, filepath: str) -> bool:
20
+ return re.search(self._filepattern, filepath) is not None
21
21
 
22
- def extract(self, file, existing_entries):
23
- csvfile = open(file=file.name, encoding="windows_1252")
22
+ def account(self, filepath: str) -> data.Account:
23
+ return self._account
24
+
25
+ def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
26
+ csvfile = open(file=filepath, encoding="windows_1252")
24
27
  reader = csv.reader(csvfile, delimiter=";")
25
- meta = data.new_metadata(file.name, 0)
28
+ meta = data.new_metadata(filepath, 0)
26
29
  entries = []
27
30
 
28
31
  for row in reader:
29
32
  try:
30
- book_date, text, credit, debit, val_date, balance = tuple(row)
31
- book_date = datetime.strptime(book_date, "%Y-%m-%d").date()
33
+ book_date_str, text, credit, debit, val_date, balance_str = tuple(row)
34
+ book_date = datetime.strptime(book_date_str, "%Y-%m-%d").date()
32
35
  if credit:
33
36
  amount = data.Amount(Decimal(credit), self.currency)
34
37
  elif debit:
35
38
  amount = data.Amount(Decimal(debit), self.currency)
36
39
  else:
37
40
  amount = None
38
- if balance:
39
- balance = data.Amount(Decimal(balance), self.currency)
41
+ if balance_str:
42
+ balance = data.Amount(Decimal(balance_str), self.currency)
40
43
  else:
41
44
  balance = None
42
45
  except Exception as e:
43
46
  logging.debug(e)
44
47
  else:
45
48
  logging.debug((book_date, text, amount, val_date, balance))
46
- posting = data.Posting(self.account, amount, None, None, None, None)
49
+ posting = data.Posting(self._account, amount, None, None, None, None)
47
50
  entry = data.Transaction(
48
51
  meta,
49
52
  book_date,
@@ -59,7 +62,7 @@ class Importer(IdentifyMixin, ImporterProtocol):
59
62
  book_date = book_date + timedelta(days=1)
60
63
  if balance and book_date.day == 1:
61
64
  entry = data.Balance(
62
- meta, book_date, self.account, balance, None, None
65
+ meta, book_date, self._account, balance, None, None
63
66
  )
64
67
  entries.append(entry)
65
68
 
@@ -5,11 +5,11 @@ from hashlib import md5
5
5
  from os import path
6
6
  from typing import Dict, List, NamedTuple
7
7
 
8
+ import beangulp
8
9
  import requests
9
10
  import yaml
10
11
  from beancount.core import amount, data
11
12
  from beancount.core.number import D
12
- from beancount.ingest import importer
13
13
  from undictify import type_checked_constructor
14
14
 
15
15
 
@@ -152,32 +152,32 @@ class QuickFile:
152
152
  return QuickFileBankSearch(**body)
153
153
 
154
154
 
155
- class Importer(importer.ImporterProtocol):
155
+ class Importer(beangulp.Importer):
156
156
  """An importer for QuickFile"""
157
157
 
158
158
  def __init__(self):
159
159
  self.quickfile = None
160
160
  self.config = None
161
- self.existing_entries = None
161
+ self.existing = None
162
162
 
163
- def _configure(self, file, existing_entries):
164
- with open(file.name, "r") as config_file:
163
+ def _configure(self, filepath, existing):
164
+ with open(filepath, "r") as config_file:
165
165
  self.config = yaml.safe_load(config_file)
166
166
  self.quickfile = QuickFile(
167
167
  account_number=self.config["account_number"],
168
168
  api_key=self.config["api_key"],
169
169
  app_id=self.config["app_id"],
170
170
  )
171
- self.existing_entries = existing_entries
171
+ self.existing = existing
172
172
 
173
- def identify(self, file):
174
- return path.basename(file.name) == "quickfile.yaml"
173
+ def identify(self, filepath):
174
+ return path.basename(filepath) == "quickfile.yaml"
175
175
 
176
- def file_account(self, file):
176
+ def account(self, filepath):
177
177
  return ""
178
178
 
179
- def extract(self, file, existing_entries=None):
180
- self._configure(file, existing_entries)
179
+ def extract(self, filepath, existing=None):
180
+ self._configure(filepath, existing)
181
181
  entries = []
182
182
 
183
183
  for bank_account in self.config["accounts"].keys():
@@ -1,4 +1,5 @@
1
1
  import re
2
+ from typing import Any
2
3
 
3
4
  from tariochbctools.importers.general import mt940importer
4
5
 
@@ -8,21 +9,16 @@ class RaiffeisenCHImporter(mt940importer.Importer):
8
9
 
9
10
  """To get the correct file, choose SWIFT -> 'Période prédéfinie du relevé de compte' -> Sans détails"""
10
11
 
11
- def prepare_payee(self, trxdata):
12
+ def prepare_payee(self, trxdata: dict[str, Any]) -> str:
12
13
  return ""
13
14
 
14
- def prepare_narration(self, trxdata):
15
+ def prepare_narration(self, trxdata: dict[str, Any]) -> str:
15
16
  extra = trxdata["extra_details"]
16
17
  details = trxdata["transaction_details"]
17
18
 
18
- extraReplacements = {}
19
-
20
19
  detailsReplacements = {}
21
20
  detailsReplacements[r"\n"] = ", "
22
21
 
23
- for pattern, replacement in extraReplacements.items():
24
- extra = re.sub(pattern, replacement, extra)
25
-
26
22
  for pattern, replacement in detailsReplacements.items():
27
23
  details = re.sub(pattern, replacement, details)
28
24
 
@@ -1,34 +1,39 @@
1
1
  import csv
2
2
  import logging
3
+ import re
3
4
  from datetime import timedelta
4
- from io import StringIO
5
+ from typing import Any
5
6
 
7
+ import beangulp
6
8
  from beancount.core import amount, data
7
9
  from beancount.core.number import ZERO, D
8
- from beancount.ingest import importer
9
- from beancount.ingest.importers.mixins import identifier
10
10
  from dateutil.parser import parse
11
11
 
12
12
 
13
- class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
13
+ class Importer(beangulp.Importer):
14
14
  """An importer for Revolut CSV files."""
15
15
 
16
- def __init__(self, regexps, account, currency, fee=None):
17
- identifier.IdentifyMixin.__init__(self, matchers=[("filename", regexps)])
18
- self.account = account
16
+ def __init__(
17
+ self, filepattern: str, account: data.Account, currency: str, fee: Any = None
18
+ ):
19
+ self._filepattern = filepattern
20
+ self._account = account
19
21
  self.currency = currency
20
22
  self._fee = fee
21
23
 
22
- def name(self):
23
- return super().name() + self.account
24
+ def name(self) -> str:
25
+ return super().name() + self._account
24
26
 
25
- def file_account(self, file):
26
- return self.account
27
+ def identify(self, filepath: str) -> bool:
28
+ return re.search(self._filepattern, filepath) is not None
27
29
 
28
- def extract(self, file, existing_entries):
30
+ def account(self, filepath: str) -> data.Account:
31
+ return self._account
32
+
33
+ def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
29
34
  entries = []
30
35
 
31
- with StringIO(file.contents()) as csvfile:
36
+ with open(filepath) as csvfile:
32
37
  reader = csv.DictReader(
33
38
  csvfile,
34
39
  [
@@ -65,12 +70,12 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
65
70
  continue
66
71
 
67
72
  postings = [
68
- data.Posting(self.account, amt, None, None, None, None),
73
+ data.Posting(self._account, amt, None, None, None, None),
69
74
  ]
70
75
  description = row["Description"].strip()
71
76
  if is_fee_mode:
72
77
  postings = [
73
- data.Posting(self.account, fee, None, None, None, None),
78
+ data.Posting(self._account, fee, None, None, None, None),
74
79
  data.Posting(
75
80
  self._fee["account"], -fee, None, None, None, None
76
81
  ),
@@ -82,7 +87,7 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
82
87
  ), "Actual type of description is " + str(type(description))
83
88
 
84
89
  entry = data.Transaction(
85
- data.new_metadata(file.name, 0, {}),
90
+ data.new_metadata(filepath, 0, {}),
86
91
  book_date,
87
92
  "*",
88
93
  "",
@@ -98,9 +103,9 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
98
103
  try:
99
104
  book_date = book_date + timedelta(days=1)
100
105
  entry = data.Balance(
101
- data.new_metadata(file.name, 0, {}),
106
+ data.new_metadata(filepath, 0, {}),
102
107
  book_date,
103
- self.account,
108
+ self._account,
104
109
  balance,
105
110
  None,
106
111
  None,
@@ -1,24 +1,25 @@
1
1
  import datetime
2
2
  from os import path
3
+ from typing import Any
3
4
 
5
+ import beangulp
4
6
  import yaml
5
7
  from beancount.core import amount, data
6
8
  from beancount.core.number import D
7
- from beancount.ingest import importer
8
9
  from dateutil.relativedelta import relativedelta
9
10
 
10
11
 
11
- class Importer(importer.ImporterProtocol):
12
+ class Importer(beangulp.Importer):
12
13
  """An importer for Scheduled/Recurring Transactions."""
13
14
 
14
- def identify(self, file):
15
- return path.basename(file.name).endswith("schedule.yaml")
15
+ def identify(self, filepath: str) -> bool:
16
+ return path.basename(filepath).endswith("schedule.yaml")
16
17
 
17
- def file_account(self, file):
18
+ def account(self, filepath: str) -> data.Account:
18
19
  return ""
19
20
 
20
- def extract(self, file, existing_entries):
21
- with open(file.name, "r") as f:
21
+ def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
22
+ with open(filepath, "r") as f:
22
23
  config = yaml.safe_load(f)
23
24
  self.transactions = config["transactions"]
24
25
 
@@ -30,7 +31,9 @@ class Importer(importer.ImporterProtocol):
30
31
 
31
32
  return result
32
33
 
33
- def createForDate(self, trx, date):
34
+ def createForDate(
35
+ self, trx: dict[str, Any], date: datetime.date
36
+ ) -> data.Transaction:
34
37
  postings = []
35
38
  for post in trx["postings"]:
36
39
  amt = None
@@ -1,29 +1,31 @@
1
1
  import csv
2
- from io import StringIO
2
+ import re
3
3
 
4
4
  from beancount.core import amount, data
5
5
  from beancount.core.number import D
6
- from beancount.ingest import importer
7
- from beancount.ingest.importers.mixins import identifier
6
+ from beangulp import Importer
8
7
  from dateutil.parser import parse
9
8
 
10
9
 
11
- class SwisscardImporter(identifier.IdentifyMixin, importer.ImporterProtocol):
10
+ class SwisscardImporter(Importer):
12
11
  """An importer for Swisscard's cashback CSV files."""
13
12
 
14
- def __init__(self, regexps, account):
15
- identifier.IdentifyMixin.__init__(self, matchers=[("filename", regexps)])
16
- self.account = account
13
+ def __init__(self, filepattern: str, account: data.Account):
14
+ self._filepattern = filepattern
15
+ self._account = account
17
16
 
18
- def name(self):
19
- return super().name() + self.account
17
+ def name(self) -> str:
18
+ return super().name() + self._account
20
19
 
21
- def file_account(self, file):
22
- return self.account
20
+ def identify(self, filepath: str) -> bool:
21
+ return re.search(self._filepattern, filepath) is not None
23
22
 
24
- def extract(self, file, existing_entries):
23
+ def account(self, filepath: str) -> data.Account:
24
+ return self._account
25
+
26
+ def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
25
27
  entries = []
26
- with StringIO(file.contents()) as csvfile:
28
+ with open(filepath) as csvfile:
27
29
  reader = csv.DictReader(
28
30
  csvfile,
29
31
  delimiter=",",
@@ -36,7 +38,7 @@ class SwisscardImporter(identifier.IdentifyMixin, importer.ImporterProtocol):
36
38
  "merchant": row["Merchant Category"],
37
39
  "category": row["Registered Category"],
38
40
  }
39
- meta = data.new_metadata(file.name, 0, metakv)
41
+ meta = data.new_metadata(filepath, 0, metakv)
40
42
  description = row["Description"].strip()
41
43
  entry = data.Transaction(
42
44
  meta,
@@ -47,7 +49,7 @@ class SwisscardImporter(identifier.IdentifyMixin, importer.ImporterProtocol):
47
49
  data.EMPTY_SET,
48
50
  data.EMPTY_SET,
49
51
  [
50
- data.Posting(self.account, amt, None, None, None, None),
52
+ data.Posting(self._account, amt, None, None, None, None),
51
53
  ],
52
54
  )
53
55
  entries.append(entry)