swiftlib 1.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.
swiftlib/__init__.py ADDED
@@ -0,0 +1,23 @@
1
+ """
2
+ swiftlib — Python library for SWIFT MT and ISO 20022 message parsing and generation.
3
+ """
4
+
5
+ from swiftlib.exceptions import (
6
+ FieldFormatError,
7
+ GenerationError,
8
+ ParseError,
9
+ SwiftLibError,
10
+ UnsupportedMessageType,
11
+ ValidationError,
12
+ )
13
+
14
+ __version__ = "1.0.0"
15
+ __all__ = [
16
+ "__version__",
17
+ "SwiftLibError",
18
+ "ParseError",
19
+ "ValidationError",
20
+ "GenerationError",
21
+ "UnsupportedMessageType",
22
+ "FieldFormatError",
23
+ ]
@@ -0,0 +1,18 @@
1
+ """
2
+ swiftlib.converters — MT ↔ ISO 20022 message converters.
3
+ """
4
+
5
+ from swiftlib.converters.iso_to_mt import Pacs008ToMT103Converter, Pain001ToMT103Converter
6
+ from swiftlib.converters.mt_to_iso import (
7
+ MT103ToPacs008Converter,
8
+ MT103ToPain001Converter,
9
+ MT940ToCamt053Converter,
10
+ )
11
+
12
+ __all__ = [
13
+ "MT103ToPain001Converter",
14
+ "MT103ToPacs008Converter",
15
+ "MT940ToCamt053Converter",
16
+ "Pain001ToMT103Converter",
17
+ "Pacs008ToMT103Converter",
18
+ ]
@@ -0,0 +1,129 @@
1
+ """
2
+ swiftlib.converters.iso_to_mt — Convert ISO 20022 messages to SWIFT MT.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from datetime import datetime
8
+
9
+ __all__ = [
10
+ "Pain001ToMT103Converter",
11
+ "Pacs008ToMT103Converter",
12
+ ]
13
+
14
+
15
+ class Pain001ToMT103Converter:
16
+ """Convert a pain.001 :class:`~swiftlib.iso20022.messages.pain001.Pain001` to a list of MT103 messages."""
17
+
18
+ def convert(self, pain001: "Pain001") -> "list[MT103]": # noqa: F821
19
+ from swiftlib.mt.messages.mt103 import MT103Builder
20
+
21
+ result = []
22
+ for pi in pain001.payment_information:
23
+ for tx in pi.credit_transfer_transactions:
24
+ builder = MT103Builder()
25
+
26
+ # Transaction reference
27
+ ref = tx.payment_id.end_to_end_id or tx.payment_id.instruction_id or "NOTPROVIDED"
28
+ builder.transaction_reference(ref[:16])
29
+
30
+ # Bank operation code
31
+ builder.bank_operation_code("CRED")
32
+
33
+ # Value date + currency + amount
34
+ from swiftlib.iso20022.messages.pain001 import InstructedAmount
35
+ if isinstance(tx.amount, InstructedAmount):
36
+ ccy = tx.amount.currency
37
+ amount = tx.amount.amount
38
+ else:
39
+ ccy = tx.amount.currency_of_transfer
40
+ amount = tx.amount.amount
41
+
42
+ builder.value_date_currency_amount(pi.requested_execution_date, ccy, amount)
43
+
44
+ # Ordering customer (debtor)
45
+ dbtr = pi.debtor
46
+ dbtr_acct = pi.debtor_account
47
+ acct_id = dbtr_acct.iban or dbtr_acct.other_id or "NOTPROVIDED"
48
+ builder.ordering_customer(
49
+ account=acct_id,
50
+ name=dbtr.name[:35],
51
+ address=[],
52
+ )
53
+
54
+ # Account with institution (creditor agent)
55
+ if tx.creditor_agent is not None:
56
+ bic = tx.creditor_agent.financial_institution.bic
57
+ if bic:
58
+ builder.account_with_institution("A", bic)
59
+
60
+ # Beneficiary (creditor)
61
+ cdtr = tx.creditor
62
+ cdtr_acct = tx.creditor_account
63
+ cdtr_acct_id = cdtr_acct.iban or cdtr_acct.other_id or "NOTPROVIDED"
64
+ builder.beneficiary(
65
+ account=cdtr_acct_id,
66
+ name=cdtr.name[:35],
67
+ address=[],
68
+ )
69
+
70
+ # Remittance information
71
+ if tx.remittance_info is not None:
72
+ for ustrd in tx.remittance_info.unstructured:
73
+ builder.remittance_info(ustrd[:140])
74
+ break # only one :70: allowed
75
+
76
+ # Charges
77
+ charge_map = {"DEBT": "OUR", "CRED": "BEN", "SHAR": "SHA", "SLEV": "SHA"}
78
+ builder.charges(charge_map.get(tx.charge_bearer, "SHA"))
79
+
80
+ result.append(builder.build())
81
+
82
+ return result
83
+
84
+
85
+ class Pacs008ToMT103Converter:
86
+ """Convert a pacs.008 :class:`~swiftlib.iso20022.messages.pacs008.FIToFICreditTransfer` to a list of MT103 messages."""
87
+
88
+ def convert(self, pacs008: "FIToFICreditTransfer") -> "list[MT103]": # noqa: F821
89
+ from swiftlib.mt.messages.mt103 import MT103Builder
90
+
91
+ result = []
92
+ for tx in pacs008.credit_transfer_transaction_information:
93
+ builder = MT103Builder()
94
+
95
+ ref = tx.payment_id.end_to_end_id or tx.payment_id.transaction_id or "NOTPROVIDED"
96
+ builder.transaction_reference(ref[:16])
97
+ builder.bank_operation_code("CRED")
98
+
99
+ ccy = tx.interbank_settlement_amount.currency
100
+ amount = tx.interbank_settlement_amount.amount
101
+ builder.value_date_currency_amount(tx.interbank_settlement_date, ccy, amount)
102
+
103
+ # Debtor
104
+ dbtr_acct_id = tx.debtor_account.iban or tx.debtor_account.other_id or "NOTPROVIDED"
105
+ builder.ordering_customer(account=dbtr_acct_id, name=tx.debtor.name[:35])
106
+
107
+ # Debtor agent
108
+ if tx.debtor_agent.financial_institution.bic:
109
+ builder.ordering_institution("A", tx.debtor_agent.financial_institution.bic)
110
+
111
+ # Creditor agent
112
+ if tx.creditor_agent.financial_institution.bic:
113
+ builder.account_with_institution("A", tx.creditor_agent.financial_institution.bic)
114
+
115
+ # Creditor
116
+ cdtr_acct_id = tx.creditor_account.iban or tx.creditor_account.other_id or "NOTPROVIDED"
117
+ builder.beneficiary(account=cdtr_acct_id, name=tx.creditor.name[:35])
118
+
119
+ if tx.remittance_info:
120
+ for ustrd in tx.remittance_info.unstructured:
121
+ builder.remittance_info(ustrd[:140])
122
+ break
123
+
124
+ charge_map = {"DEBT": "OUR", "CRED": "BEN", "SHAR": "SHA", "SLEV": "SHA"}
125
+ builder.charges(charge_map.get(tx.charge_bearer, "SHA"))
126
+
127
+ result.append(builder.build())
128
+
129
+ return result
@@ -0,0 +1,337 @@
1
+ """
2
+ swiftlib.converters.mt_to_iso — Convert SWIFT MT messages to ISO 20022.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import re
8
+ from datetime import date, datetime
9
+ from decimal import Decimal
10
+
11
+ from swiftlib.exceptions import GenerationError
12
+
13
+ __all__ = [
14
+ "MT103ToPain001Converter",
15
+ "MT103ToPacs008Converter",
16
+ "MT940ToCamt053Converter",
17
+ ]
18
+
19
+
20
+ def _parse_party_field(swift_party: str) -> tuple[str | None, str, list[str]]:
21
+ """Parse a SWIFT party field (e.g., :50K: or :59:) into (account, name, address)."""
22
+ lines = swift_party.strip().split("\n")
23
+ account: str | None = None
24
+ name: str = ""
25
+ address: list[str] = []
26
+
27
+ if not lines:
28
+ return account, name, address
29
+
30
+ first = lines[0]
31
+ if first.startswith("/"):
32
+ account = first[1:].strip()
33
+ if len(lines) > 1:
34
+ name = lines[1]
35
+ address = lines[2:]
36
+ else:
37
+ name = first
38
+ address = lines[1:]
39
+
40
+ return account, name, address
41
+
42
+
43
+ class MT103ToPain001Converter:
44
+ """Convert an :class:`~swiftlib.mt.messages.mt103.MT103` to a :class:`~swiftlib.iso20022.messages.pain001.Pain001`."""
45
+
46
+ def convert(self, mt103: "MT103") -> "Pain001": # noqa: F821
47
+ from swiftlib.iso20022.messages.pain001 import (
48
+ AccountIdentification,
49
+ BranchAndFinancialInstitutionIdentification,
50
+ CreditTransferTransaction,
51
+ FinancialInstitutionIdentification,
52
+ GroupHeader,
53
+ InstructedAmount,
54
+ Pain001,
55
+ Party,
56
+ PaymentIdentification,
57
+ PaymentInformation,
58
+ RemittanceInformation,
59
+ )
60
+
61
+ msg_id = mt103.transaction_reference or f"MT103-{id(mt103)}"
62
+ now = datetime.now()
63
+
64
+ init_pty = Party(name=mt103.sender[:35] if mt103.sender else "UNKNOWN")
65
+ gh = GroupHeader(
66
+ message_id=msg_id,
67
+ creation_datetime=now,
68
+ number_of_transactions=1,
69
+ control_sum=mt103.amount or Decimal("0"),
70
+ initiating_party=init_pty,
71
+ )
72
+
73
+ # Ordering customer (debtor)
74
+ ord_cust_raw = mt103.ordering_customer
75
+ oc_account, oc_name, oc_address = _parse_party_field(ord_cust_raw)
76
+ debtor = self._map_party(ord_cust_raw)
77
+ debtor_account = AccountIdentification(iban=oc_account)
78
+
79
+ # Ordering institution (debtor agent)
80
+ dbtr_agt_bic: str | None = None
81
+ for tag in ("52A", "52D"):
82
+ v = mt103.get_field_value(tag)
83
+ if v:
84
+ # BIC is typically the last line or the whole value
85
+ lines = v.strip().split("\n")
86
+ dbtr_agt_bic = lines[-1].strip()
87
+ break
88
+ debtor_agent = BranchAndFinancialInstitutionIdentification(
89
+ financial_institution=FinancialInstitutionIdentification(bic=dbtr_agt_bic)
90
+ )
91
+
92
+ # Account with institution (creditor agent)
93
+ cdtr_agt_bic: str | None = None
94
+ for tag in ("57A", "57D"):
95
+ v = mt103.get_field_value(tag)
96
+ if v:
97
+ lines = v.strip().split("\n")
98
+ cdtr_agt_bic = lines[-1].strip()
99
+ break
100
+ creditor_agent = BranchAndFinancialInstitutionIdentification(
101
+ financial_institution=FinancialInstitutionIdentification(bic=cdtr_agt_bic)
102
+ )
103
+
104
+ # Beneficiary (creditor)
105
+ bene_raw = mt103.beneficiary
106
+ bene_account, bene_name, bene_address = _parse_party_field(bene_raw)
107
+ creditor = self._map_party(bene_raw)
108
+ creditor_account = AccountIdentification(iban=bene_account)
109
+
110
+ # Amount
111
+ amount = self._map_amount(mt103)
112
+
113
+ # Remittance
114
+ rmt_info: RemittanceInformation | None = None
115
+ rmt_raw = mt103.remittance_info
116
+ if rmt_raw:
117
+ rmt_info = RemittanceInformation(unstructured=[rmt_raw])
118
+
119
+ pmt_id = PaymentIdentification(
120
+ instruction_id=msg_id,
121
+ end_to_end_id=msg_id,
122
+ )
123
+
124
+ charge_bearer_map = {"OUR": "DEBT", "BEN": "CRED", "SHA": "SHAR"}
125
+ charge_bearer = charge_bearer_map.get(mt103.charges, "SLEV")
126
+
127
+ tx = CreditTransferTransaction(
128
+ payment_id=pmt_id,
129
+ amount=amount,
130
+ charge_bearer=charge_bearer,
131
+ creditor_agent=creditor_agent,
132
+ creditor=creditor,
133
+ creditor_account=creditor_account,
134
+ remittance_info=rmt_info,
135
+ )
136
+
137
+ exec_date = mt103.value_date or date.today()
138
+
139
+ pi = PaymentInformation(
140
+ payment_info_id=f"{msg_id}-001",
141
+ payment_method="TRF",
142
+ batch_booking=False,
143
+ number_of_transactions=1,
144
+ control_sum=mt103.amount or Decimal("0"),
145
+ payment_type_info=None,
146
+ requested_execution_date=exec_date,
147
+ debtor=debtor,
148
+ debtor_account=debtor_account,
149
+ debtor_agent=debtor_agent,
150
+ credit_transfer_transactions=[tx],
151
+ )
152
+
153
+ return Pain001(group_header=gh, payment_information=[pi])
154
+
155
+ def _map_party(self, swift_party: str) -> "Party": # noqa: F821
156
+ from swiftlib.iso20022.messages.pain001 import Party, PostalAddress
157
+ account, name, address = _parse_party_field(swift_party)
158
+ postal: PostalAddress | None = None
159
+ if address:
160
+ postal = PostalAddress(address_lines=address)
161
+ return Party(name=name or "", postal_address=postal)
162
+
163
+ def _map_amount(self, mt103: "MT103") -> "InstructedAmount": # noqa: F821
164
+ from swiftlib.iso20022.messages.pain001 import InstructedAmount
165
+ return InstructedAmount(
166
+ currency=mt103.currency or "",
167
+ amount=mt103.amount or Decimal("0"),
168
+ )
169
+
170
+
171
+ class MT103ToPacs008Converter:
172
+ """Convert an MT103 to a pacs.008 :class:`~swiftlib.iso20022.messages.pacs008.FIToFICreditTransfer`."""
173
+
174
+ def convert(self, mt103: "MT103") -> "FIToFICreditTransfer": # noqa: F821
175
+ from swiftlib.iso20022.messages.pacs008 import (
176
+ CreditTransferTransactionInfo,
177
+ FIToFICreditTransfer,
178
+ GroupHeader,
179
+ InterBankSettlementAmount,
180
+ SettlementInstruction,
181
+ )
182
+ from swiftlib.iso20022.messages.pain001 import (
183
+ AccountIdentification,
184
+ BranchAndFinancialInstitutionIdentification,
185
+ FinancialInstitutionIdentification,
186
+ Party,
187
+ PaymentIdentification,
188
+ RemittanceInformation,
189
+ )
190
+
191
+ now = datetime.now()
192
+ msg_id = mt103.transaction_reference or f"PACS008-{id(mt103)}"
193
+
194
+ gh = GroupHeader(
195
+ message_id=msg_id,
196
+ creation_datetime=now,
197
+ number_of_transactions=1,
198
+ settlement_information=SettlementInstruction(settlement_method="INDA"),
199
+ )
200
+
201
+ iba = InterBankSettlementAmount(
202
+ currency=mt103.currency or "",
203
+ amount=mt103.amount or Decimal("0"),
204
+ )
205
+
206
+ # Parties
207
+ ord_raw = mt103.ordering_customer
208
+ oc_account, oc_name, _ = _parse_party_field(ord_raw)
209
+ debtor = Party(name=oc_name or "")
210
+ debtor_account = AccountIdentification(iban=oc_account)
211
+
212
+ bene_raw = mt103.beneficiary
213
+ bene_account, bene_name, _ = _parse_party_field(bene_raw)
214
+ creditor = Party(name=bene_name or "")
215
+ creditor_account = AccountIdentification(iban=bene_account)
216
+
217
+ def _get_bic(tags: list[str]) -> str | None:
218
+ for tag in tags:
219
+ v = mt103.get_field_value(tag)
220
+ if v:
221
+ return v.strip().split("\n")[-1].strip()
222
+ return None
223
+
224
+ dbtr_agt = BranchAndFinancialInstitutionIdentification(
225
+ financial_institution=FinancialInstitutionIdentification(
226
+ bic=_get_bic(["52A", "52D"])
227
+ )
228
+ )
229
+ cdtr_agt = BranchAndFinancialInstitutionIdentification(
230
+ financial_institution=FinancialInstitutionIdentification(
231
+ bic=_get_bic(["57A", "57D"])
232
+ )
233
+ )
234
+
235
+ rmt_info: RemittanceInformation | None = None
236
+ if mt103.remittance_info:
237
+ rmt_info = RemittanceInformation(unstructured=[mt103.remittance_info])
238
+
239
+ charge_bearer_map = {"OUR": "DEBT", "BEN": "CRED", "SHA": "SHAR"}
240
+
241
+ tx = CreditTransferTransactionInfo(
242
+ payment_id=PaymentIdentification(
243
+ instruction_id=msg_id,
244
+ end_to_end_id=msg_id,
245
+ transaction_id=msg_id,
246
+ ),
247
+ interbank_settlement_amount=iba,
248
+ interbank_settlement_date=mt103.value_date or date.today(),
249
+ charge_bearer=charge_bearer_map.get(mt103.charges, "SLEV"),
250
+ instructing_agent=None,
251
+ instructed_agent=None,
252
+ debtor=debtor,
253
+ debtor_account=debtor_account,
254
+ debtor_agent=dbtr_agt,
255
+ creditor_agent=cdtr_agt,
256
+ creditor=creditor,
257
+ creditor_account=creditor_account,
258
+ remittance_info=rmt_info,
259
+ )
260
+
261
+ return FIToFICreditTransfer(
262
+ group_header=gh, credit_transfer_transaction_information=[tx]
263
+ )
264
+
265
+
266
+ class MT940ToCamt053Converter:
267
+ """Convert an MT940 to a camt.053 :class:`~swiftlib.iso20022.messages.camt053.Camt053`."""
268
+
269
+ def convert(self, mt940: "MT940") -> "Camt053": # noqa: F821
270
+ from swiftlib.iso20022.messages.camt053 import (
271
+ Balance,
272
+ CashAccount,
273
+ Camt053,
274
+ GroupHeader053,
275
+ ReportEntry,
276
+ Statement,
277
+ )
278
+
279
+ now = datetime.now()
280
+ msg_id = mt940.transaction_reference or f"CAMT053-{id(mt940)}"
281
+
282
+ gh = GroupHeader053(message_id=msg_id, creation_datetime=now)
283
+
284
+ acct_raw = mt940.account
285
+ acct = CashAccount(
286
+ iban=acct_raw if acct_raw.startswith("LT") or re.match(r"[A-Z]{2}\d{2}", acct_raw) else None,
287
+ other_id=acct_raw if not (acct_raw.startswith("LT") or re.match(r"[A-Z]{2}\d{2}", acct_raw)) else None,
288
+ )
289
+
290
+ balances: list[Balance] = []
291
+ opening = mt940.opening_balance
292
+ if opening is not None:
293
+ dc = "DBIT" if opening.debit_credit == "D" else "CRDT"
294
+ balances.append(Balance(
295
+ type_code="OPBD",
296
+ amount=opening.amount,
297
+ currency=opening.currency,
298
+ credit_debit_indicator=dc,
299
+ date=opening.date,
300
+ ))
301
+
302
+ closing = mt940.closing_balance
303
+ if closing is not None:
304
+ dc = "DBIT" if closing.debit_credit == "D" else "CRDT"
305
+ balances.append(Balance(
306
+ type_code="CLBD",
307
+ amount=closing.amount,
308
+ currency=closing.currency,
309
+ credit_debit_indicator=dc,
310
+ date=closing.date,
311
+ ))
312
+
313
+ entries = [self._map_statement_line(line) for line in mt940.statement_lines]
314
+
315
+ stmt = Statement(
316
+ identification=msg_id,
317
+ creation_datetime=now,
318
+ account=acct,
319
+ balance=balances,
320
+ transactions=entries,
321
+ )
322
+
323
+ return Camt053(group_header=gh, statements=[stmt])
324
+
325
+ def _map_statement_line(self, line: "StatementLine") -> "ReportEntry": # noqa: F821
326
+ from swiftlib.iso20022.messages.camt053 import ReportEntry
327
+ dc_indicator = "DBIT" if "D" in line.debit_credit_mark else "CRDT"
328
+ return ReportEntry(
329
+ amount=line.amount,
330
+ currency="", # MT940 doesn't carry currency in field 61 directly
331
+ credit_debit_indicator=dc_indicator,
332
+ status="BOOK",
333
+ booking_date=line.value_date,
334
+ value_date=line.value_date,
335
+ account_servicer_ref=line.account_servicing_ref,
336
+ entry_reference=line.reference,
337
+ )
@@ -0,0 +1,28 @@
1
+ """
2
+ swiftlib.core — core building blocks: fields, blocks, messages.
3
+ """
4
+
5
+ from swiftlib.core.field import Field, FieldDefinition, FieldType, parse_format
6
+ from swiftlib.core.block import (
7
+ Block1,
8
+ Block2Input,
9
+ Block2Output,
10
+ Block3,
11
+ Block4,
12
+ Block5,
13
+ )
14
+ from swiftlib.core.message import SwiftMessage
15
+
16
+ __all__ = [
17
+ "Field",
18
+ "FieldDefinition",
19
+ "FieldType",
20
+ "parse_format",
21
+ "Block1",
22
+ "Block2Input",
23
+ "Block2Output",
24
+ "Block3",
25
+ "Block4",
26
+ "Block5",
27
+ "SwiftMessage",
28
+ ]