Python library for Swift MT and ISO 20022 message parsing and generation
Project description
swiftlib
A production-quality Python 3 library for parsing and generating SWIFT MT messages and ISO 20022 XML messages. Designed as a modern Python alternative to the Java prowide-core library.
Overview
swiftlib provides:
- SWIFT MT parsing and generation — full support for MT103, MT202, MT202COV, MT515, MT700, MT900, MT910, MT940, MT942, MT950
- ISO 20022 XML parsing and generation — pain.001, camt.053, pacs.008 (extensible to any message type)
- MT ↔ ISO 20022 converters — MT103 ↔ pain.001, MT103 ↔ pacs.008, MT940 ↔ camt.053
- Typed Python dataclasses — all message components are dataclasses with full type hints
- Fluent builder pattern — intuitive API for constructing messages step-by-step
Why better than prowide-core?
| Feature | swiftlib (Python) | prowide-core (Java) |
|---|---|---|
| Runtime | No JVM required | Requires JDK 8+ |
| Install | pip install swiftlib |
Maven/Gradle dependency |
| Types | Python type hints + mypy | Java generics |
| Data model | Python dataclasses |
Java POJOs/builders |
| Serialisation | Native json.dumps, no mapping required |
Requires Jackson/Gson |
| Decimal precision | decimal.Decimal (exact) |
BigDecimal (verbose) |
| Date handling | datetime.date objects |
java.time.LocalDate |
| Async compatible | Yes — no shared state | JVM threading model |
| IDE support | Full autocomplete via type hints | Depends on IDE plugin |
| Integration | FastAPI, Django, Celery, asyncio | Spring, Jakarta EE |
Installation
pip install swiftlib
Requirements: Python 3.10+, lxml>=4.9, pydantic>=2.0
Quick Start
Parsing an MT103
from swiftlib.mt import parse
raw = """{1:F01BANKBEBBAXXX0000000000}{2:I103BANKDEFFXXXXU}{4:
:20:FT21099B59YFW001
:23B:CRED
:32A:210101USD10000,
:50K:/12345678
JOHN DOE
123 MAIN ST
:59:/98765432
JANE SMITH
:70:PAYMENT FOR SERVICES
:71A:OUR
-}"""
msg = parse(raw) # Returns a typed MT103 instance
print(msg.message_type) # "103"
print(msg.amount) # Decimal("10000")
print(msg.currency) # "USD"
print(msg.value_date) # date(2021, 1, 1)
print(msg.charges) # "OUR"
Building an MT103
from swiftlib.mt.messages.mt103 import MT103Builder
from decimal import Decimal
from datetime import date
mt103 = (
MT103Builder()
.transaction_reference("FT21099B59YFW001")
.bank_operation_code("CRED")
.value_date_currency_amount(date(2021, 1, 1), "USD", Decimal("10000.00"))
.ordering_customer("/12345678", "JOHN DOE", ["123 MAIN ST", "NEW YORK NY"])
.account_with_institution("A", "BANKDEFF")
.beneficiary("/98765432", "JANE SMITH", ["456 OAK AVE", "BERLIN 10115"])
.remittance_info("PAYMENT FOR SERVICES")
.charges("OUR")
.build()
)
print(mt103.to_swift())
print(mt103.to_json())
Parsing an ISO 20022 pain.001
from swiftlib.iso20022 import parse
with open("payment.xml") as f:
msg = parse(f.read())
print(msg.group_header.message_id)
print(msg.payment_information[0].debtor.name)
print(msg.payment_information[0].credit_transfer_transactions[0].amount.amount)
Generating a pain.001
from swiftlib.iso20022.messages.pain001 import (
Pain001Builder, Party, PaymentInformation,
CreditTransferTransaction, PaymentIdentification,
InstructedAmount, AccountIdentification,
BranchAndFinancialInstitutionIdentification,
FinancialInstitutionIdentification,
)
from datetime import date, datetime
from decimal import Decimal
fi = FinancialInstitutionIdentification(bic="BANKDEFF")
agt = BranchAndFinancialInstitutionIdentification(financial_institution=fi)
tx = CreditTransferTransaction(
payment_id=PaymentIdentification(end_to_end_id="E2E-001"),
amount=InstructedAmount(currency="EUR", amount=Decimal("5000.00")),
charge_bearer="SLEV",
creditor_agent=agt,
creditor=Party(name="BENEFICIARY"),
creditor_account=AccountIdentification(iban="DE98765432109876543210"),
)
pi = PaymentInformation(
payment_info_id="PI-001",
payment_method="TRF",
batch_booking=False,
number_of_transactions=1,
control_sum=Decimal("5000.00"),
payment_type_info=None,
requested_execution_date=date(2021, 1, 1),
debtor=Party(name="PAYER"),
debtor_account=AccountIdentification(iban="US12345678901234567890"),
debtor_agent=agt,
credit_transfer_transactions=[tx],
)
pain001 = (
Pain001Builder()
.message_id("MSG-001")
.creation_datetime(datetime.now())
.initiating_party(Party(name="MY COMPANY"))
.add_payment_information(pi)
.build()
)
xml = pain001.to_xml()
Supported Message Types
SWIFT MT
| Message Type | Description | Status |
|---|---|---|
| MT103 | Single Customer Credit Transfer | Full |
| MT202 | General Financial Institution Transfer | Full |
| MT202COV | General FI Transfer (Cover) | Full |
| MT515 | Client Confirmation of Purchase/Sale | Full |
| MT700 | Issue of a Documentary Credit | Full |
| MT900 | Confirmation of Debit | Full |
| MT910 | Confirmation of Credit | Full |
| MT940 | Customer Statement Message | Full |
| MT942 | Interim Transaction Report | Full |
| MT950 | Statement Message | Full |
ISO 20022
| Message Type | Description | Status |
|---|---|---|
| pain.001.001.09 | Customer Credit Transfer Initiation | Full |
| pain.001.001.03 | Customer Credit Transfer Initiation (v3) | Supported |
| camt.053.001.08 | Bank to Customer Statement | Full |
| camt.053.001.02 | Bank to Customer Statement (v2) | Supported |
| pacs.008.001.08 | FI to FI Customer Credit Transfer | Full |
| pacs.008.001.02 | FI to FI Customer Credit Transfer (v2) | Supported |
API Reference Highlights
swiftlib.mt
from swiftlib.mt import parse, MT103, MT940, MT202
# Parse any MT message (returns typed instance)
msg = parse(raw_swift_string)
# Parse specifically as MT103
mt103 = MT103.from_swift(raw)
# Typed property access
mt103.transaction_reference # str
mt103.value_date # datetime.date
mt103.currency # str
mt103.amount # Decimal
mt103.charges # "OUR" | "SHA" | "BEN"
mt103.ordering_customer # str (multiline)
mt103.beneficiary # str (multiline)
# Validation
errors = mt103.validate() # list[str]
mt103.is_valid() # bool
# Serialisation
mt103.to_swift() # SWIFT FIN string
mt103.to_dict() # dict
mt103.to_json() # JSON string
swiftlib.iso20022
from swiftlib.iso20022 import ISO20022Parser, ISO20022Generator
parser = ISO20022Parser()
msg = parser.parse(xml_string) # auto-detect type
msg = parser.parse_file("payment.xml") # from file
generator = ISO20022Generator()
xml = generator.generate(pain001) # str
xml_bytes = generator.generate_bytes(pain001) # bytes
swiftlib.converters
from swiftlib.converters import (
MT103ToPain001Converter,
MT103ToPacs008Converter,
MT940ToCamt053Converter,
Pain001ToMT103Converter,
Pacs008ToMT103Converter,
)
# MT103 → pain.001
pain001 = MT103ToPain001Converter().convert(mt103)
# MT103 → pacs.008
pacs008 = MT103ToPacs008Converter().convert(mt103)
# pain.001 → list[MT103]
mt103_list = Pain001ToMT103Converter().convert(pain001)
# MT940 → camt.053
camt053 = MT940ToCamt053Converter().convert(mt940)
Conversion
from swiftlib.mt import MT103
from swiftlib.converters import MT103ToPain001Converter, Pain001ToMT103Converter
# Parse MT103
mt103 = MT103.from_swift(raw_mt103)
# Convert to pain.001
pain001 = MT103ToPain001Converter().convert(mt103)
xml = pain001.to_xml()
# Convert back to MT103
mt103_list = Pain001ToMT103Converter().convert(pain001)
restored = mt103_list[0]
print(restored.to_swift())
Integration Examples
FastAPI
from fastapi import FastAPI, HTTPException
from swiftlib.mt import parse
from swiftlib.exceptions import ParseError
app = FastAPI()
@app.post("/parse-mt")
async def parse_mt(body: str):
try:
msg = parse(body)
return msg.to_dict()
except ParseError as e:
raise HTTPException(status_code=400, detail=str(e))
@app.post("/convert/mt103-to-xml")
async def convert_mt103_to_xml(body: str):
from swiftlib.mt.messages.mt103 import MT103
from swiftlib.converters import MT103ToPain001Converter
mt103 = MT103.from_swift(body)
pain001 = MT103ToPain001Converter().convert(mt103)
return {"xml": pain001.to_xml()}
Django
# views.py
from django.http import JsonResponse, HttpRequest
from swiftlib.mt import parse
def parse_swift(request: HttpRequest) -> JsonResponse:
raw = request.body.decode("utf-8")
try:
msg = parse(raw)
return JsonResponse(msg.to_dict())
except Exception as e:
return JsonResponse({"error": str(e)}, status=400)
Celery
from celery import Celery
from swiftlib.mt import parse
from swiftlib.converters import MT103ToPain001Converter
app = Celery("tasks", broker="redis://localhost:6379/0")
@app.task
def process_mt103(raw_swift: str) -> str:
"""Process an MT103 and return pain.001 XML."""
from swiftlib.mt.messages.mt103 import MT103
mt103 = MT103.from_swift(raw_swift)
pain001 = MT103ToPain001Converter().convert(mt103)
return pain001.to_xml()
asyncio
import asyncio
from swiftlib.mt import parse
# swiftlib is thread-safe and stateless — safe to use in async code
async def process_messages(messages: list[str]) -> list[dict]:
loop = asyncio.get_event_loop()
results = []
for raw in messages:
# Run CPU-bound parsing in thread pool to avoid blocking event loop
msg = await loop.run_in_executor(None, parse, raw)
results.append(msg.to_dict())
return results
Building from Source
git clone https://github.com/your-org/swiftlib.git
cd swiftlib
pip install -e ".[dev]"
Running Tests
# All tests
pytest
# With coverage
pytest --cov=swiftlib --cov-report=html
# Specific test file
pytest tests/mt/test_mt103.py -v
# Specific test
pytest tests/mt/test_parser.py::TestParseMT103Basic::test_parse_mt103_basic -v
Code Quality
# Lint
ruff check src/
# Type check
mypy src/swiftlib/
# Format
ruff format src/
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/mt700-improvements) - Add tests for your changes
- Ensure all tests pass (
pytest) - Ensure type checks pass (
mypy src/swiftlib/) - Submit a pull request
Adding a New MT Message Type
- Create
src/swiftlib/mt/messages/mtXXX.py - Subclass
MTMessage, define_MESSAGE_TYPE,_MANDATORY_FIELDS,_FIELD_ORDER - Add typed property accessors
- Implement a builder class
- Register in
src/swiftlib/mt/messages/__init__.py(_MESSAGE_REGISTRY) - Add tests in
tests/mt/
Adding a New ISO 20022 Message Type
- Create a dataclass module in
src/swiftlib/iso20022/messages/ - Add namespace to
namespaces.py - Add parser method in
ISO20022Parser._dispatch() - Add generator method in
ISO20022Generator.generate() - Add tests in
tests/iso20022/
License
MIT License. See LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file swiftlib-1.0.0.tar.gz.
File metadata
- Download URL: swiftlib-1.0.0.tar.gz
- Upload date:
- Size: 54.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
baa95523355d7b979de33831908aacfde802eb27f19f47eb91b3d8445e2d249e
|
|
| MD5 |
cf187eb90ec1af9d6ea9c9eeeffea470
|
|
| BLAKE2b-256 |
da896db610e133f879f408d6c1f5a559dcd042b14a2be20430470e31b7bb2cc6
|
File details
Details for the file swiftlib-1.0.0-py3-none-any.whl.
File metadata
- Download URL: swiftlib-1.0.0-py3-none-any.whl
- Upload date:
- Size: 56.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
52c633e6df48e6cbf0f78445f8751c1d6da81f45336cc446f9c8c06b3080413c
|
|
| MD5 |
43a3d5de206fbb867c19d51d2fba5aa5
|
|
| BLAKE2b-256 |
c01a2e053330f911110ba021435dfcb2093275f7b163c496e515a8f96d511b5f
|