pain001 package#
The Python pain001 module.
Subpackages#
- pain001.api package
- Submodules
- pain001.api.app module
- pain001.api.auth module
- pain001.api.guards module
- pain001.api.job_manager module
- pain001.api.job_store module
- pain001.api.metrics module
- pain001.api.models module
DataSourceTypeGenerateXMLRequestGenerateXMLResponseHealthResponseJobStatusResponseMessageTypeMessageType.PAIN_001_03MessageType.PAIN_001_04MessageType.PAIN_001_05MessageType.PAIN_001_06MessageType.PAIN_001_07MessageType.PAIN_001_08MessageType.PAIN_001_09MessageType.PAIN_001_10MessageType.PAIN_001_11MessageType.PAIN_001_12MessageType.PAIN_001_13MessageType.PAIN_008_02MessageType.PAIN_008_08
UiFilePayloadUiGenerateResponseValidationErrorValidationRequestValidationResponse
- pain001.api.ratelimit module
- pain001.api.ui module
- pain001.camt053 package
- pain001.cli package
- pain001.config package
- pain001.context package
- pain001.core package
- pain001.corpus package
- Subpackages
- Submodules
- pain001.corpus.api module
- pain001.corpus.builder module
- pain001.corpus.coverage_sets module
- pain001.corpus.identifiers module
COUNTRIESLEI_PREFIXNON_IBANaba_check_digit()aba_is_valid()account_for()belgian_check()bic_is_test()czech_mod11_ok()czech_number()dutch_eleven_proof()french_rib_key()iban_check_digits()iban_for()iban_is_valid()italian_cin()lei_check_digits()lei_is_valid()luhn_check_digit()make_aba()make_bic()make_iban()make_lei()make_uetr()mod97()spanish_control_digits()to_digits()
- pain001.corpus.inventory module
ChoiceEntryCoverageReportCoverageReport.branch_percentCoverageReport.completeCoverageReport.exemptCoverageReport.hit_branchesCoverageReport.hit_pathsCoverageReport.message_typeCoverageReport.missing_branchesCoverageReport.missing_pathsCoverageReport.path_percentCoverageReport.sourcesCoverageReport.to_dict()CoverageReport.unknown
ElementEntryInventoryMAX_DEPTHbuild_inventory()coverage()inventory_for()present_paths()
- pain001.corpus.registry module
- pain001.csv package
- pain001.data package
- pain001.db package
- pain001.json package
- pain001.logging_schema package
- IMPORTANT: PII Protection
- Request Tracing
- Log Severity Mapping (ISO 20022 Context)
- Submodules
- pain001.logging_schema.context module
- pain001.logging_schema.events module
- pain001.logging_schema.formatter module
- pain001.logging_schema.metrics module
- pain001.logging_schema.redaction module
- pain001.logging_schema.schema module
EventsEvents.CLI_ARGS_PARSEDEvents.CLI_DRY_RUNEvents.DATA_LOAD_ERROREvents.DATA_LOAD_STARTEvents.DATA_LOAD_SUCCESSEvents.EXECUTION_SUMMARYEvents.NAMESPACE_REGISTEREvents.PROCESS_ERROREvents.PROCESS_STARTEvents.PROCESS_SUCCESSEvents.VALIDATION_ERROREvents.VALIDATION_STARTEvents.VALIDATION_SUCCESSEvents.XML_GENERATE_ERROREvents.XML_GENERATE_STARTEvents.XML_GENERATE_SUCCESSEvents.XSD_VALIDATION_ERROREvents.XSD_VALIDATION_STARTEvents.XSD_VALIDATION_SUCCESS
ExecutionStatusFieldsFields.BANK_PROFILEFields.COMPONENTFields.DATA_SOURCE_TYPEFields.DRY_RUNFields.DURATION_MSFields.END_TO_END_IDFields.ERROR_FIELDFields.ERROR_INVALID_VALUEFields.ERROR_MESSAGEFields.ERROR_REASONFields.ERROR_TYPEFields.EVENTFields.FUNCTIONFields.ISO_VERSIONFields.LEVELFields.LOGGER_NAMEFields.MESSAGE_TYPEFields.MODULEFields.RECORD_COUNTFields.REQUEST_IDFields.SCHEMA_PATHFields.SIZE_BYTESFields.TEMPLATE_PATHFields.TIMESTAMPFields.TRANSACTION_COUNTFields.VALIDATION_TYPEFields.VERSION
LogLevel
- pain001.logging_schema.tracker module
ExecutionSummaryTrackerExecutionSummaryTracker.abort()ExecutionSummaryTracker.increment_event_count()ExecutionSummaryTracker.increment_processed_records()ExecutionSummaryTracker.log_summary()ExecutionSummaryTracker.set_log_file()ExecutionSummaryTracker.set_output_file()ExecutionSummaryTracker.set_validation_result()ExecutionSummaryTracker.start()
- pain001.lsp package
- pain001.mcp package
- pain001.migration package
- pain001.observability package
- pain001.pain002 package
- pain001.parquet package
- pain001.plugins package
- Compatibility
- Submodules
- pain001.plugins.builtins_gpg module
- pain001.plugins.companions module
- pain001.plugins.contracts module
- pain001.plugins.registry module
PluginRegistryPluginRegistry.get_loader()PluginRegistry.get_loader_for_extension()PluginRegistry.get_scheme()PluginRegistry.get_validator()PluginRegistry.get_writer()PluginRegistry.list_plugins()PluginRegistry.register_loader()PluginRegistry.register_scheme()PluginRegistry.register_validator()PluginRegistry.register_writer()PluginRegistry.reset()
PluginRegistryErrorPluginVersionErrorregistry
- pain001.schemas package
- pain001.schematron package
- Submodules
- pain001.schematron.evaluator module
- pain001.schematron.models module
REMEDIATIONSSchematronValidationResultSchematronValidationResult.duration_msSchematronValidationResult.error_countSchematronValidationResult.format_report()SchematronValidationResult.is_validSchematronValidationResult.rulebookSchematronValidationResult.rules_evaluatedSchematronValidationResult.rules_passedSchematronValidationResult.to_dict()SchematronValidationResult.violationsSchematronValidationResult.warning_count
SchematronViolationremediation_for()
- pain001.schematron.parser module
- pain001.schematron.validator module
- pain001.security package
- pain001.templates package
- pain001.test_fixtures package
- pain001.transport package
- pain001.twins package
- pain001.validation package
- Submodules
- pain001.validation.bic_validator module
- pain001.validation.charset module
- pain001.validation.corrections module
- pain001.validation.iban_validator module
- pain001.validation.policy module
- pain001.validation.rails module
BACS_DD_CODESDomesticPurposeMandateRailRail.address_requiredRail.address_severityRail.batch_bookingRail.bic_requiredRail.both_bics_requiredRail.charge_bearer_severityRail.charge_bearersRail.creditor_id_patternRail.creditor_referenceRail.currenciesRail.domesticRail.end_to_end_maxRail.iban_countriesRail.local_instrument_proprietaryRail.local_instrument_severityRail.local_instrumentsRail.mandate_maxRail.max_amountRail.max_amount_severityRail.messagesRail.nameRail.prefixRail.purposeRail.regulatoryRail.regulatory_patternRail.remittance_maxRail.remittance_severityRail.sequence_typesRail.service_levelsRail.sourcesRail.titleRail.uetr_required
RailProfileSEC_CODESrail()
- pain001.validation.schema_validator module
- pain001.validation.schemes module
- pain001.validation.service module
- pain001.xml package
- Submodules
- pain001.xml.create_root_element module
- pain001.xml.create_xml_element module
- pain001.xml.envelope module
BusinessApplicationHeaderBusinessApplicationHeader.biz_msg_idrBusinessApplicationHeader.biz_svcBusinessApplicationHeader.char_setBusinessApplicationHeader.cpy_dplctBusinessApplicationHeader.cre_dtBusinessApplicationHeader.from_bicBusinessApplicationHeader.from_idBusinessApplicationHeader.msg_def_idrBusinessApplicationHeader.prtyBusinessApplicationHeader.pssbl_dplctBusinessApplicationHeader.signatureBusinessApplicationHeader.to_bicBusinessApplicationHeader.to_element()BusinessApplicationHeader.to_idBusinessApplicationHeader.to_xml()
envelop_iso20022_message()extract_msg_def_idr()unpack_iso20022_message()
- pain001.xml.generate_updated_xml_file_path module
- pain001.xml.generate_xml module
- pain001.xml.message_registry module
- pain001.xml.register_namespaces module
- pain001.xml.validate_via_xsd module
- pain001.xml.write_xml_to_file module
- pain001.xml.xml_to_string module
Submodules#
pain001.async_adapter module#
Async-ready wrappers around the synchronous library entry points.
- async pain001.async_adapter.generate_xml_string_async(data: list[dict[str, object]], payment_initiation_message_type: str, xml_template_path: str, xsd_schema_path: str) str[source]#
Run generate_xml_string() in a worker thread.
- async pain001.async_adapter.load_payment_data_async(data_source: str | list[dict[str, Any]] | dict[str, Any]) list[dict[str, Any]][source]#
Run load_payment_data() in a worker thread.
- async pain001.async_adapter.process_files_async(xml_message_type: str, xml_template_file_path: str, xsd_schema_file_path: str, data_file_path: str) str[source]#
Run process_files() in a worker thread.
- async pain001.async_adapter.process_files_streaming_async(xml_message_type: str, xml_template_file_path: str, xsd_schema_file_path: str, data_file_path: str, chunk_size: int = 1000) list[str][source]#
Run process_files_streaming() in a worker thread.
- async pain001.async_adapter.validate_all_async(validation_service: ValidationService, config: ValidationConfig) Any[source]#
Run ValidationService.validate_all() in a worker thread.
pain001.constants module#
Shared constants and configuration for the pain001 library.
pain001.exceptions module#
Custom exception hierarchy for Pain001.
This module provides granular exception types to enable precise error handling in banking integrations. Instead of catching generic ValueError or TypeError, consuming applications can distinguish between data validation errors, configuration errors, and XML generation failures.
Example
>>> try:
... process_files(...)
... except PaymentValidationError as e:
... # Handle invalid IBAN/BIC - notify user
... log.error(f"Payment data invalid: {e}")
... except XMLGenerationError as e:
... # Handle XML generation failure - check templates
... log.error(f"XML generation failed: {e}")
... except ConfigurationError as e:
... # Handle config issues - check setup
... log.error(f"Configuration error: {e}")
- exception pain001.exceptions.ConfigurationError[source]#
Bases:
Pain001ErrorRaised when configuration or setup is invalid.
This exception indicates issues with: - Missing or invalid setup.cfg - Invalid CLI arguments - Missing required environment variables - Invalid file paths - Unsupported ISO 20022 version
Example
>>> try: ... load_config("pain.001.001.99") ... except ConfigurationError as e: ... # Config error - show usage help ... print(f"Configuration error: {e}") ... print_usage_help()
- exception pain001.exceptions.DataSourceError[source]#
Bases:
Pain001ErrorRaised when data source access fails.
This exception indicates issues with: - File not found (CSV, SQLite) - Database connection errors - Corrupted data files - Unsupported file formats - Empty data sources
Example
>>> try: ... load_payment_data("payments.csv") ... except DataSourceError as e: ... # Data access error - check file exists ... log.error(f"Cannot access data source: {e}")
- exception pain001.exceptions.InvalidBICError(message: str, bic: str, field: str | None = None, reason: str | None = None)[source]#
Bases:
PaymentValidationErrorRaised when BIC/SWIFT validation fails.
This exception indicates issues with: - Invalid BIC format (must be 8 or 11 characters) - Invalid BIC structure (ISO 9362) - Invalid country code in BIC - Invalid bank/branch code characters
- Parameters:
message – Human-readable error message.
bic – The invalid BIC value.
field – Optional field name (e.g., “debtor_agent”).
reason – Optional specific reason for failure.
Example
>>> try: ... validate_bic("INVALID123") ... except InvalidBICError as e: ... # BIC validation failed - prompt user to correct ... print(f"Invalid BIC: {e}") ... print(f"Field: {e.field}, Value: {e.bic}")
- exception pain001.exceptions.InvalidIBANError(message: str, iban: str, field: str | None = None, reason: str | None = None)[source]#
Bases:
PaymentValidationErrorRaised when IBAN validation fails.
This exception indicates issues with: - Invalid IBAN format (wrong structure) - Failed ISO 7064 mod-97-10 checksum validation - Unsupported country code - IBAN length mismatch for country
- Parameters:
message – Human-readable error message.
iban – The invalid IBAN value.
field – Optional field name (e.g., “debtor_account”).
reason – Optional specific reason for failure.
Example
>>> try: ... validate_iban("AT68123456") # Too short ... except InvalidIBANError as e: ... # IBAN validation failed - prompt user to correct ... print(f"Invalid IBAN: {e}") ... print(f"Field: {e.field}, Value: {e.iban}")
- exception pain001.exceptions.MissingRequiredFieldError(message: str, field: str, row_number: int | None = None, required_fields: list[str] | None = None)[source]#
Bases:
PaymentValidationErrorRaised when a required field is missing from payment data.
This exception indicates issues with: - Missing mandatory fields (debtor_name, creditor_account, etc.) - Empty/null values for required fields - Missing fields in CSV rows - Missing dictionary keys
- Parameters:
message – Human-readable error message.
field – The missing field name.
row_number – Optional row/line number where field is missing.
required_fields – Optional list of all required fields.
Example
>>> try: ... validate_required_fields(data, ["debtor_name", "amount"]) ... except MissingRequiredFieldError as e: ... # Required field missing - show user what's needed ... print(f"Missing field: {e.field}") ... print(f"Row: {e.row_number}, Expected fields: {e.required_fields}")
- exception pain001.exceptions.Pain001Error[source]#
Bases:
ExceptionBase exception for all Pain001 errors.
All custom exceptions in this library inherit from this base class, allowing consumers to catch any Pain001-specific error with a single except clause if needed.
Example
>>> try: ... process_files(...) ... except Pain001Error: ... # Catch any Pain001-specific error ... log.error("Pain001 operation failed")
- exception pain001.exceptions.PaymentValidationError(message: str, field: str | None = None)[source]#
Bases:
Pain001ErrorRaised when payment data validation fails.
This exception indicates issues with input data such as: - Invalid IBAN format - Invalid BIC/SWIFT code - Invalid amount (negative, too large, wrong format) - Missing required fields (debtor name, creditor account, etc.) - Invalid date formats
- Parameters:
message – Human-readable error message.
field – Optional field name that caused the validation error.
Example
>>> try: ... validate_payment_data(data) ... except PaymentValidationError as e: ... # User-facing error - show validation message ... return {"error": str(e), "field": e.field}
- exception pain001.exceptions.SchemaValidationError(message: str, errors: list[str] | None = None)[source]#
Bases:
Pain001ErrorRaised when XSD schema validation fails.
This exception indicates issues with: - Generated XML does not conform to ISO 20022 schema - Missing required XML elements - Invalid XML element values - Namespace mismatches
- Parameters:
message – Human-readable error message.
errors – Optional list of detailed validation errors.
Example
>>> try: ... validate_xml_against_schema(xml, xsd) ... except SchemaValidationError as e: ... # Schema validation error - check data mapping ... log.error(f"XML schema validation failed: {e}") ... log.debug(f"Validation errors: {e.errors}")
- exception pain001.exceptions.XMLGenerationError[source]#
Bases:
Pain001ErrorRaised when XML generation or validation fails.
This exception indicates issues with: - Jinja2 template rendering failures - XSD schema validation errors - XML namespace issues - Missing or corrupted template files - Invalid XML structure
Example
>>> try: ... generate_xml(data, template, schema) ... except XMLGenerationError as e: ... # System error - check templates and schemas ... log.error(f"XML generation failed: {e}") ... alert_ops_team()
- pain001.exceptions.XSDValidationError#
alias of
SchemaValidationError
pain001.migrate module#
CLI for ISO 20022 version migration.
pain001.suite module#
What the pain001 suite is, and how its versions move.
Users install more than one package — pain001, pain001-mcp, pain001-lsp, a loader or two — and the failure they hit is a version they cannot reason about: is pain001-loader-xlsx==0.0.54 supposed to work with pain001==0.0.60?
The suite answers that with a single rule: every member ships the same
version as the core. One number describes the whole suite. If the core
is at 0.0.60 then so is every wrapper and every loader, and a user
reading two different numbers is reading a mistake rather than a
deliberate difference they need to understand.
Versions advance in 0.0.1 steps and stay on the 0.0.x line;
0.1.0 follows 0.0.999, not 0.0.60. A member that jumps off
that line is not signalling independence, it is breaking the one
property this module exists to guarantee.
An earlier revision of this module split the suite in two, versioning
the loaders independently on the grounds that they implement the
published plugin contract rather than the core’s internals, so a loader
fix need not wait for a core release. That is a defensible design and it
is not the one this project uses: the cost of a user having to know
which packages track the core and which do not outweighs the cost of an
occasional no-change release. The contract generation in
PAIN001_API_VERSION still exists and the
registry still enforces it at load time — it is a safety net, not the
versioning policy.
Three rules follow, and all are checked by
scripts/check_suite_consistency.py:
Every member’s published version must equal the core’s.
Every member’s declared
pain001floor must be a version that actually exists, or the combination is uninstallable.Every member’s declared
pain001floor follows the policy recorded here (SuiteMember.floor). Every member isLOCKSTEP: each release requires the core at its own number, sopain001-mcp 0.0.74requirespain001>=0.0.74and so does each loader. The wrappers (pain001-mcp,pain001-lsp) call the core’s own API; the loaders were held at the oldest core whose plugin contract they needed (>=0.0.55for MT101,>=0.0.56for xlsx) until 0.0.74, when the family moved them to lockstep too, as the camt053 family’s loaders already are: one suite version is then one tested combination, and a floor below it promises a pairing nobody ran. Before any rule the floors drifted silently (>=0.0.55on one loader,>=0.0.56on the other,>=0.0.70on the wrappers) with nothing saying which was intended. An explicit version is still accepted here for a member that genuinely needs one; it is a decision made in this table, never a side effect of a release.
- pain001.suite.CORE: Final[str] = 'pain001'#
The core distribution every other member depends on.
- pain001.suite.LOCKSTEP: Final[str] = 'lockstep'#
Floor policy for a member whose declared
pain001floor must equal its own version: it uses the core’s API, not only the plugin contract.
- pain001.suite.SUITE: Final[mappingproxy[str, SuiteMember]] = mappingproxy({'pain001': SuiteMember(distribution='pain001', repository='sebastienrousseau/pain001', summary='Core library and CLI.', floor=None), 'pain001-mcp': SuiteMember(distribution='pain001-mcp', repository='sebastienrousseau/pain001-mcp', summary='Model Context Protocol server.', floor='lockstep'), 'pain001-lsp': SuiteMember(distribution='pain001-lsp', repository='sebastienrousseau/pain001-lsp', summary='Language server for payment data files.', floor='lockstep'), 'pain001-loader-xlsx': SuiteMember(distribution='pain001-loader-xlsx', repository='sebastienrousseau/pain001-loader-xlsx', summary='Excel (.xlsx/.xlsm) loader plugin.', floor='lockstep'), 'pain001-loader-mt101': SuiteMember(distribution='pain001-loader-mt101', repository='sebastienrousseau/pain001-loader-mt101', summary='SWIFT MT101 loader plugin.', floor='lockstep')})#
Every published member of the suite, keyed by distribution name.
Read-only: this is reference data, and a caller reshaping it would change what a consistency check believes about the world.
- class pain001.suite.SuiteMember(distribution: str, repository: str, summary: str, floor: str | None = 'lockstep')[source]#
Bases:
NamedTupleOne published package in the suite.
- Variables:
distribution (str) – The name on PyPI.
repository (str) – The GitHub repository,
owner/name.summary (str) – One line, for the README table and error messages.
floor (str | None) – The
pain001floor this member must declare:LOCKSTEP(equal to its own version), an explicit version (the oldest core whose plugin contract it needs), orNonefor the core itself.
- distribution: str#
Alias for field number 0
- floor: str | None#
Alias for field number 3
- repository: str#
Alias for field number 1
- summary: str#
Alias for field number 2
- pain001.suite.members() tuple[SuiteMember, ...][source]#
Return every member of the suite, core first.
Every member is lockstep, so there is no second accessor to pick a subset — the absence of one is the policy.
- Returns:
All suite members, core first.
Example
>>> [m.distribution for m in members()][0] 'pain001'