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 +24 -0
- mt940/__init__.py +43 -0
- mt940/_types.py +57 -0
- mt940/json.py +78 -0
- mt940/models.py +733 -0
- mt940/parser.py +161 -0
- mt940/processors.py +476 -0
- mt940/py.typed +0 -0
- mt940/tags.py +664 -0
- mt940/utils.py +84 -0
- mt_940-5.0.0.dist-info/METADATA +270 -0
- mt_940-5.0.0.dist-info/RECORD +14 -0
- mt_940-5.0.0.dist-info/WHEEL +4 -0
- mt_940-5.0.0.dist-info/licenses/LICENSE +27 -0
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)
|