tariochbctools 0.38.1__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 +7 -7
  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.1.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.1.dist-info → tariochbctools-1.0.0.dist-info}/WHEEL +1 -1
  28. tariochbctools-0.38.1.dist-info/RECORD +0 -55
  29. {tariochbctools-0.38.1.dist-info → tariochbctools-1.0.0.dist-info}/LICENSE.txt +0 -0
  30. {tariochbctools-0.38.1.dist-info → tariochbctools-1.0.0.dist-info}/entry_points.txt +0 -0
  31. {tariochbctools-0.38.1.dist-info → tariochbctools-1.0.0.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,15 @@
1
1
  import re
2
+ from typing import Any
2
3
 
3
4
  from tariochbctools.importers.general import mt940importer
4
5
 
5
6
 
6
- def strip_newline(string):
7
+ def strip_newline(string: str) -> str:
7
8
  return string.replace("\n", "").replace("\r", "")
8
9
 
9
10
 
10
11
  class BCGEImporter(mt940importer.Importer):
11
- def prepare_payee(self, trxdata):
12
+ def prepare_payee(self, trxdata: dict[str, Any]) -> str:
12
13
  transaction_details = strip_newline(trxdata["transaction_details"])
13
14
  payee = re.search(r"ORDP/([^/]+)", transaction_details)
14
15
  if payee is None:
@@ -16,7 +17,7 @@ class BCGEImporter(mt940importer.Importer):
16
17
  else:
17
18
  return payee.group(1)
18
19
 
19
- def prepare_narration(self, trxdata):
20
+ def prepare_narration(self, trxdata: dict[str, Any]) -> str:
20
21
  transaction_details = strip_newline(trxdata["transaction_details"])
21
22
  extra_details = strip_newline(trxdata["extra_details"])
22
23
  beneficiary = re.search(r"/BENM/([^/]+)", transaction_details)
@@ -1,36 +1,38 @@
1
1
  from datetime import date
2
2
  from os import path
3
+ from typing import Any
3
4
 
5
+ import beangulp
4
6
  import bitstamp.client
5
7
  import yaml
6
8
  from beancount.core import amount, data
7
9
  from beancount.core.number import MISSING, D
8
- from beancount.ingest import importer
9
10
  from dateutil.parser import parse
10
11
  from dateutil.relativedelta import relativedelta
11
12
 
12
13
  from tariochbctools.importers.general.priceLookup import PriceLookup
13
14
 
14
15
 
15
- class Importer(importer.ImporterProtocol):
16
+ class Importer(beangulp.Importer):
16
17
  """An importer for Bitstamp."""
17
18
 
18
- def identify(self, file):
19
- return path.basename(file.name).endswith("bitstamp.yaml")
19
+ def identify(self, filepath: str) -> bool:
20
+ return path.basename(filepath).endswith("bitstamp.yaml")
20
21
 
21
- def file_account(self, file):
22
+ def account(self, filepath: str) -> data.Account:
22
23
  return ""
23
24
 
24
- def extract(self, file, existing_entries):
25
- self.priceLookup = PriceLookup(existing_entries, "CHF")
25
+ def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
26
+ self.priceLookup = PriceLookup(existing, "CHF")
26
27
 
27
- config = yaml.safe_load(file.contents())
28
+ with open(filepath) as file:
29
+ config = yaml.safe_load(file)
28
30
  self.config = config
29
31
  self.client = bitstamp.client.Trading(
30
32
  username=config["username"], key=config["key"], secret=config["secret"]
31
33
  )
32
34
  self.currencies = config["currencies"]
33
- self.account = config["account"]
35
+ self._account = config["account"]
34
36
  self.otherExpensesAccount = config["otherExpensesAccount"]
35
37
  self.capGainAccount = config["capGainAccount"]
36
38
 
@@ -46,7 +48,7 @@ class Importer(importer.ImporterProtocol):
46
48
 
47
49
  return result
48
50
 
49
- def fetchSingle(self, trx):
51
+ def fetchSingle(self, trx: dict[str, Any]) -> data.Transaction:
50
52
  id = int(trx["id"])
51
53
  type = int(trx["type"])
52
54
  date = parse(trx["datetime"]).date()
@@ -67,12 +69,13 @@ class Importer(importer.ImporterProtocol):
67
69
 
68
70
  if type == 0:
69
71
  narration = "Deposit"
70
- cost = data.Cost(
71
- self.priceLookup.fetchPriceAmount(posCcy, date), "CHF", None, None
72
- )
72
+ if posCcy:
73
+ cost = data.Cost(
74
+ self.priceLookup.fetchPriceAmount(posCcy, date), "CHF", None, None
75
+ )
73
76
  postings = [
74
77
  data.Posting(
75
- self.account + ":" + posCcy,
78
+ self._account + ":" + posCcy,
76
79
  amount.Amount(posAmt, posCcy),
77
80
  cost,
78
81
  None,
@@ -84,7 +87,7 @@ class Importer(importer.ImporterProtocol):
84
87
  narration = "Withdrawal"
85
88
  postings = [
86
89
  data.Posting(
87
- self.account + ":" + negCcy,
90
+ self._account + ":" + negCcy,
88
91
  amount.Amount(negAmt, negCcy),
89
92
  None,
90
93
  None,
@@ -94,14 +97,15 @@ class Importer(importer.ImporterProtocol):
94
97
  ]
95
98
  elif type == 2:
96
99
  fee = D(trx["fee"])
97
- if posCcy.lower() + "_" + negCcy.lower() in trx:
100
+ if posCcy and negCcy and posCcy.lower() + "_" + negCcy.lower() in trx:
98
101
  feeCcy = negCcy
99
102
  negAmt -= fee
100
103
  else:
101
104
  feeCcy = posCcy
102
105
  posAmt -= fee
103
106
 
104
- rateFiatCcy = self.priceLookup.fetchPriceAmount(feeCcy, date)
107
+ if feeCcy:
108
+ rateFiatCcy = self.priceLookup.fetchPriceAmount(feeCcy, date)
105
109
  if feeCcy == posCcy:
106
110
  posCcyCost = None
107
111
  posCcyPrice = amount.Amount(rateFiatCcy, "CHF")
@@ -119,7 +123,7 @@ class Importer(importer.ImporterProtocol):
119
123
 
120
124
  postings = [
121
125
  data.Posting(
122
- self.account + ":" + posCcy,
126
+ self._account + ":" + posCcy,
123
127
  amount.Amount(posAmt, posCcy),
124
128
  posCcyCost,
125
129
  posCcyPrice,
@@ -127,7 +131,7 @@ class Importer(importer.ImporterProtocol):
127
131
  None,
128
132
  ),
129
133
  data.Posting(
130
- self.account + ":" + negCcy,
134
+ self._account + ":" + negCcy,
131
135
  amount.Amount(negAmt, negCcy),
132
136
  negCcyCost,
133
137
  negCcyPrice,
@@ -1,28 +1,29 @@
1
1
  from os import path
2
2
 
3
+ import beangulp
3
4
  import blockcypher
4
5
  import yaml
5
6
  from beancount.core import amount, data
6
7
  from beancount.core.number import D
7
- from beancount.ingest import importer
8
8
 
9
9
  from tariochbctools.importers.general.priceLookup import PriceLookup
10
10
 
11
11
 
12
- class Importer(importer.ImporterProtocol):
12
+ class Importer(beangulp.Importer):
13
13
  """An importer for Blockchain data."""
14
14
 
15
- def identify(self, file):
16
- return path.basename(file.name).endswith("blockchain.yaml")
15
+ def identify(self, filepath: str) -> bool:
16
+ return path.basename(filepath).endswith("blockchain.yaml")
17
17
 
18
- def file_account(self, file):
18
+ def account(self, filepath: str) -> data.Entries:
19
19
  return ""
20
20
 
21
- def extract(self, file, existing_entries):
22
- config = yaml.safe_load(file.contents())
21
+ def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
22
+ with open(filepath) as file:
23
+ config = yaml.safe_load(file)
23
24
  self.config = config
24
25
  baseCcy = config["base_ccy"]
25
- priceLookup = PriceLookup(existing_entries, baseCcy)
26
+ priceLookup = PriceLookup(existing, baseCcy)
26
27
 
27
28
  entries = []
28
29
  for address in self.config["addresses"]:
@@ -1,26 +1,31 @@
1
+ import datetime
1
2
  import re
2
- from datetime import datetime, timedelta
3
+ from datetime import timedelta
3
4
 
5
+ import beangulp
4
6
  import camelot
5
7
  from beancount.core import amount, data
6
8
  from beancount.core.number import D
7
- from beancount.ingest import importer
8
- from beancount.ingest.importers.mixins import identifier
9
9
 
10
10
 
11
- class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
11
+ class Importer(beangulp.Importer):
12
12
  """An importer for Cembra Card Statement PDF files."""
13
13
 
14
- def __init__(self, regexps, account):
15
- identifier.IdentifyMixin.__init__(self, matchers=[("filename", regexps)])
16
- self.account = account
14
+ def __init__(self, filepattern: str, account: data.Account):
15
+ self._filepattern = filepattern
16
+ self._account = account
17
17
  self.currency = "CHF"
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 createEntry(self, file, date, amt, text):
23
- meta = data.new_metadata(file.name, 0)
22
+ def account(self, filepath: str) -> data.Account:
23
+ return self._account
24
+
25
+ def createEntry(
26
+ self, filepath: str, date: datetime.date, amt: data.Decimal, text: str
27
+ ) -> data.Transaction:
28
+ meta = data.new_metadata(filepath, 0)
24
29
  return data.Transaction(
25
30
  meta,
26
31
  date,
@@ -30,19 +35,21 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
30
35
  data.EMPTY_SET,
31
36
  data.EMPTY_SET,
32
37
  [
33
- data.Posting(self.account, amt, None, None, None, None),
38
+ data.Posting(self._account, amt, None, None, None, None),
34
39
  ],
35
40
  )
36
41
 
37
- def createBalanceEntry(self, file, date, amt):
38
- meta = data.new_metadata(file.name, 0)
39
- return data.Balance(meta, date, self.account, amt, None, None)
42
+ def createBalanceEntry(
43
+ self, filepath: str, date: datetime.date, amt: data.Decimal
44
+ ) -> data.Balance:
45
+ meta = data.new_metadata(filepath, 0)
46
+ return data.Balance(meta, date, self._account, amt, None, None)
40
47
 
41
- def extract(self, file, existing_entries):
48
+ def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
42
49
  entries = []
43
50
 
44
51
  tables = camelot.read_pdf(
45
- file.name, pages="2-end", flavor="stream", table_areas=["50,700,560,50"]
52
+ filepath, pages="2-end", flavor="stream", table_areas=["50,700,560,50"]
46
53
  )
47
54
  for table in tables:
48
55
  df = table.df
@@ -63,7 +70,7 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
63
70
 
64
71
  # Transaction entry
65
72
  try:
66
- book_date = datetime.strptime(book_date, "%d.%m.%Y").date()
73
+ book_date = datetime.datetime.strptime(book_date, "%d.%m.%Y").date()
67
74
  except Exception:
68
75
  book_date = None
69
76
 
@@ -71,17 +78,24 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
71
78
  amount = self.getAmount(debit, credit)
72
79
 
73
80
  if amount:
74
- entries.append(self.createEntry(file, book_date, amount, text))
81
+ entries.append(
82
+ self.createEntry(filepath, book_date, amount, text)
83
+ )
75
84
  continue
76
85
 
77
86
  # Balance entry
78
87
  try:
79
- book_date = re.search(
80
- r"Saldo per (\d\d\.\d\d\.\d\d\d\d) zu unseren Gunsten CHF", text
81
- ).group(1)
82
- book_date = datetime.strptime(book_date, "%d.%m.%Y").date()
83
- # add 1 day: cembra provides balance at EOD, but beancount checks it at SOD
84
- book_date = book_date + timedelta(days=1)
88
+ m = re.search(
89
+ r"Saldo per (\d\d\.\d\d\.\d\d\d\d) zu unseren Gunsten CHF",
90
+ text,
91
+ )
92
+ if m:
93
+ book_date = m.group(1)
94
+ book_date = datetime.datetime.strptime(
95
+ book_date, "%d.%m.%Y"
96
+ ).date()
97
+ # add 1 day: cembra provides balance at EOD, but beancount checks it at SOD
98
+ book_date = book_date + timedelta(days=1)
85
99
  except Exception:
86
100
  book_date = None
87
101
 
@@ -89,14 +103,16 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
89
103
  amount = self.getAmount(debit, credit)
90
104
 
91
105
  if amount:
92
- entries.append(self.createBalanceEntry(file, book_date, amount))
106
+ entries.append(
107
+ self.createBalanceEntry(filepath, book_date, amount)
108
+ )
93
109
 
94
110
  return entries
95
111
 
96
- def cleanDecimal(self, formattedNumber):
112
+ def cleanDecimal(self, formattedNumber: str) -> data.Decimal:
97
113
  return D(formattedNumber.replace("'", ""))
98
114
 
99
- def getAmount(self, debit, credit):
115
+ def getAmount(self, debit: str, credit: str) -> data.Amount:
100
116
  amt = -self.cleanDecimal(debit) if debit else self.cleanDecimal(credit)
101
117
  if amt:
102
118
  return amount.Amount(amt, self.currency)
@@ -2,24 +2,26 @@ import tempfile
2
2
  from os import path
3
3
 
4
4
  import yaml
5
- from beancount.ingest import cache, importer
5
+ from beancount.core import data
6
+ from beangulp import Importer
6
7
  from imap_tools import MailBox
7
8
 
8
9
 
9
- class MailAdapterImporter(importer.ImporterProtocol):
10
+ class MailAdapterImporter(Importer):
10
11
  """An importer adapter that fetches file from mails and then calls another importer."""
11
12
 
12
- def __init__(self, importers):
13
+ def __init__(self, importers: list[Importer]):
13
14
  self.importers = importers
14
15
 
15
- def identify(self, file):
16
- return "mail.yaml" == path.basename(file.name)
16
+ def identify(self, filepath: str) -> bool:
17
+ return "mail.yaml" == path.basename(filepath)
17
18
 
18
- def file_account(self, file):
19
+ def account(self, filepath: str) -> data.Account:
19
20
  return ""
20
21
 
21
- def extract(self, file, existing_entries):
22
- config = yaml.safe_load(file.contents())
22
+ def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
23
+ with open(filepath) as file:
24
+ config = yaml.safe_load(file)
23
25
 
24
26
  with MailBox(config["host"]).login(
25
27
  config["user"], config["password"], initial_folder=config["folder"]
@@ -33,13 +35,10 @@ class MailAdapterImporter(importer.ImporterProtocol):
33
35
  with open(attFileName, "wb") as attFile:
34
36
  attFile.write(att.payload)
35
37
  attFile.flush()
36
- fileMemo = cache.get_file(attFileName)
37
38
 
38
39
  for delegate in self.importers:
39
- if delegate.identify(fileMemo):
40
- newEntries = delegate.extract(
41
- fileMemo, existing_entries
42
- )
40
+ if delegate.identify(attFileName):
41
+ newEntries = delegate.extract(attFileName, existing)
43
42
  result.extend(newEntries)
44
43
  processed = True
45
44
 
@@ -1,29 +1,28 @@
1
+ import re
2
+ from typing import Any
3
+
4
+ import beangulp
1
5
  import mt940
2
6
  from beancount.core import amount, data
3
7
  from beancount.core.number import D
4
- from beancount.ingest import importer
5
- from beancount.ingest.importers.mixins import identifier
6
8
 
7
9
 
8
- class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
10
+ class Importer(beangulp.Importer):
9
11
  """An importer for MT940 files."""
10
12
 
11
- def __init__(self, regexps, account):
12
- identifier.IdentifyMixin.__init__(self, matchers=[("filename", regexps)])
13
- self.account = account
14
-
15
- def identify(self, file):
16
- if file.mimetype() != "text/plain":
17
- return False
13
+ def __init__(self, filepattern: str, account: data.Account):
14
+ self._filepattern = filepattern
15
+ self._account = account
18
16
 
19
- return super().identify(file)
17
+ def identify(self, filepath: str) -> bool:
18
+ return re.search(self._filepattern, filepath) is not None
20
19
 
21
- def file_account(self, file):
22
- return self.account
20
+ def account(self, filepath: str) -> data.Account:
21
+ return self._account
23
22
 
24
- def extract(self, file, existing_entries):
23
+ def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
25
24
  entries = []
26
- transactions = mt940.parse(file.contents())
25
+ transactions = mt940.parse(filepath)
27
26
  for trx in transactions:
28
27
  trxdata = trx.data
29
28
  ref = trxdata["bank_reference"]
@@ -31,7 +30,7 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
31
30
  metakv = {"ref": ref}
32
31
  else:
33
32
  metakv = None
34
- meta = data.new_metadata(file.name, 0, metakv)
33
+ meta = data.new_metadata(filepath, 0, metakv)
35
34
  if "entry_date" in trxdata:
36
35
  date = trxdata["entry_date"]
37
36
  else:
@@ -46,7 +45,7 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
46
45
  data.EMPTY_SET,
47
46
  [
48
47
  data.Posting(
49
- self.account,
48
+ self._account,
50
49
  amount.Amount(
51
50
  D(trxdata["amount"].amount), trxdata["amount"].currency
52
51
  ),
@@ -61,8 +60,8 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
61
60
 
62
61
  return entries
63
62
 
64
- def prepare_payee(self, trxdata):
63
+ def prepare_payee(self, trxdata: dict[str, Any]) -> str:
65
64
  return ""
66
65
 
67
- def prepare_narration(self, trxdata):
66
+ def prepare_narration(self, trxdata: dict[str, Any]) -> str:
68
67
  return trxdata["transaction_details"] + " " + trxdata["extra_details"]
@@ -1,18 +1,18 @@
1
1
  from datetime import date
2
2
 
3
- from beancount.core import amount, prices
3
+ from beancount.core import amount, data, prices
4
4
  from beancount.core.number import D
5
5
 
6
6
 
7
7
  class PriceLookup:
8
- def __init__(self, existing_entries, baseCcy: str):
9
- if existing_entries:
10
- self.priceMap = prices.build_price_map(existing_entries)
8
+ def __init__(self, existing: data.Entries, baseCcy: str):
9
+ if existing:
10
+ self.priceMap = prices.build_price_map(existing)
11
11
  else:
12
12
  self.priceMap = None
13
13
  self.baseCcy = baseCcy
14
14
 
15
- def fetchPriceAmount(self, instrument: str, date: date):
15
+ def fetchPriceAmount(self, instrument: str, date: date) -> data.Amount:
16
16
  if self.priceMap:
17
17
  price = prices.get_price(
18
18
  self.priceMap, tuple([instrument, self.baseCcy]), date
@@ -21,7 +21,7 @@ class PriceLookup:
21
21
  else:
22
22
  return D(1)
23
23
 
24
- def fetchPrice(self, instrument: str, date: date):
24
+ def fetchPrice(self, instrument: str, date: date) -> data.Amount:
25
25
  if instrument == self.baseCcy:
26
26
  return None
27
27
 
@@ -2,27 +2,30 @@ import re
2
2
  from datetime import date
3
3
  from decimal import Decimal
4
4
  from os import path
5
+ from typing import Any
5
6
 
7
+ import beangulp
6
8
  import yaml
7
9
  from beancount.core import amount, data
8
10
  from beancount.core.number import D
9
- from beancount.ingest import importer
10
11
  from ibflex import Types, client, parser
11
12
  from ibflex.enums import CashAction
12
13
 
13
14
  from tariochbctools.importers.general.priceLookup import PriceLookup
14
15
 
15
16
 
16
- class Importer(importer.ImporterProtocol):
17
+ class Importer(beangulp.Importer):
17
18
  """An importer for Interactive Broker using the flex query service."""
18
19
 
19
- def identify(self, file):
20
- return path.basename(file.name).endswith("ibkr.yaml")
20
+ def identify(self, filepath: str) -> bool:
21
+ return path.basename(filepath).endswith("ibkr.yaml")
21
22
 
22
- def file_account(self, file):
23
+ def account(self, filepath: str) -> data.Account:
23
24
  return ""
24
25
 
25
- def matches(self, trx, t, account):
26
+ def matches(
27
+ self, trx: Types.CashTransaction, t: Any, account: data.Account
28
+ ) -> bool:
26
29
  p = re.compile(r".* (?P<perShare>\d+\.?\d+) PER SHARE")
27
30
 
28
31
  trxPerShareGroups = p.search(trx.description)
@@ -38,13 +41,13 @@ class Importer(importer.ImporterProtocol):
38
41
  and t["account"] == account
39
42
  )
40
43
 
41
- def extract(self, file, existing_entries):
42
- with open(file.name, "r") as f:
44
+ def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
45
+ with open(filepath, "r") as f:
43
46
  config = yaml.safe_load(f)
44
47
  token = config["token"]
45
48
  queryId = config["queryId"]
46
49
 
47
- priceLookup = PriceLookup(existing_entries, config["baseCcy"])
50
+ priceLookup = PriceLookup(existing, config["baseCcy"])
48
51
 
49
52
  response = client.download(token, queryId)
50
53
  statement = parser.parse(response)
@@ -52,7 +55,7 @@ class Importer(importer.ImporterProtocol):
52
55
 
53
56
  result = []
54
57
  for stmt in statement.FlexStatements:
55
- transactions = []
58
+ transactions: list = []
56
59
  account = stmt.accountId
57
60
  for trx in stmt.Trades:
58
61
  result.append(
@@ -147,7 +150,7 @@ class Importer(importer.ImporterProtocol):
147
150
  priceLookup: PriceLookup,
148
151
  description: str,
149
152
  account: str,
150
- ):
153
+ ) -> data.Transaction:
151
154
  narration = "Dividend: " + description
152
155
  liquidityAccount = self.getLiquidityAccount(account, currency)
153
156
  incomeAccount = self.getIncomeAccount(account)
@@ -190,7 +193,7 @@ class Importer(importer.ImporterProtocol):
190
193
  def createBuy(
191
194
  self,
192
195
  date: date,
193
- account: str,
196
+ account: data.Account,
194
197
  asset: str,
195
198
  quantity: Decimal,
196
199
  currency: str,
@@ -199,7 +202,7 @@ class Importer(importer.ImporterProtocol):
199
202
  netCash: amount.Amount,
200
203
  baseCcy: str,
201
204
  fxRateToBase: Decimal,
202
- ):
205
+ ) -> data.Transaction:
203
206
  narration = "Buy"
204
207
  feeAccount = self.getFeeAccount(account)
205
208
  liquidityAccount = self.getLiquidityAccount(account, currency)
@@ -238,17 +241,17 @@ class Importer(importer.ImporterProtocol):
238
241
  meta, date, "*", "", narration, data.EMPTY_SET, data.EMPTY_SET, postings
239
242
  )
240
243
 
241
- def getAssetAccount(self, account: str, asset: str):
244
+ def getAssetAccount(self, account: str, asset: str) -> data.Account:
242
245
  return f"Assets:{account}:Investment:IB:{asset}"
243
246
 
244
- def getLiquidityAccount(self, account: str, currency: str):
247
+ def getLiquidityAccount(self, account: str, currency: str) -> data.Account:
245
248
  return f"Assets:{account}:Liquidity:IB:{currency}"
246
249
 
247
- def getReceivableAccount(self, account: str):
250
+ def getReceivableAccount(self, account: str) -> data.Account:
248
251
  return f"Assets:{account}:Receivable:Verrechnungssteuer"
249
252
 
250
- def getIncomeAccount(self, account: str):
253
+ def getIncomeAccount(self, account: str) -> data.Account:
251
254
  return f"Income:{account}:Interest"
252
255
 
253
- def getFeeAccount(self, account: str):
256
+ def getFeeAccount(self, account: str) -> data.Account:
254
257
  return f"Expenses:{account}:Fees"
@@ -1,30 +1,32 @@
1
1
  import csv
2
- from io import StringIO
2
+ import re
3
3
 
4
+ import beangulp
4
5
  from beancount.core import amount, data
5
6
  from beancount.core.number import D
6
- from beancount.ingest import importer
7
- from beancount.ingest.importers.mixins import identifier
8
7
  from dateutil.parser import parse
9
8
 
10
9
 
11
- class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
10
+ class Importer(beangulp.Importer):
12
11
  """An importer for Neon 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 identify(self, filepath: str) -> bool:
18
+ return re.search(self._filepattern, filepath) is not None
20
19
 
21
- def file_account(self, file):
22
- return self.account
20
+ def name(self) -> str:
21
+ return super().name() + self._account
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
28
 
27
- with StringIO(file.contents()) as csvfile:
29
+ with open(filepath) as csvfile:
28
30
  reader = csv.DictReader(
29
31
  csvfile,
30
32
  [
@@ -55,7 +57,7 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
55
57
  metakv["original_amount"] = row["Original amount"]
56
58
  metakv["exchange_rate"] = row["Exchange rate"]
57
59
 
58
- meta = data.new_metadata(file.name, 0, metakv)
60
+ meta = data.new_metadata(filepath, 0, metakv)
59
61
  description = row["Description"].strip()
60
62
  if row["Subject"].strip() != "":
61
63
  description = description + ": " + row["Subject"].strip()
@@ -69,7 +71,7 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
69
71
  data.EMPTY_SET,
70
72
  data.EMPTY_SET,
71
73
  [
72
- data.Posting(self.account, amt, None, None, None, None),
74
+ data.Posting(self._account, amt, None, None, None, None),
73
75
  ],
74
76
  )
75
77
  entries.append(entry)