-
-
Save danielquinn/5347d332c4875f2df652265e664454d1 to your computer and use it in GitHub Desktop.
# | |
# This has since been turned into a proper Python3 module, so if you're | |
# running a modern version of Python, you can install it with pip: | |
# | |
# pip install mt103 | |
# | |
# The project is here: https://github.com/danielquinn/mt103 | |
# | |
from __future__ import unicode_literals | |
import re | |
from datetime import date | |
class MT103(object): | |
""" | |
Parses an MT103 standard banking format string into a string-like Python | |
object so you can do things like `mt103.basic_header` or `print(mt103)`. | |
Usage: | |
mt103 = MT103("some-mt-103-string") | |
print("basic header: {}, bank op code: {}, complete message: {}".format( | |
mt103.basic_header, | |
mt103.text.bank_operation_code | |
mt103 | |
)) | |
With considerable help from: | |
http://www.sepaforcorporates.com/swift-for-corporates/read-swift-message-structure/ | |
""" | |
MESSAGE_REGEX = re.compile( | |
"^" | |
"({1:(?P<basic_header>[^}]+)})?" | |
"({2:(?P<application_header>I[^}]+)})?" | |
"({3:(?P<user_header>({113:[A-Z]{4}})?({108:[A-Z0-9]{0,16}}))})?" | |
"({4:\s*(?P<text>.+?)\s*-})?" | |
"({5:(?P<trailer>[^}]+)})?" | |
"$", | |
re.DOTALL | |
) | |
def __init__(self, message): | |
if message is None: | |
message = "" | |
self.raw = message.strip() | |
self.basic_header = None | |
self.application_header = None | |
self.user_header = None | |
self.text = None | |
self.trailer = None | |
self._boolean = False | |
self._populate_by_parsing() | |
def __str__(self): | |
return self.raw | |
def __repr__(self): | |
return str(self) | |
def __bool__(self): | |
return self._boolean | |
__nonzero__ = __bool__ # Python 2 | |
def _populate_by_parsing(self): | |
if not self.raw: | |
return | |
m = self.MESSAGE_REGEX.match(self.raw) | |
self._boolean = bool(m) | |
if not m: | |
return None | |
self.basic_header = m.group("basic_header") | |
self.application_header = m.group("application_header") | |
self.user_header = m.group("user_header") | |
self.trailer = m.group("trailer") | |
self.text = Text(m.group("text") or "") | |
class Text(object): | |
""" | |
With considerable help from: | |
https://en.wikipedia.org/wiki/MT103 and | |
https://gist.github.com/dmcruz/9940a6b217ff701b8f3e | |
""" | |
REGEX = re.compile( | |
"^" | |
"(:20:(?P<transaction_reference>[^\s:]+)\s*)?" | |
"(:23B:(?P<bank_operation_code>[^\s:]+)\s*)?" | |
"(:32A:" | |
"(?P<value_date_year>\d\d)" # NOQA | |
"(?P<value_date_month>\d\d)" | |
"(?P<value_date_day>\d\d)" | |
"(?P<interbank_settled_currency>[A-Z]{3})" | |
"(?P<interbank_settled_amount>[\d,]+)" | |
"\s*)?" | |
"(:33B:" | |
"(?P<original_ordered_currency>[A-Z]{3})" | |
"(?P<original_ordered_amount>[\d,]+)" | |
"\s*)?" | |
"(:50[AFK]:(?P<ordering_customer>.*?)\s*(?=(:\d\d)?))?" | |
"(:52[AD]:(?P<ordering_institution>.*?)\s*(?=(:\d\d)?))?" | |
"(:53[ABD]:(?P<sender_correspondent>[^\s:]*)\s*)?" | |
"(:54[ABD]:(?P<receiver_correspondent>.*?)\s*(?=(:\d\d)?))?" | |
"(:56[ACD]:(?P<intermediary>.*?)\s*(?=(:\d\d)?))?" | |
"(:57[ABCD]:(?P<account_with_institution>.*?)\s*(?=(:\d\d)?))?" | |
"(:59A?:(?P<beneficiary>.*?)\s*(?=(:\d\d)?))?" | |
"(:70:(?P<remittance_information>.*?)\s*(?=(:\d\d)?))?" | |
"(:71A:(?P<details_of_charges>.*?)\s*(?=(:\d\d)?))?" | |
"(:72:(?P<sender_to_receiver_information>.*?)\s*(?=(:\d\d)?))?" | |
"(:77A:(?P<regulatory_reporting>.*?)\s*(?=(:\d\d)?))?" | |
"$", | |
re.DOTALL | |
) | |
def __init__(self, raw): | |
self.raw = raw | |
self.transaction_reference = None | |
self.bank_operation_code = None | |
self.interbank_settled_currency = None | |
self.interbank_settled_amount = None | |
self.original_ordered_currency = None | |
self.original_ordered_amount = None | |
self.ordering_customer = None | |
self.ordering_institution = None | |
self.sender_correspondent = None | |
self.receiver_correspondent = None | |
self.intermediary = None | |
self.account_with_institution = None | |
self.beneficiary = None | |
self.remittance_information = None | |
self.details_of_charges = None | |
self.sender_to_receiver_information = None | |
self.regulatory_reporting = None | |
self.date = None | |
self._boolean = False | |
self._populate_by_parsing() | |
def __str__(self): | |
return self.raw | |
def __repr__(self): | |
return str(self) | |
def __bool__(self): | |
return self._boolean | |
__nonzero__ = __bool__ # Python 2 | |
def _populate_by_parsing(self): | |
if not self.raw: | |
return | |
m = self.REGEX.match(self.raw) | |
self._boolean = bool(m) | |
if not m: | |
return | |
for k, v in m.groupdict().items(): | |
if v is None: | |
continue | |
if k.startswith("value_date_"): | |
continue | |
setattr(self, k, v) | |
try: | |
self.date = date( | |
2000 + int(m.group("value_date_year")), | |
int(m.group("value_date_month")), | |
int(m.group("value_date_day")) | |
) | |
except (ValueError, TypeError): | |
pass # Defaults to None |
This gist was just the beginning of the now polished mt103 module which you can install from PyPI. If that module fails with the same result you got on 5
above, then I'm guessing the parser is missing something and that this is a bug. Feel free to file an issue and I'll have a look when I can.
Update: I've used your supplied MT103 sample against the library and sure enough, it fails silently. I've created an issue to work on this, but I'll have to get to it when I have time as this is no longer my full-time job. Feel free to submit a pull request with a passing test and I'll roll your fix into the library.
Thanks a lot Daniel 👍 . I'll try to use mt103 module.
Hello, unfortunately the issue is still present :(. This code is the only option i have found over the internet so far after 2 days of searching and it seems that m = self.REGEX.match(self.raw) always returns none in both MT103 and Text classes. I took the liberty of editing your script and that is how i found the issue, I've tested with many examples but always same response. I think there might be an issue in the MESSAGE_REGEX definition. Will you be so kind and check it out ?
@khantrv as I said earlier, you should not be using this gist and instead pip install mt103
. If that fails to support your MT103 message, then you should open a bug report on that repo and one of us will get to it when we have time. Or better yet, you can submit a pull request if you figure it out yourself :-D
I copied your code and it compiled.
Then I tried to use it this way :-
It gave Invalid Syntax, probably because you missed a comma at the end of this line :
mt103.text.bank_operation_code
So, after correction, now my code looks like this :
Now I'm getting the following error :
'NoneType' object has no attribute 'bank_operation_code'
However, when I try to print mt103, it prints the entire MT103 message correctly.
I'm trying to parse the sample message provided in this link, by using your code.
Note : In place of "some-mt-103-string", I have used an actual string which contains the sample MT103 text mentioned in #7.