Skip to content

Commit c55ab01

Browse files
committed
Add type stubs for example import configuration
1 parent a48e7f0 commit c55ab01

6 files changed

Lines changed: 134 additions & 7 deletions

File tree

stubs/beangulp/__init__.pyi

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import datetime
22
from abc import ABC
33
from abc import abstractmethod
4+
from collections.abc import Callable
45
from collections.abc import Sequence
56

67
from fava.beans.abc import Account
@@ -24,3 +25,23 @@ class Importer(ABC):
2425
def sort(
2526
self, entries: list[Directive], reverse: bool = False
2627
) -> None: ...
28+
29+
class Ingest:
30+
def __init__(
31+
self,
32+
importer: Sequence[Importer],
33+
hooks: Sequence[
34+
Callable[
35+
[
36+
Sequence[
37+
tuple[str, Sequence[Directive], str, Importer]
38+
], # new_entries
39+
Sequence[Directive], # existing_entries
40+
],
41+
Sequence[
42+
tuple[str, Sequence[Directive], str, Importer]
43+
], # (return value)
44+
]
45+
],
46+
) -> None: ...
47+
def __call__(self) -> None: ...
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# This file is intentionally left empty to make the directory a package
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import csv
2+
import datetime
3+
import enum
4+
from collections.abc import Sequence
5+
from typing import Any
6+
from typing import NamedTuple
7+
from typing import TypeAlias
8+
9+
import beancount.core.amount
10+
import beangulp
11+
from beancount.core import data
12+
13+
from fava.beans.abc import Directive
14+
15+
Row: TypeAlias = NamedTuple
16+
17+
class Order(enum.Enum):
18+
ASCENDING = ...
19+
DESCENDING = ...
20+
21+
class Column:
22+
name: str
23+
def __init__(self, name: str) -> None: ...
24+
def parse(self, value: str) -> Any: ...
25+
26+
class Date(Column):
27+
format: str
28+
def __init__(self, name: str, frmt: str) -> None: ...
29+
def parse(self, value: str) -> datetime.date: ...
30+
31+
class Amount(Column):
32+
subs: dict[str, str]
33+
def __init__(
34+
self, name: str, subs: dict[str, str] | None = None
35+
) -> None: ...
36+
def parse(self, value: str) -> beancount.core.amount.Amount: ...
37+
38+
class Columns(Column):
39+
columns: list[str]
40+
sep: str
41+
def __init__(self, *columns: str, sep: str = " ") -> None: ...
42+
def parse(self, value: str) -> str: ...
43+
44+
class CreditOrDebit(Column):
45+
credit_name: str
46+
debit_name: str
47+
def __init__(self, credit_name: str, debit_name: str) -> None: ...
48+
def parse(self, value: str) -> beancount.core.amount.Amount: ...
49+
50+
class CSVMeta(type): ...
51+
52+
class CSVReader:
53+
encoding: str
54+
skiplines: int
55+
names: bool
56+
dialect: str | csv.Dialect
57+
comments: str
58+
order: Order | None
59+
60+
def read(self, filepath: str) -> list[Row]: ...
61+
62+
class Importer(beangulp.Importer, CSVReader):
63+
def __init__(
64+
self, account: str, currency: str, flag: str = "*"
65+
) -> None: ...
66+
def account(self, filepath: str) -> str: ...
67+
def date(self, filepath: str) -> datetime.date: ...
68+
def extract(
69+
self, filepath: str, existing: Sequence[Directive]
70+
) -> list[Directive]: ...
71+
def finalize(
72+
self, txn: data.Transaction, row: Row
73+
) -> data.Transaction | None: ...
74+
def identify(self, filepath: str) -> bool: ...
75+
def metadata(self, filepath: str, lineno: int, row: Row) -> data.Meta: ...

tests/data/import_example_for_docs.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
11
# ruff: noqa: ERA001, INP001, ARG002
2+
# mypy: disable-error-code="assignment"
23
"""An example import configuration with explanations."""
34

45
from __future__ import annotations
56

67
import csv
78
from pathlib import Path
89
from tempfile import NamedTemporaryFile
10+
from typing import Any
911
from typing import TYPE_CHECKING
1012
from typing import TypeAlias
1113

1214
import beangulp # Importing tools
13-
import beangulp.importer
1415
from beancount.core import data # Transaction, Posting, ...
1516
from beangulp.importers import csvbase
1617

1718
if TYPE_CHECKING:
1819
import beancount
1920

20-
Importer: TypeAlias = beangulp.importer.Importer
21+
Importer: TypeAlias = beangulp.Importer
2122
Meta: TypeAlias = beancount.core.data.Meta
2223
Transaction: TypeAlias = beancount.core.data.Transaction
2324
Directive: TypeAlias = beancount.core.data.Directive
2425
# dynamically created NamedTuple, see docs of using functions
25-
Row: TypeAlias = "Row"
26+
Row: TypeAlias = Any
2627

2728

2829
class MyCSVImporter(csvbase.Importer):
@@ -149,7 +150,8 @@ def finalize(self, txn: Transaction, row: Row) -> Transaction:
149150
txn.postings.append(
150151
data.Posting(
151152
"Expenses:Unknown",
152-
-txn.postings[0].units,
153+
# "and" handles case if .units is None
154+
(txn.postings[0].units and -txn.postings[0].units),
153155
None,
154156
None,
155157
None,
@@ -197,5 +199,5 @@ def example_hook(
197199
# Allows to call this script as './import extract <filename.csv>'. Not needed
198200
# for Fava, but useful for debugging
199201
if __name__ == "__main__":
200-
ingest = beangulp.Ingest(CONFIG, HOOKS)
202+
ingest = beangulp.Ingest(CONFIG, HOOKS) # type: ignore[arg-type]
201203
ingest()

tests/importer.pyi

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import abc
2+
import datetime
3+
from collections.abc import Sequence
4+
5+
from fava.beans.abc import Account
6+
from fava.beans.abc import Directive
7+
8+
class Importer(abc.ABC):
9+
@property
10+
def name(self) -> str: ...
11+
@abc.abstractmethod
12+
def identify(self, filepath: str) -> bool: ...
13+
@abc.abstractmethod
14+
def account(self, filepath: str) -> Account: ...
15+
def date(self, filepath: str) -> datetime.date | None: ...
16+
def filename(self, filepath: str) -> str | None: ...
17+
def extract(
18+
self, filepath: str, existing: Sequence[Directive]
19+
) -> list[Directive]: ...
20+
def deduplicate(
21+
self, entries: list[Directive], existing: Sequence[Directive]
22+
) -> None: ...
23+
def sort(
24+
self, entries: list[Directive], reverse: bool = False
25+
) -> None: ...
26+
27+
class ImporterProtocol: ...
28+
class Adapter: ...

tests/test_import_documentation_example.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import TYPE_CHECKING
55

66
import beangulp
7-
import beangulp.extract
7+
import beangulp.extract # type: ignore[import-untyped]
88

99
import fava
1010
from fava.core import FavaLedger
@@ -40,7 +40,7 @@ def test_example_import(test_data_dir: Path) -> None:
4040
# This tests needs multiple steps as the User clicks multiple buttons until
4141
# the import starts
4242

43-
ing = fava.core.IngestModule(ledger)
43+
ing = fava.core.IngestModule(ledger) # type: ignore[attr-defined]
4444
# Read import configuration as defined in the fava option "import-config"
4545
ing.load_file()
4646
# Identify (file, importer) pairs based on the files it sees in the import

0 commit comments

Comments
 (0)