mt-940 5.0.0__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.
mt940/__about__.py ADDED
@@ -0,0 +1,24 @@
1
+ from importlib.metadata import (
2
+ PackageNotFoundError,
3
+ version as _version,
4
+ )
5
+
6
+ __title__ = 'MT940'
7
+ __package_name__ = 'mt-940'
8
+ __author__ = 'Rick van Hattem (wolph)'
9
+ __description__ = (
10
+ 'A library to parse MT940 files and returns smart Python collections for '
11
+ 'statistics and manipulation.'
12
+ )
13
+ __email__ = 'wolph@wol.ph'
14
+ __license__ = 'BSD'
15
+ __copyright__ = 'Copyright 2015 Rick van Hattem (wolph)'
16
+ __url__ = 'https://github.com/WoLpH/mt940'
17
+
18
+ # The version is single-sourced from the installed package metadata
19
+ # (pyproject.toml).
20
+ __version__: str
21
+ try:
22
+ __version__ = _version('mt-940')
23
+ except PackageNotFoundError: # pragma: no cover
24
+ __version__ = '0.0.0'
mt940/__init__.py ADDED
@@ -0,0 +1,43 @@
1
+ """mt940 — parse MT940 bank statement files into rich Python objects.
2
+
3
+ The high-level entry point is :func:`parse`, which accepts a filename, a file
4
+ handle, or raw ``str``/``bytes`` and returns a
5
+ :class:`~mt940.models.Transactions` collection you can iterate over. Use
6
+ :func:`parse_statements` for files that concatenate several statements, and
7
+ :class:`JSONEncoder` to serialize the result to JSON.
8
+
9
+ Example:
10
+ >>> import mt940
11
+ >>> data = (
12
+ ... ':20:REF\\n'
13
+ ... ':25:NL00BANK0123456789\\n'
14
+ ... ':28C:1/1\\n'
15
+ ... ':60F:C091019EUR1000,00\\n'
16
+ ... ':61:0910201020C500,00NTRFNONREF//B\\n'
17
+ ... ':86:Example transaction\\n'
18
+ ... ':62F:C091020EUR1500,00\\n'
19
+ ... )
20
+ >>> transactions = mt940.parse(data)
21
+ >>> len(transactions)
22
+ 1
23
+ >>> transactions[0].data['amount']
24
+ <500.00 EUR>
25
+ """
26
+
27
+ from . import json, models, parser, processors, tags, utils
28
+ from .__about__ import __version__
29
+ from .json import JSONEncoder
30
+ from .parser import parse, parse_statements
31
+
32
+ __all__ = [
33
+ 'JSONEncoder',
34
+ '__version__',
35
+ 'json',
36
+ 'models',
37
+ 'parse',
38
+ 'parse_statements',
39
+ 'parser',
40
+ 'processors',
41
+ 'tags',
42
+ 'utils',
43
+ ]
mt940/_types.py ADDED
@@ -0,0 +1,57 @@
1
+ """Shared type aliases and processor protocols.
2
+
3
+ These are the precise types used across the public API. The module is a *leaf*:
4
+ it imports nothing from the rest of the package, so it never participates in an
5
+ import cycle. The processor protocols therefore type their ``transactions`` and
6
+ ``tag`` arguments as :data:`~typing.Any`; the concrete processor functions in
7
+ :mod:`mt940.processors` still annotate them precisely.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ from collections.abc import Callable
14
+ from typing import IO, Any, Protocol
15
+
16
+ #: Accepted input for :func:`mt940.parse` and :func:`mt940.parse_statements`:
17
+ #: a path, raw ``str``/``bytes`` data, or an open binary/text file handle.
18
+ Source = str | bytes | os.PathLike[str] | IO[str] | IO[bytes]
19
+
20
+ #: A parsed tag dictionary. Intentionally dynamic: the available keys are
21
+ #: tag- and bank-specific, so a precise ``TypedDict`` would misrepresent it.
22
+ TagDict = dict[str, Any]
23
+
24
+
25
+ class PreProcessor(Protocol):
26
+ """A callable run *before* a tag value is turned into a model object."""
27
+
28
+ def __call__(
29
+ self,
30
+ transactions: Any,
31
+ tag: Any,
32
+ tag_dict: TagDict,
33
+ /,
34
+ *args: Any,
35
+ ) -> TagDict: ...
36
+
37
+
38
+ class PostProcessor(Protocol):
39
+ """A callable run *after* a tag value is turned into a model object."""
40
+
41
+ def __call__(
42
+ self,
43
+ transactions: Any,
44
+ tag: Any,
45
+ tag_dict: TagDict,
46
+ result: TagDict,
47
+ /,
48
+ ) -> TagDict: ...
49
+
50
+
51
+ #: A pre- or post-processor as stored in :attr:`Transactions.processors`.
52
+ #: The container mixes both kinds keyed by ``pre_*``/``post_*``, so the element
53
+ #: type stays callable-flexible while pinning the return type.
54
+ Processor = Callable[..., TagDict]
55
+
56
+ #: Mapping of processor-slot name to the processors registered for it.
57
+ Processors = dict[str, list[Processor]]
mt940/json.py ADDED
@@ -0,0 +1,78 @@
1
+ """JSON serialization for MT940 models.
2
+
3
+ This module exposes :class:`JSONEncoder`, a :class:`json.JSONEncoder` subclass
4
+ that knows how to serialize the model types returned by the parser (balances,
5
+ amounts, dates and the transaction collections).
6
+
7
+ Example:
8
+ >>> import json
9
+ >>> import mt940
10
+ >>> transactions = mt940.models.Transactions()
11
+ >>> json.dumps(transactions, cls=mt940.JSONEncoder)
12
+ '{"transactions": []}'
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import datetime
18
+ import decimal
19
+ import json
20
+ from typing import Any
21
+
22
+ from . import models
23
+
24
+
25
+ class JSONEncoder(json.JSONEncoder):
26
+ """Serialize MT940 model objects to JSON-compatible primitives.
27
+
28
+ Dates, datetimes, timedeltas, timezones and decimals are rendered as
29
+ strings; :class:`~mt940.models.Transactions`,
30
+ :class:`~mt940.models.Transaction`, :class:`~mt940.models.Balance` and
31
+ :class:`~mt940.models.Amount` are rendered as their ``data``/``__dict__``
32
+ mappings. Pass it as the ``cls`` argument to :func:`json.dumps`.
33
+ """
34
+
35
+ def default(self, o: Any) -> Any:
36
+ """Return a JSON-serializable representation of ``o``.
37
+
38
+ Args:
39
+ o: The object to serialize. ``o`` keeps the permissive ``Any`` type
40
+ of the overridden :meth:`json.JSONEncoder.default`.
41
+
42
+ Returns:
43
+ The serialized form of the object.
44
+ """
45
+ # The following types should simply be cast to strings
46
+ str_types = (
47
+ datetime.date,
48
+ datetime.datetime,
49
+ datetime.timedelta,
50
+ datetime.tzinfo,
51
+ decimal.Decimal,
52
+ )
53
+
54
+ dict_types = (models.Balance, models.Amount)
55
+
56
+ # Handle native types that should be converted to strings
57
+ if isinstance(o, str_types):
58
+ return str(o)
59
+
60
+ # Handling of the Transaction objects to include the
61
+ # actual transactions
62
+ elif isinstance(o, models.Transactions):
63
+ data: dict[str, Any] = o.data.copy()
64
+ data['transactions'] = o.transactions
65
+ return data
66
+
67
+ # If an object has a `data` attribute, return that instead of the
68
+ # `__dict__` to prevent loops
69
+ elif hasattr(o, 'data'):
70
+ return o.data
71
+
72
+ # Handle types that have a `__dict__` containing the data (doesn't work
73
+ # for classes using `__slots__` such as `datetime`)
74
+ elif isinstance(o, dict_types):
75
+ return o.__dict__
76
+
77
+ else: # pragma: no cover
78
+ return super().default(o)