Skip to content

Instantly share code, notes, and snippets.

@danielquinn
Last active August 4, 2023 09:07
Show Gist options
  • Save danielquinn/5347d332c4875f2df652265e664454d1 to your computer and use it in GitHub Desktop.
Save danielquinn/5347d332c4875f2df652265e664454d1 to your computer and use it in GitHub Desktop.
An MT103 parser
#
# 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
@bbroto06
Copy link

bbroto06 commented Aug 8, 2019

  1. I copied your code and it compiled.

  2. Then I tried to use it this way :-

    mt103 = MT103("some-mt-103-string")
    print("basic header: {}, bank op code: {}, complete message: {}".format(
        mt103.basic_header,
        mt103.text.bank_operation_code
        mt103
    ))
  1. It gave Invalid Syntax, probably because you missed a comma at the end of this line :
    mt103.text.bank_operation_code

  2. So, after correction, now my code looks like this :

    mt103 = MT103("some-mt-103-string")
    print("basic header: {}, bank op code: {}, complete message: {}".format(
        mt103.basic_header,
        mt103.text.bank_operation_code,
        mt103
    ))
  1. Now I'm getting the following error :
    'NoneType' object has no attribute 'bank_operation_code'

  2. However, when I try to print mt103, it prints the entire MT103 message correctly.

  3. I'm trying to parse the sample message provided in this link, by using your code.

  4. Note : In place of "some-mt-103-string", I have used an actual string which contains the sample MT103 text mentioned in #7.

@danielquinn
Copy link
Author

danielquinn commented Aug 8, 2019

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.

@bbroto06
Copy link

bbroto06 commented Aug 8, 2019

Thanks a lot Daniel 👍 . I'll try to use mt103 module.

@khantrv
Copy link

khantrv commented Apr 2, 2021

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 ?

@danielquinn
Copy link
Author

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment