diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..170ffb8 --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +ignore = E203, E266, E501, W503, F403, F401 +max-line-length = 79 +max-complexity = 18 +exclude = tests/* \ No newline at end of file diff --git a/.gitignore b/.gitignore index 146d9ac..1408772 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,21 @@ pain001/bin/pip3.11 pain001/bin/python pain001/bin/python3 pain001/bin/python3.11 +tests/data/empty.csv +tests/data/invalid_data.csv +tests/data/single_column.csv +tests/data/single_row.csv +tests/data/valid_data.csv +tests/data/invalid_data.db +tests/data/valid_data.db +tests/data/unsupported_data_type.txt +tests/data/empty_unique.csv +tests/data/invalid_data_unique.csv +tests/data/invalid_data_unique.db +tests/data/single_column_unique.csv +tests/data/single_row_unique.csv +tests/data/template_unique.xml +tests/data/template_unique.xsd +tests/data/unsupported_data_type_unique.txt +tests/data/valid_data_unique.csv +tests/data/valid_data_unique.db diff --git a/README.md b/README.md index 766a5bd..e6f81f0 100644 --- a/README.md +++ b/README.md @@ -8,30 +8,51 @@ ## Overview -**Pain001** is an open-source Python Library that you can use to create **ISO 20022-Compliant Payment Files** directly from your **CSV** or **SQLite** Data Files. +**Pain001** is an open-source Python Library that you can use to create **ISO +20022-Compliant Payment Files** directly from your **CSV** or **SQLite** Data +Files. - **Website:** - **Source code:** - **Bug reports:** -The Python library focuses specifically on **Payment Initiation and Advice Messages**, commonly known as **Pain**. In a very simplified way, a **pain.001** is a message that initiates the customer payment. - -As of today the library is designed to be compatible with the: - -- **Payments Initiation V03 (pain.001.001.03)**: This version is used for initiating credit transfers within the SEPA (Single Euro Payments Area). -- **Payments Initiation V04 (pain.001.001.04)**: Introduced support for non-SEPA payments and additional functionalities. -- **Payments Initiation V05 (pain.001.001.05)**: Brought further enhancements and clarifications. -- **Payments Initiation V06 (pain.001.001.06)**: Focused on introducing support for instant payments. -- **Payments Initiation V07 (pain.001.001.07)**: Added support for Request for Large Payment (RLP) and Request to Modify Payment (RTP) functionalities. -- **Payments Initiation V08 (pain.001.001.08)**: Included support for the TARGET Instant Settlement Service (TISS) and introduced a new pain.002 message type for debit transfers. -- **Payments Initiation V09 (pain.001.001.09)**: The latest version, which introduced support for Request for Account Information (RAI) functionality. - -Payments usually start with a **pain.001 payment initiation message**. The payer sends it to the payee (or the payee’s bank) via a secure network. This -network could be **SWIFT** or **SEPA (Single Euro Payments Area) network**, or other payment networks such as **CHAPS**, **BACS**, **Faster Payments**, etc. The message contains the payer’s and payee’s bank account details, payment amount, and other information required to process the payment. - -The **Pain001** library can reduce payment processing complexity and costs by generating ISO 20022-compliant payment files. These files automatically remove the need to create and validate them manually, making the payment process more efficient and cost-effective. It will save you time and resources and minimises the risk of errors, making sure accurate and seamless payment processing. - -Use the **Pain001** library to simplify, accelerate and automate your payment processing. +The Python library focuses specifically on **Payment Initiation and Advice +Messages**, commonly known as **Pain**. In a very simplified way, a +**pain.001** is a message that initiates the customer payment. + +As of today, the library is designed to be compatible with the: + +- **Payments Initiation V03 (pain.001.001.03)**: This version is used for + initiating credit transfers within the SEPA (Single Euro Payments Area). +- **Payments Initiation V04 (pain.001.001.04)**: Introduced support for non-SEPA + payments and additional functionalities. +- **Payments Initiation V05 (pain.001.001.05)**: Brought further enhancements + and clarifications. +- **Payments Initiation V06 (pain.001.001.06)**: Focused on introducing support + for instant payments. +- **Payments Initiation V07 (pain.001.001.07)**: Added support for Request for + Large Payment (RLP) and Request to Modify Payment (RTP) functionalities. +- **Payments Initiation V08 (pain.001.001.08)**: Included support for the TARGET + Instant Settlement Service (TISS) and introduced a new pain.002 message type + for debit transfers. +- **Payments Initiation V09 (pain.001.001.09)**: The latest version, which + introduced support for Request for Account Information (RAI) functionality. + +Payments usually start with a **pain.001 payment initiation message**. The payer +sends it to the payee (or the payee’s bank) via a secure network. This network +could be **SWIFT** or **SEPA (Single Euro Payments Area) network**, or other +payment networks such as **CHAPS**, **BACS**, **Faster Payments**, etc. The +message contains the payer’s and payee’s bank account details, payment amount, +and other information required to process the payment. + +The **Pain001** library can reduce payment processing complexity and costs by +generating ISO 20022-compliant payment files. These files automatically remove +the need to create and validate them manually, making the payment process more +efficient and cost-effective. It will save you time and resources and minimize +the risk of errors, ensuring accurate and seamless payment processing. + +Use the **Pain001** library to simplify, accelerate, and automate your payment +processing. ## Table of Contents @@ -52,6 +73,13 @@ Use the **Pain001** library to simplify, accelerate and automate your payment pr - [Using a CSV Data File as the source](#using-a-csv-data-file-as-the-source) - [Using a SQLite Data File as the source](#using-a-sqlite-data-file-as-the-source) - [Using the Source code](#using-the-source-code) + - [Pain.001.001.03](#pain00100103) + - [Pain.001.001.04](#pain00100104) + - [Pain.001.001.05](#pain00100105) + - [Pain.001.001.06](#pain00100106) + - [Pain.001.001.07](#pain00100107) + - [Pain.001.001.08](#pain00100108) + - [Pain.001.001.09](#pain00100109) - [Embedded in an Application](#embedded-in-an-application) - [Validation](#validation) - [Documentation](#documentation) @@ -70,20 +98,20 @@ Use the **Pain001** library to simplify, accelerate and automate your payment pr - **Open-source**: The library is open-source and free to use, making it accessible to everyone. - **Secure**: The library is secure and does not store any sensitive data, - making sure that all information remains confidential. -- **Customizable**: The library allows developers to customise the output, + ensuring that all information remains confidential. +- **Customizable**: The library allows developers to customize the output, making it adaptable to specific business requirements and preferences. - **Scalable solution**: The **Pain001** library can handle varying volumes of payment files, making it suitable for businesses of different sizes and transaction volumes. -- **Time-saving**: The automated file creation process reduces the time spent - on manual data entry and file generation, increasing overall productivity. +- **Time-saving**: The automated file creation process reduces the time spent on + manual data entry and file generation, increasing overall productivity. - **Seamless integration**: As a Python package, the Pain001 library is compatible with various Python-based applications and easily integrates into any existing projects or workflows. -- **Cross-border compatibility**: The library supports both Single Euro - Payments Area (SEPA) and non-SEPA credit transfers, making it versatile for - use in different countries and regions. +- **Cross-border compatibility**: The library supports both Single Euro Payments + Area (SEPA) and non-SEPA credit transfers, making it versatile for use in + different countries and regions. - **Improve accuracy** by providing precise data; the library reduces errors in payment file creation and processing. - **Enhance efficiency** by automating the creation of Payment Initiation @@ -93,17 +121,21 @@ Use the **Pain001** library to simplify, accelerate and automate your payment pr - **Guarantee the highest quality and compliance** by validating all payment files to meet the ISO 20022 standards. - **Simplify ISO 20022-compliant payment initiation message creation** by - providing a standardised payment file format. + providing a standardized payment file format. - **Reduce costs** by removing manual data entry and file generation, reducing payment processing time, and reducing errors. ## Requirements -**Pain001** works with macOS, Linux and Windows and requires Python 3.9.0 and above. +**Pain001** works with macOS, Linux, and Windows and requires Python 3.9.0 and +above. ## Installation -We recommend creating a virtual environment to install **Pain001**. This will ensure that the package is installed in an isolated environment and will not affect other projects. To install **Pain001** in a virtual environment, follow these steps: +We recommend creating a virtual environment to install **Pain001**. This will +ensure that the package is installed in an isolated environment and will not +affect other projects. To install **Pain001** in a virtual environment, follow +these steps: ### Install `virtualenv` @@ -117,9 +149,9 @@ python -m pip install virtualenv python -m venv venv ``` -| Code | Explanation | -|---|---| -| `-m` | executes module `venv` | +| Code | Explanation | +| ----- | ------------------------------- | +| `-m` | executes module `venv` | | `env` | name of the virtual environment | ### Activate environment @@ -130,7 +162,8 @@ source venv/bin/activate ### Getting Started -It takes just a few seconds to get up and running with **Pain001**. You can install Pain001 from PyPI with pip or your favourite package manager: +It takes just a few seconds to get up and running with **Pain001**. You can +install Pain001 from PyPI with pip or your favourite package manager: Open your terminal and run the following command to add the latest version: @@ -138,7 +171,8 @@ Open your terminal and run the following command to add the latest version: python -m pip install pain001 ``` -Add the -U switch to update to the current version, if `pain001` is already installed. +Add the -U switch to update to the current version, if `pain001` is already +installed. ```sh python -m pip install -U pain001 @@ -146,11 +180,14 @@ python -m pip install -U pain001 ## Quick Start -After installation, you can run **Pain001** directly from the command line. Simply call the main module pain001 with the paths of your: +After installation, you can run **Pain001** directly from the command line. +Simply call the main module pain001 with the paths of your: -- **XML template file** containing the various parameters you want to pass from your Data file, +- **XML template file** containing the various parameters you want to pass from + your Data file, - **XSD schema file** to validate the generated XML file, and -- **Data file (CSV or SQLite)** containing the payment instructions that you want to submit. +- **Data file (CSV or SQLite)** containing the payment instructions that you + want to submit. Here’s how you would do that: @@ -178,8 +215,9 @@ When running **Pain001**, you will need to specify four arguments: - pain.001.001.08 - pain.001.001.09 -- An `xml_template_file_path`: This is the path to the XML template file you - are using that contains variables that will be replaced by the values in your +- An `xml_template_file_path`: This is the path to the XML template file you are + using that contains variables that will be replaced by the values in your + Data file. - An `xsd_schema_file_path`: This is the path to the XSD schema file you are @@ -223,15 +261,96 @@ GitHub: git clone https://github.com/sebastienrousseau/pain001.git ``` +#### Pain.001.001.03 + +This will generate a payment initiation message in the format of +Pain.001.001.03. + ```sh - python -m pain001 \ +python -m pain001 \ -t pain.001.001.03 \ -m templates/pain.001.001.03/template.xml \ -s templates/pain.001.001.03/pain.001.001.03.xsd \ -d templates/pain.001.001.03/template.csv - ``` +``` + +#### Pain.001.001.04 + +This will generate a payment initiation message in the format of +Pain.001.001.04. + +```sh +python -m pain001 \ + -t pain.001.001.04 \ + -m templates/pain.001.001.04/template.xml \ + -s templates/pain.001.001.04/pain.001.001.04.xsd \ + -d templates/pain.001.001.04/template.csv +``` + +#### Pain.001.001.05 + +This will generate a payment initiation message in the format of +Pain.001.001.05. + +```sh +python -m pain001 \ + -t pain.001.001.05 \ + -m templates/pain.001.001.05/template.xml \ + -s templates/pain.001.001.05/pain.001.001.05.xsd \ + -d templates/pain.001.001.05/template.csv +``` + +#### Pain.001.001.06 -This will generate a payment initiation message from the sample CSV Data file. +This will generate a payment initiation message in the format of +Pain.001.001.06. + +```sh +python -m pain001 \ + -t pain.001.001.06 \ + -m templates/pain.001.001.06/template.xml \ + -s templates/pain.001.001.06/pain.001.001.06.xsd \ + -d templates/pain.001.001.06/template.csv +``` + +#### Pain.001.001.07 + +This will generate a payment initiation message in the format of +Pain.001.001.07. + +```sh +python -m pain001 \ + -t pain.001.001.07 \ + -m templates/pain.001.001.07/template.xml \ + -s templates/pain.001.001.07/pain.001.001.07.xsd \ + -d templates/pain.001.001.07/template.csv +``` + +#### Pain.001.001.08 + +This will generate a payment initiation message in the format of +Pain.001.001.08. + +```sh +python -m pain001 \ + -t pain.001.001.08 \ + -m templates/pain.001.001.08/template.xml \ + -s templates/pain.001.001.08/pain.001.001.08.xsd \ + -d templates/pain.001.001.08/template.csv +``` + +#### Pain.001.001.09 + +This will generate a payment initiation message in the format of +Pain.001.001.09. + +```sh +python -m pain001 \ + -t pain.001.001.09 \ + -m templates/pain.001.001.09/template.xml \ + -s templates/pain.001.001.09/pain.001.001.09.xsd \ + -d templates/pain.001.001.09/template.csv +``` You can do the same with the sample SQLite Data file: @@ -244,9 +363,9 @@ python3 -m pain001 \ ``` > **Note:** The XML file that **Pain001** generates will automatically be -validated against the XSD template file before the new XML file is saved. If -the validation fails, **Pain001** will stop running and display an error -message in your terminal. +> validated against the XSD template file before the new XML file is saved. If +> the validation fails, **Pain001** will stop running and display an error +> message in your terminal. ### Embedded in an Application @@ -259,22 +378,22 @@ Here's an example: from pain001 import main if __name__ == '__main__': - xml_message_type = 'pain.001.001.03' - xml_template_file_path = 'template.xml' - xsd_schema_file_path = 'schema.xsd' - data_file_path = 'data.csv' - main( - xml_message_type, - xml_template_file_path, - xsd_schema_file_path, - data_file_path - ) + xml_message_type = 'pain.001.001.03' + xml_template_file_path = 'template.xml' + xsd_schema_file_path = 'schema.xsd' + data_file_path = 'data.csv' + main( + xml_message_type, + xml_template_file_path, + xsd_schema_file_path, + data_file_path + ) ``` ### Validation -To validate the generated XML file against a given xsd schema, use the -following method: +To validate the generated XML file against a given xsd schema, use the following +method: ```python from pain001.core import validate_xml_against_xsd @@ -284,9 +403,9 @@ xml_file = 'generated.xml' xsd_file = 'schema.xsd' is_valid = validate_xml_against_xsd( - xml_message_type, - xml_file, - xsd_file + xml_message_type, + xml_file, + xsd_file ) print(f"XML validation result: {is_valid}") ``` @@ -297,8 +416,8 @@ print(f"XML validation result: {is_valid}") ### Supported messages -This section gives access to the documentation related to the ISO 20022 -message definitions supported by **Pain001**. +This section gives access to the documentation related to the ISO 20022 message +definitions supported by **Pain001**. #### Bank-to-Customer Cash Management @@ -306,51 +425,51 @@ Set of messages used to request and provide account information for reconciliation and cash positioning between an account servicer and its customer. -| Status | Message type | Name | -|---|---|---| -| ⏳ | camt.052.001.10 | Bank-to-Customer Account Statement | -| ⏳ | camt.053.001.10 | Customer Account Identification | -| ⏳ | camt.054.001.10 | Customer Account Statement Request | -| ⏳ | camt.060.001.10 | Customer Account Notification | +| Status | Message type | Name | +| ------ | --------------- | ---------------------------------- | +| ⏳ | camt.052.001.10 | Bank-to-Customer Account Statement | +| ⏳ | camt.053.001.10 | Customer Account Identification | +| ⏳ | camt.054.001.10 | Customer Account Statement Request | +| ⏳ | camt.060.001.10 | Customer Account Notification | #### Payments Clearing and Settlement Set of messages used between financial institutions for the clearing and settlement of payment transactions. -| Status | Message type | Name | -|---|---|---| -| ⏳ | pacs.002.001.12 | Credit Transfer Notification | -| ⏳ | pacs.003.001.09 | Direct Debit Initiation | -| ⏳ | pacs.004.001.11 | Direct Debit Reversal | -| ⏳ | pacs.007.001.11 | Customer Direct Debit Confirmation | -| ⏳ | pacs.008.001.10 | Credit Transfer Initiation | -| ⏳ | pacs.009.001.10 | Credit Transfer Reversal | -| ⏳ | pacs.010.001.05 | Account Identification | -| ⏳ | pacs.028.001.05 | Account Statement Request | +| Status | Message type | Name | +| ------ | --------------- | ---------------------------------- | +| ⏳ | pacs.002.001.12 | Credit Transfer Notification | +| ⏳ | pacs.003.001.09 | Direct Debit Initiation | +| ⏳ | pacs.004.001.11 | Direct Debit Reversal | +| ⏳ | pacs.007.001.11 | Customer Direct Debit Confirmation | +| ⏳ | pacs.008.001.10 | Credit Transfer Initiation | +| ⏳ | pacs.009.001.10 | Credit Transfer Reversal | +| ⏳ | pacs.010.001.05 | Account Identification | +| ⏳ | pacs.028.001.05 | Account Statement Request | #### Payments Initiation -Set of messages exchanged between a debtor (or buyer) and its bank or -between a creditor (or seller) and its bank to initiate, collect, manage -and monitor payments. - -| Status | Message type | Name | -|---|---|---| -| ✅ | [pain.001.001.03][pain.001.001.03] | Customer Credit Transfer Initiation | -| ✅ | [pain.001.001.04][pain.001.001.04] | Customer Direct Debit Initiation | -| ✅ | [pain.001.001.05][pain.001.001.05] | Customer Direct Debit Reversal | -| ✅ | [pain.001.001.06][pain.001.001.06] | Customer Credit Transfer Reversal | -| ✅ | [pain.001.001.07][pain.001.001.07] | Customer Account Notification | -| ✅ | [pain.001.001.08][pain.001.001.08] | Customer Account Statement | -| ✅ | [pain.001.001.09][pain.001.001.09] | Customer Credit Transfer Initiation | -| ⏳ | pain.001.001.10 | Customer Account Closure Request | -| ⏳ | pain.001.001.11 | Customer Account Change Request | +Set of messages exchanged between a debtor (or buyer) and its bank or between a +creditor (or seller) and its bank to initiate, collect, manage and monitor +payments. + +| Status | Message type | Name | +| ------ | ---------------- | ----------------------------------- | +| ✅ | pain.001.001.03 | Customer Credit Transfer Initiation | +| ✅ | pain.001.001.04 | Customer Direct Debit Initiation | +| ✅ | pain.001.001.05 | Customer Direct Debit Reversal | +| ✅ | pain.001.001.06 | Customer Credit Transfer Reversal | +| ✅ | pain.001.001.07 | Customer Account Notification | +| ✅ | pain.001.001.08 | Customer Account Statement | +| ✅ | pain.001.001.09 | Customer Credit Transfer Initiation | +| ⏳ | pain.001.001.10 | Customer Account Closure Request | +| ⏳ | pain.001.001.11 | Customer Account Change Request | ## License -The project is licensed under the terms of both the MIT license and the -Apache License (Version 2.0). +The project is licensed under the terms of both the MIT license and the Apache +License (Version 2.0). - [Apache License, Version 2.0][01] - [MIT license][02] @@ -360,15 +479,14 @@ Apache License (Version 2.0). We welcome contributions to **Pain001**. Please see the [contributing instructions][04] for more information. -Unless you explicitly state otherwise, any contribution intentionally -submitted for inclusion in the work by you, as defined in the -Apache-2.0 license, shall be dual licensed as above, without any -additional terms or conditions. +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. ## Acknowledgements -We would like to extend a big thank you to all the awesome contributors -of [Pain001][05] for their help and support. +We would like to extend a big thank you to all the awesome contributors of +[Pain001][05] for their help and support. [00]: https://pain001.com [01]: https://opensource.org/license/apache-2-0/ @@ -379,13 +497,6 @@ of [Pain001][05] for their help and support. [06]: https://codecov.io/github/sebastienrousseau/pain001?branch=main [07]: https://pypi.org/project/pain001/ -[pain.001.001.03]: https://pain001.com/pain.001.001.03/index.html -[pain.001.001.04]: https://pain001.com/pain.001.001.04/index.html -[pain.001.001.05]: https://pain001.com/pain.001.001.05/index.html -[pain.001.001.06]: https://pain001.com/pain.001.001.06/index.html -[pain.001.001.07]: https://pain001.com/pain.001.001.07/index.html -[pain.001.001.08]: https://pain001.com/pain.001.001.08/index.html -[pain.001.001.09]: https://pain001.com/pain.001.001.09/index.html [banner]: https://kura.pro/pain001/images/banners/banner-pain001.svg 'Pain001, A Python Library for Automating ISO 20022-Compliant Payment Files Using CSV Or SQlite Data Files.' [codecov-badge]: https://img.shields.io/codecov/c/github/sebastienrousseau/pain001?style=for-the-badge&token=AaUxKfRiou 'Codecov badge' diff --git a/pain001/__init__.py b/pain001/__init__.py index bf12afc..fffaf9a 100644 --- a/pain001/__init__.py +++ b/pain001/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,4 +14,4 @@ # limitations under the License. """The Python pain001 module.""" -__version__ = "0.0.24" +__version__ = "0.0.25" diff --git a/pain001/__main__.py b/pain001/__main__.py index 3c11444..005703b 100644 --- a/pain001/__main__.py +++ b/pain001/__main__.py @@ -1,40 +1,10 @@ -# Copyright (C) 2023 Sebastien Rousseau. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# pylint: disable=invalid-name -""" -Enables use of Python Pain001 as a "main" function (i.e. -"python3 -m pain001 - -"). - -This allows using Pain001 with third-party libraries without modifying -their code. -""" - - +# Other imports remain the same +import click import os import sys -import click - from pain001.constants.constants import valid_xml_types from pain001.context.context import Context from pain001.core.core import process_files - -# from pain001.xml.validate_via_xsd import validate_via_xsd - from rich.console import Console from rich.table import Table from rich import box @@ -48,10 +18,7 @@ """ title = "Pain001" -table = Table( - box=box.ROUNDED, safe_box=True, show_header=False, title=title -) - +table = Table(box=box.ROUNDED, safe_box=True, show_header=False, title=title) table.add_column(justify="center", no_wrap=False, vertical="middle") table.add_row(description) table.width = 80 @@ -59,9 +26,7 @@ @click.command( - help=( - "To use Pain001, you must specify the following options:\n\n" - ), + help=("To use Pain001, you must specify the following options:\n\n"), context_settings=dict(help_option_names=["-h", "--help"]), ) @click.option( @@ -91,100 +56,96 @@ type=click.Path(), help="Path to data file (CSV or SQLite) (required)", ) -def main( +def cli( xml_message_type, xml_template_file_path, xsd_schema_file_path, data_file_path, ): - """Initialize the context and log a message.""" - logger = Context.get_instance().get_logger() + main( + xml_message_type, + xml_template_file_path, + xsd_schema_file_path, + data_file_path, + ) - # print("Inside main function") - def check_variable(variable, name): - if variable is None: - print(f"Error: {name} is required.") +def main( + xml_message_type, + xml_template_file_path, + xsd_schema_file_path, + data_file_path, +): + try: + # Check that the required arguments are provided + if not xml_message_type: + console.print("The XML message type is required.") sys.exit(1) - # Check that xml_message_type is provided - check_variable(xml_message_type, "xml_message_type") - - # Check that xsd_schema_file_path is provided - check_variable(xsd_schema_file_path, "xsd_schema_file_path") - - # Check that data_file_path is provided - check_variable(data_file_path, "data_file_path") + if not xml_template_file_path: + console.print("The XML template file path is required.") + sys.exit(1) - # Check that xml_template_file_path is not invalid - if not os.path.isfile(xml_template_file_path): - print( - f"The XML template file '{xml_template_file_path}' does not exist." - ) - sys.exit(1) + if not xsd_schema_file_path: + console.print("The XSD schema file path is required.") + sys.exit(1) - # Check that xsd_schema_file_path is not invalid - if not os.path.isfile(xsd_schema_file_path): - print( - f"The XSD template file '{xsd_schema_file_path}' does not exist." - ) - sys.exit(1) + if not data_file_path: + console.print("The data file path is required.") + sys.exit(1) - # Check that data_file_path is not invalid - if not os.path.isfile(data_file_path): - print(f"The data file '{data_file_path}' does not exist.") - sys.exit(1) + logger = Context.get_instance().get_logger() - # Check that other necessary arguments are provided - if ( - xml_template_file_path is None - or xsd_schema_file_path is None - or data_file_path is None - ): - print(click.get_current_context().get_help()) - sys.exit(1) + logger.info("Parsing command line arguments.") - logger = Context.get_instance().get_logger() + # Check that the XML message type is valid + if xml_message_type not in valid_xml_types: + logger.info(f"Invalid XML message type: {xml_message_type}.") + console.print(f"Invalid XML message type: {xml_message_type}.") + sys.exit(1) - logger.info("Parsing command line arguments.") + if not os.path.isfile(xml_template_file_path): + logger.info( + f""" + The XML template file '{xml_template_file_path}' does not exist. + """ + ) + console.print( + f""" + The XML template file '{xml_template_file_path}' does not exist. + """ + ) + sys.exit(1) - # Check that the XML message type is valid - if xml_message_type not in valid_xml_types: - logger.info(f"Invalid XML message type: {xml_message_type}.") - print(f"Invalid XML message type: {xml_message_type}.") - sys.exit(1) + if not os.path.isfile(xsd_schema_file_path): + logger.info( + f""" + The XSD template file '{xsd_schema_file_path}' does not exist. + """ + ) + console.print( + f""" + The XSD template file '{xsd_schema_file_path}' does not exist. + """ + ) + sys.exit(1) - if not os.path.isfile(xml_template_file_path): - logger.info( - f"The XML template file '{xml_template_file_path}' does not exist." - ) - print( - f"The XML template file '{xml_template_file_path}' does not exist." - ) - sys.exit(1) + if not os.path.isfile(data_file_path): + logger.info(f"The data file '{data_file_path}' does not exist.") + console.print(f"The data file '{data_file_path}' does not exist.") + sys.exit(1) - if not os.path.isfile(xsd_schema_file_path): - logger.info( - f"The XSD template file '{xsd_schema_file_path}' does not exist." + process_files( + xml_message_type, + xml_template_file_path, + xsd_schema_file_path, + data_file_path, ) - print( - f"The XSD template file '{xsd_schema_file_path}' does not exist." - ) - sys.exit(1) - - if not os.path.isfile(data_file_path): - logger.info(f"The data file '{data_file_path}' does not exist.") - print(f"The data file '{data_file_path}' does not exist.") + except Exception as e: + console.print(f"An error occurred: {e}") sys.exit(1) - process_files( - xml_message_type, - xml_template_file_path, - xsd_schema_file_path, - data_file_path, - ) - if __name__ == "__main__": # pylint: disable=no-value-for-parameter - main() + cli() diff --git a/pain001/constants/constants.py b/pain001/constants/constants.py index 807afaf..4b12624 100644 --- a/pain001/constants/constants.py +++ b/pain001/constants/constants.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,6 +23,6 @@ "pain.001.001.07", # Request for Reversal (pain.001.001.07) "pain.001.001.08", # Notification of Reversal (pain.001.001.08) "pain.001.001.09", # Request for Amendment (pain.001.001.09) - "pain.001.001.10" # Notification of Amendment (pain.001.001.10) + "pain.001.001.10" # Notification of Amendment (pain.001.001.10) "pain.001.001.11", # Request for Cancellation (pain.001.001.11) ] diff --git a/pain001/context/__init__.py b/pain001/context/__init__.py index f66c4d6..038cbe6 100644 --- a/pain001/context/__init__.py +++ b/pain001/context/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/pain001/context/context.py b/pain001/context/context.py index 0b625a8..6466189 100644 --- a/pain001/context/context.py +++ b/pain001/context/context.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -83,9 +83,7 @@ def set_log_level(self, log_level): "CRITICAL": logging.CRITICAL, } - if isinstance( - log_level, int - ): # Check if log_level is an integer + if isinstance(log_level, int): # Check if log_level is an integer if log_level in valid_log_levels.values(): self.log_level = log_level else: diff --git a/pain001/core/__init__.py b/pain001/core/__init__.py index f66c4d6..038cbe6 100644 --- a/pain001/core/__init__.py +++ b/pain001/core/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/pain001/core/core.py b/pain001/core/core.py index 8837124..a0e110f 100644 --- a/pain001/core/core.py +++ b/pain001/core/core.py @@ -93,9 +93,7 @@ def process_files( # Check if the data file exists if not os.path.exists(data_file_path): - error_message = ( - f"Error: Data file '{data_file_path}' does not exist." - ) + error_message = f"Error: Data file '{data_file_path}' does not exist." logger.error(error_message) raise FileNotFoundError(error_message) diff --git a/pain001/csv/load_csv_data.py b/pain001/csv/load_csv_data.py index 07844bc..1c8c234 100644 --- a/pain001/csv/load_csv_data.py +++ b/pain001/csv/load_csv_data.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,21 +14,49 @@ # limitations under the License. import csv -import os +import logging -# Load the CSV file into a list of dictionaries with the column names as -# keys +logging.basicConfig(level=logging.ERROR, format="%(levelname)s: %(message)s") -def load_csv_data(csv_file_path): - if not os.path.exists(csv_file_path): - raise FileNotFoundError( - f"CSV file '{csv_file_path}' does not exist." - ) +def load_csv_data(file_path): + """Load CSV data from a file. + + Args: + file_path (str): The path to the CSV file. + Returns: + list: A list of dictionaries containing the CSV data. + + Raises: + FileNotFoundError: If the file does not exist. + IOError: If there is an issue reading the file. + UnicodeDecodeError: If there is an issue decoding the file's content. + ValueError: If the CSV file is empty. + """ data = [] - with open(csv_file_path, "r") as csv_file: - reader = csv.DictReader(csv_file) - for row in reader: - data.append(row) + try: + with open(file_path, mode="r", encoding="utf-8") as file: + csv_reader = csv.DictReader(file) + for row in csv_reader: + data.append(row) + except FileNotFoundError: + logging.error(f"File '{file_path}' not found.") + raise + except IOError: + logging.error( + f"An IOError occurred while reading the file '{file_path}'." + ) + raise + except UnicodeDecodeError: + logging.error( + "A UnicodeDecodeError occurred while decoding the file '" + + file_path + + "'." + ) + raise + + if not data: + raise ValueError(f"The CSV file '{file_path}' is empty.") + return data diff --git a/pain001/csv/validate_csv_data.py b/pain001/csv/validate_csv_data.py index 75b7315..28057fa 100644 --- a/pain001/csv/validate_csv_data.py +++ b/pain001/csv/validate_csv_data.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -40,6 +40,9 @@ # - remittance_information (str) - remittance information +import datetime + + def validate_csv_data(data): """Validate the CSV data before processing it. @@ -51,15 +54,15 @@ def validate_csv_data(data): """ required_columns = { "id": int, - "date": str, + "date": datetime.datetime, "nb_of_txs": int, - "ctrl_sum": str, + "ctrl_sum": float, "initiator_name": str, "payment_information_id": str, "payment_method": str, "batch_booking": bool, "service_level_code": str, - "requested_execution_date": str, + "requested_execution_date": datetime.datetime, "debtor_name": str, "debtor_account_IBAN": str, "debtor_agent_BIC": str, @@ -74,31 +77,57 @@ def validate_csv_data(data): "remittance_information": str, } + if not data: + print("Error: The CSV data is empty.") + return False + + is_valid = True + for row in data: + missing_columns = [] + invalid_columns = [] for column, data_type in required_columns.items(): value = row.get(column) if value is None or value.strip() == "": - print( - f"Error: Missing value for column '{column}' " - f"in row: {row}" - ) - return False - - try: - if data_type == int: - int(value) - elif data_type == float: - float(value) - elif data_type == bool: - if value.lower() not in ["true", "false"]: - raise ValueError - else: - str(value) - except ValueError: - print( - f"Error: Invalid data type for column '{column}', " - f"expected {data_type.__name__} in row: {row}" - ) - return False + missing_columns.append(column) + is_valid = False + else: + try: + if data_type == int: + int(value) + elif data_type == float: + float(value) + elif data_type == bool: + if value.strip().lower() not in [ + "true", + "false", + ]: + raise ValueError + elif data_type == datetime.datetime: + try: + # Handle the "Z" suffix for UTC + if value.endswith("Z"): + value = value[:-1] + "+00:00" + datetime.datetime.fromisoformat(value) + except ValueError: + datetime.datetime.strptime(value, "%Y-%m-%d") + else: + str(value) + except ValueError: + invalid_columns.append(column) + is_valid = False + if missing_columns: + print( + f"Error: Missing value(s) for column(s) {missing_columns} " + f"in row: {row}" + ) + if invalid_columns: + expected_types = [ + required_columns[col].__name__ for col in invalid_columns + ] + print( + f"Error: Invalid data type for column(s) {invalid_columns}, " + f"expected {expected_types} in row: {row}" + ) - return True + return is_valid diff --git a/pain001/db/load_db_data.py b/pain001/db/load_db_data.py index b56cbde..bed4844 100644 --- a/pain001/db/load_db_data.py +++ b/pain001/db/load_db_data.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/pain001/db/validate_db_data.py b/pain001/db/validate_db_data.py index 71a6178..f54892e 100644 --- a/pain001/db/validate_db_data.py +++ b/pain001/db/validate_db_data.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,66 +13,82 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging + +# Configure the logger +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.ERROR) + def validate_db_data(data): - """Validate the SQLite data before processing it. + """ + Validate the data from a database. Args: - data (list): A list of dictionaries containing the SQLite data. + data (list of dict): The data to validate. Returns: bool: True if the data is valid, False otherwise. """ - required_columns = { - "id": int, - "date": str, - "nb_of_txs": int, - "ctrl_sum": str, - "initiator_name": str, - "payment_information_id": str, - "payment_method": str, - "batch_booking": bool, - "service_level_code": str, - "requested_execution_date": str, - "debtor_name": str, - "debtor_account_IBAN": str, - "debtor_agent_BIC": str, - "forwarding_agent_BIC": str, - "charge_bearer": str, - "payment_id": str, - "payment_amount": float, - "currency": str, - "creditor_agent_BIC": str, - "creditor_name": str, - "creditor_account_IBAN": str, - "remittance_information": str, - } + required_columns = [ + "id", + "date", + "nb_of_txs", + "initiator_name", + "initiator_street_name", + "initiator_building_number", + "initiator_postal_code", + "initiator_town_name", + "initiator_country_code", + "payment_information_id", + "payment_method", + "batch_booking", + "requested_execution_date", + "debtor_name", + "debtor_street_name", + "debtor_building_number", + "debtor_postal_code", + "debtor_town_name", + "debtor_country_code", + "debtor_account_IBAN", + "debtor_agent_BIC", + "charge_bearer", + "payment_id", + "payment_amount", + "currency", + "payment_currency", + "ctrl_sum", + "creditor_agent_BIC", + "creditor_name", + "creditor_street_name", + "creditor_building_number", + "creditor_postal_code", + "creditor_town_name", + "creditor_country_code", + "creditor_account_IBAN", + "purpose_code", + "reference_number", + "reference_date", + "service_level_code", + "end_to_end_id", + "payment_instruction_id", + "instruction_id", + "category_purpose", + "remittance_info_unstructured", + "remittance_info_structured", + "addtl_end_to_end_id", + "payment_info_structured", + "forwarding_agent_BIC", + "remittance_information", + ] for row in data: - for column, data_type in required_columns.items(): - value = row.get(column) - if value is None or str(value).strip() == "": - print( - f"Error: Missing value for column '{column}' " - f"in row: {row}" + for column in required_columns: + if column not in row or row[column] is None: + logger.error( + "Error: Missing value for column '%s' in row: %s", + column, + row, ) return False - - try: - if data_type == int: - int(value) - elif data_type == float: - float(value) - elif data_type == bool: - if str(value).lower() not in ["true", "false"]: - raise ValueError - else: - str(value) - except ValueError: - print( - f"Error: Invalid data type for column '{column}', " - f"expected {data_type.__name__} in row: {row}" - ) - return False - return True diff --git a/pain001/xml/create_common_elements.py b/pain001/xml/create_common_elements.py index 9474d18..247f340 100644 --- a/pain001/xml/create_common_elements.py +++ b/pain001/xml/create_common_elements.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/pain001/xml/create_root_element.py b/pain001/xml/create_root_element.py index 09e237b..8059083 100644 --- a/pain001/xml/create_root_element.py +++ b/pain001/xml/create_root_element.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/pain001/xml/create_xml_element.py b/pain001/xml/create_xml_element.py index 4005287..cd641de 100644 --- a/pain001/xml/create_xml_element.py +++ b/pain001/xml/create_xml_element.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/pain001/xml/create_xml_v3.py b/pain001/xml/create_xml_v3.py index f04646f..9cbf509 100644 --- a/pain001/xml/create_xml_v3.py +++ b/pain001/xml/create_xml_v3.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -58,9 +58,7 @@ def create_xml_v3(root, data): env = Environment(loader=FileSystemLoader("."), autoescape=True) # Load the Jinja2 template for the pain.001.001.03 schema - template = env.get_template( - "templates/pain.001.001.03/template.xml" - ) + template = env.get_template("templates/pain.001.001.03/template.xml") # Prepare the data dictionary for rendering through the Jinja2 template # This dictionary is a reformatted version of the `data` parameter, made to @@ -71,9 +69,7 @@ def create_xml_v3(root, data): "nb_of_txs": data[0]["nb_of_txs"], "initiator_name": data[0]["initiator_name"], "initiator_street_name": data[0]["initiator_street_name"], - "initiator_building_number": data[0][ - "initiator_building_number" - ], + "initiator_building_number": data[0]["initiator_building_number"], "initiator_postal_code": data[0]["initiator_postal_code"], "initiator_town_name": data[0]["initiator_town_name"], "initiator_country_code": data[0]["initiator_country_code"], @@ -99,9 +95,7 @@ def create_xml_v3(root, data): "creditor_agent_BIC": row["creditor_agent_BIC"], "creditor_name": row["creditor_name"], "creditor_street_name": row["creditor_street_name"], - "creditor_building_number": row[ - "creditor_building_number" - ], + "creditor_building_number": row["creditor_building_number"], "creditor_postal_code": row["creditor_postal_code"], "creditor_town_name": row["creditor_town_name"], "creditor_country_code": row["creditor_country_code"], diff --git a/pain001/xml/create_xml_v4.py b/pain001/xml/create_xml_v4.py index a00d41e..2e15e50 100644 --- a/pain001/xml/create_xml_v4.py +++ b/pain001/xml/create_xml_v4.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -51,9 +51,7 @@ def create_xml_v4(root, data): env = Environment(loader=FileSystemLoader("."), autoescape=True) # Load the Jinja2 template - template = env.get_template( - "templates/pain.001.001.04/template.xml" - ) + template = env.get_template("templates/pain.001.001.04/template.xml") # Prepare the data for rendering xml_data_pain001_001_04 = { @@ -62,9 +60,7 @@ def create_xml_v4(root, data): "nb_of_txs": data[0]["nb_of_txs"], "initiator_name": data[0]["initiator_name"], "initiator_street": data[0]["initiator_street"], - "initiator_building_number": data[0][ - "initiator_building_number" - ], + "initiator_building_number": data[0]["initiator_building_number"], "initiator_postal_code": data[0]["initiator_postal_code"], "initiator_town": data[0]["initiator_town"], "initiator_country": data[0]["initiator_country"], @@ -105,9 +101,7 @@ def create_xml_v4(root, data): "creditor_agent_BIC": row["creditor_agent_BIC"], "creditor_name": row["creditor_name"], "creditor_street": row["creditor_street"], - "creditor_building_number": row[ - "creditor_building_number" - ], + "creditor_building_number": row["creditor_building_number"], "creditor_postal_code": row["creditor_postal_code"], "creditor_town": row["creditor_town"], "creditor_account_IBAN": row["creditor_account_IBAN"], diff --git a/pain001/xml/create_xml_v5.py b/pain001/xml/create_xml_v5.py index 1e690d0..fcb99cf 100644 --- a/pain001/xml/create_xml_v5.py +++ b/pain001/xml/create_xml_v5.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -50,9 +50,7 @@ def create_xml_v5(root, data): env = Environment(loader=FileSystemLoader("."), autoescape=True) # Load the Jinja2 template - template = env.get_template( - "templates/pain.001.001.05/template.xml" - ) + template = env.get_template("templates/pain.001.001.05/template.xml") # Prepare the data for rendering xml_data_pain001_001_05 = { @@ -62,9 +60,7 @@ def create_xml_v5(root, data): "ctrl_sum": data[0]["ctrl_sum"], "initiator_name": data[0]["initiator_name"], "initiator_street_name": data[0]["initiator_street_name"], - "initiator_building_number": data[0][ - "initiator_building_number" - ], + "initiator_building_number": data[0]["initiator_building_number"], "initiator_postal_code": data[0]["initiator_postal_code"], "initiator_town": data[0]["initiator_town_name"], "initiator_country": data[0]["initiator_country"], diff --git a/pain001/xml/create_xml_v6.py b/pain001/xml/create_xml_v6.py index 52d0647..ee878be 100644 --- a/pain001/xml/create_xml_v6.py +++ b/pain001/xml/create_xml_v6.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -58,9 +58,7 @@ def create_xml_v6(root, data): "ctrl_sum": data[0]["ctrl_sum"], "initiator_name": data[0]["initiator_name"], "initiator_street_name": data[0]["initiator_street_name"], - "initiator_building_number": data[0][ - "initiator_building_number" - ], + "initiator_building_number": data[0]["initiator_building_number"], "initiator_postal_code": data[0]["initiator_postal_code"], "initiator_town": data[0]["initiator_town_name"], "initiator_country": data[0]["initiator_country"], @@ -93,7 +91,7 @@ def create_xml_v6(root, data): "creditor_agent_name": data[0]["creditor_agent_name"], "purpose_code": data[0]["purpose_code"], "reference_number": data[0]["reference_number"], - "reference_date": data[0]["reference_date"] + "reference_date": data[0]["reference_date"], } # Render template diff --git a/pain001/xml/create_xml_v7.py b/pain001/xml/create_xml_v7.py index 83ab84d..c9621a8 100644 --- a/pain001/xml/create_xml_v7.py +++ b/pain001/xml/create_xml_v7.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -58,9 +58,7 @@ def create_xml_v7(root, data): "ctrl_sum": data[0]["ctrl_sum"], "initiator_name": data[0]["initiator_name"], "initiator_street_name": data[0]["initiator_street_name"], - "initiator_building_number": data[0][ - "initiator_building_number" - ], + "initiator_building_number": data[0]["initiator_building_number"], "initiator_postal_code": data[0]["initiator_postal_code"], "initiator_town": data[0]["initiator_town_name"], "initiator_country": data[0]["initiator_country"], @@ -93,7 +91,7 @@ def create_xml_v7(root, data): "creditor_agent_name": data[0]["creditor_agent_name"], "purpose_code": data[0]["purpose_code"], "reference_number": data[0]["reference_number"], - "reference_date": data[0]["reference_date"] + "reference_date": data[0]["reference_date"], } # Render template diff --git a/pain001/xml/create_xml_v8.py b/pain001/xml/create_xml_v8.py index 2ecf3ab..77db7a5 100644 --- a/pain001/xml/create_xml_v8.py +++ b/pain001/xml/create_xml_v8.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -58,9 +58,7 @@ def create_xml_v8(root, data): "ctrl_sum": data[0]["ctrl_sum"], "initiator_name": data[0]["initiator_name"], "initiator_street_name": data[0]["initiator_street_name"], - "initiator_building_number": data[0][ - "initiator_building_number" - ], + "initiator_building_number": data[0]["initiator_building_number"], "initiator_postal_code": data[0]["initiator_postal_code"], "initiator_town": data[0]["initiator_town_name"], "initiator_country": data[0]["initiator_country"], @@ -93,7 +91,7 @@ def create_xml_v8(root, data): "creditor_agent_name": data[0]["creditor_agent_name"], "purpose_code": data[0]["purpose_code"], "reference_number": data[0]["reference_number"], - "reference_date": data[0]["reference_date"] + "reference_date": data[0]["reference_date"], } # Render template diff --git a/pain001/xml/create_xml_v9.py b/pain001/xml/create_xml_v9.py index fdaa086..ae58249 100644 --- a/pain001/xml/create_xml_v9.py +++ b/pain001/xml/create_xml_v9.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -50,9 +50,7 @@ def create_xml_v9(root, data): env = Environment(loader=FileSystemLoader("."), autoescape=True) # Load the Jinja2 template - template = env.get_template( - "templates/pain.001.001.09/template.xml" - ) + template = env.get_template("templates/pain.001.001.09/template.xml") # Prepare the data xml_data_pain001_001_09 = { @@ -65,9 +63,7 @@ def create_xml_v9(root, data): "payment_id": row["payment_id"], "payment_method": row["payment_method"], "payment_nb_of_txs": row["nb_of_txs"], - "requested_execution_date": row[ - "requested_execution_date" - ], + "requested_execution_date": row["requested_execution_date"], "debtor_name": row["debtor_name"], "debtor_account_IBAN": row["debtor_account_IBAN"], "debtor_agent_BIC": row["debtor_agent_BIC"], @@ -79,9 +75,7 @@ def create_xml_v9(root, data): "payment_currency": row["currency"], "cdtr_agent_BICFI": row["creditor_agent_BIC"], "creditor_name": row["creditor_name"], - "cdtr_account_IBAN": row[ - "creditor_account_IBAN" - ], + "cdtr_account_IBAN": row["creditor_account_IBAN"], "remittance_information": row[ "remittance_information" ], diff --git a/pain001/xml/generate_updated_xml_file_path.py b/pain001/xml/generate_updated_xml_file_path.py index 7e9cc80..7417a2c 100644 --- a/pain001/xml/generate_updated_xml_file_path.py +++ b/pain001/xml/generate_updated_xml_file_path.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/pain001/xml/generate_xml.py b/pain001/xml/generate_xml.py index 45e6e46..e40ec03 100644 --- a/pain001/xml/generate_xml.py +++ b/pain001/xml/generate_xml.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -86,19 +86,13 @@ def generate_xml( "date": data[0]["date"], "nb_of_txs": data[0]["nb_of_txs"], "initiator_name": data[0]["initiator_name"], - "initiator_street_name": data[0][ - "initiator_street_name" - ], + "initiator_street_name": data[0]["initiator_street_name"], "initiator_building_number": data[0][ "initiator_building_number" ], - "initiator_postal_code": data[0][ - "initiator_postal_code" - ], + "initiator_postal_code": data[0]["initiator_postal_code"], "initiator_town_name": data[0]["initiator_town_name"], - "initiator_country_code": data[0][ - "initiator_country_code" - ], + "initiator_country_code": data[0]["initiator_country_code"], "payment_id": data[0]["payment_id"], "payment_method": data[0]["payment_method"], "batch_booking": data[0]["batch_booking"], @@ -107,9 +101,7 @@ def generate_xml( ], "debtor_name": data[0]["debtor_name"], "debtor_street_name": data[0]["debtor_street_name"], - "debtor_building_number": data[0][ - "debtor_building_number" - ], + "debtor_building_number": data[0]["debtor_building_number"], "debtor_postal_code": data[0]["debtor_postal_code"], "debtor_town_name": data[0]["debtor_town_name"], "debtor_country_code": data[0]["debtor_country_code"], @@ -120,28 +112,18 @@ def generate_xml( { "payment_id": row["payment_id"], "payment_amount": row.get("payment_amount", ""), - "payment_currency": row.get( - "payment_currency", "" - ), + "payment_currency": row.get("payment_currency", ""), "charge_bearer": row["charge_bearer"], "creditor_agent_BIC": row["creditor_agent_BIC"], "creditor_name": row["creditor_name"], - "creditor_street_name": row[ - "creditor_street_name" - ], + "creditor_street_name": row["creditor_street_name"], "creditor_building_number": row[ "creditor_building_number" ], - "creditor_postal_code": row[ - "creditor_postal_code" - ], + "creditor_postal_code": row["creditor_postal_code"], "creditor_town_name": row["creditor_town_name"], - "creditor_country_code": row[ - "creditor_country_code" - ], - "creditor_account_IBAN": row[ - "creditor_account_IBAN" - ], + "creditor_country_code": row["creditor_country_code"], + "creditor_account_IBAN": row["creditor_account_IBAN"], "purpose_code": row["purpose_code"], "reference_number": row["reference_number"], "reference_date": row["reference_date"], @@ -159,9 +141,7 @@ def generate_xml( "initiator_building_number": data[0][ "initiator_building_number" ], - "initiator_postal_code": data[0][ - "initiator_postal_code" - ], + "initiator_postal_code": data[0]["initiator_postal_code"], "initiator_town": data[0]["initiator_town_name"], "initiator_country": data[0]["initiator_country_code"], "payment_information_id": data[0]["payment_id"], @@ -172,9 +152,7 @@ def generate_xml( ], "debtor_name": data[0]["debtor_name"], "debtor_street": data[0]["debtor_street_name"], - "debtor_building_number": data[0][ - "debtor_building_number" - ], + "debtor_building_number": data[0]["debtor_building_number"], "debtor_postal_code": data[0]["debtor_postal_code"], "debtor_town": data[0]["debtor_town_name"], "debtor_country": data[0]["debtor_country_code"], @@ -189,12 +167,8 @@ def generate_xml( "charge_bearer": data[0]["charge_bearer"], "charge_account_IBAN": data[0]["charge_account_IBAN"], "charge_agent_BICFI": data[0]["charge_agent_BICFI"], - "payment_instruction_id": data[0][ - "payment_instruction_id" - ], - "payment_end_to_end_id": data[0][ - "payment_end_to_end_id" - ], + "payment_instruction_id": data[0]["payment_instruction_id"], + "payment_end_to_end_id": data[0]["payment_end_to_end_id"], "payment_currency": data[0]["payment_currency"], "payment_amount": data[0]["payment_amount"], "creditor_agent_BIC": data[0]["creditor_agent_BIC"], @@ -205,21 +179,15 @@ def generate_xml( ], "creditor_postal_code": data[0]["creditor_postal_code"], "creditor_town": data[0]["creditor_town"], - "creditor_account_IBAN": data[0][ - "creditor_account_IBAN" - ], + "creditor_account_IBAN": data[0]["creditor_account_IBAN"], "purpose_code": data[0]["purpose_code"], "reference_number": data[0]["reference_number"], "reference_date": data[0]["reference_date"], "transactions": [ { "payment_instruction_id": row["payment_id"], - "payment_end_to_end_id": row[ - "reference_number" - ], - "payment_currency": row.get( - "payment_currency", "EUR" - ), + "payment_end_to_end_id": row["reference_number"], + "payment_currency": row.get("payment_currency", "EUR"), "payment_amount": row.get("payment_amount", ""), "charge_bearer": row["charge_bearer"], "creditor_agent_BIC": row["creditor_agent_BIC"], @@ -228,13 +196,9 @@ def generate_xml( "creditor_building_number": row[ "creditor_building_number" ], - "creditor_postal_code": row[ - "creditor_postal_code" - ], + "creditor_postal_code": row["creditor_postal_code"], "creditor_town": row["creditor_town_name"], - "creditor_account_IBAN": row[ - "creditor_account_IBAN" - ], + "creditor_account_IBAN": row["creditor_account_IBAN"], "purpose_code": row["purpose_code"], "reference_number": row["reference_number"], "reference_date": row["reference_date"], @@ -249,15 +213,11 @@ def generate_xml( "nb_of_txs": data[0]["nb_of_txs"], "ctrl_sum": data[0]["ctrl_sum"], "initiator_name": data[0]["initiator_name"], - "initiator_street_name": data[0][ - "initiator_street_name" - ], + "initiator_street_name": data[0]["initiator_street_name"], "initiator_building_number": data[0][ "initiator_building_number" ], - "initiator_postal_code": data[0][ - "initiator_postal_code" - ], + "initiator_postal_code": data[0]["initiator_postal_code"], "initiator_town": data[0]["initiator_town_name"], "initiator_country": data[0]["initiator_country"], "ultimate_debtor_name": data[0]["ultimate_debtor_name"], @@ -265,27 +225,19 @@ def generate_xml( "requested_execution_date": data[0][ "requested_execution_date" ], - "payment_information_id": data[0][ - "payment_information_id" - ], + "payment_information_id": data[0]["payment_information_id"], "payment_method": data[0]["payment_method"], "batch_booking": data[0]["batch_booking"], "debtor_name": data[0]["debtor_name"], "debtor_street": data[0]["debtor_street"], - "debtor_building_number": data[0][ - "debtor_building_number" - ], + "debtor_building_number": data[0]["debtor_building_number"], "debtor_postal_code": data[0]["debtor_postal_code"], "debtor_town": data[0]["debtor_town"], "debtor_country": data[0]["debtor_country"], "debtor_account_IBAN": data[0]["debtor_account_IBAN"], "debtor_agent_BIC": data[0]["debtor_agent_BIC"], - "payment_instruction_id": data[0][ - "payment_instruction_id" - ], - "payment_end_to_end_id": data[0][ - "payment_end_to_end_id" - ], + "payment_instruction_id": data[0]["payment_instruction_id"], + "payment_end_to_end_id": data[0]["payment_end_to_end_id"], "payment_currency": data[0]["payment_currency"], "payment_amount": data[0]["payment_amount"], "charge_bearer": data[0]["charge_bearer"], @@ -297,9 +249,7 @@ def generate_xml( "creditor_postal_code": data[0]["creditor_postal_code"], "creditor_town": data[0]["creditor_town"], "creditor_country": data[0]["creditor_country"], - "creditor_account_IBAN": data[0][ - "creditor_account_IBAN" - ], + "creditor_account_IBAN": data[0]["creditor_account_IBAN"], "creditor_agent_BICFI": data[0]["creditor_agent_BICFI"], "purpose_code": data[0]["purpose_code"], "reference_number": data[0]["reference_number"], @@ -353,25 +303,21 @@ def generate_xml( "reference_number": data[0]["reference_number"], "reference_date": data[0]["reference_date"], "transactions": [ - { - "payment_id": row["payment_id"], - "payment_amount": row["payment_amount"], - "payment_currency": row.get( - "payment_currency", "" - ), - "charge_bearer": row["charge_bearer"], - "creditor_agent_BIC": row["creditor_agent_BIC"], - "creditor_name": row["creditor_name"], - "creditor_account_IBAN": row[ - "creditor_account_IBAN" - ], - "creditor_remittance_information": row[ - "remittance_information" - ], - } - for row in data[0:] - ], - } + { + "payment_id": row["payment_id"], + "payment_amount": row["payment_amount"], + "payment_currency": row.get("payment_currency", ""), + "charge_bearer": row["charge_bearer"], + "creditor_agent_BIC": row["creditor_agent_BIC"], + "creditor_name": row["creditor_name"], + "creditor_account_IBAN": row["creditor_account_IBAN"], + "creditor_remittance_information": row[ + "remittance_information" + ], + } + for row in data[0:] + ], + } elif payment_initiation_message_type == "pain.001.001.07": xml_data_pain001_001_07 = { @@ -420,25 +366,21 @@ def generate_xml( "reference_number": data[0]["reference_number"], "reference_date": data[0]["reference_date"], "transactions": [ - { - "payment_id": row["payment_id"], - "payment_amount": row["payment_amount"], - "payment_currency": row.get( - "payment_currency", "" - ), - "charge_bearer": row["charge_bearer"], - "creditor_agent_BIC": row["creditor_agent_BIC"], - "creditor_name": row["creditor_name"], - "creditor_account_IBAN": row[ - "creditor_account_IBAN" - ], - "creditor_remittance_information": row[ - "remittance_information" - ], - } - for row in data[0:] - ], - } + { + "payment_id": row["payment_id"], + "payment_amount": row["payment_amount"], + "payment_currency": row.get("payment_currency", ""), + "charge_bearer": row["charge_bearer"], + "creditor_agent_BIC": row["creditor_agent_BIC"], + "creditor_name": row["creditor_name"], + "creditor_account_IBAN": row["creditor_account_IBAN"], + "creditor_remittance_information": row[ + "remittance_information" + ], + } + for row in data[0:] + ], + } elif payment_initiation_message_type == "pain.001.001.08": xml_data_pain001_001_08 = { @@ -487,25 +429,21 @@ def generate_xml( "reference_number": data[0]["reference_number"], "reference_date": data[0]["reference_date"], "transactions": [ - { - "payment_id": row["payment_id"], - "payment_amount": row["payment_amount"], - "payment_currency": row.get( - "payment_currency", "" - ), - "charge_bearer": row["charge_bearer"], - "creditor_agent_BIC": row["creditor_agent_BIC"], - "creditor_name": row["creditor_name"], - "creditor_account_IBAN": row[ - "creditor_account_IBAN" - ], - "creditor_remittance_information": row[ - "remittance_information" - ], - } - for row in data[0:] - ], - } + { + "payment_id": row["payment_id"], + "payment_amount": row["payment_amount"], + "payment_currency": row.get("payment_currency", ""), + "charge_bearer": row["charge_bearer"], + "creditor_agent_BIC": row["creditor_agent_BIC"], + "creditor_name": row["creditor_name"], + "creditor_account_IBAN": row["creditor_account_IBAN"], + "creditor_remittance_information": row[ + "remittance_information" + ], + } + for row in data[0:] + ], + } elif payment_initiation_message_type == "pain.001.001.09": xml_data_pain001_001_09 = { @@ -527,15 +465,11 @@ def generate_xml( { "payment_id": row["payment_id"], "payment_amount": row["payment_amount"], - "payment_currency": row.get( - "payment_currency", "" - ), + "payment_currency": row.get("payment_currency", ""), "charge_bearer": row["charge_bearer"], "creditor_agent_BIC": row["creditor_agent_BIC"], "creditor_name": row["creditor_name"], - "creditor_account_IBAN": row[ - "creditor_account_IBAN" - ], + "creditor_account_IBAN": row["creditor_account_IBAN"], "creditor_remittance_information": row[ "remittance_information" ], @@ -594,22 +528,16 @@ def generate_xml( with open(updated_xml_file_path, "w") as xml_file: xml_file.write(xml_content) - print( - f"A new XML file has been created at `{updated_xml_file_path}`" - ) + print(f"A new XML file has been created at `{updated_xml_file_path}`") # Validate the updated XML file against the XSD schema - is_valid = validate_via_xsd( - updated_xml_file_path, xsd_file_path - ) + is_valid = validate_via_xsd(updated_xml_file_path, xsd_file_path) if not is_valid: print("Error: Invalid XML data.") sys.exit(1) else: - print( - f"The XML has been validated against `{xsd_file_path}`" - ) + print(f"The XML has been validated against `{xsd_file_path}`") else: # Handle the case when the payment_initiation_message_type is diff --git a/pain001/xml/register_namespaces.py b/pain001/xml/register_namespaces.py index 90d3f06..5c21cba 100644 --- a/pain001/xml/register_namespaces.py +++ b/pain001/xml/register_namespaces.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -34,12 +34,9 @@ def register_namespaces(payment_initiation_message_type): # Create the namespace for the payment initiation message type. namespace = ( - "urn:iso:std:iso:20022:tech:xsd:" - + payment_initiation_message_type + "urn:iso:std:iso:20022:tech:xsd:" + payment_initiation_message_type ) # Register the namespaces. et.register_namespace("", namespace) - et.register_namespace( - "xsi", "http://www.w3.org/2001/XMLSchema-instance" - ) + et.register_namespace("xsi", "http://www.w3.org/2001/XMLSchema-instance") diff --git a/pain001/xml/validate_via_xsd.py b/pain001/xml/validate_via_xsd.py index b5790e5..c811d88 100644 --- a/pain001/xml/validate_via_xsd.py +++ b/pain001/xml/validate_via_xsd.py @@ -1,6 +1,6 @@ import xmlschema -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/pain001/xml/write_xml_to_file.py b/pain001/xml/write_xml_to_file.py index e402027..6760b39 100644 --- a/pain001/xml/write_xml_to_file.py +++ b/pain001/xml/write_xml_to_file.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Sebastien Rousseau. +# Copyright (C) 2023-2024 Sebastien Rousseau. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/poetry.lock b/poetry.lock index da30b89..1f82957 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,139 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "alabaster" +version = "0.7.16" +description = "A light, configurable Sphinx theme" +optional = false +python-versions = ">=3.9" +files = [ + {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, + {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, +] + +[[package]] +name = "babel" +version = "2.15.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, + {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, +] + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "certifi" +version = "2024.2.2" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] [[package]] name = "click" @@ -25,6 +160,88 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coverage" +version = "7.5.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e"}, + {file = "coverage-7.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146"}, + {file = "coverage-7.5.1-cp310-cp310-win32.whl", hash = "sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228"}, + {file = "coverage-7.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8"}, + {file = "coverage-7.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428"}, + {file = "coverage-7.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987"}, + {file = "coverage-7.5.1-cp311-cp311-win32.whl", hash = "sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136"}, + {file = "coverage-7.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd"}, + {file = "coverage-7.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206"}, + {file = "coverage-7.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7"}, + {file = "coverage-7.5.1-cp312-cp312-win32.whl", hash = "sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19"}, + {file = "coverage-7.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596"}, + {file = "coverage-7.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7"}, + {file = "coverage-7.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d"}, + {file = "coverage-7.5.1-cp38-cp38-win32.whl", hash = "sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41"}, + {file = "coverage-7.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de"}, + {file = "coverage-7.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1"}, + {file = "coverage-7.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668"}, + {file = "coverage-7.5.1-cp39-cp39-win32.whl", hash = "sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981"}, + {file = "coverage-7.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f"}, + {file = "coverage-7.5.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312"}, + {file = "coverage-7.5.1.tar.gz", hash = "sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "datetime" +version = "5.5" +description = "This package provides a DateTime data type, as known from Zope. Unless you need to communicate with Zope APIs, you're probably better off using Python's built-in datetime module." +optional = false +python-versions = ">=3.7" +files = [ + {file = "DateTime-5.5-py3-none-any.whl", hash = "sha256:0abf6c51cb4ba7cee775ca46ccc727f3afdde463be28dbbe8803631fefd4a120"}, + {file = "DateTime-5.5.tar.gz", hash = "sha256:21ec6331f87a7fcb57bd7c59e8a68bfffe6fcbf5acdbbc7b356d6a9a020191d3"}, +] + +[package.dependencies] +pytz = "*" +"zope.interface" = "*" + [[package]] name = "defusedxml" version = "0.7.1" @@ -36,20 +253,97 @@ files = [ {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, ] +[[package]] +name = "docutils" +version = "0.21.2" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.9" +files = [ + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, +] + [[package]] name = "elementpath" -version = "4.1.5" +version = "4.4.0" description = "XPath 1.0/2.0/3.0/3.1 parsers and selectors for ElementTree and lxml" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "elementpath-4.1.5-py3-none-any.whl", hash = "sha256:2ac1a2fb31eb22bbbf817f8cf6752f844513216263f0e3892c8e79782fe4bb55"}, - {file = "elementpath-4.1.5.tar.gz", hash = "sha256:c2d6dc524b29ef751ecfc416b0627668119d8812441c555d7471da41d4bacb8d"}, + {file = "elementpath-4.4.0-py3-none-any.whl", hash = "sha256:cda092281afe508ece1bf65373905b30196c9426f3730cfea46059e103a131bd"}, + {file = "elementpath-4.4.0.tar.gz", hash = "sha256:dfc4b8ca3d87966dcb0df40b5b6d04a98f053683271930fad9e7fa000924dfb2"}, ] [package.extras] dev = ["Sphinx", "coverage", "flake8", "lxml", "lxml-stubs", "memory-profiler", "memray", "mypy", "tox", "xmlschema (>=2.0.0)"] +[[package]] +name = "exceptiongroup" +version = "1.2.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "importlib-metadata" +version = "7.1.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, + {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + [[package]] name = "jinja2" version = "3.1.4" @@ -93,71 +387,71 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" -version = "2.1.3" +version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] [[package]] @@ -171,30 +465,127 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + [[package]] name = "pygments" -version = "2.17.1" +version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pygments-2.17.1-py3-none-any.whl", hash = "sha256:1b37f1b1e1bff2af52ecaf28cc601e2ef7077000b227a0675da25aef85784bc4"}, - {file = "pygments-2.17.1.tar.gz", hash = "sha256:e45a0e74bf9c530f564ca81b8952343be986a29f6afe7f5ad95c5f06b7bdf5e8"}, + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] [package.extras] -plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pytest" +version = "8.2.0" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"}, + {file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2.0" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytz" +version = "2024.1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + [[package]] name = "rich" -version = "13.7.0" +version = "13.7.1" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, - {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, + {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, + {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, ] [package.dependencies] @@ -204,26 +595,271 @@ pygments = ">=2.13.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "setuptools" +version = "69.5.1" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "sphinx" +version = "7.3.7" +description = "Python documentation generator" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinx-7.3.7-py3-none-any.whl", hash = "sha256:413f75440be4cacf328f580b4274ada4565fb2187d696a84970c23f77b64d8c3"}, + {file = "sphinx-7.3.7.tar.gz", hash = "sha256:a4a7db75ed37531c05002d56ed6948d4c42f473a36f46e1382b0bd76ca9627bc"}, +] + +[package.dependencies] +alabaster = ">=0.7.14,<0.8.0" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.18.1,<0.22" +imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.14" +requests = ">=2.25.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.9" +tomli = {version = ">=2", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["flake8 (>=3.5.0)", "importlib_metadata", "mypy (==1.9.0)", "pytest (>=6.0)", "ruff (==0.3.7)", "sphinx-lint", "tomli", "types-docutils", "types-requests"] +test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=6.0)", "setuptools (>=67.0)"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.8" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_applehelp-1.0.8-py3-none-any.whl", hash = "sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4"}, + {file = "sphinxcontrib_applehelp-1.0.8.tar.gz", hash = "sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.6" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_devhelp-1.0.6-py3-none-any.whl", hash = "sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f"}, + {file = "sphinxcontrib_devhelp-1.0.6.tar.gz", hash = "sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.5" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_htmlhelp-2.0.5-py3-none-any.whl", hash = "sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04"}, + {file = "sphinxcontrib_htmlhelp-2.0.5.tar.gz", hash = "sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.7" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_qthelp-1.0.7-py3-none-any.whl", hash = "sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182"}, + {file = "sphinxcontrib_qthelp-1.0.7.tar.gz", hash = "sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.10" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_serializinghtml-1.1.10-py3-none-any.whl", hash = "sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7"}, + {file = "sphinxcontrib_serializinghtml-1.1.10.tar.gz", hash = "sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "urllib3" +version = "2.2.1" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + [[package]] name = "xmlschema" -version = "2.5.0" +version = "3.3.1" description = "An XML Schema validator and decoder" optional = false +python-versions = ">=3.8" +files = [ + {file = "xmlschema-3.3.1-py3-none-any.whl", hash = "sha256:93547e999896f1525a821e229991d31e4dd02563f93b0667ce6566cf2b853751"}, + {file = "xmlschema-3.3.1.tar.gz", hash = "sha256:2066ecbc9728112073f6f44d17c5c16723aff1c7d22a7c4c6421e2d68ec5f0ea"}, +] + +[package.dependencies] +elementpath = ">=4.4.0,<5.0.0" + +[package.extras] +codegen = ["elementpath (>=4.4.0,<5.0.0)", "jinja2"] +dev = ["Sphinx", "coverage", "elementpath (>=4.4.0,<5.0.0)", "flake8", "jinja2", "lxml", "lxml-stubs", "memory-profiler", "mypy", "sphinx-rtd-theme", "tox"] +docs = ["Sphinx", "elementpath (>=4.4.0,<5.0.0)", "jinja2", "sphinx-rtd-theme"] + +[[package]] +name = "zipp" +version = "3.18.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.18.2-py3-none-any.whl", hash = "sha256:dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e"}, + {file = "zipp-3.18.2.tar.gz", hash = "sha256:6278d9ddbcfb1f1089a88fde84481528b07b0e10474e09dcfe53dad4069fa059"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "zope-interface" +version = "6.4" +description = "Interfaces for Python" +optional = false python-versions = ">=3.7" files = [ - {file = "xmlschema-2.5.0-py3-none-any.whl", hash = "sha256:f2b29c45485fac414cc1fdb38d18a220c5987d7d3aa996e6df6ff35ee94d5a63"}, - {file = "xmlschema-2.5.0.tar.gz", hash = "sha256:276a03e0fd3c94c148d528bff4d9482f9b99bf8c7b4056a2e8e703d28149d454"}, + {file = "zope.interface-6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72faa868fcfde49a29d287dce3c83180322467eecd725dd351098efe96e8d4bb"}, + {file = "zope.interface-6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:855b7233fa5d0d1f3be8c14fadf4718dee1c928e1d75f1584bea6ecec6dcc4af"}, + {file = "zope.interface-6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36ee6e507a9fd4f1f0aab8e8dfc801d162e7211c27503cbfb47e1d558941a7fa"}, + {file = "zope.interface-6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:604fa920478dfc0c76cdb7c203572400a8317ffcdac288245c408b42b3d9aee9"}, + {file = "zope.interface-6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c04bd4ee4766d285e83c6d8c042663a98efb934389e05ccd643fefb066c88a9d"}, + {file = "zope.interface-6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4782e173c2fde4f649c2a9a68082445bc1f2c27f41907de06bf1ba82585847f2"}, + {file = "zope.interface-6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:646cd83d24065d074f22f61fe101d20dbf4b729ca7831cc782ec986eb9156f93"}, + {file = "zope.interface-6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0f61ccbc26e08031d0e72b6a0cbf9b4030f035913cb2b39f940aa42eb8e0063"}, + {file = "zope.interface-6.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:414e6dccdf4a5c96c0c98da68ba040dbf9ba7511b61b34e228f11b0ed90c439d"}, + {file = "zope.interface-6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5092f2712e1fd07579fc3101b18e9c95857c853e836847598bf992c8e672434"}, + {file = "zope.interface-6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21732994aa3ca43bbb6b36335c288023428a3c5b7322b637c7b0a03053937578"}, + {file = "zope.interface-6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe636b49c333bfc5b0913590e36a2f151167c462fb36d9f4acc66029e45c974b"}, + {file = "zope.interface-6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57f34b7997f8de7d2db08363eaccd05dad20f106e39efe95bed4fac84af2d022"}, + {file = "zope.interface-6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6494dc0314e782ce4fb0e624b4ce2458f54d074382f50a920c7700c05cbcef28"}, + {file = "zope.interface-6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cda82ab32f984985f09e4ec20a4f9665b26779a1b8e443b34a148de256f2052"}, + {file = "zope.interface-6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f78e1eac48c4f4e0168a91cabcd8d1aedb972836df5c8769071fc6173294a0a3"}, + {file = "zope.interface-6.4-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:8e246357f52952ae5fa950d19eda8572594c49e6cb1e5462508e6cec561a37de"}, + {file = "zope.interface-6.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93f28d84517dcd6c240979bd9b2f262a373832baef856fe663a24b9171d7f04d"}, + {file = "zope.interface-6.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cd56eb9a23767958c9a0654306b9a4a74def485f645b3a7378cc6ab661ef31c"}, + {file = "zope.interface-6.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:502d2c9c4231d022b20225dba5c6c736236ed65e1d7e2f6f402b5aa6a7040ec9"}, + {file = "zope.interface-6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ee1e3ca6c98efe213a96dece89100a8aa52e210ac354861d8039d69bd1d6e5ff"}, + {file = "zope.interface-6.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62e6b756663deade5270f67899753437b39d970f9eecd49e19fae3b880310cf0"}, + {file = "zope.interface-6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f33af86ed460eb28dc9da1de1f3305795271a19c665161c1d973a737596b2081"}, + {file = "zope.interface-6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:86e85eada0eb551950df05d72dc0e892320f14daa78bc434059e834d4b1f9300"}, + {file = "zope.interface-6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3945f4fda92c1b6fb0cb6eaaaf72599e5c2c2059654bdc42bc09c6e711c214c8"}, + {file = "zope.interface-6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbbb290751f5c4ed81e54ae73fe8557c4a85973f5ab019edbb0f746244ecea6"}, + {file = "zope.interface-6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e4cc017206c1429a6d8fdd8a25c6efc15512065eec0a8d45c350df96a0911ed"}, + {file = "zope_interface-6.4.tar.gz", hash = "sha256:b11f2b67ccc990a1522fa8cd3f5d185a068459f944ab2d0e7a1b15d31bcb4af4"}, ] [package.dependencies] -elementpath = ">=4.1.5,<5.0.0" +setuptools = "*" [package.extras] -codegen = ["elementpath (>=4.1.5,<5.0.0)", "jinja2"] -dev = ["Sphinx", "coverage", "elementpath (>=4.1.5,<5.0.0)", "flake8", "jinja2", "lxml", "lxml-stubs", "memory-profiler", "mypy", "sphinx-rtd-theme", "tox"] -docs = ["Sphinx", "elementpath (>=4.1.5,<5.0.0)", "jinja2", "sphinx-rtd-theme"] +docs = ["Sphinx", "repoze.sphinx.autointerface", "sphinx-rtd-theme"] +test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] +testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "2e59b18dba3603c1510ba0a04777a321f3a1eb4be33cf24f218688652aea7908" +content-hash = "3d6e44a3939706e8dcf547f5b6aeba53219ac653aae4bbc930ed951e5277ab90" diff --git a/pyproject.toml b/pyproject.toml index 46209ac..b94a2a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pain001" -version = "0.0.24" +version = "0.0.25" description = "Pain001 is a Python Library for Automating ISO 20022-Compliant Payment Files Using CSV Data." authors = ["Sebastien Rousseau "] license = "Apache Software License" @@ -9,30 +9,41 @@ repository = "https://github.com/sebastienrousseau/pain001" homepage = "https://pain001.com" [tool.poetry.dependencies] -click = "^8.1.7" -colorama = "^0.4.6" -defusedxml = "^0.7.1" -elementpath = "^4.1.5" -jinja2 = "^3.1.4" -markdown-it-py = "^3.0.0" -markupsafe = "^2.1.3" -mdurl = "^0.1.2" -pygments = "^2.16.1" +click = "8.1.7" +colorama = "0.4.6" +datetime = "5.5" +defusedxml = "0.7.1" +elementpath = "4.4.0" +jinja2 = "3.1.4" +markdown-it-py = "3.0.0" +markupsafe = "2.1.5" +mdurl = "0.1.2" +pygments = "2.18.0" python = "^3.9" -rich = "^13.5.2" -xmlschema = "^2.4.0" +rich = "13.7.1" +xmlschema = "3.3.1" + +[tool.poetry.group.dev.dependencies] +pytest = "^8.1.0" +pytest-cov = "^5.0.0" + +[tool.poetry.group.docs] +optional = true + +[tool.poetry.group.docs.dependencies] +Sphinx = "^7.3.7" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.black] -line-length = 72 +line-length = 79 target-version = ['py39'] [tool.isort] profile = "black" -line_length = 72 +line_length = 79 multi_line_output = 3 include_trailing_comma = true force_grid_wrap = 0 diff --git a/requirements.txt b/requirements.txt index 34580b4..54375aa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,88 +4,126 @@ click==8.1.7 ; python_version >= "3.9" and python_version < "4.0" \ colorama==0.4.6 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 +datetime==5.5 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:0abf6c51cb4ba7cee775ca46ccc727f3afdde463be28dbbe8803631fefd4a120 \ + --hash=sha256:21ec6331f87a7fcb57bd7c59e8a68bfffe6fcbf5acdbbc7b356d6a9a020191d3 defusedxml==0.7.1 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \ --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 -elementpath==4.1.5 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:2ac1a2fb31eb22bbbf817f8cf6752f844513216263f0e3892c8e79782fe4bb55 \ - --hash=sha256:c2d6dc524b29ef751ecfc416b0627668119d8812441c555d7471da41d4bacb8d -jinja2==3.1.2 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ - --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 +elementpath==4.4.0 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:cda092281afe508ece1bf65373905b30196c9426f3730cfea46059e103a131bd \ + --hash=sha256:dfc4b8ca3d87966dcb0df40b5b6d04a98f053683271930fad9e7fa000924dfb2 +jinja2==3.1.4 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ + --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d markdown-it-py==3.0.0 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb -markupsafe==2.1.3 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \ - --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \ - --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \ - --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \ - --hash=sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c \ - --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \ - --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \ - --hash=sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb \ - --hash=sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939 \ - --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \ - --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \ - --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \ - --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \ - --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \ - --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \ - --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \ - --hash=sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd \ - --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \ - --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \ - --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \ - --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \ - --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \ - --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \ - --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \ - --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \ - --hash=sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007 \ - --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \ - --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \ - --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \ - --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \ - --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \ - --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \ - --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \ - --hash=sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1 \ - --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \ - --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \ - --hash=sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c \ - --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \ - --hash=sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823 \ - --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \ - --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \ - --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \ - --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \ - --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \ - --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \ - --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \ - --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \ - --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \ - --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \ - --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \ - --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \ - --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \ - --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \ - --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \ - --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \ - --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \ - --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \ - --hash=sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc \ - --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 \ - --hash=sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11 +markupsafe==2.1.5 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \ + --hash=sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff \ + --hash=sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f \ + --hash=sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3 \ + --hash=sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532 \ + --hash=sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f \ + --hash=sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617 \ + --hash=sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df \ + --hash=sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4 \ + --hash=sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906 \ + --hash=sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f \ + --hash=sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 \ + --hash=sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8 \ + --hash=sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371 \ + --hash=sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2 \ + --hash=sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465 \ + --hash=sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52 \ + --hash=sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6 \ + --hash=sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169 \ + --hash=sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad \ + --hash=sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2 \ + --hash=sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0 \ + --hash=sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029 \ + --hash=sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f \ + --hash=sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a \ + --hash=sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced \ + --hash=sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5 \ + --hash=sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c \ + --hash=sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf \ + --hash=sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9 \ + --hash=sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb \ + --hash=sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad \ + --hash=sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3 \ + --hash=sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 \ + --hash=sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46 \ + --hash=sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc \ + --hash=sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a \ + --hash=sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee \ + --hash=sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900 \ + --hash=sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5 \ + --hash=sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea \ + --hash=sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f \ + --hash=sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5 \ + --hash=sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e \ + --hash=sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a \ + --hash=sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f \ + --hash=sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50 \ + --hash=sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a \ + --hash=sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b \ + --hash=sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4 \ + --hash=sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff \ + --hash=sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2 \ + --hash=sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46 \ + --hash=sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b \ + --hash=sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf \ + --hash=sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 \ + --hash=sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5 \ + --hash=sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab \ + --hash=sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd \ + --hash=sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68 mdurl==0.1.2 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba -pygments==2.17.1 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:1b37f1b1e1bff2af52ecaf28cc601e2ef7077000b227a0675da25aef85784bc4 \ - --hash=sha256:e45a0e74bf9c530f564ca81b8952343be986a29f6afe7f5ad95c5f06b7bdf5e8 -rich==13.7.0 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa \ - --hash=sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235 -xmlschema==2.5.0 ; python_version >= "3.9" and python_version < "4.0" \ - --hash=sha256:276a03e0fd3c94c148d528bff4d9482f9b99bf8c7b4056a2e8e703d28149d454 \ - --hash=sha256:f2b29c45485fac414cc1fdb38d18a220c5987d7d3aa996e6df6ff35ee94d5a63 +pygments==2.18.0 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \ + --hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a +pytz==2024.1 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812 \ + --hash=sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319 +rich==13.7.1 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222 \ + --hash=sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432 +setuptools==69.5.1 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987 \ + --hash=sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32 +xmlschema==3.3.1 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:2066ecbc9728112073f6f44d17c5c16723aff1c7d22a7c4c6421e2d68ec5f0ea \ + --hash=sha256:93547e999896f1525a821e229991d31e4dd02563f93b0667ce6566cf2b853751 +zope-interface==6.4 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:21732994aa3ca43bbb6b36335c288023428a3c5b7322b637c7b0a03053937578 \ + --hash=sha256:36ee6e507a9fd4f1f0aab8e8dfc801d162e7211c27503cbfb47e1d558941a7fa \ + --hash=sha256:3945f4fda92c1b6fb0cb6eaaaf72599e5c2c2059654bdc42bc09c6e711c214c8 \ + --hash=sha256:414e6dccdf4a5c96c0c98da68ba040dbf9ba7511b61b34e228f11b0ed90c439d \ + --hash=sha256:4782e173c2fde4f649c2a9a68082445bc1f2c27f41907de06bf1ba82585847f2 \ + --hash=sha256:4cd56eb9a23767958c9a0654306b9a4a74def485f645b3a7378cc6ab661ef31c \ + --hash=sha256:502d2c9c4231d022b20225dba5c6c736236ed65e1d7e2f6f402b5aa6a7040ec9 \ + --hash=sha256:57f34b7997f8de7d2db08363eaccd05dad20f106e39efe95bed4fac84af2d022 \ + --hash=sha256:5fbbb290751f5c4ed81e54ae73fe8557c4a85973f5ab019edbb0f746244ecea6 \ + --hash=sha256:604fa920478dfc0c76cdb7c203572400a8317ffcdac288245c408b42b3d9aee9 \ + --hash=sha256:62e6b756663deade5270f67899753437b39d970f9eecd49e19fae3b880310cf0 \ + --hash=sha256:646cd83d24065d074f22f61fe101d20dbf4b729ca7831cc782ec986eb9156f93 \ + --hash=sha256:6494dc0314e782ce4fb0e624b4ce2458f54d074382f50a920c7700c05cbcef28 \ + --hash=sha256:6e4cc017206c1429a6d8fdd8a25c6efc15512065eec0a8d45c350df96a0911ed \ + --hash=sha256:72faa868fcfde49a29d287dce3c83180322467eecd725dd351098efe96e8d4bb \ + --hash=sha256:7cda82ab32f984985f09e4ec20a4f9665b26779a1b8e443b34a148de256f2052 \ + --hash=sha256:855b7233fa5d0d1f3be8c14fadf4718dee1c928e1d75f1584bea6ecec6dcc4af \ + --hash=sha256:86e85eada0eb551950df05d72dc0e892320f14daa78bc434059e834d4b1f9300 \ + --hash=sha256:8e246357f52952ae5fa950d19eda8572594c49e6cb1e5462508e6cec561a37de \ + --hash=sha256:93f28d84517dcd6c240979bd9b2f262a373832baef856fe663a24b9171d7f04d \ + --hash=sha256:b0f61ccbc26e08031d0e72b6a0cbf9b4030f035913cb2b39f940aa42eb8e0063 \ + --hash=sha256:b11f2b67ccc990a1522fa8cd3f5d185a068459f944ab2d0e7a1b15d31bcb4af4 \ + --hash=sha256:c04bd4ee4766d285e83c6d8c042663a98efb934389e05ccd643fefb066c88a9d \ + --hash=sha256:ee1e3ca6c98efe213a96dece89100a8aa52e210ac354861d8039d69bd1d6e5ff \ + --hash=sha256:f33af86ed460eb28dc9da1de1f3305795271a19c665161c1d973a737596b2081 \ + --hash=sha256:f5092f2712e1fd07579fc3101b18e9c95857c853e836847598bf992c8e672434 \ + --hash=sha256:f78e1eac48c4f4e0168a91cabcd8d1aedb972836df5c8769071fc6173294a0a3 \ + --hash=sha256:fe636b49c333bfc5b0913590e36a2f151167c462fb36d9f4acc66029e45c974b diff --git a/setup.cfg b/setup.cfg index 0fc57ed..733b31f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,28 +15,63 @@ # limitations under the License. [metadata] +author = "Sebastien Rousseau " +description = "Pain001 is a Python Library for Automating ISO 20022-Compliant Payment Files Using CSV Data." +license = "Apache Software License" name = pain001 -version = 0.0.24 +version = 0.0.25 [options] +packages = find: +include_package_data = true install_requires = click==8.1.7 colorama==0.4.6 + datetime==5.5 defusedxml==0.7.1 - elementpath==4.1.5 - jinja2==3.1.2 + elementpath==4.4.0 + jinja2==3.1.4 markdown-it-py==3.0.0 - markupsafe==2.1.3 + markupsafe==2.1.5 mdurl==0.1.2 - pygments==2.17.1 - rich==13.7.0 - xmlschema==2.5.0 + pygments==2.18.0 + python=3.9 + rich==13.7.1 + xmlschema==3.3.1 + +[options.extras_require] +dev = + pytest==8.1.0 + pytest-cov==5.0.0 + +[options.entry_points] +console_scripts = + pain001 = pain001.cli:main [aliases] test = pytest -[tool:pytest] -testpaths = tests +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.black] +line-length = 79 +target-version = ['py39'] + +[tool.isort] +profile = "black" +line_length = 79 +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +combine_as_imports = true +known_first_party = "pain001" +known_third_party = "xmlschema" + +[tool.pytest] +addopts = "--cov=pain001 --cov-report=term-missing --cov-report=xml --cov-report=html --cov-fail-under=100" +testpaths = "tests" [wheel] universal = 1 diff --git a/setup.py b/setup.py index 98e10fb..dfffd16 100644 --- a/setup.py +++ b/setup.py @@ -14,65 +14,148 @@ # See the License for the specific language governing permissions and # limitations under the License. -from pathlib import Path -from setuptools import setup, find_packages - - -this_directory = Path(__file__).parent -long_description = (this_directory / "README.md").read_text() - -setup_requirements = [] -test_requirements = ["pytest>=7.4.2"] - -setup( - name="pain001", - version="0.0.24", - description=""" -Pain001, A Python Library for Automating ISO 20022-Compliant Payment Files -Using CSV Or SQLite Data Files. -""", - long_description=long_description, - long_description_content_type="text/markdown", - author="Sebastien Rousseau", - author_email="sebastian.rousseau@gmail.com", - url="https://pain001.com", - license="Apache Software License", - classifiers=[ - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - "Intended Audience :: Financial and Insurance Industry", - "License :: OSI Approved :: Apache Software License", - "Operating System :: MacOS", - "Operating System :: OS Independent", - "Operating System :: POSIX", - "Operating System :: Unix", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python", - "Topic :: Software Development :: Libraries :: Python Modules", - ], - keywords=""" -pain001,iso20022,payment-processing,automate-payments,sepa,financial, -banking-payments,csv,sqlite -""", - packages=find_packages(exclude=["docs", "tests*"]), - install_requires=[ - "click==8.1.7", - "colorama==0.4.6", - "defusedxml==0.7.1", - "elementpath==4.1.5", - "jinja2==3.1.3", - "markdown-it-py==3.0.0", - "markupsafe==2.1.3", - "mdurl==0.1.2", - "pygments==2.17.1", - "rich==13.7.0", - "xmlschema==2.5.0", - ], - python_requires=">=3.9,<3.13", - setup_requires=["pytest-runner"], - tests_require=["pytest"], - test_suite="tests", +import os +import sys +import click +import configparser + +from pain001.constants.constants import valid_xml_types +from pain001.context.context import Context +from pain001.core.core import process_files +from pain001.xml.validate_via_xsd import validate_via_xsd + +from rich.console import Console +from rich.table import Table +from rich import box + +console = Console() + +description = """ +A powerful Python library that enables you to create +ISO 20022-compliant payment files directly from CSV or SQLite Data files.\n +https://pain001.com +""" +title = "Pain001" + +table = Table(box=box.ROUNDED, safe_box=True, show_header=False, title=title) + +table.add_column(justify="center", no_wrap=False, vertical="middle") +table.add_row(description) +table.width = 80 +console.print(table) + + +@click.command( + help=("To use Pain001, you must specify the following options:\n\n"), + context_settings=dict(help_option_names=["-h", "--help"]), +) +@click.option( + "-t", + "--xml_message_type", + default=None, + help="Type of XML message (required)", +) +@click.option( + "-m", + "--xml_template_file_path", + default=None, + type=click.Path(), + help="Path to XML template file (required)", +) +@click.option( + "-s", + "--xsd_schema_file_path", + default=None, + type=click.Path(), + help="Path to XSD template file (required)", +) +@click.option( + "-d", + "--data_file_path", + default=None, + type=click.Path(), + help="Path to data file (CSV or SQLite) (required)", ) +@click.option( + "-c", + "--config_file", + default=None, + type=click.Path(), + help="Path to configuration file (optional)", +) +def main( + xml_message_type, + xml_template_file_path, + xsd_schema_file_path, + data_file_path, + config_file, +): + # Expand user-friendly paths + xml_template_file_path = os.path.expanduser(xml_template_file_path) + xsd_schema_file_path = os.path.expanduser(xsd_schema_file_path) + data_file_path = os.path.expanduser(data_file_path) + + # Load configuration file if provided + if config_file: + config = configparser.ConfigParser() + config.read(config_file) + if "Paths" in config: + xml_template_file_path = config["Paths"].get( + "xml_template_file_path", xml_template_file_path + ) + xsd_schema_file_path = config["Paths"].get( + "xsd_schema_file_path", xsd_schema_file_path + ) + data_file_path = config["Paths"].get( + "data_file_path", data_file_path + ) + + # Check that the required arguments are provided + if not xml_message_type: + print("The XML message type is required.") + sys.exit(1) + + # Check file existence + for file_path in [ + xml_template_file_path, + xsd_schema_file_path, + data_file_path, + ]: + if not os.path.isfile(file_path): + print(f"The file '{file_path}' does not exist.") + sys.exit(1) + + logger = Context.get_instance().get_logger() + + logger.info("Parsing command line arguments.") + + # Check that the XML message type is valid + if xml_message_type not in valid_xml_types: + logger.info(f"Invalid XML message type: {xml_message_type}.") + print( + f""" + Invalid XML message type: {xml_message_type}. + Valid types are: {', '.join(valid_xml_types)}. + """ + ) + sys.exit(1) + + # Validate XML and XSD schemas + try: + validate_via_xsd(xml_template_file_path, xsd_schema_file_path) + except Exception as e: + logger.error(f"Schema validation failed: {e}") + print(f"Schema validation failed: {e}") + sys.exit(1) + + process_files( + xml_message_type, + xml_template_file_path, + xsd_schema_file_path, + data_file_path, + ) + + +if __name__ == "__main__": + # pylint: disable=no-value-for-parameter + main() diff --git a/templates/pain.001.001.03/pain.001.001.03.xml b/templates/pain.001.001.03/pain.001.001.03.xml index c5cce2e..0fd7257 100644 --- a/templates/pain.001.001.03/pain.001.001.03.xml +++ b/templates/pain.001.001.03/pain.001.001.03.xml @@ -49,6 +49,49 @@ TX-1 + PaymentID6789 + + + 150 + + SLEV + + + SPUEDE2UXXX + + + + Global Tech + + Global Street + 3 + 11223 + Global Town + DE + + + + + + DE68210501700024690959 + + + + + OTHR + + + + + Invoice-98765 + 2023-03-09 + + + + + + + TX-2 PaymentID4321 @@ -91,7 +134,7 @@ - TX-2 + TX-3 PaymentID1357 @@ -134,7 +177,7 @@ - TX-3 + TX-4 PaymentID8642 diff --git a/templates/pain.001.001.09/pain.001.001.09.xml b/templates/pain.001.001.09/pain.001.001.09.xml index e3e700c..53713aa 100644 --- a/templates/pain.001.001.09/pain.001.001.09.xml +++ b/templates/pain.001.001.09/pain.001.001.09.xml @@ -31,6 +31,33 @@ DEBT + + PaymentID6789 + + + 150.00 + + + + DEUTDEFFXXX + + + + Global Tech + + + + DE68210501700024690959 + + + + + + + + + + PaymentID4321 diff --git a/tests/test_context.py b/tests/test_context.py index 8e96eaa..54ddd05 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -1,3 +1,19 @@ +# Copyright (C) 2023-2024 Sebastien Rousseau. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + import logging import unittest diff --git a/tests/test_core.py b/tests/test_core.py index ed35e8f..309381e 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,145 +1,292 @@ -import pytest -import sys -from contextlib import contextmanager +# Copyright (C) 2023-2024 Sebastien Rousseau. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import os +import unittest +from unittest.mock import patch from io import StringIO from pain001.core.core import process_files -@contextmanager -def catch_stdout(): - old_out = None # type: StringIO - try: - old_out, sys.stdout = sys.stdout, StringIO() - yield sys.stdout - finally: - sys.stdout = old_out +class TestProcessFiles(unittest.TestCase): + def setUp(self): + self.xml_message_type = "pain.001.001.03" + self.xml_template_file_path = "tests/data/template_unique.xml" + self.xsd_schema_file_path = "tests/data/template_unique.xsd" + self.csv_file_path = "tests/data/valid_data_unique.csv" + self.invalid_csv_file_path = "tests/data/invalid_data_unique.csv" + self.empty_csv_file_path = "tests/data/empty_unique.csv" + self.single_column_csv_file_path = ( + "tests/data/single_column_unique.csv" + ) + self.single_row_csv_file_path = "tests/data/single_row_unique.csv" + self.sqlite_file_path = "tests/data/valid_data_unique.db" + self.invalid_sqlite_file_path = "tests/data/invalid_data_unique.db" + self.unsupported_file_path = ( + "tests/data/unsupported_data_type_unique.txt" + ) -class TestProcessFiles: - def test_invalid_csv_data(self): - """ - Test case for processing files with invalid CSV data. - """ - with catch_stdout(): - with pytest.raises(SystemExit) as exc_info: - process_files( - "pain.001.001.03", - "tests/data/template.xml", - "tests/data/template.xsd", - "tests/data/invalid.csv", - ) + self.create_test_files() + + def create_test_files(self): + os.makedirs("tests/data", exist_ok=True) + + # Create valid_data_unique.csv + with open(self.csv_file_path, "w") as f: + f.write( + "id,date,nb_of_txs,initiator_name,initiator_street_name," + "initiator_building_number,initiator_postal_code," + "initiator_town_name,initiator_country_code," + "payment_information_id,payment_method,batch_booking," + "requested_execution_date,debtor_name,debtor_street_name," + "debtor_building_number,debtor_postal_code,debtor_town_name," + "debtor_country_code,debtor_account_IBAN,debtor_agent_BIC," + "charge_bearer,payment_id,payment_amount,currency," + "payment_currency,ctrl_sum,creditor_agent_BIC," + "creditor_name,creditor_street_name,creditor_building_number," + "creditor_postal_code,creditor_town_name,creditor_country_code," + "creditor_account_IBAN,purpose_code,reference_number," + "reference_date,service_level_code,end_to_end_id," + "payment_instruction_id,instruction_id,category_purpose," + "remittance_info_unstructured,remittance_info_structured," + "addtl_end_to_end_id,payment_info_structured,forwarding_agent_BIC," + "remittance_information\n" + "1,2023-03-10T15:30:47,2,John Doe,John's Street,1,12345," + "John's Town,DE,Payment-Info-12345,TRF,false,2023-03-15," + "Debtor Name,Debtor Street,1,12345,Debtor Town,DE," + "DE89370400440532013000,BICCODE,DEBT,12345,100.00,EUR,EUR," + "100.00,BICCODE,Creditor Name,Creditor Street,1,12345," + "Creditor Town,DE,DE89370400440532013000,SCOR,Reference-12345," + "2023-03-10,SEPA,End-to-End-Id-123,Payment-Instruction-Id-123," + "Instruction-Id-123,Category-Purpose-123," + "Remittance-Info-Unstructured-123,Remittance-Info-Structured-123," + "Addtl-End-to-End-Id-123,Payment-Info-Structured-123," + "Forwarding-Agent-BIC-123,Remittance-Information-123\n" + ) + + # Create invalid_data_unique.csv + with open(self.invalid_csv_file_path, "w") as f: + f.write("id,date,nb_of_txs\n1,invalid_date,2\n") - assert exc_info.value.code == 1 + # Create empty_unique.csv + with open(self.empty_csv_file_path, "w") as f: + f.write("") + + # Create single_column_unique.csv + with open(self.single_column_csv_file_path, "w") as f: + f.write("id\n1\n") + + # Create single_row_unique.csv + with open(self.single_row_csv_file_path, "w") as f: + f.write( + "id,date,nb_of_txs,initiator_name,initiator_street_name," + "initiator_building_number,initiator_postal_code," + "initiator_town_name,initiator_country_code," + "payment_information_id,payment_method,batch_booking," + "requested_execution_date,debtor_name,debtor_street_name," + "debtor_building_number,debtor_postal_code,debtor_town_name," + "debtor_country_code,debtor_account_IBAN,debtor_agent_BIC," + "charge_bearer,payment_id,payment_amount,currency," + "payment_currency,ctrl_sum,creditor_agent_BIC," + "creditor_name,creditor_street_name,creditor_building_number," + "creditor_postal_code,creditor_town_name,creditor_country_code," + "creditor_account_IBAN,purpose_code,reference_number," + "reference_date,service_level_code,forwarding_agent_BIC," + "remittance_information,charge_account_IBAN\n" + "1,2023-03-10T15:30:47,2,John Doe,John's Street,1,12345," + "John's Town,DE,Payment-Info-12345,TRF,false,2023-03-15," + "Debtor Name,Debtor Street,1,12345,Debtor Town,DE," + "DE89370400440532013000,BICCODE,DEBT,12345,100.00,EUR,EUR," + "100.00,BICCODE,Creditor Name,Creditor Street,1,12345," + "Creditor Town,DE,DE89370400440532013000,SCOR,Reference-12345," + "2023-03-10,SEPA,End-to-End-Id-123,Payment-Instruction-Id-123," + "Instruction-Id-123,Category-Purpose-123," + "Remittance-Info-Unstructured-123,Remittance-Info-Structured-123," + "Addtl-End-to-End-Id-123,Payment-Info-Structured-123," + "Forwarding-Agent-BIC-123,Remittance-Information-123\n" + ) + + # Create template_unique.xml + with open(self.xml_template_file_path, "w") as f: + f.write( + """ + + {{ to }} + {{ from }} + {{ heading }} + {{ body }} + """ + ) + + # Create template_unique.xsd with valid XSD content + with open(self.xsd_schema_file_path, "w") as f: + f.write( + """ + + + + + + + + + + + + """ + ) + + # Create valid_data_unique.db + with open(self.sqlite_file_path, "w") as f: + f.write("SQLite format 3") + + # Create invalid_data_unique.db + with open(self.invalid_sqlite_file_path, "w") as f: + f.write("") + + # Create unsupported_data_type_unique.txt + with open(self.unsupported_file_path, "w") as f: + f.write("Unsupported content") def test_invalid_xml_message_type(self): - """ - Test case for processing files with an invalid XML message type. - """ - with pytest.raises(ValueError) as exc_info: + with self.assertRaises(ValueError): process_files( - "invalid", - "tests/data/template.xml", - "tests/data/template.xsd", - "tests/data/template.csv", + "invalid.type", + self.xml_template_file_path, + self.xsd_schema_file_path, + self.csv_file_path, ) - error_message = str(exc_info.value) - expected_error_message = ( - "Error: Invalid XML message type: 'invalid'." + def test_missing_xml_template_file(self): + with self.assertRaises(FileNotFoundError): + with self.assertLogs(level="ERROR") as log: + process_files( + self.xml_message_type, + "tests/data/non_existent_template.xml", + self.xsd_schema_file_path, + self.csv_file_path, + ) + self.assertIn( + "Error: XML template 'tests/data/non_existent_template.xml' " + "does not exist.", + log.output[0], ) - assert error_message == expected_error_message - def test_nonexistent_data_file_path(self): - """ - Test case for processing files with a non-existent data file path. - """ - with pytest.raises(FileNotFoundError) as exc_info: + def test_missing_xsd_schema_file(self): + with self.assertRaises(FileNotFoundError): process_files( - "pain.001.001.03", - "tests/data/template.xml", - "tests/data/template.xsd", - "tests/data/nonexistent.csv", + self.xml_message_type, + self.xml_template_file_path, + "tests/data/non_existent_schema.xsd", + self.csv_file_path, ) - assert ( - str(exc_info.value) - == "Error: Data file 'tests/data/nonexistent.csv' does not exist." - ) - def test_nonexistent_xml_file_path(self): - """ - Test case for processing files with a non-existent XML file path. - """ - with pytest.raises(FileNotFoundError): + def test_missing_csv_file(self): + with self.assertRaises(FileNotFoundError): process_files( - "pain.001.001.03", - "tests/data/nonexistent.xml", - "tests/data/template.xsd", - "tests/data/template.csv", + self.xml_message_type, + self.xml_template_file_path, + self.xsd_schema_file_path, + "tests/data/non_existent_data.csv", ) - # assert exc_info.value.code == 1 - def test_nonexistent_xsd_file_path(self): - """ - Test case for processing files with a non-existent XSD file path. - """ - with pytest.raises(FileNotFoundError): + def test_empty_csv_data(self): + with self.assertRaises(ValueError): process_files( - "pain.001.001.03", - "tests/data/template.xml", - "tests/data/nonexistent.xsd", - "tests/data/template.csv", + self.xml_message_type, + self.xml_template_file_path, + self.xsd_schema_file_path, + self.empty_csv_file_path, ) - # assert exc_info.value.code == 1 - - def test_successful_execution(self): - """ - Test case for successful execution of file processing. - """ - process_files( - "pain.001.001.03", - "tests/data/template.xml", - "tests/data/template.xsd", - "tests/data/template.csv", - ) - def test_unsupported_data_file_type(self): - """ - Test case for processing files with an unsupported data file type. - """ - with pytest.raises(ValueError) as exc_info: + @patch("sys.stdout", new_callable=StringIO) + def test_single_column_csv_data(self, mock_stdout): + with self.assertRaises(ValueError): process_files( - "pain.001.001.03", - "tests/data/template.xml", - "tests/data/template.xsd", - "tests/data/invalid.rtf", + self.xml_message_type, + self.xml_template_file_path, + self.xsd_schema_file_path, + self.single_column_csv_file_path, ) - assert ( - str(exc_info.value) == "Error: Unsupported data file type." + self.assertIn( + "Error: Missing value(s) for column(s)", + mock_stdout.getvalue(), ) - def test_uses_sqlite_database(self): - """ - Test case for processing files using an SQLite database. - """ - xml_message_type = "pain.001.001.03" - xml_file_path = "tests/data/template.xml" - xsd_file_path = "tests/data/template.xsd" - data_file_path = "tests/data/template.db" - - process_files( - xml_message_type, - xml_file_path, - xsd_file_path, - data_file_path, - ) + def test_invalid_csv_data(self): + with self.assertRaises(ValueError): + process_files( + self.xml_message_type, + self.xml_template_file_path, + self.xsd_schema_file_path, + self.invalid_csv_file_path, + ) + + def test_valid_csv_data(self): + with patch("pain001.core.core.generate_xml") as mock_generate_xml: + process_files( + self.xml_message_type, + self.xml_template_file_path, + self.xsd_schema_file_path, + self.csv_file_path, + ) + mock_generate_xml.assert_called_once() - def test_valid_xml_message_type(self): - """ - Test case for processing files with a valid XML message type. - """ - process_files( - "pain.001.001.03", - "tests/data/template.xml", - "tests/data/template.xsd", - "tests/data/template.csv", + def test_valid_sqlite_data(self): + with ( + patch("pain001.core.core.load_db_data", return_value=[{}]), + patch("pain001.core.core.validate_db_data", return_value=True), + patch("pain001.core.core.generate_xml") as mock_generate_xml, + ): + process_files( + self.xml_message_type, + self.xml_template_file_path, + self.xsd_schema_file_path, + self.sqlite_file_path, + ) + mock_generate_xml.assert_called_once() + + def test_invalid_sqlite_data(self): + with ( + patch("pain001.core.core.load_db_data", return_value=[{}]), + patch("pain001.core.core.validate_db_data", return_value=False), + ): + with self.assertRaises(ValueError): + process_files( + self.xml_message_type, + self.xml_template_file_path, + self.xsd_schema_file_path, + self.sqlite_file_path, + ) + + def test_unsupported_data_file_type(self): + with self.assertRaises(ValueError): + with self.assertLogs(level="ERROR") as log: + process_files( + self.xml_message_type, + self.xml_template_file_path, + self.xsd_schema_file_path, + self.unsupported_file_path, + ) + self.assertIn( + "Error: Unsupported data file type.", + log.output[0], ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_create_root_element.py b/tests/test_create_root_element.py index 32cd66b..4e12985 100644 --- a/tests/test_create_root_element.py +++ b/tests/test_create_root_element.py @@ -1,3 +1,19 @@ +# Copyright (C) 2023-2024 Sebastien Rousseau. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + from pain001.xml.create_root_element import create_root_element import xml.etree.ElementTree as ET @@ -24,8 +40,7 @@ def test_create_root_element(): # Check if xsi:schemaLocation attribute is set correctly schema_location = ( - "urn:iso:std:iso:20022:tech:xsd:pain.001.001.03 " - "pain.001.001.03.xsd" + "urn:iso:std:iso:20022:tech:xsd:pain.001.001.03 " "pain.001.001.03.xsd" ) assert root.attrib["xsi:schemaLocation"] == schema_location @@ -87,8 +102,7 @@ def test_create_root_element_sets_all_expected_attributes_correctly(): # Check if xsi:schemaLocation attribute is set correctly assert root.attrib["xsi:schemaLocation"] == ( - "urn:iso:std:iso:20022:tech:xsd:pain.001.001.03 " - "pain.001.001.03.xsd" + "urn:iso:std:iso:20022:tech:xsd:pain.001.001.03 " "pain.001.001.03.xsd" ) # Check if optional attributes are set correctly diff --git a/tests/test_create_xml_element.py b/tests/test_create_xml_element.py index 0c9f1c4..c4ac49f 100644 --- a/tests/test_create_xml_element.py +++ b/tests/test_create_xml_element.py @@ -1,3 +1,19 @@ +# Copyright (C) 2023-2024 Sebastien Rousseau. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + import unittest import xml.etree.ElementTree as ET from pain001.xml.create_xml_element import create_xml_element diff --git a/tests/test_data.py b/tests/test_data.py new file mode 100644 index 0000000..ace37b5 --- /dev/null +++ b/tests/test_data.py @@ -0,0 +1,618 @@ +# Copyright (C) 2023-2024 Sebastien Rousseau. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import unittest +import os +import csv +from pain001.core.core import process_files +from pain001.csv.load_csv_data import load_csv_data +from pain001.csv.validate_csv_data import validate_csv_data + + +class TestLoadCsvData(unittest.TestCase): + + def setUp(self): + """Create test files before each test.""" + os.makedirs("tests/data", exist_ok=True) + + # valid_data.csv + valid_data = [ + [ + "id", + "date", + "nb_of_txs", + "initiator_name", + "initiator_street_name", + "initiator_building_number", + "initiator_postal_code", + "initiator_town_name", + "initiator_country_code", + "payment_information_id", + "payment_method", + "batch_booking", + "requested_execution_date", + "debtor_name", + "debtor_street_name", + "debtor_building_number", + "debtor_postal_code", + "debtor_town_name", + "debtor_country_code", + "debtor_account_IBAN", + "debtor_agent_BIC", + "charge_bearer", + "payment_id", + "payment_amount", + "currency", + "payment_currency", + "ctrl_sum", + "creditor_agent_BIC", + "creditor_name", + "creditor_street_name", + "creditor_building_number", + "creditor_postal_code", + "creditor_town_name", + "creditor_country_code", + "creditor_account_IBAN", + "purpose_code", + "reference_number", + "reference_date", + "service_level_code", + "forwarding_agent_BIC", + "remittance_information", + "charge_account_IBAN", + ], + [ + "1", + "2023-03-10T15:30:47.000Z", + "2", + "John Doe", + "John's Street", + "1", + "12345", + "John's Town", + "DE", + "Payment-Info-12345", + "TRF", + "true", + "2023-03-12", + "Acme Corp", + "Acme Street", + "2", + "67890", + "Acme Town", + "DE", + "DE75512108001245126162", + "BANKDEFFXXX", + "SLEV", + "PaymentID6789", + "150", + "EUR", + "EUR", + "15000", + "SPUEDE2UXXX", + "Global Tech", + "Global Street", + "3", + "11223", + "Global Town", + "DE", + "DE68210501700024690959", + "OTHR", + "Invoice-98765", + "2023-03-09", + "SEPA", + "SPUEDE2UXXX", + "Invoice-12345", + "CHARGE-IBAN-12345", + ], + [ + "2", + "2023-03-11T10:20:18.000Z", + "3", + "Jane Smith", + "Jane's Street", + "10", + "67890", + "Jane's Town", + "DE", + "Payment-Info-67890", + "TRF", + "true", + "2023-03-14", + "Brown Industries", + "Brown Street", + "20", + "45678", + "Brown Town", + "DE", + "DE44500105175407324931", + "BANKDEFFXXX", + "SHAR", + "PaymentID4321", + "300", + "EUR", + "EUR", + "30000", + "SPUEDE2UXXX", + "Green Energy", + "Green Street", + "30", + "78901", + "Green Town", + "DE", + "DE89370400440532013008", + "OTHR", + "Invoice-12345", + "2023-03-13", + "SEPA", + "SPUEDE2UXXX", + "Invoice-67890", + "CHARGE-IBAN-67890", + ], + [ + "3", + "2023-03-11T11:45:23.000Z", + "1", + "Michael Johnson", + "Michael's Street", + "15", + "89101", + "Michael's Town", + "DE", + "Payment-Info-24680", + "TRF", + "true", + "2023-03-13", + "Alpha Electronics", + "Alpha Street", + "25", + "32165", + "Alpha Town", + "DE", + "DE47500105175711000100", + "BANKDEFFXXX", + "SLEV", + "PaymentID1357", + "250", + "EUR", + "EUR", + "25000", + "SPUEDE2UXXX", + "Beta Chemicals", + "Beta Street", + "35", + "65432", + "Beta Town", + "DE", + "DE44500105175407123457", + "OTHR", + "Invoice-24680", + "2023-03-11", + "SEPA", + "SPUEDE2UXXX", + "Invoice-24680", + "CHARGE-IBAN-24680", + ], + [ + "4", + "2023-03-12T09:12:34.000Z", + "2", + "Emily Wilson", + "Emily's Street", + "4", + "34567", + "Emily's Town", + "DE", + "Payment-Info-36912", + "TRF", + "true", + "2023-03-15", + "Zeta Services", + "Zeta Street", + "8", + "98765", + "Zeta Town", + "DE", + "DE68120933300112345608", + "BANKDEFFXXX", + "SHAR", + "PaymentID8642", + "200", + "EUR", + "EUR", + "20000", + "SPUEDE2UXXX", + "Theta Solutions", + "Theta Street", + "38", + "54321", + "Theta Town", + "DE", + "DE75512108001245123456", + "OTHR", + "Invoice-36912", + "2023-03-14", + "SEPA", + "SPUEDE2UXXX", + "Invoice-36912", + "CHARGE-IBAN-36912", + ], + ] + with open("tests/data/valid_data.csv", "w", newline="") as file: + writer = csv.writer(file) + writer.writerows(valid_data) + + # invalid_data.csv + invalid_data = [ + [ + "id", + "date", + "nb_of_txs", + "initiator_name", + "initiator_street_name", + "initiator_building_number", + "initiator_postal_code", + "initiator_town_name", + "initiator_country_code", + "payment_information_id", + "payment_method", + "batch_booking", + "requested_execution_date", + "debtor_name", + "debtor_street_name", + "debtor_building_number", + "debtor_postal_code", + "debtor_town_name", + "debtor_country_code", + "debtor_account_IBAN", + "debtor_agent_BIC", + "charge_bearer", + "payment_id", + "payment_amount", + "currency", + "payment_currency", + "ctrl_sum", + "creditor_agent_BIC", + "creditor_name", + "creditor_street_name", + "creditor_building_number", + "creditor_postal_code", + "creditor_town_name", + "creditor_country_code", + "creditor_account_IBAN", + "purpose_code", + "reference_number", + "reference_date", + "service_level_code", + "forwarding_agent_BIC", + "remittance_information", + "charge_account_IBAN", + ], + [ + "1", + "not-a-date", + "2", + "John Doe", + "John's Street", + "1", + "12345", + "John's Town", + "DE", + "Payment-Info-12345", + "TRF", + "true", + "2023-03-12", + "Acme Corp", + "Acme Street", + "2", + "67890", + "Acme Town", + "DE", + "DE75512108001245126162", + "BANKDEFFXXX", + "SLEV", + "PaymentID6789", + "150", + "EUR", + "EUR", + "15000", + "SPUEDE2UXXX", + "Global Tech", + "Global Street", + "3", + "11223", + "Global Town", + "DE", + "DE68210501700024690959", + "OTHR", + "Invoice-98765", + "2023-03-09", + "SEPA", + "SPUEDE2UXXX", + "Invoice-12345", + "CHARGE-IBAN-12345", + ], + ] + with open("tests/data/invalid_data.csv", "w", newline="") as file: + writer = csv.writer(file) + writer.writerows(invalid_data) + + # empty.csv + open("tests/data/empty.csv", "w").close() + + # single_column.csv + single_column = [["id"], ["1"], ["2"], ["3"]] + with open("tests/data/single_column.csv", "w", newline="") as file: + writer = csv.writer(file) + writer.writerows(single_column) + + # single_row.csv + single_row = [ + [ + "id", + "date", + "nb_of_txs", + "initiator_name", + "initiator_street_name", + "initiator_building_number", + "initiator_postal_code", + "initiator_town_name", + "initiator_country_code", + "payment_information_id", + "payment_method", + "batch_booking", + "requested_execution_date", + "debtor_name", + "debtor_street_name", + "debtor_building_number", + "debtor_postal_code", + "debtor_town_name", + "debtor_country_code", + "debtor_account_IBAN", + "debtor_agent_BIC", + "charge_bearer", + "payment_id", + "payment_amount", + "currency", + "payment_currency", + "ctrl_sum", + "creditor_agent_BIC", + "creditor_name", + "creditor_street_name", + "creditor_building_number", + "creditor_postal_code", + "creditor_town_name", + "creditor_country_code", + "creditor_account_IBAN", + "purpose_code", + "reference_number", + "reference_date", + "service_level_code", + "forwarding_agent_BIC", + "remittance_information", + "charge_account_IBAN", + ], + [ + "1", + "2023-03-10T15:30:47.000Z", + "2", + "John Doe", + "John's Street", + "1", + "12345", + "John's Town", + "DE", + "Payment-Info-12345", + "TRF", + "true", + "2023-03-12", + "Acme Corp", + "Acme Street", + "2", + "67890", + "Acme Town", + "DE", + "DE75512108001245126162", + "BANKDEFFXXX", + "SLEV", + "PaymentID6789", + "150", + "EUR", + "EUR", + "15000", + "SPUEDE2UXXX", + "Global Tech", + "Global Street", + "3", + "11223", + "Global Town", + "DE", + "DE68210501700024690959", + "OTHR", + "Invoice-98765", + "2023-03-09", + "SEPA", + "SPUEDE2UXXX", + "Invoice-12345", + "CHARGE-IBAN-12345", + ], + ] + with open("tests/data/single_row.csv", "w", newline="") as file: + writer = csv.writer(file) + writer.writerows(single_row) + + def tearDown(self): + """Delete test files after each test.""" + files = [ + "tests/data/valid_data.csv", + "tests/data/invalid_data.csv", + "tests/data/empty.csv", + "tests/data/single_column.csv", + "tests/data/single_row.csv", + ] + for file in files: + if os.path.exists(file): + os.remove(file) + + def test_load_valid_csv(self): + file_path = "tests/data/valid_data.csv" + data = load_csv_data(file_path) + self.assertEqual(len(data), 4) + self.assertEqual(data[0]["id"], "1") + + def test_load_empty_csv(self): + file_path = "tests/data/empty.csv" + with self.assertRaises(ValueError): + load_csv_data(file_path) + + def test_load_csv_with_invalid_data(self): + file_path = "tests/data/invalid_data.csv" + data = load_csv_data(file_path) + self.assertFalse(validate_csv_data(data)) + + def test_load_single_row_csv(self): + file_path = "tests/data/single_row.csv" + data = load_csv_data(file_path) + self.assertEqual(len(data), 1) + self.assertEqual(data[0]["id"], "1") + + def test_load_single_column_csv(self): + file_path = "tests/data/single_column.csv" + data = load_csv_data(file_path) + self.assertEqual(len(data), 3) + self.assertEqual(data[0]["id"], "1") + + +class TestProcessFiles(unittest.TestCase): + def test_successful_execution(self): + process_files( + "pain.001.001.03", + "tests/data/template.xml", + "tests/data/template.xsd", + "tests/data/template.csv", + ) + + def test_valid_xml_message_type(self): + process_files( + "pain.001.001.03", + "tests/data/template.xml", + "tests/data/template.xsd", + "tests/data/template.csv", + ) + + +class TestValidateCsvData(unittest.TestCase): + def test_validate_csv_with_valid_data(self): + data = [ + { + "id": "1", + "date": "2023-03-10T15:30:47.000Z", + "nb_of_txs": "2", + "initiator_name": "John Doe", + "initiator_street_name": "John's Street", + "initiator_building_number": "1", + "initiator_postal_code": "12345", + "initiator_town_name": "John's Town", + "initiator_country_code": "DE", + "payment_information_id": "Payment-Info-12345", + "payment_method": "TRF", + "batch_booking": "true", + "requested_execution_date": "2023-03-12", + "debtor_name": "Acme Corp", + "debtor_street_name": "Acme Street", + "debtor_building_number": "2", + "debtor_postal_code": "67890", + "debtor_town_name": "Acme Town", + "debtor_country_code": "DE", + "debtor_account_IBAN": "DE75512108001245126162", + "debtor_agent_BIC": "BANKDEFFXXX", + "charge_bearer": "SLEV", + "payment_id": "PaymentID6789", + "payment_amount": "150", + "currency": "EUR", + "payment_currency": "EUR", + "ctrl_sum": "15000", + "creditor_agent_BIC": "SPUEDE2UXXX", + "creditor_name": "Global Tech", + "creditor_street_name": "Global Street", + "creditor_building_number": "3", + "creditor_postal_code": "11223", + "creditor_town_name": "Global Town", + "creditor_country_code": "DE", + "creditor_account_IBAN": "DE68210501700024690959", + "purpose_code": "OTHR", + "reference_number": "Invoice-98765", + "reference_date": "2023-03-09", + "service_level_code": "SEPA", + "forwarding_agent_BIC": "SPUEDE2UXXX", + "remittance_information": "Invoice-12345", + "charge_account_IBAN": "CHARGE-IBAN-12345", + } + ] + self.assertTrue(validate_csv_data(data)) + + def test_validate_csv_with_invalid_data(self): + data = [ + { + "id": "1", + "date": "not-a-date", + "nb_of_txs": "2", + "initiator_name": "John Doe", + "initiator_street_name": "John's Street", + "initiator_building_number": "1", + "initiator_postal_code": "12345", + "initiator_town_name": "John's Town", + "initiator_country_code": "DE", + "payment_information_id": "Payment-Info-12345", + "payment_method": "TRF", + "batch_booking": "true", + "requested_execution_date": "2023-03-12", + "debtor_name": "Acme Corp", + "debtor_street_name": "Acme Street", + "debtor_building_number": "2", + "debtor_postal_code": "67890", + "debtor_town_name": "Acme Town", + "debtor_country_code": "DE", + "debtor_account_IBAN": "DE75512108001245126162", + "debtor_agent_BIC": "BANKDEFFXXX", + "charge_bearer": "SLEV", + "payment_id": "PaymentID6789", + "payment_amount": "150", + "currency": "EUR", + "payment_currency": "EUR", + "ctrl_sum": "15000", + "creditor_agent_BIC": "SPUEDE2UXXX", + "creditor_name": "Global Tech", + "creditor_street_name": "Global Street", + "creditor_building_number": "3", + "creditor_postal_code": "11223", + "creditor_town_name": "Global Town", + "creditor_country_code": "DE", + "creditor_account_IBAN": "DE68210501700024690959", + "purpose_code": "OTHR", + "reference_number": "Invoice-98765", + "reference_date": "2023-03-09", + "service_level_code": "SEPA", + "forwarding_agent_BIC": "SPUEDE2UXXX", + "remittance_information": "Invoice-12345", + "charge_account_IBAN": "CHARGE-IBAN-12345", + } + ] + self.assertFalse(validate_csv_data(data)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_generate_updated_xml_file_path.py b/tests/test_generate_updated_xml_file_path.py index c814045..200f692 100644 --- a/tests/test_generate_updated_xml_file_path.py +++ b/tests/test_generate_updated_xml_file_path.py @@ -1,3 +1,19 @@ +# Copyright (C) 2023-2024 Sebastien Rousseau. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + from pain001.xml.generate_updated_xml_file_path import ( generate_updated_xml_file_path, ) diff --git a/tests/test_generate_xml.py b/tests/test_generate_xml.py index 185ce03..f2389de 100644 --- a/tests/test_generate_xml.py +++ b/tests/test_generate_xml.py @@ -1,6 +1,27 @@ +# Copyright (C) 2023-2024 Sebastien Rousseau. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + import unittest import xml.etree.ElementTree as ET from pain001.xml.create_xml_v3 import create_xml_v3 +from pain001.xml.create_xml_v4 import create_xml_v4 +from pain001.xml.create_xml_v5 import create_xml_v5 +from pain001.xml.create_xml_v6 import create_xml_v6 +from pain001.xml.create_xml_v7 import create_xml_v7 +from pain001.xml.create_xml_v8 import create_xml_v8 from pain001.xml.create_xml_v9 import create_xml_v9 from pain001.xml.create_common_elements import ( create_common_elements, @@ -56,6 +77,227 @@ def setUp(self): } ], } + self.row_v4 = { + "id": "1", + "date": "2023-03-10T15:30:47.000Z", + "nb_of_txs": "2", + "initiator_name": "Initiator", + "initiator_street": "Street", + "initiator_building_number": "1", + "initiator_postal_code": "12345", + "initiator_town": "Town", + "initiator_country": "DE", + "payment_information_id": "PID123", + "payment_id": "PID123", # Added payment_id + "payment_method": "pain.001.001.04", + "batch_booking": "true", + "requested_execution_date": "2023-05-21", + "debtor_name": "Debtor", + "debtor_street": "Street", + "debtor_building_number": "1", + "debtor_postal_code": "12345", + "debtor_town": "Town", + "debtor_country": "DE", + "debtor_account_IBAN": "DE123456789", + "debtor_agent_BIC": "DEUTDEFF", + "charge_bearer": "SLEV", + "creditor_agent_BIC": "NOLADE21KIE", + "creditor_name": "Creditor", + "creditor_street": "Street", + "creditor_building_number": "1", + "creditor_postal_code": "12345", + "creditor_town": "Town", + "creditor_country": "DE", + "creditor_account_IBAN": "DE123456789", + "payment_instruction_id": "PID123", + "payment_end_to_end_id": "E2E123", + "payment_currency": "EUR", + "payment_amount": "1500", + "purpose_code": "GDDS", + "reference_number": "RF123", + "reference_date": "2023-03-10T15:30:47.000Z", + } + self.row_v5 = { + "id": "1", + "date": "2023-03-10T15:30:47.000Z", + "nb_of_txs": "2", + "ctrl_sum": "3000", + "initiator_name": "Initiator", + "initiator_street_name": "Street", + "initiator_building_number": "1", + "initiator_postal_code": "12345", + "initiator_town_name": "Town", + "initiator_country": "DE", + "ultimate_debtor_name": "Ultimate Debtor", + "service_level_code": "SEPA", + "payment_information_id": "PID123", + "payment_id": "PID123", + "payment_method": "pain.001.001.05", + "batch_booking": "true", + "requested_execution_date": "2023-05-21", + "debtor_name": "Debtor", + "debtor_street": "Street", + "debtor_building_number": "1", + "debtor_postal_code": "12345", + "debtor_town": "Town", + "debtor_country": "DE", + "debtor_account_IBAN": "DE123456789", + "debtor_agent_BIC": "DEUTDEFF", + "charge_bearer": "SLEV", + "payment_instruction_id": "PID123", + "payment_end_to_end_id": "E2E123", + "payment_currency": "EUR", + "payment_amount": "1500", + "creditor_name": "Creditor", + "creditor_street": "Street", + "creditor_building_number": "1", + "creditor_postal_code": "12345", + "creditor_town": "Town", + "creditor_country": "DE", + "creditor_account_IBAN": "DE123456789", + "creditor_agent_BICFI": "NOLADE21KIE", + "purpose_code": "GDDS", + "reference_number": "RF123", + "reference_date": "2023-03-10T15:30:47.000Z", + } + self.row_v6 = { + "id": "2", + "date": "2023-03-11T15:30:47.000Z", + "nb_of_txs": "3", + "ctrl_sum": "4500", + "initiator_name": "Initiator 6", + "initiator_street_name": "Street 6", + "initiator_building_number": "6", + "initiator_postal_code": "12346", + "initiator_town_name": "Town 6", + "initiator_country": "DE", + "ultimate_debtor_name": "Ultimate Debtor 6", + "service_level_code": "SEPA", + "payment_information_id": "PID123", + "payment_id": "PID123", + "payment_method": "pain.001.001.06", + "batch_booking": "true", + "requested_execution_date": "2023-06-21", + "debtor_name": "Debtor 6", + "debtor_street": "Street 6", + "debtor_building_number": "6", + "debtor_postal_code": "12346", + "debtor_town": "Town 6", + "debtor_country": "DE", + "debtor_account_IBAN": "DE123456780", + "debtor_agent_name": "Agent", + "debtor_agent_BIC": "DEUTDEFG", + "charge_bearer": "SLEV", + "payment_instruction_id": "PID123", + "payment_end_to_end_id": "E2E124", + "payment_currency": "USD", + "payment_amount": "1600", + "creditor_name": "Creditor 6", + "creditor_street": "Street 6", + "creditor_building_number": "6", + "creditor_postal_code": "12346", + "creditor_town": "Town 6", + "creditor_country": "DE", + "creditor_account_IBAN": "DE123456780", + "creditor_agent_BICFI": "NOLADE21KIF", + "creditor_agent_name": "Creditor Agent", + "purpose_code": "GDDS", + "reference_number": "RF124", + "reference_date": "2023-03-11T15:30:47.000Z", + } + + self.row_v7 = { + "id": "3", + "date": "2023-03-12T15:30:47.000Z", + "nb_of_txs": "4", + "ctrl_sum": "6000", + "initiator_name": "Initiator 7", + "initiator_street_name": "Street 7", + "initiator_building_number": "7", + "initiator_postal_code": "12347", + "initiator_town_name": "Town 7", + "initiator_country": "DE", + "ultimate_debtor_name": "Ultimate Debtor 7", + "service_level_code": "SEPA", + "payment_information_id": "PID123", + "payment_id": "PID123", + "payment_method": "pain.001.001.07", + "batch_booking": "true", + "requested_execution_date": "2023-07-21", + "debtor_name": "Debtor 7", + "debtor_street": "Street 7", + "debtor_building_number": "7", + "debtor_postal_code": "12347", + "debtor_town": "Town 7", + "debtor_country": "DE", + "debtor_account_IBAN": "DE123456781", + "debtor_agent_name": "Agent", + "debtor_agent_BIC": "DEUTDEFH", + "charge_bearer": "SLEV", + "payment_instruction_id": "PID123", + "payment_end_to_end_id": "E2E125", + "payment_currency": "GBP", + "payment_amount": "1700", + "creditor_name": "Creditor 7", + "creditor_street": "Street 7", + "creditor_building_number": "7", + "creditor_postal_code": "12347", + "creditor_town": "Town 7", + "creditor_country": "DE", + "creditor_account_IBAN": "DE123456781", + "creditor_agent_BICFI": "NOLADE21KIG", + "creditor_agent_name": "Creditor Agent", + "purpose_code": "GDDS", + "reference_number": "RF125", + "reference_date": "2023-03-12T15:30:47.000Z", + } + + self.row_v8 = { + "id": "4", + "date": "2023-03-13T15:30:47.000Z", + "nb_of_txs": "5", + "ctrl_sum": "7500", + "initiator_name": "Initiator 8", + "initiator_street_name": "Street 8", + "initiator_building_number": "8", + "initiator_postal_code": "12348", + "initiator_town_name": "Town 8", + "initiator_country": "DE", + "ultimate_debtor_name": "Ultimate Debtor 8", + "service_level_code": "SEPA", + "payment_information_id": "PID123", + "payment_id": "PID123", + "payment_method": "pain.001.001.08", + "batch_booking": "true", + "requested_execution_date": "2023-08-21", + "debtor_name": "Debtor 8", + "debtor_street": "Street 8", + "debtor_building_number": "8", + "debtor_postal_code": "12348", + "debtor_town": "Town 8", + "debtor_country": "DE", + "debtor_account_IBAN": "DE123456782", + "debtor_agent_name": "Agent", + "debtor_agent_BIC": "DEUTDEFH", + "charge_bearer": "SLEV", + "payment_instruction_id": "PID123", + "payment_end_to_end_id": "E2E126", + "payment_currency": "JPY", + "payment_amount": "1800", + "creditor_name": "Creditor 8", + "creditor_street": "Street 8", + "creditor_building_number": "8", + "creditor_postal_code": "12348", + "creditor_town": "Town 8", + "creditor_country": "DE", + "creditor_account_IBAN": "DE123456782", + "creditor_agent_name": "Creditor Agent", + "creditor_agent_BICFI": "NOLADE21KIH", + "purpose_code": "GDDS", + "reference_number": "RF126", + "reference_date": "2023-03-13T15:30:47.000Z", + } + self.row_v9 = { "id": "1", "date": "2023-03-10T15:30:47.000Z", @@ -85,6 +327,7 @@ def setUp(self): "payment_amount": "1500", "payment_currency": "EUR", "charge_bearer": "SLEV", + "creditor_agent_name": "Creditor Agent", "creditor_agent_BIC": "NOLADE21KIE", "creditor_name": "Creditor", "creditor_street_name": "Street", @@ -113,6 +356,11 @@ def generate_xml(self): Generate XML using create_xml_v3 """ create_xml_v3(self.root, [self.row_v3]) + create_xml_v4(self.root, [self.row_v4]) + create_xml_v5(self.root, [self.row_v5]) + create_xml_v6(self.root, [self.row_v6]) + create_xml_v7(self.root, [self.row_v7]) + create_xml_v8(self.root, [self.row_v8]) create_xml_v9(self.root, [self.row_v9]) def test_create_common_elements_v3(self): @@ -126,6 +374,61 @@ def test_create_common_elements_v3(self): self.assertEqual(self.root[1].tag, "PmtMtd") self.assertEqual(self.root[1].text, "pain.001.001.03") + def test_create_common_elements_v4(self): + """ + Test create_common_elements for version 4 + """ + create_common_elements(self.root, self.row_v4, self.mapping) + self.assertEqual(len(self.root), 2) + self.assertEqual(self.root[0].tag, "PmtInfId") + self.assertEqual(self.root[0].text, "PID123") + self.assertEqual(self.root[1].tag, "PmtMtd") + self.assertEqual(self.root[1].text, "pain.001.001.04") + + def test_create_common_elements_v5(self): + """ + Test create_common_elements for version 5 + """ + create_common_elements(self.root, self.row_v5, self.mapping) + self.assertEqual(len(self.root), 2) + self.assertEqual(self.root[0].tag, "PmtInfId") + self.assertEqual(self.root[0].text, "PID123") + self.assertEqual(self.root[1].tag, "PmtMtd") + self.assertEqual(self.root[1].text, "pain.001.001.05") + + def test_create_common_elements_v6(self): + """ + Test create_common_elements for version 6 + """ + create_common_elements(self.root, self.row_v6, self.mapping) + self.assertEqual(len(self.root), 2) + self.assertEqual(self.root[0].tag, "PmtInfId") + self.assertEqual(self.root[0].text, "PID123") + self.assertEqual(self.root[1].tag, "PmtMtd") + self.assertEqual(self.root[1].text, "pain.001.001.06") + + def test_create_common_elements_v7(self): + """ + Test create_common_elements for version 7 + """ + create_common_elements(self.root, self.row_v7, self.mapping) + self.assertEqual(len(self.root), 2) + self.assertEqual(self.root[0].tag, "PmtInfId") + self.assertEqual(self.root[0].text, "PID123") + self.assertEqual(self.root[1].tag, "PmtMtd") + self.assertEqual(self.root[1].text, "pain.001.001.07") + + def test_create_common_elements_v8(self): + """ + Test create_common_elements for version 8 + """ + create_common_elements(self.root, self.row_v8, self.mapping) + self.assertEqual(len(self.root), 2) + self.assertEqual(self.root[0].tag, "PmtInfId") + self.assertEqual(self.root[0].text, "PID123") + self.assertEqual(self.root[1].tag, "PmtMtd") + self.assertEqual(self.root[1].text, "pain.001.001.08") + def test_create_common_elements_v9(self): """ Test create_common_elements for version 9 @@ -143,9 +446,47 @@ def test_create_xml_v3(self): """ create_xml_v3(self.root, [self.row_v3]) cstmr_cdt_trf_initn_element = self.root[0] - self.assertEqual( - cstmr_cdt_trf_initn_element.tag, "CstmrCdtTrfInitn" - ) + self.assertEqual(cstmr_cdt_trf_initn_element.tag, "CstmrCdtTrfInitn") + + def test_create_xml_v4(self): + """ + Test create_xml_v4 + """ + create_xml_v4(self.root, [self.row_v4]) + cstmr_cdt_trf_initn_element = self.root[0] + self.assertEqual(cstmr_cdt_trf_initn_element.tag, "CstmrCdtTrfInitn") + + def test_create_xml_v5(self): + """ + Test create_xml_v5 + """ + create_xml_v5(self.root, [self.row_v5]) + cstmr_cdt_trf_initn_element = self.root[0] + self.assertEqual(cstmr_cdt_trf_initn_element.tag, "CstmrCdtTrfInitn") + + def test_create_xml_v6(self): + """ + Test create_xml_v6 + """ + create_xml_v6(self.root, [self.row_v6]) + cstmr_cdt_trf_initn_element = self.root[0] + self.assertEqual(cstmr_cdt_trf_initn_element.tag, "CstmrCdtTrfInitn") + + def test_create_xml_v7(self): + """ + Test create_xml_v7 + """ + create_xml_v7(self.root, [self.row_v7]) + cstmr_cdt_trf_initn_element = self.root[0] + self.assertEqual(cstmr_cdt_trf_initn_element.tag, "CstmrCdtTrfInitn") + + def test_create_xml_v8(self): + """ + Test create_xml_v8 + """ + create_xml_v8(self.root, [self.row_v8]) + cstmr_cdt_trf_initn_element = self.root[0] + self.assertEqual(cstmr_cdt_trf_initn_element.tag, "CstmrCdtTrfInitn") def test_create_xml_v9(self): """ @@ -153,9 +494,7 @@ def test_create_xml_v9(self): """ create_xml_v9(self.root, [self.row_v9]) cstmr_cdt_trf_initn_element = self.root[0] - self.assertEqual( - cstmr_cdt_trf_initn_element.tag, "CstmrCdtTrfInitn" - ) + self.assertEqual(cstmr_cdt_trf_initn_element.tag, "CstmrCdtTrfInitn") if __name__ == "__main__": diff --git a/tests/test_load_csv_data.py b/tests/test_load_csv_data.py index 73c65c2..a4c1735 100644 --- a/tests/test_load_csv_data.py +++ b/tests/test_load_csv_data.py @@ -1,30 +1,496 @@ +# Copyright (C) 2023-2024 Sebastien Rousseau. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import unittest +import os import csv from pain001.csv.load_csv_data import load_csv_data +from pain001.csv.validate_csv_data import validate_csv_data + + +class TestLoadCsvData(unittest.TestCase): + + def setUp(self): + """Create test files before each test.""" + os.makedirs("tests/data", exist_ok=True) + + # valid_data.csv + valid_data = [ + [ + "id", + "date", + "nb_of_txs", + "initiator_name", + "initiator_street_name", + "initiator_building_number", + "initiator_postal_code", + "initiator_town_name", + "initiator_country_code", + "payment_information_id", + "payment_method", + "batch_booking", + "requested_execution_date", + "debtor_name", + "debtor_street_name", + "debtor_building_number", + "debtor_postal_code", + "debtor_town_name", + "debtor_country_code", + "debtor_account_IBAN", + "debtor_agent_BIC", + "charge_bearer", + "payment_id", + "payment_amount", + "currency", + "payment_currency", + "ctrl_sum", + "creditor_agent_BIC", + "creditor_name", + "creditor_street_name", + "creditor_building_number", + "creditor_postal_code", + "creditor_town_name", + "creditor_country_code", + "creditor_account_IBAN", + "purpose_code", + "reference_number", + "reference_date", + "service_level_code", + "forwarding_agent_BIC", + "remittance_information", + "charge_account_IBAN", + ], + [ + "1", + "2023-03-10T15:30:47.000Z", + "2", + "John Doe", + "John's Street", + "1", + "12345", + "John's Town", + "DE", + "Payment-Info-12345", + "TRF", + "true", + "2023-03-12", + "Acme Corp", + "Acme Street", + "2", + "67890", + "Acme Town", + "DE", + "DE75512108001245126162", + "BANKDEFFXXX", + "SLEV", + "PaymentID6789", + "150", + "EUR", + "EUR", + "15000", + "SPUEDE2UXXX", + "Global Tech", + "Global Street", + "3", + "11223", + "Global Town", + "DE", + "DE68210501700024690959", + "OTHR", + "Invoice-98765", + "2023-03-09", + "SEPA", + "SPUEDE2UXXX", + "Invoice-12345", + "CHARGE-IBAN-12345", + ], + [ + "2", + "2023-03-11T10:20:18.000Z", + "3", + "Jane Smith", + "Jane's Street", + "10", + "67890", + "Jane's Town", + "DE", + "Payment-Info-67890", + "TRF", + "true", + "2023-03-14", + "Brown Industries", + "Brown Street", + "20", + "45678", + "Brown Town", + "DE", + "DE44500105175407324931", + "BANKDEFFXXX", + "SHAR", + "PaymentID4321", + "300", + "EUR", + "EUR", + "30000", + "SPUEDE2UXXX", + "Green Energy", + "Green Street", + "30", + "78901", + "Green Town", + "DE", + "DE89370400440532013008", + "OTHR", + "Invoice-12345", + "2023-03-13", + "SEPA", + "SPUEDE2UXXX", + "Invoice-67890", + "CHARGE-IBAN-67890", + ], + [ + "3", + "2023-03-11T11:45:23.000Z", + "1", + "Michael Johnson", + "Michael's Street", + "15", + "89101", + "Michael's Town", + "DE", + "Payment-Info-24680", + "TRF", + "true", + "2023-03-13", + "Alpha Electronics", + "Alpha Street", + "25", + "32165", + "Alpha Town", + "DE", + "DE47500105175711000100", + "BANKDEFFXXX", + "SLEV", + "PaymentID1357", + "250", + "EUR", + "EUR", + "25000", + "SPUEDE2UXXX", + "Beta Chemicals", + "Beta Street", + "35", + "65432", + "Beta Town", + "DE", + "DE44500105175407123457", + "OTHR", + "Invoice-24680", + "2023-03-11", + "SEPA", + "SPUEDE2UXXX", + "Invoice-24680", + "CHARGE-IBAN-24680", + ], + [ + "4", + "2023-03-12T09:12:34.000Z", + "2", + "Emily Wilson", + "Emily's Street", + "4", + "34567", + "Emily's Town", + "DE", + "Payment-Info-36912", + "TRF", + "true", + "2023-03-15", + "Zeta Services", + "Zeta Street", + "8", + "98765", + "Zeta Town", + "DE", + "DE68120933300112345608", + "BANKDEFFXXX", + "SHAR", + "PaymentID8642", + "200", + "EUR", + "EUR", + "20000", + "SPUEDE2UXXX", + "Theta Solutions", + "Theta Street", + "38", + "54321", + "Theta Town", + "DE", + "DE75512108001245123456", + "OTHR", + "Invoice-36912", + "2023-03-14", + "SEPA", + "SPUEDE2UXXX", + "Invoice-36912", + "CHARGE-IBAN-36912", + ], + ] + with open("tests/data/valid_data.csv", "w", newline="") as file: + writer = csv.writer(file) + writer.writerows(valid_data) + + # invalid_data.csv + invalid_data = [ + [ + "id", + "date", + "nb_of_txs", + "initiator_name", + "initiator_street_name", + "initiator_building_number", + "initiator_postal_code", + "initiator_town_name", + "initiator_country_code", + "payment_information_id", + "payment_method", + "batch_booking", + "requested_execution_date", + "debtor_name", + "debtor_street_name", + "debtor_building_number", + "debtor_postal_code", + "debtor_town_name", + "debtor_country_code", + "debtor_account_IBAN", + "debtor_agent_BIC", + "charge_bearer", + "payment_id", + "payment_amount", + "currency", + "payment_currency", + "ctrl_sum", + "creditor_agent_BIC", + "creditor_name", + "creditor_street_name", + "creditor_building_number", + "creditor_postal_code", + "creditor_town_name", + "creditor_country_code", + "creditor_account_IBAN", + "purpose_code", + "reference_number", + "reference_date", + "service_level_code", + "forwarding_agent_BIC", + "remittance_information", + "charge_account_IBAN", + ], + [ + "1", + "not-a-date", + "2", + "John Doe", + "John's Street", + "1", + "12345", + "John's Town", + "DE", + "Payment-Info-12345", + "TRF", + "true", + "2023-03-12", + "Acme Corp", + "Acme Street", + "2", + "67890", + "Acme Town", + "DE", + "DE75512108001245126162", + "BANKDEFFXXX", + "SLEV", + "PaymentID6789", + "150", + "EUR", + "EUR", + "15000", + "SPUEDE2UXXX", + "Global Tech", + "Global Street", + "3", + "11223", + "Global Town", + "DE", + "DE68210501700024690959", + "OTHR", + "Invoice-98765", + "2023-03-09", + "SEPA", + "SPUEDE2UXXX", + "Invoice-12345", + "CHARGE-IBAN-12345", + ], + ] + with open("tests/data/invalid_data.csv", "w", newline="") as file: + writer = csv.writer(file) + writer.writerows(invalid_data) + + # empty.csv + open("tests/data/empty.csv", "w").close() + + # single_column.csv + single_column = [["id"], ["1"], ["2"], ["3"]] + with open("tests/data/single_column.csv", "w", newline="") as file: + writer = csv.writer(file) + writer.writerows(single_column) + + # single_row.csv + single_row = [ + [ + "id", + "date", + "nb_of_txs", + "initiator_name", + "initiator_street_name", + "initiator_building_number", + "initiator_postal_code", + "initiator_town_name", + "initiator_country_code", + "payment_information_id", + "payment_method", + "batch_booking", + "requested_execution_date", + "debtor_name", + "debtor_street_name", + "debtor_building_number", + "debtor_postal_code", + "debtor_town_name", + "debtor_country_code", + "debtor_account_IBAN", + "debtor_agent_BIC", + "charge_bearer", + "payment_id", + "payment_amount", + "currency", + "payment_currency", + "ctrl_sum", + "creditor_agent_BIC", + "creditor_name", + "creditor_street_name", + "creditor_building_number", + "creditor_postal_code", + "creditor_town_name", + "creditor_country_code", + "creditor_account_IBAN", + "purpose_code", + "reference_number", + "reference_date", + "service_level_code", + "forwarding_agent_BIC", + "remittance_information", + "charge_account_IBAN", + ], + [ + "1", + "2023-03-10T15:30:47.000Z", + "2", + "John Doe", + "John's Street", + "1", + "12345", + "John's Town", + "DE", + "Payment-Info-12345", + "TRF", + "true", + "2023-03-12", + "Acme Corp", + "Acme Street", + "2", + "67890", + "Acme Town", + "DE", + "DE75512108001245126162", + "BANKDEFFXXX", + "SLEV", + "PaymentID6789", + "150", + "EUR", + "EUR", + "15000", + "SPUEDE2UXXX", + "Global Tech", + "Global Street", + "3", + "11223", + "Global Town", + "DE", + "DE68210501700024690959", + "OTHR", + "Invoice-98765", + "2023-03-09", + "SEPA", + "SPUEDE2UXXX", + "Invoice-12345", + "CHARGE-IBAN-12345", + ], + ] + with open("tests/data/single_row.csv", "w", newline="") as file: + writer = csv.writer(file) + writer.writerows(single_row) + + def tearDown(self): + """Delete test files after each test.""" + files = [ + "tests/data/valid_data.csv", + "tests/data/invalid_data.csv", + "tests/data/empty.csv", + "tests/data/single_column.csv", + "tests/data/single_row.csv", + ] + for file in files: + if os.path.exists(file): + os.remove(file) + + def test_load_valid_csv(self): + file_path = "tests/data/valid_data.csv" + data = load_csv_data(file_path) + self.assertEqual(len(data), 4) + + def test_load_csv_with_invalid_data(self): + file_path = "tests/data/invalid_data.csv" + data = load_csv_data(file_path) + self.assertFalse(validate_csv_data(data)) + + def test_load_empty_csv(self): + file_path = "tests/data/empty.csv" + with self.assertRaises(ValueError): + load_csv_data(file_path) + + def test_load_single_column_csv(self): + file_path = "tests/data/single_column.csv" + data = load_csv_data(file_path) + self.assertEqual(len(data), 3) + + def test_load_single_row_csv(self): + file_path = "tests/data/single_row.csv" + data = load_csv_data(file_path) + self.assertEqual(len(data), 1) + -# Test if the CSV data is loaded correctly - - -def test_load_csv_data(): - # Test with an existing CSV file - csv_file_path = "tests/data/existing_file.csv" - expected_output = [ - {"col1": "val1", "col2": "val2", "col3": "val3"}, - {"col1": "val4", "col2": "val5", "col3": "val6"}, - {"col1": "val7", "col2": "val8", "col3": "val9"}, - ] - with open(csv_file_path, "w", newline="") as csv_file: - writer = csv.DictWriter( - csv_file, fieldnames=["col1", "col2", "col3"] - ) - writer.writeheader() - writer.writerows(expected_output) - assert load_csv_data(csv_file_path) == expected_output - - # Test with a non-existing CSV file - csv_file_path = "path/to/non_existing_file.csv" - try: - load_csv_data(csv_file_path) - except FileNotFoundError as e: - assert str(e) == f"CSV file '{csv_file_path}' does not exist." - else: - assert False, "Expected FileNotFoundError not raised." +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_load_db_data.py b/tests/test_load_db_data.py new file mode 100644 index 0000000..5d02b75 --- /dev/null +++ b/tests/test_load_db_data.py @@ -0,0 +1,63 @@ +# Copyright (C) 2023-2024 Sebastien Rousseau. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import pytest +import sqlite3 +from pain001.db.load_db_data import sanitize_table_name, load_db_data + + +# Test sanitize_table_name function +def test_sanitize_table_name(): + assert sanitize_table_name("valid_table_name") == "valid_table_name" + assert sanitize_table_name("invalid table name") == "invalid_table_name" + assert sanitize_table_name("123invalidname") == "table_123invalidname" + assert sanitize_table_name("table!@#name") == "table___name" + + +# Test load_db_data function +def test_load_db_data(tmp_path): + # Create a temporary SQLite database + db_file = tmp_path / "test.db" + conn = sqlite3.connect(db_file) + cursor = conn.cursor() + + # Create a test table and insert data + cursor.execute( + "CREATE TABLE test_table (id INTEGER PRIMARY KEY, name TEXT)" + ) + cursor.execute("INSERT INTO test_table (name) VALUES ('Alice')") + cursor.execute("INSERT INTO test_table (name) VALUES ('Bob')") + conn.commit() + conn.close() + + # Test loading data from the table + data = load_db_data(db_file, "test_table") + assert len(data) == 2 + assert data[0]["name"] == "Alice" + assert data[1]["name"] == "Bob" + + # Test FileNotFoundError + with pytest.raises(FileNotFoundError): + load_db_data("non_existent.db", "test_table") + + # Test sqlite3.OperationalError for non-existent table + with pytest.raises(sqlite3.OperationalError): + load_db_data(db_file, "non_existent_table") + + +# If the script is executed directly, run the tests +if __name__ == "__main__": + pytest.main() diff --git a/tests/test_main.py b/tests/test_main.py index 56068fd..bc306d7 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,5 +1,20 @@ +# Copyright (C) 2023-2024 Sebastien Rousseau. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from click.testing import CliRunner -from pain001.__main__ import main +from pain001.__main__ import cli class TestMain: @@ -12,7 +27,7 @@ def setup_method(self): def test_main_with_valid_files(self): result = self.runner.invoke( - main, + cli, [ "--xml_message_type", self.xml_message_type, @@ -24,15 +39,15 @@ def test_main_with_valid_files(self): self.csv_file, ], ) + assert result.exit_code == 0 assert ( - "The XML has been validated against `tests/data/template.xsd`\n" + "The XML has been validated against `tests/data/template.xsd`" in result.output ) - assert result.exit_code == 0 def test_main_with_missing_xml_message_type(self): result = self.runner.invoke( - main, + cli, [ "--xml_template_file_path", self.xml_file, @@ -43,11 +58,11 @@ def test_main_with_missing_xml_message_type(self): ], ) assert result.exit_code == 1 - assert "Error: xml_message_type is required." in result.output + assert "The XML message type is required." in result.output def test_main_with_missing_xsd_template_file(self): result = self.runner.invoke( - main, + cli, [ "--xml_message_type", self.xml_message_type, @@ -58,13 +73,11 @@ def test_main_with_missing_xsd_template_file(self): ], ) assert result.exit_code == 1 - assert ( - "Error: xsd_schema_file_path is required." in result.output - ) + assert "The XSD schema file path is required." in result.output def test_main_with_missing_data_file(self): result = self.runner.invoke( - main, + cli, [ "--xml_message_type", self.xml_message_type, @@ -75,11 +88,11 @@ def test_main_with_missing_data_file(self): ], ) assert result.exit_code == 1 - assert "Error: data_file_path is required." in result.output + assert "The data file path is required." in result.output def test_main_with_invalid_xml_message_type(self): result = self.runner.invoke( - main, + cli, [ "--xml_message_type", "invalid", @@ -96,7 +109,7 @@ def test_main_with_invalid_xml_message_type(self): def test_main_with_invalid_xml_template_file(self): result = self.runner.invoke( - main, + cli, [ "--xml_message_type", self.xml_message_type, @@ -110,13 +123,12 @@ def test_main_with_invalid_xml_template_file(self): ) assert result.exit_code == 1 assert ( - "The XML template file 'invalid' does not exist." - in result.output + "The XML template file 'invalid' does not exist." in result.output ) def test_main_with_invalid_xsd_template_file(self): result = self.runner.invoke( - main, + cli, [ "--xml_message_type", self.xml_message_type, @@ -130,13 +142,12 @@ def test_main_with_invalid_xsd_template_file(self): ) assert result.exit_code == 1 assert ( - "The XSD template file 'invalid' does not exist." - in result.output + "The XSD template file 'invalid' does not exist." in result.output ) def test_main_with_invalid_data_file(self): result = self.runner.invoke( - main, + cli, [ "--xml_message_type", self.xml_message_type, @@ -149,64 +160,4 @@ def test_main_with_invalid_data_file(self): ], ) assert result.exit_code == 1 - assert ( - "The data file 'invalid' does not exist." in result.output - ) - - def test_invalid_xml_template_file_path(self): - """ - Test that the `print(click.get_current_context().get_help())` line is - executed when the `xml_template_file_path` argument is set to an - invalid value. - """ - - result = self.runner.invoke( - main, - [ - "--xml_message_type", - "pain.001.001.03", - "--xml_template_file_path", - "invalid", - "--xsd_schema_file_path", - self.xsd_file, - "--data_file_path", - self.csv_file, - ], - ) - - assert result.exit_code == 1 - assert ( - "The XML template file 'invalid' does not exist." - in result.output - ) - assert ( - "The XML template file 'invalid' does not exist." - in result.output - ) - - def test_non_existent_xml_template_file_path(self): - """ - Test that the `logger.info()` and `print()` lines are executed - when the `xml_template_file_path` argument is set to a non-existent - file path. - """ - - result = self.runner.invoke( - main, - [ - "--xml_message_type", - "pain.001.001.03", - "--xml_template_file_path", - "non_existent_file.xml", - "--xsd_schema_file_path", - self.xsd_file, - "--data_file_path", - self.csv_file, - ], - ) - - assert result.exit_code == 1 - assert ( - "The XML template file 'non_existent_file.xml' does not exist." - in result.output - ) + assert "The data file 'invalid' does not exist." in result.output diff --git a/tests/test_validate_csv_data.py b/tests/test_validate_csv_data.py index 4ff94b3..5f1ac9b 100644 --- a/tests/test_validate_csv_data.py +++ b/tests/test_validate_csv_data.py @@ -1,115 +1,122 @@ +# Copyright (C) 2023-2024 Sebastien Rousseau. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + import unittest from pain001.csv.validate_csv_data import validate_csv_data -# Test if the CSV data is validated correctly - class TestValidateCsvData(unittest.TestCase): - def test_valid_data(self): - # Test valid data + def test_validate_csv_with_valid_data(self): data = [ { "id": "1", - "date": "2022-01-01", - "nb_of_txs": "1", + "date": "2023-03-10T15:30:47.000Z", + "nb_of_txs": "2", "initiator_name": "John Doe", - "payment_information_id": "12345", + "initiator_street_name": "John's Street", + "initiator_building_number": "1", + "initiator_postal_code": "12345", + "initiator_town_name": "John's Town", + "initiator_country_code": "DE", + "payment_information_id": "Payment-Info-12345", "payment_method": "TRF", - "batch_booking": "false", - "ctrl_sum": "100", - "service_level_code": "SEPA", - "requested_execution_date": "2022-01-01", - "debtor_name": "John Doe", - "debtor_account_IBAN": "DE89370400440532013000", - "debtor_agent_BIC": "DEUTDEDBFRA", - "forwarding_agent_BIC": "FORWARD", - "charge_bearer": "SHA", - "payment_id": "12345", - "payment_amount": "100.00", + "batch_booking": "true", + "requested_execution_date": "2023-03-12", + "debtor_name": "Acme Corp", + "debtor_street_name": "Acme Street", + "debtor_building_number": "2", + "debtor_postal_code": "67890", + "debtor_town_name": "Acme Town", + "debtor_country_code": "DE", + "debtor_account_IBAN": "DE75512108001245126162", + "debtor_agent_BIC": "BANKDEFFXXX", + "charge_bearer": "SLEV", + "payment_id": "PaymentID6789", + "payment_amount": "150", "currency": "EUR", - "creditor_agent_BIC": "DABADEHHXXX", - "creditor_name": "Jane Doe", - "creditor_account_IBAN": "DE89370400440532013001", - "remittance_information": "Invoice 1234", - }, - { - "id": "2", - "date": "2022-01-02", - "nb_of_txs": "1", - "initiator_name": "Jane Doe", - "payment_information_id": "67890", - "payment_method": "TRF", - "batch_booking": "false", - "ctrl_sum": "200", + "payment_currency": "EUR", + "ctrl_sum": "15000", + "creditor_agent_BIC": "SPUEDE2UXXX", + "creditor_name": "Global Tech", + "creditor_street_name": "Global Street", + "creditor_building_number": "3", + "creditor_postal_code": "11223", + "creditor_town_name": "Global Town", + "creditor_country_code": "DE", + "creditor_account_IBAN": "DE68210501700024690959", + "purpose_code": "OTHR", + "reference_number": "Invoice-98765", + "reference_date": "2023-03-09", "service_level_code": "SEPA", - "requested_execution_date": "2022-01-02", - "debtor_name": "Jane Doe", - "debtor_account_IBAN": "DE89370400440532013001", - "debtor_agent_BIC": "DEUTDEDBFRA", - "forwarding_agent_BIC": "FORWARD2", - "charge_bearer": "SHA", - "payment_id": "67890", - "payment_amount": "200.00", - "currency": "EUR", - "creditor_agent_BIC": "DABADEHHXXX", - "creditor_name": "John Doe", - "creditor_account_IBAN": "DE89370400440532013000", - "remittance_information": "Invoice 5678", - }, + "forwarding_agent_BIC": "SPUEDE2UXXX", + "remittance_information": "Invoice-12345", + "charge_account_IBAN": "CHARGE-IBAN-12345", + } ] + self.assertTrue(validate_csv_data(data)) - assert validate_csv_data(data) is True - - def test_missing_required_columns(self): - # Test missing required columns + def test_validate_csv_with_invalid_data(self): data = [ { "id": "1", - "date": "2022-01-01", - "nb_of_txs": "1", - "payment_information_id": "12345", + "date": "not-a-date", + "nb_of_txs": "2", + "initiator_name": "John Doe", + "initiator_street_name": "John's Street", + "initiator_building_number": "1", + "initiator_postal_code": "12345", + "initiator_town_name": "John's Town", + "initiator_country_code": "DE", + "payment_information_id": "Payment-Info-12345", "payment_method": "TRF", - "batch_booking": "false", - "ctrl_sum": "100", - "service_level_code": "SEPA", - "requested_execution_date": "2022-01-01", - "debtor_name": "John Doe", - "debtor_account_IBAN": "DE89370400440532013000", - "debtor_agent_BIC": "DEUTDEDBFRA", - "forwarding_agent_BIC": "FORWARD", - "charge_bearer": "SHA", - "payment_id": "12345", - "payment_amount": "100.00", + "batch_booking": "true", + "requested_execution_date": "2023-03-12", + "debtor_name": "Acme Corp", + "debtor_street_name": "Acme Street", + "debtor_building_number": "2", + "debtor_postal_code": "67890", + "debtor_town_name": "Acme Town", + "debtor_country_code": "DE", + "debtor_account_IBAN": "DE75512108001245126162", + "debtor_agent_BIC": "BANKDEFFXXX", + "charge_bearer": "SLEV", + "payment_id": "PaymentID6789", + "payment_amount": "150", "currency": "EUR", - "creditor_agent_BIC": "DABADEHHXXX", - "creditor_name": "Jane Doe", - "creditor_account_IBAN": "DE89370400440532013001", - "remittance_information": "Invoice 1234", - }, - { - "id": "2", - "date": "2022-01-02", - "nb_of_txs": "1", - "initiator_name": "Jane Doe", - "payment_information_id": "67890", - "payment_method": "TRF", - "batch_booking": "false", - "ctrl_sum": "200", + "payment_currency": "EUR", + "ctrl_sum": "15000", + "creditor_agent_BIC": "SPUEDE2UXXX", + "creditor_name": "Global Tech", + "creditor_street_name": "Global Street", + "creditor_building_number": "3", + "creditor_postal_code": "11223", + "creditor_town_name": "Global Town", + "creditor_country_code": "DE", + "creditor_account_IBAN": "DE68210501700024690959", + "purpose_code": "OTHR", + "reference_number": "Invoice-98765", + "reference_date": "2023-03-09", "service_level_code": "SEPA", - "requested_execution_date": "2022-01-02", - "debtor_name": "Jane Doe", - "debtor_account_IBAN": "DE89370400440532013001", - "debtor_agent_BIC": "DEUTDEDBFRA", - "forwarding_agent_BIC": "FORWARD", - "charge_bearer": "SHA", - "payment_id": "67890", - "payment_amount": "200.00", - "currency": "EUR", - "creditor_agent_BIC": "DABADEHHXXX", - "creditor_name": "John Doe", - "creditor_account_IBAN": "DE89370400440532013000", - "remittance_information": "Invoice 5678", - }, + "forwarding_agent_BIC": "SPUEDE2UXXX", + "remittance_information": "Invoice-12345", + "charge_account_IBAN": "CHARGE-IBAN-12345", + } ] + self.assertFalse(validate_csv_data(data)) + - assert not validate_csv_data(data) +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_validate_db_data.py b/tests/test_validate_db_data.py new file mode 100644 index 0000000..a8e569f --- /dev/null +++ b/tests/test_validate_db_data.py @@ -0,0 +1,94 @@ +# Copyright (C) 2023-2024 Sebastien Rousseau. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import unittest +from unittest.mock import patch +from pain001.db.validate_db_data import validate_db_data + + +class TestValidateDbData(unittest.TestCase): + def setUp(self): + self.valid_data = [ + { + "id": 1, + "date": "2023-03-10T15:30:47", + "nb_of_txs": 2, + "initiator_name": "John Doe", + "initiator_street_name": "John's Street", + "initiator_building_number": 1, + "initiator_postal_code": "12345", + "initiator_town_name": "John's Town", + "initiator_country_code": "DE", + "payment_information_id": "Payment-Info-12345", + "payment_method": "TRF", + "batch_booking": False, + "requested_execution_date": "2023-03-15", + "debtor_name": "Debtor Name", + "debtor_street_name": "Debtor Street", + "debtor_building_number": 1, + "debtor_postal_code": "12345", + "debtor_town_name": "Debtor Town", + "debtor_country_code": "DE", + "debtor_account_IBAN": "DE89370400440532013000", + "debtor_agent_BIC": "BICCODE", + "charge_bearer": "DEBT", + "payment_id": "12345", + "payment_amount": "100.00", + "currency": "EUR", + "payment_currency": "EUR", + "ctrl_sum": "100.00", + "creditor_agent_BIC": "BICCODE", + "creditor_name": "Creditor Name", + "creditor_street_name": "Creditor Street", + "creditor_building_number": 1, + "creditor_postal_code": "12345", + "creditor_town_name": "Creditor Town", + "creditor_country_code": "DE", + "creditor_account_IBAN": "DE89370400440532013000", + "purpose_code": "SCOR", + "reference_number": "Reference-12345", + "reference_date": "2023-03-10", + "service_level_code": "SEPA", + "end_to_end_id": "End-to-End-Id-123", + "payment_instruction_id": "Payment-Instruction-Id-123", + "instruction_id": "Instruction-Id-123", + "category_purpose": "Category-Purpose-123", + "remittance_info_unstructured": "Remittance-Info-Unstructured-123", + "remittance_info_structured": "Remittance-Info-Structured-123", + "addtl_end_to_end_id": "Addtl-End-to-End-Id-123", + "payment_info_structured": "Payment-Info-Structured-123", + "forwarding_agent_BIC": "Forwarding-Agent-BIC-123", + "remittance_information": "Remittance-Information-123", + } + ] + + def test_validate_db_data_valid(self): + self.assertTrue(validate_db_data(self.valid_data)) + + @patch("pain001.db.validate_db_data.logger.error") + def test_validate_db_data_logging(self, mock_logging_error): + invalid_data = self.valid_data.copy() + invalid_data[0].pop("id") + validate_db_data(invalid_data) + mock_logging_error.assert_called_once_with( + "Error: Missing value for column '%s' in row: %s", + "id", + invalid_data[0], + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_validate_via_xsd.py b/tests/test_validate_via_xsd.py index 5b727ef..78812ad 100644 --- a/tests/test_validate_via_xsd.py +++ b/tests/test_validate_via_xsd.py @@ -1,3 +1,19 @@ +# Copyright (C) 2023-2024 Sebastien Rousseau. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + import unittest import os from pain001.xml.validate_via_xsd import validate_via_xsd @@ -70,9 +86,5 @@ def test_invalid_xml(self): """ Test case for validating an invalid XML file against an XSD schema. """ - assert not validate_via_xsd( - self.invalid_xml_file, self.xsd_file - ) - assert not validate_via_xsd( - self.invalid_xml_file, self.xsd_file - ) + assert not validate_via_xsd(self.invalid_xml_file, self.xsd_file) + assert not validate_via_xsd(self.invalid_xml_file, self.xsd_file) diff --git a/tests/test_xml_generator.py b/tests/test_xml_generator.py index 5b0de53..b981e4c 100644 --- a/tests/test_xml_generator.py +++ b/tests/test_xml_generator.py @@ -1,3 +1,19 @@ +# Copyright (C) 2023-2024 Sebastien Rousseau. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + import unittest from pain001.xml.generate_xml import generate_xml